There are two entity
#Entity
public class Event{
...
#ManyToMany(fetch = FetchType.LAZY)
private Set<EventGroup> eventGroups;
}
#Entity
public class EventGroup {
...
#ManyToMany(fetch = FetchType.LAZY)
private Set<Event> events;
}
I need to get Events which has EventGroups with given ids.
Using spring data CrudRepository.
#Repository
public interface EventRepository extends CrudRepository<Event, Long>, JpaSpecificationExecutor {
}
Im calling
eventRepository.findAll(buildSpecification(filter);
This is how i build specification:
private Specification<Event> buildSpecification(final EventFilter filter) {
final Specification<Event> specification = new Specification<Event>() {
#Override
public Predicate toPredicate(Root<Event> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
root = criteriaQuery.distinct(true).from(Event.class);
Predicate predicate = cb.conjunction();
if (filter.getEventGroupIds() != null) {
Join<Event, EventGroup> join = root.join(Event_.eventGroups);
predicate.getExpressions().add( join.get(EventGroup_.id).in(filter.getEventGroupIds()) );
}
return criteriaQuery.where(predicate).getRestriction();
}
};
return specification;
}
But result query is
SELECT DISTINCT
event0_.id AS id1_1_,
event0_.createdAt AS createdA2_1_,
event0_.date AS date3_1_,
event0_.image_id AS image_id6_1_,
event0_.moderated AS moderate4_1_,
event0_.name AS name5_1_,
event0_.owner_id AS owner_id7_1_
FROM Event event0_
CROSS JOIN Event event1_
INNER JOIN Event_EventGroup eventgroup2_ ON event1_.id = eventgroup2_.Event_id
INNER JOIN EventGroup eventgroup3_ ON eventgroup2_.eventGroups_id = eventgroup3_.id
WHERE eventgroup3_.id IN (15)
This cross join corrupt everything.
What should i do? May be there is another way to get it?
Solved
private Specification<Event> buildSpecification(final EventFilter filter) {
final Specification<Event> specification = new Specification<Event>() {
#Override
public Predicate toPredicate(Root<Event> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
cq.distinct(true);
Predicate predicate = cb.conjunction();
if (filter.getEventGroupIds() != null) {
Join<Event, EventGroup> join = root.join(Event_.eventGroups);
predicate.getExpressions().add(join.get(EventGroup_.id).in(filter.getEventGroupIds()) );
}
return predicate;
}
};
return specification;
}
Related
I have an entity, says:
class MyEntity {
Long id;
String attr1;
String attr2;
String attr3;
String attr4;
Double attr5;
}
I use Specification to query the result filtered by attributes like:
class MySpecification implements Specification<MyEntity> {
private String attr1;
private String attr2;
private String attr3;
private String attr4;
#Override
public Predicate toPredicate(Root<MyEntity> root, CriteriaQuery<?> query, CriteriaBuilder criteriaBuilder) {
List<Predicate> restriction = new ArrayList<>();
if (!StringUtils.isEmpty(attr1)) {
restriction.add(criteriaBuilder.equal(root.get("attr1"), attr1));
}
if (!StringUtils.isEmpty(attr2)) {
restriction.add(criteriaBuilder.equal(root.get("attr2"), attr2));
}
// And so on
Predicate predicate = criteriaBuilder.disjunction();
predicate.getExpressions().add(criteriaBuilder.and(restriction.toArray(new Predicate[restriction.size()])));
return predicate;
}
}
Now I want to get sum of attr5 by the Specification, how can I do that ?
Thank you in advanced.
After some research, here is solution:
Create an interface:
interface MyRepositoryCustom {
<S extends Number> S sum(Specification<MyEntity> spec, Class<S> resultType, String fieldName);
}
Implementation:
#Repository
class MyRepositoryCustomImpl implements MyRepositoryCustom {
#Autowired
private EntityManager entityManager;
#Override
public <S extends Number> S sum(Specification<MyEntity> spec, Class<S> resultType, String fieldName) {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<S> query = builder.createQuery(resultType);
Root<MyEntity> root = applySpecificationToCriteria(spec, query);
query.select(builder.sum(root.get(fieldName).as(resultType)));
TypedQuery<S> typedQuery = entityManager.createQuery(query);
return typedQuery.getSingleResult();
}
protected <S> Root<MyEntity> applySpecificationToCriteria(Specification<MyEntity> spec, CriteriaQuery<S> query) {
Root<MyEntity> root = query.from(MyEntity.class);
if (spec == null) {
return root;
}
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
Predicate predicate = spec.toPredicate(root, query, builder);
if (predicate != null) {
query.where(predicate);
}
return root;
}
}
Main repository should extend both JpaRepository and MyRepositoryCustom:
#Repository
interface MyEntityRepository extends JpaRepository<MyEntity, Long>, MyRepositoryCustom {
}
I'm trying to build dynamic subquery with a JPA Specification. How can I add predicates to the subquery and build it?
for example, I'll have 2 tables:
User and Usercard:
#Entity
#Table(name = "users", schema = "someschema")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private String surName;
private String email;
#OneToMany
private List<Usercard> usercardList;
//other methods...
}
and
#Entity
#Table(name = "usercard", schema = "someschema")
public class Usercard {
#Id
private Long id;
private String account;
private String value;
#ManyToOne
private User user;
//other methods...
}
I have my repo:
#Repository
public interface UserRepository extends JpaRepository<User, Long>,
JpaSpecificationExecutor<User> {}
And trying to build smth like:
public List<User> findByPredicate(String email) {
return userRepository.findAll((Specification<User>) (root,
criteriaQuery, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
if (email != null) {
predicates.add(criteriaBuilder.and(criteriaBuilder.equal(
root.get("email"), email)));
}
return criteriaBuilder.and(predicates.toArray(new
Predicate[predicates.size()]));
});
}
but for subquery with predicates.
I've tried methods like this:
public List<User> findByUsercardAccount(String email, String account) {
return userRepository.findAll(new Specification<User>() {
#Override
public Predicate toPredicate(Root<User> root, CriteriaQuery<?>
criteriaQuery, CriteriaBuilder criteriaBuilder) {
Subquery<Usercard> subquery =
criteriaQuery.subquery(Usercard.class);
Root<Usercard> subRoot = subquery.from(Usercard.class);
List<Predicate> predicates = new ArrayList<>();
//predicates for Users table
predicates.add(criteriaBuilder.and(criteriaBuilder.equal(
root.get("email"), email)));
//predicates for Usercard table
predicates.add(criteriaBuilder.equal(subRoot.get("account"),
account));
return criteriaBuilder.and(predicates.toArray(new
Predicate[predicates.size()]));
}
});
}
So, I need a method for dynamic search within a few tables where I can pass arguments for a dynamic query as well as a dynamic subquery. I would be grateful for any help.
Found this solution:
public List<User> findByUsercardAccount(String account, String email) {
return userRepository.findAll((Specification<User>) (root, criteriaQuery, criteriaBuilder) -> {
Subquery<User> subquery = criteriaQuery.subquery(User.class);
Root<Usercard> subRoot = subquery.from(Usercard.class);
List<Predicate> predicates = new ArrayList<>();
List<Predicate> subPredicates = new ArrayList<>();
if (account != null)
subPredicates.add(criteriaBuilder.equal(subRoot.get("account"), account));
if (email != null)
predicates.add(criteriaBuilder.and(criteriaBuilder.equal(root.get("email"), email)));
subquery.select(subRoot.get("id")).where(subPredicates.toArray(new Predicate[predicates.size()]));
predicates.add(criteriaBuilder.exists(subquery));
return criteriaBuilder.and(predicates.toArray(new Predicate[predicates.size()]));
});
}
}
So, I've created subroot and subquery, added some restrictions (subPredicates) and passed them to the main root as a predicate.
Suppose I have a bidirectional 1-1 association with the Person entity
#Entity
public class Person {
#OneToOne(optional=false)
#JoinColumn(name = "contact_id")
private Contact contact;
// getters/setters/constructors
}
And the Contact Entity
#Entity
public class Contact {
#OneToOne(mappedBy="contact")
private Person person;
// getters/setters/
}
I couldn't find a way to select parent object for Person Entity using the Contact entity. Like so...
criteriaQuery.select(root.get(Contact_.person));
I get this error:
Incompatible types. Required Selection<? extends capture of ?> but 'get' was inferred to Path<Y>: no instance(s) of type variable(s) exist so that Person conforms to capture of ?
Is there a way of doing this? I wanted to return a Predicate for Person Entity using the Contact root. For eg.
public static Specification<Person> phoneWithCountryCode(String countryCode) {
return new Specification<Person>() {
#Override
public Predicate toPredicate(
Root<Contact> root,
CriteriaQuery<?> criteriaQuery,
CriteriaBuilder criteriaBuilder
) {
String startsWithPattern = countryCode + "%";
criteriaQuery.select(root.get(Contact_.person));
return criteriaBuilder.like(
root.get(Contact_.phone), startsWithPattern
);
}
};
}
Yes, you can do.
I did it.I Have Relationship ( Book -- Review).
In your case create Specification<Person> and use join with contact.
like this,
Join joins = root.join("contact");
If help requires just follow my code.
public class BookSpecification {
public static Specification<Book> getBookByNameAndReviewId(){
return new Specification<Book> () {
#Override
public Predicate toPredicate(Root<Book> root, CriteriaQuery<?> query, CriteriaBuilder cb)
{
//List<javax.persistence.criteria.Predicate>
List<Predicate> predicates = new ArrayList<>();
predicates.add(cb.equal(root.get("name"), "spring boot"));
Join joins = root.join("reviews");
predicates.add(cb.equal(joins.get("no") , 584));
return cb.and(predicates.toArray(new Predicate[predicates.size()]));
// return cb.equal(root, predicates);
}
};
}
}
I have a book class with a list of authors:
#Entity
#Table(name = "book")
public class Book extends Content {
#ManyToMany(fetch = FetchType.LAZY)
private List<Author> authors;
...}
Now, this is my BookSpecifications class:
public static Specification<Book> authorIdIs(Long authorId) {
return new Specification<Book>() {
#Override
public Predicate toPredicate(Root<Book> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
return cb.isTrue(root.get("authors").get("id").in(authorId));
}
};
}
How can check the given authorId is in the list?
Error:
java.lang.IllegalStateException: Illegal attempt to dereference path source [null.authors] of basic type
at org.hibernate.jpa.criteria.path.AbstractPathImpl.illegalDereference(AbstractPathImpl.java:98) ~[hibernate-entitymanager-4.3.11.Final.jar:4.3.11.Final]
at org.hibernate.jpa.criteria.path.AbstractPathImpl.get(AbstractPathImpl.java:191) ~[hibernate-entitymanager-4.3.11.Final.jar:4.3.11.Final]
at com.tarameshgroup.derakht.service.specs.BookSpecifications$3.toPredicate(BookSpecifications.java:40) ~[classes/:na]
at org.springframework.data.jpa.domain.Specifications$ComposedSpecification.toPredicate(Specifications.java:189) ~[spring-data-jpa-1.9.2.RELEASE.jar:na]
For that you can use Join with predicate:
Refer below code,
public static Specification<Book> authorIdIs(Long authorId) {
return new Specification<Book>() {
#Override
public Predicate toPredicate(Root<Book> root, CriteriaQuery<?> criteriaQuery, CriteriaBuilder cb) {
Join join = root.join("authors");
return cb.equal(join.get("id"),authorId);
}
};
}
I have to implement a search method with a filter. This is the structure:
My Jpa interface:
public interface FooRepository extends JpaRepository<Foo, Long>, FooRepositoryCustom {
}
My custom interface:
public interface FooRepositoryCustom {
Page<Foo> findByFilter(FooFilter filter, Pageable pageable);
}
My custom implementation:
public class FooRepositoryImpl implements FooRepositoryCustom {
#PersistenceContext
private EntityManager em;
#Override
public Page<Foo> findByFilter(FppFilter filter, Pageable pageable) {
CriteriaQuery<Foo> criteriaQuery = em.getCriteriaBuilder().createQuery(Foo.class);
Root<Foo> root = criteriaQuery.from(Foo.class);
criteriaQuery.select(root);
List<Predicate> predicates = new ArrayList<Predicate>();
if (filter.getFooAttr1() != null) {
predicates.add(em.getCriteriaBuilder().equal(root.get("fooAttr1"), filter.getFooAttr1()));
}
if (filter.getOtherFooId() != null) {
Join<Foo, OtherFoo> join = root.join("otherFoo", JoinType.LEFT);
predicates.add(em.getCriteriaBuilder().equal(join.get("id"), filter.getOtherFooId()));
}
criteriaQuery.where(predicates.toArray(new Predicate[] {}));
// Order
List<Order> orderList = new ArrayList<Order>();
for (org.springframework.data.domain.Sort.Order order : pageable.getSort()) {
if (order.getDirection().equals(Direction.ASC)) {
orderList.add(em.getCriteriaBuilder().asc(root.get(order.getProperty())));
} else {
orderList.add(em.getCriteriaBuilder().desc(root.get(order.getProperty())));
}
}
criteriaQuery.orderBy(orderList);
int totalRows = em.createQuery(criteriaQuery).getResultList().size();
em.createQuery(criteriaQuery).setFirstResult(pageable.getPageNumber() * pageable.getPageSize());
em.createQuery(criteriaQuery).setMaxResults(pageable.getPageSize());
Page<Foo> page = new PageImpl<Foo>(em.createQuery(criteriaQuery).getResultList(), pageable, totalRows);
return page;
}
}
There is a simple way to return a Page <Foo> without Criteria. Otherwise to add sorting and paging?
I did not know the specifications. This is my solution:
I implemented my espeficicación:
public class FooSpecifications {
public static Specification<Foo> withFilter(final FooFilter filter) {
return new Specification<Foo>() {
#Override
public Predicate toPredicate(Root<Foo> root, CriteriaQuery<?> query,
CriteriaBuilder builder) {
List<Predicate> predicates = new ArrayList<Predicate>();
if (filter.getAttr1() != null) {
predicates.add(builder.equal(root.get("attr1"), filter.getAttr1()));
}
if (filter.getOtherFooId() != null) {
Join<Foo, OtherFoo> join = root.join("otherFoo", JoinType.LEFT);
predicates.add(builder.equal(join.get("id"), filter.getOtherFooId()));
}
return builder.and(predicates.toArray(new Predicate[] {}));
}
};
}
}
I add the method to the interface:
public interface FooRepository extends JpaRepository<Foo, Long>, FooRepositoryCustom {
Page<Foo> findAll(Specification<Foo> specification, Pageable pageable);
}
And for use in Service:
fooRepo.findAll(FooSpecifications.withFilter(filter), pageable);