I have a setup where cats can be owned by many owners, and each owner can own several cats. Given this, I would like to write a specification to help me find all cats with a given owner name.
Here is a simple class setup.
@Entity
public class Cat extends AbstractEntity {
@Column
private String name;
}
* No getters/setters for conciseness. Id field is in the superclass.
@Entity
public class Owner extends AbstractEntity {
@Column
private String name;
@ManyToMany(fetch = FetchType.LAZY)
@JoinTable(name = "OWNER_2_CATS",
joinColumns = @JoinColumn(name = "OWNER_ID"),
inverseJoinColumns = @JoinColumn(name = "CAT_ID"))
@OrderColumn(name = "order_column")
private List<Cat> cats = Lists.newArrayList();
}
* No getters/setters for conciseness. Id field is in the superclass.
And here is a repository with a query that does work and a spec that does not work.
public interface CatRepository extends AtomicsRepository<Cat, Long> {
// This query works.
@Query("SELECT c FROM Owner o INNER JOIN o.cats c WHERE o.name = ?")
List<Cat> findAllByOwner(String ownerName);
// But how do I accomplish this in a specification?
public static class Specs {
static Specification<Cat> hasOwnerName(final String ownerName) {
return (root, query, cb) -> {
// These next lines don't work! What do I put here?
Root<Owner> owner = query.from(Owner.class);
owner.join("cats");
return cb.equal(owner.get("name"), ownerName);
};
}
}
}
Please help me write the specification.
What I'm having trouble with is that this relationship seems to need to go against the grain of how the relationship is expressed: an owner has a cats
list, but a cat does not have an owners
list.
One-To-One mapping is an association between one persistence object and another one related persistence object. If one persistence object uses other and in back if other is not using the first persistence object then it becomes unidirectional.
Specifications provide us with a way to write reusable queries and also fluent APIs with which we can combine and build more sophisticated queries. All in all, Spring JPA Specifications is a great tool whether we want to create reusable predicates or want to generate typesafe queries programmatically.
A Course can have many Students and a Student can take many courses. This creates a Many to Many relationship. The following example shows a unidirectional Many to Many mapping in Hibernate.
Its findById method retrieves an entity by its id. The return value is Optional<T> . Optional<T> is a container object which may or may not contain a non-null value. If a value is present, isPresent returns true and get returns the value.
The trickiness with this Spec is that you are querying the Cat with no direct relationship to Owner.
The general idea is to:
My preferred approach would be to use a subquery to introduce the Owner:
// Subquery using Cat membership in the Owner.cats relation
public static class Specs {
static Specification<Cat> hasOwnerName(final String ownerName) {
return (root, query, cb) -> {
query.distinct(true);
Root<Cat> cat = root;
Subquery<Owner> ownerSubQuery = query.subquery(Owner.class);
Root<Owner> owner = ownerSubQuery.from(Owner.class);
Expression<Collection<Cat>> ownerCats = owner.get("cats");
ownerSubQuery.select(owner);
ownerSubQuery.where(cb.equal(owner.get("name"), ownerName), cb.isMember(cat, ownerCats));
return cb.exists(ownerSubQuery);
};
}
}
Which Hibernate 4.3.x generates SQL query like:
select cat0_.id as id1_1_
from cat cat0_
where
exists (
select owner1_.id from owner owner1_ where
owner1_.name=?
and (cat0_.id in (
select cats2_.cat_id from owner_2_cats cats2_ where owner1_.id=cats2_.owner_id
))
)
An alternative is to use a cartesian product to introduce the Owner:
// Cat membership in the Owner.cats relation using cartesian product
public static class Specs {
static Specification<Cat> hasOwnerName(final String ownerName) {
return (root, query, cb) -> {
query.distinct(true);
Root<Cat> cat = root;
Root<Owner> owner = query.from(Owner.class);
Expression<Collection<Cat>> ownerCats = owner.get("cats");
return cb.and(cb.equal(owner.get("name"), ownerName), cb.isMember(cat, ownerCats));
};
}
}
Which Hibernate 4.3.x generates SQL query like:
select cat0_.id as id1_1_
from cat cat0_
cross join owner owner1_
where
owner1_.name=?
and (cat0_.id in (
select cats2_.cat_id from owner_2_cats cats2_ where owner1_.id=cats2_.owner_id
))
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With