I'd like to test that all Hibernate association annotations (@ManyToOne, @OneToMany, @OneToOne, @ManyToMany) are using fetch = FetchType.LAZY
. This is what works:
private static final Set<Class<? extends Annotation>> associations =
Set.of(ManyToOne.class, OneToMany.class, OneToOne.class, ManyToMany.class);
@ArchTest
public static final ArchRule allEntityRelationsShouldBeLazy = fields().that()
.areDeclaredInClassesThat().areAnnotatedWith(Entity.class)
.and()
.areAnnotatedWith(ManyToOne.class)
.or().areAnnotatedWith(OneToMany.class)
.or().areAnnotatedWith(OneToOne.class)
.or().areAnnotatedWith(ManyToMany.class)
// what I'd like: .areAnnotatedWith(Any.of(associations))
.should()
.notBeAnnotatedWith(new DescribedPredicate<>("FetchType.EAGER or undefined FetchType") {
@Override
public boolean apply(JavaAnnotation<?> input) {
JavaClass rawType = input.getRawType();
if (!rawType.isEquivalentTo(OneToOne.class)
// what I'd like: if (!Any.of(associations).apply(input)) {
&& !rawType.isEquivalentTo(OneToMany.class)
&& !rawType.isEquivalentTo(ManyToOne.class)
&& !rawType.isEquivalentTo(ManyToMany.class)) {
// Filter again, because a field can contain multiple annotations.
return false;
}
return input.get("fetch")
.transform(JavaEnumConstant.class::cast)
.transform(fetchType -> !FetchType.LAZY.name().equals(fetchType.name()))
.or(true);
}
});
I have to filter twice because a field can have several annotations like so:
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "otherEntity", referencedColumnName = "id")
private OtherEntity otherEntity;
What I don't like is that I have to write the Annotations (ManyToOne…) twice. Why isn't there a "anyOf"-Predicate in ArchUnit? Or is there any other way I could avoid repeating them?
Using a custom condition for the entity fields, you can directly operate on the relevant annotations for associations
and don't have to repeat them at all:
@ArchTest
ArchRule allEntityRelationsShouldBeLazy = fields()
.that().areDeclaredInClassesThat().areAnnotatedWith(Entity.class)
.should(new ArchCondition<JavaField>("be annotated with FetchType.LAZY if an association") {
@Override
public void check(JavaField field, ConditionEvents events) {
field.getAnnotations().stream()
.filter(annotation -> associations.stream().anyMatch(annotation.getRawType()::isEquivalentTo))
.forEach(association -> {
JavaEnumConstant fetchType = (JavaEnumConstant) association.get("fetch").get();
if (!FetchType.LAZY.name().equals(fetchType.name())) {
String message = field.getDescription() + " is not LAZY in " + field.getSourceCodeLocation();
events.add(SimpleConditionEvent.violated(field, message));
}
});
}
});