How to query for entities by their collection value - jpa

I'm using jpa and I have the following entity:
#Entity
#Table(name="favorites_folders")
public class FavoritesFolder {
private static final long serialVersionUID = 1L;
#Id
private String id;
#NotNull
#Size(min = 1, max = 50)
public String name;
#ElementCollection(fetch = FetchType.LAZY)
#CollectionTable(
name="favorites_products",
joinColumns=#JoinColumn(name="folder_id")
)
#Column(name="product_id")
#NotNull
private Set<String> productsIds = new HashSet<String>();
}
What I want to do is to get a set of FavoritesFolder entities that contains the string "favorite-id" in their productsIds member set.
Does anyone know how can it be done in criteria api?
Update:
I'm thinking the following sql should do the trick but I'm not sure how to do it in either JPQL or Criteria API:
select * from favorites_folders join favorites_products on favorites_folders.id = favorites_products.folder_id where favorites_products.product_id = 'favorite-id'

To get a set of FavoritesFolder entities that contains the string "favorite-id" in their productsIds member set using criteria api you should do the following:
CriteriaBuilder cb = em.getCriteriaBuilder(); //em is EntityManager
CriteriaQuery<FavoritesFolder> cq = cb.createQuery(FavoritesFolder.class);
Root<FavoritesFolder> root = cq.from(FavoritesFolder.class);
Expression<Collection<String>> productIds = root.get("productsIds");
Predicate containsFavoritedProduct = cb.isMember("favorite-id", productIds);
cq.where(containsFavoritedProduct);
List<FavoritesFolder> favoritesFolders = em.createQuery(cq).getResultList();
More information on Collections in JPQL and Criteria Queries.

Just another way using IN
#Entity
public class UserCategory implements Serializable {
private static final long serialVersionUID = 8261676013650495854L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#ElementCollection
private List<String> categoryName;
(...)
}
Then you can write a Criteria query like
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<UserCategory> q = cb.createQuery(UserCategory.class);
Root<UserCategory> root = q.from(UserCategory.class);
Predicate predicate = cb.conjunction();
Predicate p1 = cb.equal(root.get(UserCategory_.targetSiteType), siteType.getName());
Predicate p2 = root.get(UserCategory_.categoryName).in(category);
predicate = cb.and(p1,p2);
q.where(predicate);
TypedQuery<UserCategory> tq = entityManager.createQuery(q);
List<UserCategory> all = tq.getResultList();
if (all == null || all.size() == 0){
return null;
}else if (all.size() > 1){
throw new Exception("Unexpected result - "+all.size());
}else{
return all.get(0);
}

This is my work around that works.
I'm using Springboot 1.5.9. I don't have time to identify the root cause. What I know is such nested property been ignored when get through JacksonMappingAwareSortTranslator.
So what I did to workaround this is not to use Sort object created by resolvers.
Here's my code in Kotlin. Without doing this, the pageable.sort is null and sorting does not work. And my code will create a new PageRequest object that has non-null sort that works.
#RequestMapping("/searchAds", method = arrayOf(RequestMethod.POST))
fun searchAds(
#RequestBody cmd: AdsSearchCommand,
pageable: Pageable,
resourceAssembler: PersistentEntityResourceAssembler,
sort: String? = null
): ResponseEntity<PagedResources<Resource<Ads>>> {
val page = adsService.searchAds(cmd, pageable.repairSortIfNeeded(sort))
resourceAssembler as ResourceAssembler<Ads, Resource<Ads>>
return adsPagedResourcesAssembler.toResource(page, resourceAssembler).toResponseEntity()
}
fun Pageable.repairSortIfNeeded(sort: String?): Pageable {
return if (sort.isNullOrEmpty() || this.sort != null) {
this
} else {
sort as String
val sa = sort.split(",")
val direction = if (sa.size > 1) Sort.Direction.valueOf(sa[1]) else Sort.Direction.ASC
val property = sa[0]
PageRequest(this.pageNumber, this.pageSize, direction, property)
}
}

Related

How to query a many to many entity list via Specification API in Spring Data JPA

I have 2 entities with Many-To-Many relationships
public class Enterprise{
#Id
#Column(name = "id", nullable = false, length = 50)
#GeneratedValue(generator = "jpa-uuid")
private String id;
fields...
#ManyToMany
#JoinTable(name = "enterprise_to_tag",
joinColumns = #JoinColumn(name = "enterprise"),
inverseJoinColumns = #JoinColumn(name = "tag"))
private Set<EnterpriseTag> tags;
}
and
public class EnterpriseTag{
#Id
#Column(name = "id", nullable = false, length = 50)
#GeneratedValue(generator = "jpa-uuid")
private String id;
fields...
#ManyToMany(mappedBy = "tags")
private Set<Enterprise> enterprises;
}
I want to query enterprise list by some tags' ID then pack them to Page
private Page<Enterprise> searchEnterprise(int number, int size, String keyword, String tags, String county)
throws BusinessException {
validPageNumberAndPageSize(number, size);
Pageable pageable = PageRequest.of(number, size);
Specification<Enterprise> specification = (Specification<Enterprise>) (root, criteriaQuery, criteriaBuilder) -> {
criteriaQuery.distinct(true);
List<Predicate> predicates = new ArrayList<>();
if (StringUtils.isNoneBlank(keyword)) {
Predicate predicateName = criteriaBuilder.like(root.get("name"), "%" + keyword + "%");
Predicate predicateSerialNumber = criteriaBuilder.like(root.get("serialNumber"), "%" + keyword + "%");
predicates.add(criteriaBuilder.and(criteriaBuilder.or(predicateName, predicateSerialNumber, predicateOrganizationCode)));
}
return criteriaQuery.where(predicates.toArray(new Predicate[0])).getRestriction();
};
//filter by tags here
if (StringUtils.isNoneBlank(tags)) {
List<String> tagIds = Arrays.asList(StringUtils.split(tags, ','));
List<Enterprise> enterprises = enterpriseRepository.findAll(specification).stream().filter(enterprise ->
enterprise.getTags().stream().map(EnterpriseTag::getId).collect(Collectors.toList()).containsAll(tagIds))
.collect(Collectors.toList());
return new PageImpl<>(enterprises, pageable, enterprises.size());
} else {
return enterpriseRepository.findAll(specification, pageable);
}
}
I don't know how to write this query. I have to handle it base on a database query result. But it's risky. If too much data is queried from the database, it will take up a lot of memory. Please help me to write this query by Specification API. Thanks.

Why doesn't JPA repeat persist method throw an exception?

Product product = new Product();
product.setName( "foo" );
product.setPrice(BigDecimal.valueOf( 4.5 ) );
pm.create( product ); // pm delegates calls to an entity manager object using persist method and tx is immediately commited after the call
List<Product> products = pm.findAllProducts();
products.stream().forEach( System.out::println ); // New product is listed too.
pm.create( product ); // Causes no exception! But, as per API, it should.
products = pm.findAllProducts(); // Fetch successful
products.stream().forEach( System.out::println ); // No difference from first print.
As per persistence API, if an entity alredy exists, persist(called from pm.create) throw's EntityExistsException, but its not happening as per code.
Pesistence provider(PP) - EclipseLink.
Why is PP ignoring repeat persist?
In what circumstances does a PP choose to throw an exception?
EDIT:
Product.java
NOTE:
Excluded getters and setters(for all fields) and toString() for brevity.
I tried my best to format code as per guidelines, but its not happening, please bear.
#Entity #Table(name = "PRODUCTS") #XmlRootElement #NamedQueries({
#NamedQuery(name = "Product.findAll", query = "SELECT p FROM Product p")
, #NamedQuery(name = "Product.findById", query = "SELECT p FROM Product p WHERE p.id = :id")
, #NamedQuery(name = "Product.findByName", query = "SELECT p FROM Product p WHERE p.name like :name")
, #NamedQuery(name = "Product.findByPrice", query = "SELECT p FROM Product p WHERE p.price = :price")
, #NamedQuery(name = "Product.findByBestBefore", query = "SELECT p FROM Product p WHERE p.bestBefore = :bestBefore")
, #NamedQuery(name = "Product.findByVersion", query = "SELECT p FROM Product p WHERE p.version = :version")
, #NamedQuery(name = "Product.findTotal", query = "SELECT count(p.id), sum(p.price) FROM Product p WHERE p.id in :ids" ) })
public class Product implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#NotNull
#SequenceGenerator( name="pidGen", sequenceName="PID_SEQ", allocationSize=1 )
#GeneratedValue( strategy=SEQUENCE, generator="pidGen" )
private Integer id;
#Basic(optional = false)
#NotNull
#Size(min = 3, max = 40, message="{prod.name}")
private String name;
// #Max(value=?) #Min(value=?)//if you know range of your decimal fields consider using these annotations to enforce field validation
#Basic(optional = false)
#NotNull
#Max( value=1000, message="{prod.price.max}")
#Min( value=1, message="{prod.price.min}")
private BigDecimal price;
#Column(name = "BEST_BEFORE")
#Temporal(TemporalType.DATE)
//private Date bestBefore;
private LocalDate bestBefore;
#Version
private Integer version;
public Product() {
}
public Product(Integer id) {
this.id = id;
}
public Product(Integer id, String name, BigDecimal price) {
this.id = id;
this.name = name;
this.price = price;
}
#Override
public int hashCode() {
int hash = 0;
hash += (id != null ? id.hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
// TODO: Warning - this method won't work in the case the id fields are not set
if (!(object instanceof Product)) {
return false;
}
Product other = (Product) object;
if ((this.id == null && other.id != null) || (this.id != null && !this.id.equals(other.id))) {
return false;
}
return true;
}
}
As per the JPA Spec:
If X is a new entity, it becomes managed. The entity X will be entered into the database at or
before transaction commit or as a result of the flush operation.
If X is a preexisting managed entity, it is ignored by the persist operation (...)
If X is a detached object, the EntityExistsException may be thrown when the persist
operation is invoked, or the EntityExistsException or another PersistenceException may be thrown at flush or commit time
When you invoke EntityManager.persist(product), product becomes a managed entity (#1). Any subsequent calls to EntityManager.persist(product) are ignored, as described in #2. The final point applies only when you try to invoke persist() on a detached entity.

How to use JPA criteriaBuilder to search on attributes in a collection of sub-attributes

I have an Entity that maps to a table defined this way:
#Entity
#Table(name = "cmmn_calendar_evnt")
public class CommonCalendarEvent implements java.io.Serializable
{
private Integer cevId;
private Set<CommonCalendarEventPart> commonCalendarEventParts = new HashSet<CommonCalendarEventPart>(0)
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "CEV_ID", unique = true, nullable = false)
public Integer getCevId()
{
return this.cevId;
}
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "commonCalendarEvent")
public Set<CommonCalendarEventPart> getCommonCalendarEventParts()
{
return this.commonCalendarEventParts;
}
}
and CommonCalendarEventPart is defined like this:
#Entity
#Table(name = "cmmn_calendar_evnt_part")
public class CommonCalendarEventPart implements java.io.Serializable
{
private static final long serialVersionUID = 1L;
private Integer ceeId;
private CommonCalendarEvent commonCalendarEvent;
private PartParticipant partParticipant;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "CEE_ID", unique = true, nullable = false)
public Integer getCeeId()
{
return this.ceeId;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "CEE_CEV_ID", nullable = false)
public CommonCalendarEvent getCommonCalendarEvent()
{
return this.commonCalendarEvent;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "CEE_PPT_ID", nullable = false)
public PartParticipant getPartParticipant()
{
return this.partParticipant;
}
}
and finally:
#Entity
#Table(name = "part_participant")
public class PartParticipant implements java.io.Serializable
{
private static final long serialVersionUID = 1L;
private Integer pptId;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "PPT_ID", unique = true, nullable = false)
public Integer getPptId()
{
return this.pptId;
}
}
I want to use the CriteriaBuilder to generate a query finding all CommonCalendarEvent for a specific Participant ID.
In Hql it would look something like this: (although I have not confirmed that this Hql is correct either)
"from commonCalendarEvent cce where :pptId in (cce.commonCalendarEventParts.partParticipant.pptId)"
I've tried some approaches of what I thought were intuitive attempts at writing a criteriaBuilder approach, but my attempts have resulted in errors ranging from:
“unexpected end of subtree” to just implementation errors.
.....
CriteriaBuilder builder = getEntityManager().getCriteriaBuilder();
CriteriaQuery<CommonCalendarEvent> criteria = builder.createQuery(CommonCalendarEvent.class);
Root<CommonCalendarEvent> root = criteria.from(CommonCalendarEvent.class);
Fetch<CommonCalendarEvent, CommonCalendarEventPart> evf = root.fetch(CommonCalendarEvent_.commonCalendarEventParts, JoinType.LEFT);
Join<CommonCalendarEvent, CommonCalendarEventPart> evj = (Join<CommonCalendarEvent, CommonCalendarEventPart>) evf;
Join<CommonCalendarEventPart, PartParticipant> evpj = evj.join(CommonCalendarEventPart_.partParticipant);
List<Predicate> pred = new ArrayList<Predicate>();
pred.add(builder.equal(evpj.get(PartParticipant_.pptId), pptId));
criteria.where(builder.and(pred.toArray(new Predicate[] {})));
return getEntityManager().createQuery(criteria).getResultList();
.............
above yields an "unexpected end of subtree" error.
Any Help is appreciated.
+1 for using Lazy initialization. The JPA model is Object, or Entity oriented, so you need to get used to thinking in those terms. A PartParticipant is not identified by its id in JPA, but by the object itself. Assuming you have a list of participants:
PartParticipant pp = em.find(PartParticipant.class, 2);
List<PartParticipant> pps = new ArrayList<PartParticipant>();
pps.add(pp);
Then you pass that list to the queries. In JPQL:
TypedQuery<CommonCalendarEvent> cev = em.createQuery("select cev from CommonCalendarEvent cev join fetch cev.commonCalendarEventParts cce where cce.partParticipant in :pps", CommonCalendarEvent.class);
List<CommonCalendarEvent> cevs = cev.setParameter("pps", pps).getResultList();
Notice the fetch is needed to prevent LazyInitializationExceptions.
Knowing the JPQL, the CriteriaQuery should follow pretty much the same:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<CommonCalendarEvent> q = cb.createQuery(CommonCalendarEvent.class);
Root<CommonCalendarEvent> r = q.from(CommonCalendarEvent.class);
Join<CommonCalendarEvent, CommonCalendarEventPart> j = r.join("commonCalendarEventParts");
r.fetch("commonCalendarEventParts");
q.select(r).where(j.get("partParticipant").in(pps));
List<CommonCalendarEvent> rs = em.createQuery(q).getResultList();
You don't need to do anything special with the fetch other than execute it. As you can see, the query uses the PartParticipant Id.
select
commoncale0_.CEV_ID as CEV_ID1_0_0_,
commoncale1_.CEE_ID as CEE_ID1_1_1_,
commoncale1_.CEE_CEV_ID as CEE_CEV_2_1_1_,
commoncale1_.CEE_PPT_ID as CEE_PPT_3_1_1_,
commoncale1_.CEE_CEV_ID as CEE_CEV_2_0_0__,
commoncale1_.CEE_ID as CEE_ID1_1_0__
from cmmn_calendar_evnt commoncale0_
inner join cmmn_calendar_evnt_part commoncale1_ on commoncale0_.CEV_ID=commoncale1_.CEE_CEV_ID
where commoncale1_.CEE_PPT_ID in (?)
Fetch<CommonCalendarEvent, CommonCalendarEventPart> evf is not necessary, and the first join statement should be corrected:
Join<CommonCalendarEvent, CommonCalendarEventPart> evj =
root.join(CommonCalendarEvent_.commonCalendarEventParts);
The rest of the query seems correct.

JPA dynamic criteria-api query

I wonder if there is a generic way to use the criteria api in combination with a little more complex model?
I have an entity class that has one-to-one relationships to other entities. My service wrapper that does the database query via the criteria api gets the parameters from front end to figure out pagination, sorting and filtering.
Entities
#Entity
public class Person implements Serializable {
#Id
private Long id;
private String name;
private String givenName;
#Temporal(TemporalType.DATE)
private Date birthdate;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "INFORMATION_ID")
private Information information;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "ADDRESS_ID")
private Address address;
...
}
#Entity
public class Information implements Serializable {
#Id
private Long id;
private String detail;
...
}
#Entity
public class Address implements Serializable {
#Id
private Long id;
private String street;
private String city;
...
}
Service
#Stateless
public class PersonService {
#PersistenceContext(unitName = "ProblemGenericDatatableFilterPU")
private EntityManager em;
public List<Person> findAllPersons222(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters) {
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Person> criteriaQuery = builder.createQuery(Person.class);
Root<Person> rootPerson = criteriaQuery.from(Person.class);
Join<Person, Information> joinPersonInformation = rootPerson.join(Person_.information);
Join<Person, Address> joinPersonAddress = rootPerson.join(Person_.address);
// select
criteriaQuery.select(rootPerson);
// filter
List<Predicate> allPredicates = new ArrayList<>();
for(Entry<String, Object> currentEntry : filters.entrySet()) {
Predicate currentPredicate;
if(currentEntry.getKey().startsWith("information_")) {
currentPredicate = builder.like(
builder.lower(joinPersonInformation.<String>get(currentEntry.getKey())),
builder.lower(builder.literal(String.valueOf(currentEntry.getValue())))
);
}
else if(currentEntry.getKey().startsWith("address_")) {
currentPredicate = builder.like(
builder.lower(joinPersonAddress.<String>get(currentEntry.getKey())),
builder.lower(builder.literal(String.valueOf(currentEntry.getValue())))
);
}
else {
currentPredicate = builder.like(
builder.lower(rootPerson.<String>get(currentEntry.getKey())),
builder.lower(builder.literal(String.valueOf(currentEntry.getValue())))
);
}
allPredicates.add(currentPredicate);
}
criteriaQuery.where(builder.and(allPredicates.toArray(new Predicate[0])));
// order
if(sortField != null && !sortField.isEmpty()) {
Order orderBy;
if(sortField.startsWith("information_")) {
orderBy = (sortOrder == SortOrder.DESCENDING
? builder.desc(joinPersonInformation.get(sortField))
: builder.asc(joinPersonInformation.get(sortField)));
}
else if(sortField.startsWith("address_")) {
orderBy = (sortOrder == SortOrder.DESCENDING
? builder.desc(joinPersonAddress.get(sortField))
: builder.asc(joinPersonAddress.get(sortField)));
}
else {
orderBy = (sortOrder == SortOrder.DESCENDING
? builder.desc(rootPerson.get(sortField))
: builder.asc(rootPerson.get(sortField)));
}
criteriaQuery.orderBy(orderBy);
}
Query query = em.createQuery(criteriaQuery);
// pagination
query.setFirstResult(first);
query.setMaxResults(pageSize);
return query.getResultList();
}
}
I need to do a distinction of cases for filtering and sorting depending on the root/join on which I am accessing the property. Plus I need to use a naming convention in the facelet. The same goes for the count-query except for sorting.
Now I ask myself whether there is some "dot-notation" or anything which makes the case dispensable. In e. g. native SQL I would do something like create a subquery and select all alias values from the inner projection (select * from (select person.name as name, address.street as street, ...) where name = ... and street like ...).
I would be grateful for any advice.
Finally I got the time to deal with my problem. I found a solution thats not perfect, but works for me.
As I searched for another problem I came to this article by Leonardo Shikida and found a very handy Path<?> getPath(...) method (I also had a deeper look into the brilliant inheritance relationships in the CriteriaAPI: Path, Root, Join, From, etc). With that in mind I remermbered my former problem and thought for a more gerneric way of this method. So here is what I made of this:
At first I create all the joins I need (i. e. Root<?> and Join<? ?>) and put them in a Map<String, From<?, ?>> where the String is an element on which an attribute is queried in a dotted notation (naming convention and the downside on the complete solution) and the From is the corresponding source.
With the Map I can do filtering and sorting in a more or less generic way.
To make it work the front end needs to use the very same naming convention and pass the filters-Map accordingly (i. e. JSF using primefaces field attribute in p:column).
public List<Person> newFindAllPersons(int first, int pageSize, String sortField, SortOrder sortOrder, Map<String, Object> filters)
{
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Person> criteriaQuery = builder.createQuery(Person.class);
// setting up the required joins
Root<Person> rootPerson = criteriaQuery.from(Person.class);
Join<Person, Information> joinPersonInformation = rootPerson.join(Person_.information);
Join<Person, Address> joinPersonAddress = rootPerson.join(Person_.address);
Join<Address, Information> joinAddressInformation = joinPersonAddress.join(Address_.information);
// putting all joins into a map with a dot`ted name
Map<String, From<?, ?>> mapFieldToFrom = new HashMap<>();
mapFieldToFrom.put("person", rootPerson);
mapFieldToFrom.put("person.address", joinPersonAddress);
mapFieldToFrom.put("person.information", joinPersonInformation);
mapFieldToFrom.put("person.address.information", joinAddressInformation);
// select
criteriaQuery.select(rootPerson);
// filter
List<Predicate> allPredicates = new ArrayList<>();
for(Entry<String, Object> currentEntry : filters.entrySet())
{
Predicate currentPredicate = builder.like(
builder.lower(getStringPath(currentEntry.getKey(), mapFieldToFrom)),
builder.lower(builder.literal("%" + String.valueOf(currentEntry.getValue()) + "%"))
);
allPredicates.add(currentPredicate);
}
criteriaQuery.where(builder.and(allPredicates.toArray(new Predicate[0])));
// order
if(sortField != null && !sortField.isEmpty())
{
Path<?> actualPath = getStringPath(sortField, mapFieldToFrom);
Order orderBy = (sortOrder == SortOrder.DESCENDING
? builder.desc(actualPath)
: builder.asc(actualPath));
criteriaQuery.orderBy(orderBy);
}
Query query = em.createQuery(criteriaQuery);
// pagination
query.setFirstResult(first);
query.setMaxResults(pageSize);
return query.getResultList();
}
/**
* divides the given field at the last dot and takes <br>
* - the first part as the key in the map to retrieve the From<?, ?> <br>
* - the last part as the name of the column in the entity
*/
private Path<String> getStringPath(String field, Map<String, From<?, ?>> mapFieldToFrom)
{
if(!field.matches(".+\\..+"))
{
throw new IllegalArgumentException("field '" + field + "' needs to be a dotted path (i. e. customer.address.city.zipcode)");
}
String fromPart = field.substring(0, field.lastIndexOf('.'));
String fieldPart = field.substring(field.lastIndexOf('.') + 1);
From<?, ?> actualFrom = mapFieldToFrom.get(fromPart);
if(actualFrom == null)
{
throw new IllegalStateException("the given map does not contain a from or for the value '" + fromPart + "' or is null");
}
return actualFrom.get(fieldPart);
}
Example front end
<p:dataTable>
<!-- mapFieldToFrom.put("person", rootPerson); -->
<p:column field="person.name">
</p:column>
<!-- mapFieldToFrom.put("person.address", joinPersonAddress); -->
<p:column field="person.address.street">
</p:column>
</p:dataTable>

JPA Criteria API and making query with WHERE predicate containing .in()

I would like to make some query where my predicate is like this:
CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder();
CriteriaQuery<ProviderService> criteriaQuery = criteriaBuilder.createQuery(ProviderService.class);
// FROM
Root<ProviderService> providerService = criteriaQuery.from(ProviderService.class);
// SELECT
criteriaQuery.select(providerService);
// WHERE'S PREDICATE
List<Predicate> predicates = new ArrayList<>();
if(providers != null && providers.size() > 0) {
predicates.add(providerService.get(ProviderService_.provider).in(providers));
}
criteriaQuery.where(predicates.toArray(new Predicate[] { }));
TypedQuery<ProviderService> query = getEntityManager().createQuery(criteriaQuery);
return query.getResultList();
I check search for provider offers (ProviderService) that are supplied by any provider form collection (List) of providers.
I have received exception something like this:
Caused by: java.lang.IllegalArgumentException: Unaware how to convert value [pl.salonea.entities.Provider#85eab3b6 : pl.salonea.entities.Provider] to requested type [java.lang.Long]
at org.hibernate.jpa.criteria.ValueHandlerFactory.unknownConversion(ValueHandlerFactory.java:258)
at org.hibernate.jpa.criteria.ValueHandlerFactory.access$000(ValueHandlerFactory.java:34)
at org.hibernate.jpa.criteria.ValueHandlerFactory$LongValueHandler.convert(ValueHandlerFactory.java:152)
at org.hibernate.jpa.criteria.ValueHandlerFactory$LongValueHandler.convert(ValueHandlerFactory.java:139)
at org.hibernate.jpa.criteria.predicate.InPredicate.<init>(InPredicate.java:130)
at org.hibernate.jpa.criteria.predicate.InPredicate.<init>(InPredicate.java:108)
at org.hibernate.jpa.criteria.CriteriaBuilderImpl.in(CriteriaBuilderImpl.java:529)
at org.hibernate.jpa.criteria.expression.ExpressionImpl.in(ExpressionImpl.java:79)
EDITED:
I think it could have something to do with ProviderService composite Id that consists of (Provider, Service) and is defined as follows:
#Id
#NotNull
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "provider_id", referencedColumnName = "provider_id", nullable = false, columnDefinition = "BIGINT UNSIGNED")
public Provider getProvider() {
return provider;
}
public void setProvider(Provider provider) {
this.provider = provider;
}
#Id
#NotNull
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "service_id", referencedColumnName = "service_id", nullable = false, columnDefinition = "INT UNSIGNED")
public Service getService() {
return service;
}
public void setService(Service service) {
this.service = service;
}
And have proper IdClass as below:
public class ProviderServiceId implements Serializable {
private Long provider;
private Integer service;
/* constructors */
public ProviderServiceId() { }
public ProviderServiceId(Long providerId, Integer serviceId) {
this.provider = providerId;
this.service = serviceId;
}
// etc.
OR maybe there isn't such possibilities to compare entity attribute against list of possible values of that attribute (list of entities)
Suggested joining seems to work correctly:
// inner joining
if(provider == null) provider = providerService.join(ProviderService_.provider);
predicates.add(provider.in(providers));
I think that searching by passing list of Provider IDs rather than Provider entities will also work but haven't checked it.