Multiple joins to same association with different aliases in Hibernate - java

I am building a messaging system for my web application using Spring MVC with Spring Data JPA and Hibernate as my JPA provider.
I have five entities: Thread, ThreadParticipant, Participant, Account and Company. Each message thread has at least two participants, one of which is associated with a user (Account entity), and the other is associated with a Company. This constraint is enforced by the application. The database is designed like this to support future features. An example of two participants for a given thread in the database looks as follows:
id account_id company_id
1 44 NULL
2 NULL 123
The row with id=1 is the user, and the row with id=2 is the company. What I want to do is to write an HQL query that extracts all Thread objects for a given account, containing both the user/account participant as well as the company participant. I have tried to use different alias for my joins, like this:
select distinct t
from Thread t
inner join fetch t.threadParticipants user_tp
inner join fetch t.threadParticipants company_tp
inner join fetch user_tp.participant user_p
inner join fetch user_p.account a
inner join fetch company_tp.participant receiver_p
inner join fetch receiver_p.company
where a.id = :accountId
I get the exception cannot simultaneously fetch multiple bags due to the two fetches of t.threadParticipants. If I only do a single join here, the generated SQL simply ignores my additional join and only joins to Participant once, which requires a participant to have both an account and a company associated. With raw SQL, I can do like this, and it works fine:
select *
from thread t
inner join thread_participant user_tp on (user_tp.thread_id = t.id)
inner join thread_participant company_tp on (company_tp.thread_id = t.id)
inner join participant user_p on (user_p.id = user_tp.participant_id)
inner join account a on (a.id = user_p.account_id)
inner join participant company_p on (company_p.id = company_tp.participant_id)
inner join company c on (c.id = company_p.company_id)
where a.id = 123;
If I don't use different alias for the same table (see the below query), the query runs fine, but I only get one of the thread participants returned - the one that is associated with the account.
select distinct t
from Thread t
inner join fetch t.threadParticipants tp
inner join fetch tp.participant p
inner join fetch p.account a
left join fetch p.company
where a.id = :accountId
Is there any way that I can do what I am trying to do with HQL, or do I have to go with using native SQL?
My mapping is as follows:
Thread entity
#Entity
#Table(name = "thread")
public class Thread {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "thread", cascade = { CascadeType.PERSIST, CascadeType.MERGE })
private Collection<ThreadParticipant> threadParticipants = new HashSet<>();
// Getters and setters
}
Participant entity
#Entity
#Table(name = "participant")
public class Participant {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column
private int id;
#ManyToOne(fetch = FetchType.LAZY, optional = true, targetEntity = Account.class, cascade = { CascadeType.PERSIST })
#JoinColumn(name = "account_id")
private Account account;
#ManyToOne(fetch = FetchType.LAZY, optional = true, targetEntity = Company.class)
#JoinColumn(name = "company_id")
private Company company;
// Getters and setters
}
ThreadParticipant entity
#Entity
#Table(name = "thread_participant")
#IdClass(ThreadParticipantPK.class)
public class ThreadParticipant implements Serializable {
#Id
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Participant.class, cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JoinColumn(name = "participant_id")
private Participant participant;
#Id
#ManyToOne(fetch = FetchType.LAZY, targetEntity = Thread.class)
#JoinColumn(name = "thread_id")
private Thread thread;
#Column(name = "last_viewed", nullable = true)
private Date lastViewed;
// Getters and setters
}
ThreadParticipantPK
public class ThreadParticipantPK implements Serializable {
private Thread thread;
private Participant participant;
public ThreadParticipantPK() { }
public ThreadParticipantPK(Thread thread, Participant participant) {
this.thread = thread;
this.participant = participant;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof ThreadParticipantPK)) return false;
ThreadParticipantPK that = (ThreadParticipantPK) o;
if (!participant.equals(that.participant)) return false;
if (!thread.equals(that.thread)) return false;
return true;
}
#Override
public int hashCode() {
int result = thread.hashCode();
result = 31 * result + participant.hashCode();
return result;
}
// Getters and setters
}
Thank you in advance!

Try changing the type of the threadParticipants collection to Set instead of a Collection:
private Set<ThreadParticipant> threadParticipants;

Related

QueryDsl extra query, even though I'm using a join

I have Car and Color classes. Car class has both Entity and Id fields of Color.
Here is the Car class code:
public class Car {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JoinColumn(name = "color_id", insertable = false, updatable = false)
#ManyToOne(targetEntity = CarColor.class, fetch = FetchType.EAGER)
private CarColor color;
#Column(name = "color_id")
private Long colorId;
}
Here is the Color class code:
public class CarColor {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#JsonIgnore
#OneToMany(mappedBy = "color")
List<Car> cars;
}
When I'm fetching a Car i have an extra query to get a color Entity which is causing n+1 problem. So I have tried to use joins.
return query.select(qCar)
.from(qCar)
.innerJoin(qCar.color(), QCarColor.carColor)
.where(predicate)
.fetch();
I'm getting these queries:
select
car0_.id as id1_3_,
car0_.color_id as color_id3_3_,
from
car car0_
inner join
color carcolor2_
on car0_.color_id=carcolor2_.id
Hibernate:
select
carcolor0_.id as id1_6_0_,
carcolor0_.name as name2_6_0_
from
color carcolor0_
where
carcolor0_.id=?
As you can see, at the end I have an extra query to get color even though it has already joined Color entity. That is the main problem. Maybe it somehow connected with my decision to store both Entity and Id.
Queries don't use the association eagerness to add joins arbitrarily to your query. After that the entity loader still notices that this is an eager association and will fetch it separately.
You can however simply add the join yourself. Important here is to add the join as fetch join, so that the join result will be initialised as the value of the association.
return query.select(qCar)
.from(qCar)
.innerJoin(qCar.color(), QCarColor.carColor).fetchJoin()
.where(predicate)
.fetch();

How to fetch join nested entities in JPA-eclipselink, in case of sub-entity being in relation #ManyToOne

I have multiple entities that i want to fetch in single query, because N+1 queries last too long.
For example SQL join query lasts 5 seconds on DB, but elcipselink persistence fetching lasts 50-80 seconds due to N+1 fetching.
I found out that LEFT JOIN FETCH is not working as soon as #ManyToOne relation is implemented.
Does anyone know solution to LEFT JOIN FETCH for this case?
Please find below simplified entities.
#Entity
#Table(name="SITUATION_DATA")
#NamedQuery(name="SituationData.findAll", query="SELECT s FROM SituationData s")
public class DatexSituationData implements Serializable {
private static final long serialVersionUID = 1L;
//bi-directional many-to-one association to SituationRecord
#OneToMany(mappedBy="datexSituationData", cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
#JoinFetch(value=JoinFetchType.OUTER)
private List<SituationRecord> situationRecords;
}
#Entity
#Table(name="SituationRecord")
#NamedQuery(name="SituationRecord.findAll", query="SELECT s FROM SituationRecord s")
public class SituationRecord implements Serializable {
private static final long serialVersionUID = 1L;
#OneToMany(mappedBy="situationRecord", cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
#JoinFetch(value=JoinFetchType.OUTER)
private List<SituationRecordComment> situationRecordComment;
#OneToMany(mappedBy="situationRecord", cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
#JoinFetch(value=JoinFetchType.OUTER)
private List<SituationRecordTypeElement> situationRecordTypeElements;
//bi-directional many-to-one association to SituationLocation
#ManyToOne(cascade = CascadeType.PERSIST, fetch = FetchType.EAGER)
#JoinFetch(value=JoinFetchType.OUTER)
#JoinColumn(name="ID_LOKACIJE")
private SituationLocation situationLocation;
//bi-directional many-to-one association to DatexSituationData
#ManyToOne()
#JoinColumns({
#JoinColumn(name="SITUATION_ID", referencedColumnName="ID", nullable=false),
#JoinColumn(name="SITUATION_VERSION", referencedColumnName="VERSION", nullable=false)
})
private DatexSituationData datexSituationData;
}
#Entity
#Table(name="SITUATION_LOCATIONS")
#NamedQuery(name="SituationLocation.findAll", query="SELECT s FROM SituationLocation s")
public class SituationLocation implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="situation_location_seq")
#SequenceGenerator(name="situation_location_seq", sequenceName="SEQ_SITUATION_LOCATION", allocationSize=1)
#Column(name="ID_LOKACIJE", unique=true, nullable=false)
private long idLokacije;
//bi-directional many-to-one association to SituationRecord
#OneToMany(mappedBy="situationLocation", cascade = CascadeType.PERSIST)
private List<SituationRecord> situationRecords;
}
This is how i fetch it.
I have tried all of the below combinations, but every combinations makes query for each row (object) in SituationData, or in some cases for each joined SituationData join SituationRecord.
String sQuery =
//"select * from SITUATION_DATA t";
//"SELECT * FROM (select t.*, rank() over(partition by t.id order by version desc) rnk from SITUATION_DATA t) where rnk = 1";
"SELECT ds FROM SituationData ds LEFT JOIN FETCH ds.situationRecords sr LEFT JOIN FETCH sr.situationLocation sl LEFT JOIN FETCH sr.situationRecordTypeElements sre LEFT JOIN FETCH sr.situationRecordComment src";
EntityManager em = Emf.getInstance().getFactory().createEntityManager();
//Query q = em.createNativeQuery(sQuery, DatexSituationData.class);
Query q = em.createQuery(sQuery, DatexSituationData.class);
// q.setHint("eclipselink.LEFT_FETCH", "t.situationRecords.situationRecordComment");
q.setHint("eclipselink.LEFT_FETCH", "ds.sr.sl");
q.setHint("eclipselink.LEFT_FETCH", "ds.sr.sre");
q.setHint("eclipselink.LEFT_FETCH", "ds.sr.src");
// q.setHint("eclipselink.JDBC_FETCH_SIZE", "100");
lResult = q.getResultList();
Since you are not explicitly state EclipseLink version being used, I'll just assume version 2.6.
As per the official EclipseLink documentation, eclipselink.LEFT_FETCH is not a supported query hint. You are probably trying to use eclipselink.join-fetch or eclipselink.left-join-fetch as documented here.
The proper way to use this hint is along the lines of:
String sQuery = "SELECT ds FROM DatexSituationData ds";
EntityManager em = emf.getInstance().getFactory().createEntityManager();
TypedQuery q = em.createQuery(sQuery, DatexSituationData.class);
q.setHint("eclipselink.join-fetch", "ds.situationRecords");
q.setHint("eclipselink.join-fetch", "ds.situationLocation");
// ...
lResult = q.getResultList();
Another approach would be batch fetching.
This would work as:
//...
em
.createQuery("SELECT ds FROM DatexSituationData ds")
.setHint("eclipselink.batch", "ds.situationRecords")
//...
.setHint("eclipselink.batch.type", "IN")
.setHint("eclipselink.batch_size", "1000");
//...
You would probably test both approaches and gather some metrics; then decide which one works better (in terms of performance) for your DB schema and usage patterns.

JPA Hibernate Lazy load collection in a self relationship

I'm trying to lazily load the ingredients of a product in a self relationship. A product can have zero or more ingredients. The relationship is stored in the ProductComposition entity.
These are my entities:
Product
#Entity(name = Product.TABLE_NAME)
//#NamedEntityGraph(name = "graph.Product.ingredients", attributeNodes = //#NamedAttributeNode("ingredients"))
public class Product {
public static final String TABLE_NAME = "Product";
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long idProduct;
private String name;
#OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.MERGE}, mappedBy = "product")
private List<OrderDetail> orders;
#OneToMany(fetch = FetchType.LAZY,cascade = CascadeType.ALL, mappedBy = "ingredient", orphanRemoval=true)
private List<ProductComposition> ingredients;
ProductComposition
#Entity(name = ProductComposition.TABLE_NAME)
#IdClass(ProductCompositionId.class)
public class ProductComposition {
public static final String TABLE_NAME = "ProductComposition";
#Id
#ManyToOne //(fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn(name = "PrincipalProductID")
private Product principalProduct;
#Id
#ManyToOne //(fetch = FetchType.LAZY)
#PrimaryKeyJoinColumn(name = "IngredientID")
private Product ingredient;
private int quantity;
ProductCompositionId
class ProductCompositionId implements Serializable{
private long principalProduct;
private long ingredient;
In the method get of my Dao I've tried different things:
Fetching with a CriteriaQuery the ingredients and then set them to the product
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<ProductComposition> q = cb.createQuery(ProductComposition.class);
Root<ProductComposition> product = q.from(ProductComposition.class);
product.fetch("principalProduct", JoinType.LEFT);
q.select(product).where(cb.equal(product.get("principalProduct"), id));
List<ProductComposition> ingredients = entityManager.createQuery(q).getResultList();
Product p = entityManager.find(Product.class, id);
p.setIngredients(ingredients);
Using an Entity Graph
EntityGraph<Product> graph = (EntityGraph<Product>) entityManager.getEntityGraph("graph.Product.ingredients");
Map<String, Object> ingredients = new HashMap<>();
ingredients.put("javax.persistence.fetchgraph", graph);
Product p = entityManager.find(entityClass, id, ingredients);
Calling the method initialize
p = productDao.get(p.getIdProduct()); //the get here just calls entityManager.find(Product.class, id)
Hibernate.initialize(p.getIngredients());
System.out.println("Ingredients size: "+p.getIngredients().size()); //gives 0
After calling those two above lines I get the following two logs, but p still has no ingredients after:
Hibernate: select product0_.idProduct as idProduc1_4_0_, product0_.name as name2_4_0_, orders1_.product_idProduct as product_3_3_1_, orders1_.foodOrder_idFoodOrder as foodOrde2_3_1_, orders1_.foodOrder_idFoodOrder as foodOrde2_3_2_, orders1_.product_idProduct as product_3_3_2_, orders1_.quantity as quantity1_3_2_, foodorder2_.idFoodOrder as idFoodOr1_2_3_, foodorder2_.CustomerID as Customer2_2_3_, foodorder2_.DeliverymanID as Delivery3_2_3_, foodorder2_.RestaurantID as Restaura4_2_3_ from Product product0_ left outer join OrderDetail orders1_ on product0_.idProduct=orders1_.product_idProduct left outer join FoodOrder foodorder2_ on orders1_.foodOrder_idFoodOrder=foodorder2_.idFoodOrder where product0_.idProduct=?
Hibernate: select ingredient0_.ingredient_idProduct as ingredie2_5_0_, ingredient0_.principalProduct_idProduct as principa3_5_0_, ingredient0_.ingredient_idProduct as ingredie2_5_1_, ingredient0_.principalProduct_idProduct as principa3_5_1_, ingredient0_.quantity as quantity1_5_1_, product1_.idProduct as idProduc1_4_2_, product1_.name as name2_4_2_ from ProductComposition ingredient0_ inner join Product product1_ on ingredient0_.principalProduct_idProduct=product1_.idProduct where ingredient0_.ingredient_idProduct=?
However, all tries can't load the ingredients. They just return an empty list.
What am I doing wrong in this methods?
I would prefer to keep the relationship as lazy. Also because otherwise hibernate will return cannot simultaneously fetch multiple bags referring to Product.orders and Product.ingredients

How to avoid unwanted queries with Hibernate?

I want to make a query against an entity , called Utilisateur :
#Override
#Transactional
public List<Utilisateur> list() {
String hql = "from Utilisateur where deleted is null or deleted <> 1";
Query query = sessionFactory.getCurrentSession().createQuery(hql);
#SuppressWarnings("unchecked")
List<Utilisateur> listUser = (List<Utilisateur>) query.list();
return listUser;
}
#Entity
#Table(name = "utilisateur")
public class Utilisateur {
#Id
#SequenceGenerator(name="s_utilisateur", sequenceName="s_utilisateur", allocationSize=1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="s_utilisateur")
#Column(name = "user_code")
private Long code;
#Column(name = "user_nom")
private String nom;
#Column(name = "user_prenom")
private String prenom;
#Column(name = "user_login")
private String login;
#ManyToOne
#JoinColumn(name = "struct_code")
private Structure structure;
...
}
As you can see there is the attribute structure of type Structure. So here is the entity Structure :
#Entity
#Table(name = "structure")
public class Structure {
#Id()
#Column(name="struct_code")
private String code;
#ManyToOne
#JoinColumn(name = "str_struct_code")
private Structure parent;
#ManyToOne
#JoinColumn(name="niv_struct_code")
private NiveauStructure niveauStructure;
#ManyToMany(fetch = FetchType.EAGER, mappedBy = "structures")
#JsonBackReference
private Set<Cdmt> programmes = new HashSet<Cdmt>();
#Column(name="struct_lib")
private String lib;
...
}
Again there is the attribute programmes , here is its code :
#Entity
#Table(name = "cdmt")
public class Cdmt {
#Id
#Column(name = "cdmt_code")
private String code;
#ManyToOne
#JoinColumn(name = "class_cdmt_code")
private ClasseCdmt classeCdmt;
#Column(name="cdmt_design")
#Lob
private String lib;
#ManyToOne
#JoinColumn(name = "prog_code")
private Pmo pmo;
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.MERGE)
#JoinTable(name = "programme_structure" , joinColumns = {#JoinColumn(name = "cdmt_code")} , inverseJoinColumns = {#JoinColumn(name = "struct_code")} )
#JsonManagedReference
private Set<Structure> structures = new HashSet<Structure>();
#ManyToOne
#JoinColumn(name = "cdm_cdmt_code")
private Cdmt cdmtParent;
...
}
At runtime there are many queries involved displayed in the console , among those queries there are queries against the Pmo entity although there is no mention about that entity in the query ! So how to avoid this propagations of queries ?
update :
here is query trace in the console :
Hibernate: select programmes0_.struct_code as struct_code2_50_0_, programmes0_.cdmt_code as cdmt_code1_33_0_, cdmt1_.cdmt_code as cdmt_code1_4_1_, cdmt1_.cdm_cdmt_code as cdm_cdmt_code7_4_1_, cdmt1_.cdmt_comment as cdmt_comment2_4_1_, cdmt1_.class_cdmt_code as class_cdmt_code8_4_1_, cdmt1_.cdmt_debut as cdmt_debut3_4_1_, cdmt1_.deleted as deleted4_4_1_, cdmt1_.cdmt_fin as cdmt_fin5_4_1_, cdmt1_.cdmt_design as cdmt_design6_4_1_, cdmt1_.prog_code as prog_code9_4_1_, cdmt2_.cdmt_code as cdmt_code1_4_2_, cdmt2_.cdm_cdmt_code as cdm_cdmt_code7_4_2_, cdmt2_.cdmt_comment as cdmt_comment2_4_2_, cdmt2_.class_cdmt_code as class_cdmt_code8_4_2_, cdmt2_.cdmt_debut as cdmt_debut3_4_2_, cdmt2_.deleted as deleted4_4_2_, cdmt2_.cdmt_fin as cdmt_fin5_4_2_, cdmt2_.cdmt_design as cdmt_design6_4_2_, cdmt2_.prog_code as prog_code9_4_2_, classecdmt3_.class_cdmt_code as class_cdmt_code1_5_3_, classecdmt3_.class_cdmt_comment as class_cdmt_comment2_5_3_, classecdmt3_.class_cdmt_lib as class_cdmt_lib3_5_3_, classecdmt3_.class_cdmt_niveau as class_cdmt_niveau4_5_3_, pmo4_.prog_code as prog_code1_31_4_, pmo4_.class_pmo_code as class_pmo_code6_31_4_, pmo4_.prog_debut as prog_debut2_31_4_, pmo4_.prog_fin as prog_fin3_31_4_, pmo4_.prog_design as prog_design4_31_4_, pmo4_.pmo_prog_code as pmo_prog_code7_31_4_, pmo4_.pnd_code as pnd_code8_31_4_, pmo4_.prog_comment as prog_comment5_31_4_, classepmo5_.class_pmo_code as class_pmo_code1_6_5_, classepmo5_.class_pmo_comment as class_pmo_comment2_6_5_, classepmo5_.class_pmo_lib as class_pmo_lib3_6_5_, classepmo5_.class_pmo_niveau as class_pmo_niveau4_6_5_, pmo6_.prog_code as prog_code1_31_6_, pmo6_.class_pmo_code as class_pmo_code6_31_6_, pmo6_.prog_debut as prog_debut2_31_6_, pmo6_.prog_fin as prog_fin3_31_6_, pmo6_.prog_design as prog_design4_31_6_, pmo6_.pmo_prog_code as pmo_prog_code7_31_6_, pmo6_.pnd_code as pnd_code8_31_6_, pmo6_.prog_comment as prog_comment5_31_6_, pnd7_.pnd_code as pnd_code1_32_7_, pnd7_.creation as creation2_32_7_, pnd7_.pnd_debut as pnd_debut3_32_7_, pnd7_.deleted as deleted4_32_7_, pnd7_.pnd_fin as pnd_fin5_32_7_, pnd7_.pnd_intitule as pnd_intitule6_32_7_, pnd7_.modification as modification7_32_7_, pnd7_.obj_code as obj_code10_32_7_, pnd7_.owner as owner8_32_7_, pnd7_.pnd_comment as pnd_comment9_32_7_, objectif8_.obj_code as obj_code1_25_8_, objectif8_.cdmt_code as cdmt_code8_25_8_, objectif8_.obj_comment as obj_comment2_25_8_, objectif8_.creation as creation3_25_8_, objectif8_.deleted as deleted4_25_8_, objectif8_.obj_intitule as obj_intitule5_25_8_, objectif8_.modification as modification6_25_8_, objectif8_.nat_obj_code as nat_obj_code9_25_8_, objectif8_.obj_obj_code as obj_obj_code10_25_8_, objectif8_.owner as owner7_25_8_, objectif8_.prog_code as prog_code11_25_8_, objectif8_.pta_code as pta_code12_25_8_, cdmt9_.cdmt_code as cdmt_code1_4_9_, cdmt9_.cdm_cdmt_code as cdm_cdmt_code7_4_9_, cdmt9_.cdmt_comment as cdmt_comment2_4_9_, cdmt9_.class_cdmt_code as class_cdmt_code8_4_9_, cdmt9_.cdmt_debut as cdmt_debut3_4_9_, cdmt9_.deleted as deleted4_4_9_, cdmt9_.cdmt_fin as cdmt_fin5_4_9_, cdmt9_.cdmt_design as cdmt_design6_4_9_, cdmt9_.prog_code as prog_code9_4_9_, natureobje10_.nat_obj_code as nat_obj_code1_23_10_, natureobje10_.nat_obj_comment as nat_obj_comment2_23_10_, natureobje10_.nat_obj_lib as nat_obj_lib3_23_10_, objectif11_.obj_code as obj_code1_25_11_, objectif11_.cdmt_code as cdmt_code8_25_11_, objectif11_.obj_comment as obj_comment2_25_11_, objectif11_.creation as creation3_25_11_, objectif11_.deleted as deleted4_25_11_, objectif11_.obj_intitule as obj_intitule5_25_11_, objectif11_.modification as modification6_25_11_, objectif11_.nat_obj_code as nat_obj_code9_25_11_, objectif11_.obj_obj_code as obj_obj_code10_25_11_, objectif11_.owner as owner7_25_11_, objectif11_.prog_code as prog_code11_25_11_, objectif11_.pta_code as pta_code12_25_11_, pmo12_.prog_code as prog_code1_31_12_, pmo12_.class_pmo_code as class_pmo_code6_31_12_, pmo12_.prog_debut as prog_debut2_31_12_, pmo12_.prog_fin as prog_fin3_31_12_, pmo12_.prog_design as prog_design4_31_12_, pmo12_.pmo_prog_code as pmo_prog_code7_31_12_, pmo12_.pnd_code as pnd_code8_31_12_, pmo12_.prog_comment as prog_comment5_31_12_, pta13_.pta_code as pta_code1_34_13_, pta13_.cdmt_code as cdmt_code18_34_13_, pta13_.class_pta_code as class_pta_code19_34_13_, pta13_.creation as creation2_34_13_, pta13_.pta_definitif as pta_definitif3_34_13_, pta13_.deleted as deleted4_34_13_, pta13_.pta_desc as pta_desc5_34_13_, pta13_.exer_code as exer_code20_34_13_, pta13_.pta_intitule as pta_intitule6_34_13_, pta13_.modification as modification7_34_13_, pta13_.owner as owner8_34_13_, pta13_.pta_pta_code as pta_pta_code21_34_13_, pta13_.pta_activite as pta_activite9_34_13_, pta13_.pta_cloture as pta_cloture10_34_13_, pta13_.pta_limite_decaisse as pta_limite_decais11_34_13_, pta13_.pta_num_credit as pta_num_credit12_34_13_, pta13_.pta_signature as pta_signature13_34_13_, pta13_.pta_unite_execution as pta_unite_executi14_34_13_, pta13_.pta_vigueur as pta_vigueur15_34_13_, pta13_.pta_ref as pta_ref16_34_13_, pta13_.pta_resultat_annee as pta_resultat_anne17_34_13_, pta13_.sect_code as sect_code22_34_13_, pta13_.struct_code as struct_code23_34_13_, pta13_.typ_proj_code as typ_proj_code24_34_13_, cdmt14_.cdmt_code as cdmt_code1_4_14_, cdmt14_.cdm_cdmt_code as cdm_cdmt_code7_4_14_, cdmt14_.cdmt_comment as cdmt_comment2_4_14_, cdmt14_.class_cdmt_code as class_cdmt_code8_4_14_, cdmt14_.cdmt_debut as cdmt_debut3_4_14_, cdmt14_.deleted as deleted4_4_14_, cdmt14_.cdmt_fin as cdmt_fin5_4_14_, cdmt14_.cdmt_design as cdmt_design6_4_14_, cdmt14_.prog_code as prog_code9_4_14_, classepta15_.class_pta_code as class_pta_code1_7_15_, classepta15_.class_pta_comment as class_pta_comment2_7_15_, classepta15_.class_pta_lib as class_pta_lib3_7_15_, classepta15_.class_pta_niveau as class_pta_niveau4_7_15_, exer16_.exer_code as exer_code1_15_16_, exer16_.exer_en_cours as exer_en_cours2_15_16_, exer16_.exer_lib as exer_lib3_15_16_, pta17_.pta_code as pta_code1_34_17_, pta17_.cdmt_code as cdmt_code18_34_17_, pta17_.class_pta_code as class_pta_code19_34_17_, pta17_.creation as creation2_34_17_, pta17_.pta_definitif as pta_definitif3_34_17_, pta17_.deleted as deleted4_34_17_, pta17_.pta_desc as pta_desc5_34_17_, pta17_.exer_code as exer_code20_34_17_, pta17_.pta_intitule as pta_intitule6_34_17_, pta17_.modification as modification7_34_17_, pta17_.owner as owner8_34_17_, pta17_.pta_pta_code as pta_pta_code21_34_17_, pta17_.pta_activite as pta_activite9_34_17_, pta17_.pta_cloture as pta_cloture10_34_17_, pta17_.pta_limite_decaisse as pta_limite_decais11_34_17_, pta17_.pta_num_credit as pta_num_credit12_34_17_, pta17_.pta_signature as pta_signature13_34_17_, pta17_.pta_unite_execution as pta_unite_executi14_34_17_, pta17_.pta_vigueur as pta_vigueur15_34_17_, pta17_.pta_ref as pta_ref16_34_17_, pta17_.pta_resultat_annee as pta_resultat_anne17_34_17_, pta17_.sect_code as sect_code22_34_17_, pta17_.struct_code as struct_code23_34_17_, pta17_.typ_proj_code as typ_proj_code24_34_17_, secteur18_.sect_code as sect_code1_48_18_, secteur18_.sect_comm as sect_comm2_48_18_, secteur18_.sect_lib as sect_lib3_48_18_, structure19_.struct_code as struct_code1_50_19_, structure19_.struct_lib as struct_lib2_50_19_, structure19_.niv_struct_code as niv_struct_code15_50_19_, structure19_.str_struct_code as str_struct_code16_50_19_, structure19_.struct_sigle as struct_sigle3_50_19_, structure19_.struct_comment as struct_comment4_50_19_, structure19_.struct_contact as struct_contact5_50_19_, structure19_.struct_interne as struct_interne6_50_19_, structure19_.struct_mission_fonc as struct_mission_fon7_50_19_, structure19_.struct_mission_oper as struct_mission_ope8_50_19_, structure19_.struct_resp_fonc as struct_resp_fonc9_50_19_, structure19_.struct_resp_hiera as struct_resp_hiera10_50_19_, structure19_.struct_resp_oper as struct_resp_oper11_50_19_, structure19_.struct_site as struct_site12_50_19_, structure19_.struct_tache_fonc as struct_tache_fonc13_50_19_, structure19_.struct_tache_oper as struct_tache_oper14_50_19_, niveaustru20_.niv_struct_code as niv_struct_code1_24_20_, niveaustru20_.niv_struct_comment as niv_struct_comment2_24_20_, niveaustru20_.niv_struct_lib as niv_struct_lib3_24_20_, niveaustru20_.niv_struct_ordre as niv_struct_ordre4_24_20_, structure21_.struct_code as struct_code1_50_21_, structure21_.struct_lib as struct_lib2_50_21_, structure21_.niv_struct_code as niv_struct_code15_50_21_, structure21_.str_struct_code as str_struct_code16_50_21_, structure21_.struct_sigle as struct_sigle3_50_21_, structure21_.struct_comment as struct_comment4_50_21_, structure21_.struct_contact as struct_contact5_50_21_, structure21_.struct_interne as struct_interne6_50_21_, structure21_.struct_mission_fonc as struct_mission_fon7_50_21_, structure21_.struct_mission_oper as struct_mission_ope8_50_21_, structure21_.struct_resp_fonc as struct_resp_fonc9_50_21_, structure21_.struct_resp_hiera as struct_resp_hiera10_50_21_, structure21_.struct_resp_oper as struct_resp_oper11_50_21_, structure21_.struct_site as struct_site12_50_21_, structure21_.struct_tache_fonc as struct_tache_fonc13_50_21_, structure21_.struct_tache_oper as struct_tache_oper14_50_21_, typeprojet22_.typ_proj_code as typ_proj_code1_52_22_, typeprojet22_.typ_proj_comment as typ_proj_comment2_52_22_, typeprojet22_.typ_proj_lib as typ_proj_lib3_52_22_ from programme_structure programmes0_ inner join cdmt cdmt1_ on programmes0_.cdmt_code=cdmt1_.cdmt_code left outer join cdmt cdmt2_ on cdmt1_.cdm_cdmt_code=cdmt2_.cdmt_code left outer join classe_cdmt classecdmt3_ on cdmt2_.class_cdmt_code=classecdmt3_.class_cdmt_code left outer join pmo pmo4_ on cdmt2_.prog_code=pmo4_.prog_code left outer join classe_pmo classepmo5_ on pmo4_.class_pmo_code=classepmo5_.class_pmo_code left outer join pmo pmo6_ on pmo4_.pmo_prog_code=pmo6_.prog_code left outer join pnd pnd7_ on pmo6_.pnd_code=pnd7_.pnd_code left outer join objectif objectif8_ on pnd7_.obj_code=objectif8_.obj_code left outer join cdmt cdmt9_ on objectif8_.cdmt_code=cdmt9_.cdmt_code left outer join nature_objectif natureobje10_ on objectif8_.nat_obj_code=natureobje10_.nat_obj_code left outer join objectif objectif11_ on objectif8_.obj_obj_code=objectif11_.obj_code left outer join pmo pmo12_ on objectif11_.prog_code=pmo12_.prog_code left outer join pta pta13_ on objectif11_.pta_code=pta13_.pta_code left outer join cdmt cdmt14_ on pta13_.cdmt_code=cdmt14_.cdmt_code left outer join classe_pta classepta15_ on pta13_.class_pta_code=classepta15_.class_pta_code left outer join exercice exer16_ on pta13_.exer_code=exer16_.exer_code left outer join pta pta17_ on pta13_.pta_pta_code=pta17_.pta_code left outer join secteur secteur18_ on pta17_.sect_code=secteur18_.sect_code left outer join structure structure19_ on pta17_.struct_code=structure19_.struct_code left outer join niveau_structure niveaustru20_ on structure19_.niv_struct_code=niveaustru20_.niv_struct_code left outer join structure structure21_ on structure19_.str_struct_code=structure21_.struct_code left outer join type_projet typeprojet22_ on pta17_.typ_proj_code=typeprojet22_.typ_proj_code where programmes0_.struct_code=?
I had the same problem. I'm using SqlResultSetMapping.
The point of SqlResultSetMapping is to defined custom mapping for NamedNativeQuery.
With SqlResultSetMapping, you can either use :
EntityResult to map the result of a query to an Entity object
ConstructorResult to map the result of a query to "non-entity" object with the contructor
ColumnResult
Example with ConstructorResult
Let's say we have a very complex entity ComplexObject with many relations to other objects.
#Entity
#Table(name = "complex_object")
public class ComplexObject {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "id_complex_object")
private Integer id;
#Column(name = "label")
private String label;
// More relations...
}
I want a query to retrieve only the id and the label of this entity.
In ComplexObject, I define a new NamedNativeQuery as following.
#NamedNativeQueries({
#NamedNativeQuery(name = "ComplexObject.getIdAndLabel", query = "SELECT co.id_complex_object, co.label FROM complex_object", resultSetMapping = "SimpleObject")
})
The important part of this NamedNativeQuery is the resultSetMapping = "SimpleObject".
Then I can define a SimpleObject that is not a entity and match my query as following :
public class SimpleObject {
private Integer id;
private String label;
/**
* This constructor is very important !
* Its signature has to match the SqlResultSetMapping defined in the entity class.
* Otherwise, an exception will occur.
*/
public SimpleObject(Integer id, String label) {
this.id = id;
this.label = label;
}
// Getters and setters...
}
Then I can define the SqlResultSetMapping in ComplexObject as following :
#SqlResultSetMappings({
#SqlResultSetMapping(name = "SimpleObject", classes = {
#ConstructorResult(targetClass = SimpleObject.class, columns = {
#ColumnResult(name = "id_complex_object", type = Integer.class),
#ColumnResult(name = "label", type = String.class)
})
})
})
It's done.
The NamedNativeQuery will use SimpleObject SqlResultSetMapping to construct a SimpleObject (throught the constructor) so your query returns a SimpleObject instead of ComplexObject.
Try adding attribute fetch=FetchType.LAZY to the #ManyToOne on Structure object of Utilisateur entity class.
Since ManyToOne relations are eagerly loaded. I think it is trying to load Structure entity in a new SELECT statement. And this is causing entities with in Structure to be loaded further.
So adding fetch=FetchType.LAZY on Structure should prevent the initial SELECT query of Structure and thus the subsequent entities mapped in the Structure entity.
UPDATE
In the above statements, the assumption you don't want to populate Structure object as you fetch Utilisateur object. If that is not the case, then you can move the FetchType.LAZY further down the association chain. Say you want to fetch Structure but not its related entities like parent etc, then you can move FetchType.LAZY to those annotation (ManyToOne etc).
Note that with the HQL you are using from Utilisateur where deleted is null or deleted <> 1 with no joins it will by default fire a new SELECT query to fetch those LAZY associations by default. And if you want to fetch with single (or lesser number of) query that with joins you need to update your HQL to use JOIN FETCH queries.
One common issue we usually encounter is a scenario where we don't want to fetch an association, but somewhere in the toString method we navigate the associations start printing them and resulting in the LazyInitializationException.

JPA bidirectional 1..N association, avoid querying in child to set parent

I am using Spring Data JPA + Hibernate for a webapp. For a particular domain model A, we have a 1-to-many association in another domain B. Such that A will have a Set getB() and B will have A getA().
While querying for a A graph, I see hibernate is using 1+n queries. A single outer join query for fetching the A graph, but then 'n' queries for setting A in each B.
Am I missing any pattern here? Since all the childs have the same parent, is not somehow possible to avoid these 'n' queries?
#MappedSuperclass
#Data
public abstract class Batch implements Serializable {
private static final long serialVersionUID = 1L;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "batch_id", referencedColumnName = "batch_id")
protected BatchID batchId;
}
/*
//The parent class in a simplified form
*/
#Entity
#Table(name = "DRYRUN")
#Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public class DryrunBatch extends Batch {
/**
*
*/
private static final long serialVersionUID = -1596595930859735318L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Getter#Setter
protected Long id;
public DryrunTNStatus newTNStatus()
{
final DryrunTNStatus tn = new DryrunTNStatus();
tn.setBatch(this);
getTnStatus().add(tn);
return tn;
}
#OneToMany(fetch = FetchType.LAZY, mappedBy = "batch")
#Getter#Setter
private Set tnStatus = new HashSet();
}
//The child class in a simplified form
#Entity
#Table(name = "DRYRUN_TN_STATUS")
#Data
public class DryrunTNStatus implements Serializable{
/**
*
*/
private static final long serialVersionUID = -4388406636444350023L;
public DryrunTNStatus(String accountNo, String telNo) {
super();
this.accountNo = accountNo;
this.telNo = telNo;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "BATCH_ID", referencedColumnName = "BATCH_ID")
private DryrunBatch batch;
public DryrunTNStatus()
{
}
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
protected Long id;
}
The code to fetch the object graph using JpaRepository. Using Spring JPA support to enforce an outer join. I preferred this over Hibernate's #Fetch annotation.
DryrunBatch drBatch = drBatchRepo.findOne(new Specification() {
#Override
public Predicate toPredicate(Root root, CriteriaQuery query,
CriteriaBuilder cb) {
query.distinct(true);
root.fetch("tnStatus", JoinType.LEFT);
return cb.equal(root.get("batchId").get("id"),
batch.getId());
}
});
And finally the hibernate queries from log. I am running a junit that fetches a parent with 10 childs from DB.
//this query can fetch data for the complete graph??
Hibernate: select distinct dryrunbatc0_.id as id1_6_0_, tnstatus1_.id as id1_9_1_[etc..] from dryrun dryrunbatc0_ left outer join dryrun_tn_status tnstatus1_ on dryrunbatc0_.batch_id=tnstatus1_.batch_id where dryrunbatc0_.batch_id=15
//and then 10 queries like
Hibernate: select dryrunbatc0_.id as id1_6_3_, [etc..] from dryrun dryrunbatc0_ left outer join batch_id batchid1_ on dryrunbatc0_.batch_id=batchid1_.batch_id inner join users user2_ on dryrunbatc0_.created_by=user2_.login_id left outer join dryrun_tn_status tnstatus3_ on dryrunbatc0_.batch_id=tnstatus3_.batch_id where dryrunbatc0_.batch_id=?
You've encountered the famous N+1 problem with lazy loading. There is no JPA standard way to tackle this, however, every JPA provider provides means to turn on "Batch fetching", which will load all lazy references at once instead loading each in a single SQL query.
Here is information on how to turn it on in hibernate.
Here is an article with explanation of how batch fetching works and examples using eclipselink.

Categories

Resources