Hibernate: projection JPQL query to DTO issue - java

To start with, I'll list three models that I work with in a query
ProductEntity:
#Entity
#Table(name = "product")
public class ProductEntity extends BaseEntity {
//some fields
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "owner_id")
private PartnerEntity owner;
#OneToMany(
mappedBy = "product",
fetch = FetchType.LAZY
)
private List<StockProductInfoEntity> stocks;
}
PartnerEntity:
#Entity
#Table(name = "partner")
public class PartnerEntity extends AbstractDetails {
//some fields
#OneToMany(
mappedBy = "owner",
fetch = FetchType.LAZY
)
private List<ProductEntity> products;
}
and StockProductInfoEntity:
#Entity
#Table(name = "stock_product")
public class StockProductInfoEntity extends BaseEntity {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "product_id")
private ProductEntity product;
//other fields
#Column(name = "rest")
private int rest;
}
And i want to fetch from database product with partner + calculate count in all stocks.
For convenience, I created a simple DTO:
#Getter
#AllArgsConstructor
public class ProductCountDTO {
private ProductEntity productEntity;
private int count;
//hack for hibernate
public ProductCountDTO(ProductEntity productEntity, long count) {
this.productEntity = productEntity;
this.count = (int) count;
}
}
and write JPQL query in JPA repository:
#Query("select new ru.oral.market.persistence.entity.product.util.ProductCountDTO(p, sum(stocks.rest))"+
" from ProductEntity p" +
" join fetch p.owner owner" +
" join p.stocks stocks" +
" where p.id = :id" +
" group by p, owner")
Optional<ProductCountDTO> findProductWithCount(#Param("id") long id);
But my application did not even start because of a problem with the query validation. I get this message:
Caused by: org.hibernate.QueryException: query specified join
fetching, but the owner of the fetched association was not present in
the select list
Very strange, but I tried to replace join fetch -> join.
And I understood why I got this error, hibernate made such a query to the database:
select
productent0_.id as col_0_0_,
sum(stocks2_.rest) as col_1_0_
from
product productent0_
inner join
partner partnerent1_
on productent0_.owner_id=partnerent1_.user_id
inner join
stock_product stocks2_
on productent0_.id=stocks2_.product_id
where
productent0_.id=?
group by
productent0_.id ,
partnerent1_.user_id
But why does he only take the product id and nothing else?
This query with Tuple work and get all fields from product and partner
#Query("select p, sum(stocks.rest) from ProductEntity p" +
" join fetch p.owner owner" +
" join p.stocks stocks" +
" where p.id = :id" +
" group by p, owner")
Optional<Tuple> findProductWithCount(#Param("id") long id);
And this produced native query what i want:
select
productent0_.id as col_0_0_,
sum(stocks2_.rest) as col_1_0_,
partnerent1_.user_id as user_id31_12_1_,
productent0_.id as id1_14_0_,
productent0_.brand_id as brand_i17_14_0_,
productent0_.commission_volume as commissi2_14_0_,
productent0_.created as created3_14_0_,
productent0_.description as descript4_14_0_,
productent0_.height as height5_14_0_,
productent0_.length as length6_14_0_,
productent0_.long_description as long_des7_14_0_,
productent0_.name as name8_14_0_,
productent0_.old_price as old_pric9_14_0_,
productent0_.owner_id as owner_i18_14_0_,
productent0_.pitctures as pitctur10_14_0_,
productent0_.price as price11_14_0_,
productent0_.status as status12_14_0_,
productent0_.updated as updated13_14_0_,
productent0_.vendor_code as vendor_14_14_0_,
productent0_.weight as weight15_14_0_,
productent0_.width as width16_14_0_,
partnerent1_.about_company as about_co1_12_1_,
partnerent1_.bik as bik2_12_1_,
partnerent1_.bank_inn as bank_inn3_12_1_,
partnerent1_.bank_kpp as bank_kpp4_12_1_,
partnerent1_.bank as bank5_12_1_,
partnerent1_.bank_address as bank_add6_12_1_,
partnerent1_.checking_account as checking7_12_1_,
partnerent1_.correspondent_account as correspo8_12_1_,
partnerent1_.company_name as company_9_12_1_,
partnerent1_.company_inn as company10_12_1_,
partnerent1_.company_kpp as company11_12_1_,
partnerent1_.ogrn as ogrn12_12_1_,
partnerent1_.okato as okato13_12_1_,
partnerent1_.actual_address as actual_14_12_1_,
partnerent1_.director as directo15_12_1_,
partnerent1_.full_name as full_na16_12_1_,
partnerent1_.legal_address as legal_a17_12_1_,
partnerent1_.short_name as short_n18_12_1_,
partnerent1_.country as country19_12_1_,
partnerent1_.discount_conditions as discoun20_12_1_,
partnerent1_.discounts as discoun21_12_1_,
partnerent1_.logo as logo22_12_1_,
partnerent1_.min_amount_order as min_amo23_12_1_,
partnerent1_.min_shipment as min_shi24_12_1_,
partnerent1_.min_sum_order as min_sum25_12_1_,
partnerent1_.own_delivery as own_del26_12_1_,
partnerent1_.own_production as own_pro27_12_1_,
partnerent1_.phones as phones28_12_1_,
partnerent1_.return_information as return_29_12_1_,
partnerent1_.site as site30_12_1_
from
product productent0_
inner join
partner partnerent1_
on productent0_.owner_id=partnerent1_.user_id
inner join
stock_product stocks2_
on productent0_.id=stocks2_.product_id
where
productent0_.id=?
group by
productent0_.id ,
partnerent1_.user_id
But it's not very convenient.
Why DTO projection doesn't work correctrly, but tuple works fine?

Because that's how Hibernate is currently implemented.
Because you used an entity in the DTO Projection, which as the name implies, it should be used for DTOs, not entities, Hibernate is going to assume that you want to GROUP BY by the identifier because it should not GROUP BY all entity properties.
The Tuple is broken and it will only work in MySQL, but not in Oracle or PostgreSQL since your aggregate query selects columns that are not present in the GROUP BY clause.
However, this is not demanded to work according to the JPA specs. Nevertheless, you should still provide a replicating test case and open an issue so that the behavior is the same for both situations.
Anyway, once fixed, it will still GROUP BY identifier. If you want to select entities and group by as well, you will have to use a native SQL query along with the Hibernate ResultTransformer to transform the ResultSet into a graph of objects.
More, fetching entities and aggregations is a code smell. Most likely, you need a DTO projection or a read-only view.
Entities should only be fetched when you want to modify them. Otherwise, a DTO projection is more efficient and more straightforward as well.

Since Vlad already explained the why, I will focus on an alternative solution. Having to specify all attributes that you are really interested in in the SELECT clause and the GROUP BY clause is a lot of work.
If you used Blaze-Persistence Entity Views on top of Hibernate, this could look like the following
#EntityView(ProductEntity.class)
public interface ProductCountDTO {
// Or map the ProductEntity itself if you like..
#Mapping("this")
ProductView getProduct();
#Mapping("sum(stocks.rest)")
int getCount();
}
#EntityView(ProductEntity.class)
public interface ProductView {
// Whatever mappings you like
}
With the Spring Data or DeltaSpike Data integration you can even use it like that
Optional<ProductCountDTO> findById(long id);
It will produce a JPQL query like the following
SELECT
p /* All the attributes you map in ProductView */,
sum(stocks_1.rest)
FROM
ProductEntity p
LEFT JOIN
p.stocks stocks_1
GROUP BY
p /* All the attributes you map in ProductView */
Maybe give it a shot? https://github.com/Blazebit/blaze-persistence#entity-view-usage
The magic is that Blaze-Persistence handles the GROUP BY automatically when encountering an aggregate function by putting every non-aggregate expression you use into the GROUP BY clause if there is at least one aggregate function used.
When using Entity Views instead of entities directly, you won't be facing the join fetch problems as Entity Views will only put the fields you actually map into the resulting SELECT clause of the JPQL and SQL.
Even if you used entities directly or via the ProductCountDTO, the query builder used behind the scenes handles selects of entity types in case of a group by gracefully, just as you'd expect it from Hibernate.

Related

Why are lazy fields on related entities loaded

In my REST API project (Java 8, Spring Boot 2.3.1) I have a problem with some queries triggering massive query chains by loading lazy relations, even though the related objects are never accessed.
I have a UserEntity and a polymorphic CompanyEntity that are related with a ManyToMany relationship. I have an endpoint that returns all users and I include the IDs of the related companies in the JSON. I excpect a query to the user table and a query to the company table, however all related entities of one sub-entity of CompanyEntity are always loaded for each of those sub-entities resulting in large query chains.
Here are snippets of my classes:
User entity
#Entity(name = "USERS")
public class UserEntity {
#Id
#GeneratedValue
private UUID id;
#EqualsAndHashCode.Exclude
#Fetch(FetchMode.SUBSELECT)
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "users_company",
joinColumns = #JoinColumn(name = "USER_ID"),
inverseJoinColumns = #JoinColumn(name = "COMPANY_ID")
)
private Set<CompanyEntity> companies = new HashSet<>();
public List<UUID> getCompanyIds() {
return companies.stream()
.map(CompanyEntity::getId)
.collect(Collectors.toList());
}
}
Polymorphic company entity
#Entity(name = "COMPANY")
#Inheritance(strategy = InheritanceType.JOINED)
public abstract class CompanyEntity {
#Id
#GeneratedValue
private UUID id;
#Fetch(FetchMode.SUBSELECT)
#ManyToMany(mappedBy = "companies", fetch = FetchType.LAZY)
private Set<UserEntity> users = new HashSet<>();
}
Concrete company subclass that triggers the problem
#Entity(name = "CUSTOMER")
public class CustomerEntity extends CompanyEntity {
#NotNull
#OneToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY)
private ContactPersonEntity contactPerson;
#Fetch(FetchMode.SUBSELECT)
#OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}, fetch = FetchType.LAZY, mappedBy = "customer")
private Set<TransactionEntity> transactions = new HashSet<>();
public Set<UUID> getTransactionIds() {
return this.transactions.stream()
.map(TransactionEntity::getId)
.collect(Collectors.toSet());
}
}
In the REST controller I return the following mapping:
#GetMapping(value = "", produces = MediaType.APPLICATION_JSON_VALUE)
public List<UserReadModel> getUsers() {
return userRepository.findAll().stream()
.map(userEntity -> new UserReadModel(userEntity))
.collect(Collectors.toList());
}
Where the UserReadModel is a DTO:
#Data
public class UserReadModel {
private UUID id;
private List<UUID> companyIds;
}
Logging the database queries results in the following output:
// Expected
Hibernate: select userentity0_.id as id1_47_, ... from users userentity0_
Hibernate: select companies0_.user_id ... case when companyent1_1_.id is not null then 1 when companyent1_2_.id is not null then 2 when companyent1_.id is not null then 0 end as clazz_0_ from users_company companies0_ inner join company companyent1_ on companies0_.company_id=companyent1_.id left outer join customer companyent1_1_ on companyent1_.id=companyent1_1_.id left outer join external_editor companyent1_2_ on companyent1_.id=companyent1_2_.id where companies0_.user_id in (select userentity0_.id from users userentity0_)
// Unexpected as they are marked lazy and never accessed
Hibernate: select contactper0_.id ... from contact_person contactper0_ where contactper0_.id=?
Hibernate: select transactio0_.customer_id ... from transactions transactio0_ where transactio0_.customer_id=?
Hibernate: select contactper0_.id ... from contact_person contactper0_ where contactper0_.id=?
Hibernate: select transactio0_.customer_id ... from transactions transactio0_ where transactio0_.customer_id=?
...
I've read through loads of articles on entity mapping and lazy loading but I can't seem to find a reason why this behavior persists. Did anyone have this problem before?
You are accessing the collection, so Hibernate has to load the collection. Since you only need the ids and already have a DTO, I think this is a perfect use case for Blaze-Persistence Entity Views.
I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.
A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:
#EntityView(UserEntity.class)
public interface UserReadModel {
#IdMapping
UUID getId();
#Mapping("companies.id")
Set<UUID> getCompanyIds();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
UserReadModel a = entityViewManager.find(entityManager, UserReadModel.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<UserReadModel> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary! In your case, a query like the following will be generated:
select u.id, uc.company_id
from users u
left join users_company uc on uc.user_id = u.id
left join company c on c.id = uc.company_id
Depending on the Hibernate version, the join for the company might even be omitted.
I eventually figured out the solution and want to post it here, in case anyone stumbles upon this question. This was purely a mistake on my side and is not reproducible from the examples I posted.
I used lombok annotations to generate equals and hashcode methods on the customer entity (and all other entities for that matter) and forgot to annotate the contactPerson and transactions fields with #EqualsAndHashcode.Exclude. As the equals method was called somewhere along the execution, it triggered the lazy loading of those fields. Implementing equals and hashcode manually and using the guidelines from this article for that solved the problem.

Return ResultSet with intact joins in Hibernate / JPA

I have 2 entities: EntityA and EntityB. They are related with a One To Many relation.
public class EntityA {
#Identifier
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="ID", updatable = false, nullable = false)
private long id;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name="ENTITY_A_ID", referencedColumnName="ID", nullable=true)
private List<EntityB> entityBs;
/* GETTERS SETTERS ... */
}
public class EntityB {
#Identifier
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="ID", updatable = false, nullable = false)
private long id;
#Column(name="SOME_PROPERTY")
private String someProperty;
#ManyToOne
#JoinColumn(name="ENTITY_A_ID")
private EntityA entityA;
/* GETTERS SETTERS ... */
}
I have a query that joins EntityA with a LEFT JOIN to Entity B. And a 'ON' clause.
In normal SQL lingo this would be:
select * from EntityA eA left join EntityB eB
on (eA.ID = eB.ENTITY_A_ID and eB.SOME_PROPERTY = "blabla" )
where ...
So I'm having much needed information from my joined resultset. I only want records joined if they match certain properties. I need EntityA, allways, and an attached EntityB if EntityB matched the join clause.
The project is set up with Hibernate / JPA. I can't figure out how to retreive the information needed. At this moment I have:
public class EntityADAO {
public List<EntityA> findMethod() {
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<EntityA> query = builder.createQuery(EntityA.class);
Root<EntityA> entityARoot = query.from(EntityA.class);
Join<EntityA, EntityB> entityBJoin = entityARoot.join("entityB", JoinType.INNER);
entityBJoin.on(new Predicate [] {builder.equal(entityBJoin.get("someProperty"), "fixed_val_for_now"});
/* where clause left out for readability */
TypedQuery<EntityA> q = entityManager.createQuery(query);
return q.getResultList();
}
}
So here I am.. Stuck with my List of EntityAs. whenever I call getEntityBs() on a EntityA, I'm getting all of them.. And this makes sense.. But How can I retrieve the joined set?
I'm stuck with JPA and Hibernate, as this choice is not made by me.
Thanks in advance!
What you need here is a custom projection or DTO. Filtering the entity collection might cause a delete because entities always reflect the current DBMS state and are synchronized at the end of the transaction.
You can write a JPQL query, just like the SQL one, that does what you want.
SELECT a.id, b.id
FROM EntityA a
LEFT JOIN EntityB b ON a.id = b.entityA.id AND b.someProperty = 'blabla'
But this won't help you with the materialization of the results into rich objects. If an Object[] i.e. the tuples are good enough for your use case, then use this kind of query and be done, but if you want to map to rich objects, I can recommend that you take a look at Blaze-Persistence Entity-Views.
Blaze-Persitence is a query builder on top of JPA which supports many of the advanced DBMS features on top of the JPA model. I created Entity Views on top of it to allow easy mapping between JPA models and custom interface defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure the way you like and map attributes(getters) via JPQL expressions to the entity model. Since the attribute name is used as default mapping, you mostly don't need explicit mappings as 80% of the use cases is to have DTOs that are a subset of the entity model.
A mapping for your model could look as simple as the following
#EntityView(EntityA.class)
public interface EntityAView {
long getId();
#Mapping("entityBs[someProperty = 'blabla']")
List<EntityBView> getEntityBs();
}
#EntityView(EntityB.class)
public interface EntityBView {
long getId();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
EntityAView dto = entityViewManager.find(entityManager, EntityAView.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features

what to use instead of fetch.EAGER in spring data?

I'm working with spring-boot and angular5 , i have this entity in spring :
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Contrat implements Serializable{
#Id #GeneratedValue
private Long id;
private Date dateDebut ;
private Date dateFin ;
#ManyToOne
#JoinColumn(name = "Id_Project")
#JsonBackReference(value="projet-contrat")
private Project project;
#ManyToOne
#JoinColumn(name = "Id_AppUser")
#JsonBackReference(value="appuser-contrat")
private AppUser appUser;
}
A repository :
public interface ContratRepo extends JpaRepository<Contrat,Long> {
public Page<Contrat> findByAppUser(#Param("userApp") AppUser userApp, Pageable pageable);
}
As the fetch.lazy is the default one , when i try to call the method findByAppUser i get as result :
{id: 1, dateDebut: 1526083200000, dateFin: 1526083200000}
Which is normal , what i want for my case is to load also the object 'project' that exists in the entity , but i don't wan't to use the fetch.EAGER , any solution for this goal ?
Your entity is one-many relationship object. If you don't use EAGER, spring data will get the object without related member object. And if you get that with contract.getProject().getName(), then another query will be sent to get that member.
If you log the SQL, you can see that, there will be 2 queries. But if you set the field as EAGER, there will be only 1 query. You can get improvement obviously.
But you should not use EAGER always. If in 90% of time, you just need the Contract object, but no need the project data of it. It is a waste of time to get that. Because in SQL, it will relate 2 tables and get all columns of data.
SO, you should make this decision based on your usage of this entity.
[Updated based on comment]
You can use Query to write your sql expression. for example, I have a method to get the entity with detail:
#Query("select s from Contract s left join fetch s.project pr where s.id = ?1 ")
Contract findOneWithDetail(Long id);
If I need to get the detail in ONE sql, I can use this method. If I don't need the project detail, I just use findOne(Long id), which is provided interface.
And, if you just want to get some columns, you need to define a DTO, with a constructor, and write your method like this:
#Query("SELECT NEW com.mypackage.dto.ContractDTO(s.id, s.name, s.status) FROM Contract AS s WHERE s.status = ?1")
List<ContractDTO> findDTOAllByStatus(String status);
Provide the query in your repo method, e.g. (syntax may be wrong, just show you the idea)
public interface ContratRepo extends JpaRepository<Contrat,Long> {
#Query(query="from Contrat c left join fetch c.project " +
"where c.userApp = :userApp")
public Page<Contrat> findByAppUser(#Param("userApp") AppUser userApp, Pageable pageable);
}

What is the standard way to avoid both N+1 and the Cartesian Product issues while fetching collections with JPA

When one has an entity with fields part of which are collections, one wishes to fetch the data with the smallest number of queries possible and using as less memory as possible.
The first problem is addressed by "join fetch" in JPQL queries (solving the N+1 problem).
However "join fetch" (as easily seen by inspecting the respective SQL query) causes the Cartesian product problem: each "row" which corresponds to the entity fields without multiplicities, is present in the returned result set with multiplicity N_1 x N_2 x ... x N_m, where N_1 is the multiplicity of the first collection, N_2 is the multiplicity of the second, and N_m is the multiplicity of the m-th collection, assuming that the entity has m fields which are collections.
Hibernate solves this problem with FetchMode.SUBSELECT (which, If I am not mistaken, makes m+1 queries, each of which returns no redundant data). What is the standard way to resolve this issue in JPA (it seems to me I cannot mix, at least in this case, JPA annotations with those of Hibernate)?
The best way is to replace collections with queries, especially when the expected size is large enough to lead to a performance issue:
You remove the bidirectional #OneToMany side, leaving only the owning #ManyToOne side
You select the parent entity (e.g. Country) run queries like:
select c from City c where c.country = :country
select c from County c where c.country = :country
select count(p), c.name from People p join p.country group by c.name
i tried to add #fetch(FetchMode.SUBSELECT) or #fetch(FetchMode.SELECT) it dose not make any change i.e(still make join and not make subselect two quires it make all select in the same query)
When you try with
entityManager.find(PoDetail.class, poNumber)
you will have all the lists declared in the entities with #OneToMany initialized with Cartesian product no of times, which will have duplicates as well. Of course one can eliminate these duplicates by using Set but Set does not preserve the order of data insertion and when tried to display in View we have scrambled rows.
I solved this by using :
NamedQueries with parameters to avoid such fetching of Cartesian product of collections.
Doing so your view data will be as is with the persistent data insertion order.
Here is the sample code:
Parent Entity class: (It has more list fields, I am mentioning one here)
#Entity
#Table(name="PO_DETAILS")
#NamedQuery(name="PoDetail.findByPoNumber", query="SELECT p FROM PoDetail p where p.poNumber=:poNumber")
public class PoDetail implements Serializable {
#Id
#Column(name="PO_NUMBER", unique=true, nullable=false, length=30)
private String poNumber;
#Column(name="ACTION_TAKEN", length=2000)
private String actionTaken;
.....
//bi-directional one-to-many association to PcrDetail
#OneToMany(mappedBy="poDetail", cascade={CascadeType.ALL}, fetch=FetchType.EAGER, orphanRemoval=true)
private List<PcrDetail> pcrDetails;
Child Entity class:
#Entity
#Table(name="PCR_DETAILS")
public class PcrDetail implements Serializable {
#Id
#Column(name="PCR_NUMBER", unique=true, nullable=false, length=30)
private String pcrNumber;
#Column(name="CONTRACT_ID", length=30)
private String contractId;
.....
//bi-directional many-to-one association to PoDetail
#ManyToOne(cascade={CascadeType.ALL})
#JoinColumn(name="PARENT_PO_NUMBER", insertable=false, updatable=false)
private PoDetail poDetail;
JPA DAO class:
public PoBean getPoDetails(PoBean poBean) {
PoDetail poDetail = poBean.getPoDetail();
String poNumber = poDetail.getPoNumber();
entityManagerFactory = JpaUtil.getEntityManagerFactory();
entityManager = entityManagerFactory.createEntityManager();
try {
try {
poDetail = (PoDetail) entityManager
.createNamedQuery("PoDetail.findByPoNumber")
.setParameter("poNumber", poNumber).getSingleResult();
} catch (NoResultException nre) {
poBean.setStatusCode(PopVO.ERROR);
poBean.setErrorMessage("No PO details foun with PO Number : "
+ poNumber);
}
return poBean;
} catch (Exception e) {
e.printStackTrace();
poBean.setStatusCode(PopVO.ERROR);
poBean.setErrorMessage(e.getMessage());
return poBean;
} finally {
entityManager.close();
}
}

Hibernate criteria JOIN + additional condition (with clause) don't work with many-to-many association

I'm trying to add additional condition to Join clause using hibernate criteria. In fact, there are some methods, that allow this to do:
createCriteria(String associationPath, String alias, int joinType, Criterion withClause)
and
createAlias(String associationPath, String alias, int joinType, Criterion withClause)
They work properly with one-to-one and one-to-many relations. But when I'm trying to use them with entities having many-to-many relations, I'm getting following error:
Caused by: org.postgresql.util.PSQLException: No value specified for parameter 1.
Can anybody help me?
The rude example is below:
#Entity
public class Person {
#Id
#GeneratedValue
private Long id;
#ManyToMany
private Set<PersonName> names;
}
public class PersonName {
#Id
#GeneratedValue
private Long id;
#Column
private String name;
}
public class PersonDao extends HibernateDaoSupport {
public List<Person> findByName() {
Criteria criteria = getSession().createCriteria(Person.class, "p");
criteria.createCriteria("p.names", "names", JoinType.INNER_JOIN, Restrictions.eq("name", "John"));
return criteria.list();
}
}
the Query being generated is
select this_.id as y0_ from person this_
inner join debtor_info this_1_ on this_.id=this_1_.id
left outer join person_person_name personname3_ on this_.id=personname3_.person_id and ( name1_.name=? )
left outer join person_name name1_ on personname3_.person_name_id=name1_.id and ( name1_.name=? )
As you can see, join condition is being added two times, what is obviously incorrect
Thanks in advance.
BTW I'm using postgresql 9, Hibernate 3.6.3
This is a bug HHH-7355
Hibernate criteria JOIN + additional condition (with clause) don't work with many-to-many association and it will not be fixed because Hibernate Criteria API is deprecated and you should use JPA Crtiterias.
You can try to use HQL with clause
from Cat as cat
left join cat.kittens as kitten
with kitten.bodyWeight > 10.0

Categories