Hibernate fetch strategy for associations - java

#Entity
public class A {
#Id
private Long id;
#OneToMany(cascade = ALL, orphanRemoval = true)
#JoinColumn(name = "aId")
#Fetch(FetchMode.SELECT)
private List<B> bList;
}
#Entity
public class B {
#Id
private Long id;
private Long aId;
#OneToMany(cascade = ALL, orphanRemoval = true)
#JoinColumn(name = "bId")
#Fetch(FetchMode.JOIN)
private List<C> cList;
}
#Entity
public class C {
#Id
private Long id;
private Long bId;
}
When loading object of type A (session.get(A.class, 1L)) the join fechmode specified for list of C objects in B is ignored. 3 queries are executed:
select A ...
select B ...
select C ...
When loading objects of type B (session.get(B.class, 1L)) the join fetchmode is honored and only 1 query is executed
select B ...
left outer join C ...
What i want is when loading object of type A that only 2 queries are needed:
select A ...
select B ...
left outer join C ...
Anyone knows how to achieve this?

Related

Flushing entity A and B, when B's primary key is also A's? In JPA & Hibernate

I have the following entities A and B
#Entity
#Data
#Accessors(chain = true)
public class A {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "aId")
private long aId;
#OneToOne(mappedBy="a", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private B b;
#Entity
#Data
#Accessors(chain = true)
public class B {
#Id
#Column(name = "bId")
/**
*
* This is the same id as A.
*/
private long bId;
#OneToOne
#JoinColumn(name = "bId")
private A a;
How I am flushing entity A and B
A aEntity = new A();
B bEntity = new B();
aEntity.setbEntity(bEntity);
this.entityManager.persist(A);
this.entityManager.persist(B);
this.entityManager.flush();
I am trying to save both these entities in a transaction and I am having issues where B's id is not getting hydrated down by A's id.
On A.b you have a mappedBy="a", that means that B.a is the "owner" of the relation.
So you should set B.a instead of A.b to persist the relation:
B bEntity = new B();
A aEntity = new A();
bEntity.setbEntity(aEntity);
this.entityManager.persist(A);
this.entityManager.persist(B);
this.entityManager.flush();
If you need to keep working with those entities on the same transaction, calling entityManager.refresh on a entity will make sure that all properties are in sync.

Who can make this JPQL Query?

My JPA-entity classes look like this:
#Entity
#Table(name = "users")
public class User implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private Long id;
...
// bi-directional many-to-many association to Tag
#ManyToMany
#JoinTable(name = "user_tags_preferences", joinColumns = {
#JoinColumn(name = "user_id") },
inverseJoinColumns = {
#JoinColumn(name = "tag_id") })
private List<Tag> tags;
#Entity
#Table(name = "tags")
public class Tag implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private Long id;
private String name;
...
// bi-directional many-to-many association to CookEvent
#ManyToMany(mappedBy = "tags")
private List<CookEvent> cookEvents;
// bi-directional many-to-many association to User
#ManyToMany(mappedBy = "tags")
private List<User> users;
#Entity
#Table(name = "cook_events")
public class CookEvent implements Serializable {
private static final long serialVersionUID = 1L;
#Id
private Long id;
#Column(name = "takes_place_on")
private LocalDateTime takesPlaceOn;
...
// bi-directional many-to-many association to Tag
#ManyToMany
#JoinTable(name = "cook_events_tags", joinColumns = {
#JoinColumn(name = "cook_event_id") },
inverseJoinColumns = {
#JoinColumn(name = "tag_id") })
#OrderBy("name")
private List<Tag> tags;
In my database I thus have 'users', 'cook_events', 'tags', 'user_tags_preferences', 'cook_events_tags' tables.
I would need to make a JPQL query that does following:
From my front-end I have the user_id.
I would like a query that filters all cook_events that are takesPlaceOn > CURRENT_TIMESTAMP, and where at least one of the user_tags_preferences from my user_id matches the cook_event_tags.
Is there a possibility with joins to filter all this to ultimately just get a List<CookEvent> I need. Or even a List<Tag> if it's easier.
It can even be different queries and that I filter it myself in the backend, but I would like to try to filter everything within a query.
Could this just be the answer?
SELECT DISTINCT(c) from User u JOIN u.tags t JOIN t.cookEvents c
WHERE u.id = :id AND c.takesPlaceOn > CURRENT_TIMESTAMP
Step 1.
Replace List in all your many-to-many associations with Set.
Step 2.
Change the JPQL query to:
SELECT c from CookEvent c JOIN FETCH c.tags t JOIN FETCH t.users u WHERE c.takesPlaceOn >
CURRENT_TIMESTAMP And u.id = :id

JPAQuery join intermediate many to many table

I'm using QueryDSL JPA, and want to do a join between two tables. I found many similar questions here already, but my case is different from all of them by one little detail. Here is a simplified version of my classes:
#Entity(name = "TABLE_A")
public class TableA {
#Id
#Column(name = "ID_A", nullable = false)
private Long idA;
}
#Entity(name = "TABLE_B")
public class TableB {
#Id
#Column(name = "ID_B", nullable = false)
private Long idB;
}
#Entity(name = "TABLE_C")
public class TableC {
#Id
#Column(name = "ID_C", nullable = false)
private Long idC;
#JoinColumn(name = "ID_A", referencedColumnName = "ID_A")
#ManyToOne
private TableA tableA;
#JoinColumn(name = "ID_B", referencedColumnName = "ID_B")
#ManyToOne
private TableB tableB;
}
Now what I want to do is join Tables A, C and B, to find the Bs which are linked to A. I know this seems like a useless step between, why not add a relation from A to B directly. In my case this is needed, these are just example classes to illustrate.
I tried this:
QTbTableA tableA = QTbTableA.tableA;
QTbTableB tableC = QTbTableC.tableC;
JPAQuery query = new JPAQuery(entityManager).from(tableA);
query.leftJoin(tableA, tableC.tableA);
The join throws an Exception because tableC.tableA is not a root path, only a property. But how do I join these tables correctly then?
Thanks in advance!
If you want to keep your current impl, you could start from TableC and then join the other tables:
query.from(tableC)
.innerJoin(tableC.tableA, tableA)
.innerJoin(tableC.tableB, tableB)
.where(tableA.idA.eq(myId)
.list(tableB);

M:N association not fetched with Hibernate query

I have a Many-to-Many relationship between Thread and Participant through a ThreadParticipant entity (because the association has an additional field). I have the following mapping.
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
}
Now, I am trying to fetch threads with the following query (using Spring Data JPA) and Hibernate as my JPA provider.
#Repository
public interface ThreadRepository extends JpaRepository<Thread, Integer> {
#Query("select distinct t from Thread t inner join fetch t.threadParticipants tp inner join fetch tp.participant p left join fetch p.account a left join fetch p.company c")
public List<Thread> test();
}
The problem is that when the fetch type for the associations within ThreadParticipants are set to FetchType.LAZY, the Thread.threadParticipants collection is empty. Consequently, if I set the associations to FetchType.EAGER, Thread.threadParticipants contains two elements (as it should). In this case, however, Hibernate goes nuts and executes four SQL queries for fetching a single thread.
Hibernate: select thread0_.id as id1_18_0_, threadpart1_.participant_id as particip2_19_1_, threadpart1_.thread_id as thread_i3_19_1_, participan2_.id as id1_12_2_, account3_.id as id1_0_3_, company4_.id as id1_6_4_, thread0_.created as created2_18_0_, thread0_.last_activity as last_act3_18_0_, thread0_.subject as subject4_18_0_, threadpart1_.last_viewed as last_vie1_19_1_, threadpart1_.thread_id as thread_i3_18_0__, threadpart1_.participant_id as particip2_19_0__, threadpart1_.thread_id as thread_i3_19_0__, participan2_.account_id as account_2_12_2_, participan2_.company_id as company_3_12_2_, account3_.email as email2_0_3_, account3_.facebook_profile_id as facebook3_0_3_, account3_.first_name as first_na4_0_3_, account3_.last_name as last_nam5_0_3_, account3_.middle_name as middle_n6_0_3_, company4_.additional_address_text as addition2_6_4_, company4_.banner_name as banner_n3_6_4_, company4_.ci_number as ci_numbe4_6_4_, company4_.city_id as city_id22_6_4_, company4_.co_name as co_name5_6_4_, company4_.company_type_code as company_6_6_4_, company4_.created as created7_6_4_, company4_.description as descript8_6_4_, company4_.email as email9_6_4_, company4_.last_modified as last_mo10_6_4_, company4_.logo_name as logo_na11_6_4_, company4_.name as name12_6_4_, company4_.number_of_reviews as number_13_6_4_, company4_.phone_number as phone_n14_6_4_, company4_.postal_box as postal_15_6_4_, company4_.rating as rating16_6_4_, company4_.second_phone_number as second_17_6_4_, company4_.street_name as street_18_6_4_, company4_.street_number as street_19_6_4_, company4_.teaser as teaser20_6_4_, company4_.website as website21_6_4_ from thread thread0_ inner join thread_participant threadpart1_ on thread0_.id=threadpart1_.thread_id inner join participant participan2_ on threadpart1_.participant_id=participan2_.id left outer join account account3_ on participan2_.account_id=account3_.id left outer join company company4_ on participan2_.company_id=company4_.id
Hibernate: select participan0_.id as id1_12_0_, participan0_.account_id as account_2_12_0_, participan0_.company_id as company_3_12_0_ from participant participan0_ where participan0_.id=?
Hibernate: select thread0_.id as id1_18_0_, thread0_.created as created2_18_0_, thread0_.last_activity as last_act3_18_0_, thread0_.subject as subject4_18_0_ from thread thread0_ where thread0_.id=?
Hibernate: select participan0_.id as id1_12_0_, participan0_.account_id as account_2_12_0_, participan0_.company_id as company_3_12_0_ from participant participan0_ where participan0_.id=?
Apparently it's executing a query for each participant, and two queries for each thread. So, without FetchType.EAGER, my code is simply not working, but with it, my database will get killed. I tried adding a #OneToMany association between Participant and ThreadParticipant (similar to the one from Thread to ThreadParticipant), but with the same results. I also tried to add all of the aliases to my query's field list, but to no avail.
Why is this happening? Is my mapping or query wrong? Thank you in advance!
Try changing the the one-to-many collection to a Set:
#OneToMany(fetch = FetchType.LAZY, mappedBy = "thread", cascade = { CascadeType.PERSIST, CascadeType.MERGE })
private Set<ThreadParticipant> threadParticipants = new HashSet<>();
The additional queries should NEVER be generated if you only have LAZY associations. That may be because the many-to-one and one-to-one relationships are EAGER by default. Try setting those to LAZY instead.

find(Class,id) and JPQL get different object (JPA)

I am using Hibernate 4.1.10.Final as my JPA provider, Spring and Spring MVC. There are two entities:
#Entity
#Table(name = "a")
public class A {
#Id
private String id;
#OneToMany(mappedBy = "a", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<B> bs;
}
#Entity
#Table(name = "b")
public class B {
#Id
private String id;
#ManyToOne
#JoinColumn(name = "fk_a_id")
private A a;
}
I need to get an A and it's bs, so I use the find(A.class,id) of EntityManager.
A a1 = em.find(A.class, id);
a1.getBs().size();
For which the result is: the size of bs is zero (which means that there is no associated B).
But I'm sure that there are many associated Bs in the database, and indeed the data can been loaded from database while checking via the console.
When I use Query:
Query query = em.createQuery("SELECT a FROM A AS a WHERE a.id = ?1",A.class);
query.setParameter(1, id);
A a= (A) query.getSingleResult();
a.getBs().size(); // = 22
I instead get a size = 22.
What's wrong?
Since you used the mappedBy property in your #OneToMany, the owner of the relation is B and not A. That's why when you load an instance of A, the corresponding Bs are not loaded. Try modifying your annotations with the following :
In class A :
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name="fk_a_id")
private Set<B> bs;
In class B :
#ManyToOne
#JoinColumn(name = "fk_a_id", insertable=false, updatable=false)
private A a;

Categories