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

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;

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.

QueryDsl invert manyToMany join

Given we have the following entities that form a many-to-many relationship:
#Entity
public class A {
#Id
private Long id;
private String name;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(
name = "A_B",
joinColumns = #JoinColumn(name = "id_a"),
inverseJoinColumns = #JoinColumn(name = "id_b"))
private Set<B> listing;
}
#Entity
public class B {
#Id
private Long id;
}
I need to write a query that fetches B and applies some WHERE criteria on A side.
Since the relationsip is modeled from A entity's side it's very easy to write a query that joins these itsself:
new JPAQuery<>(entityManager)
.select(QB.b)
.from(QA.a)
.join(QA.a.listing,b)
.where(QA.a.name.eq("test"))
.fetch();
However since A_B table can be duplicated, this query can produce duplicate entries, which does not do for my scenario. So instead I need to start FROM B and JOIN A. And this is where I need help. I tried:
new JPAQuery<>(entityManager)
.select(QB.b)
.from(QB.b)
.join(QA.a).on(QA.a.listing.any().eq(QB.b))
.where(QA.a.name.eq("test"))
.fetch();
But that does not work as any() merely produces a subselect, instead of many to many join.
How do I write this query in Querydsl?

Specific deep level size to load association in hibernate

Recently I created a project and in my models I have ManyToMany back references.
My model is like below:
#Entity
public class A {
#ManyToMany(mappedBy = "parents", fetch = FetchType.EAGER)
private Set<A> children = new HashSet<>();
#ManyToMany
#JsonProperty(access = JsonProperty.Access.WRITE_ONLY)
#JoinTable(
name = "link_a_recursion",
joinColumns = #JoinColumn(name = "child_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "parent_id", referencedColumnName = "id")
)
private Set<A> parents = new HashSet<>();
//I removed the rest ( setter, getter and other fields )
}
When I fetch this model and I want to load children it throws StackOverFlowException error ( Recursive exception )
I want to know is there any way to say to hibernate just load one level of associate and don't go deep.
For clarify:
A.children[0].children[0].children should be null to stop recursion
I want to load the first children not all the children inside of the other children
Edited:
I add another entity:
#Entity
public class B {
#OneToMany(mappedBy = "b")
private Set<A> entities = new HashSet<>();
//setter, getter
}
and add below to A entity:
#ManyToOne
private B b;
then I changed below:
#ManyToMany(mappedBy = "parents", fetch = FetchType.EAGER)
to
#ManyToMany(mappedBy = "parents", fetch = FetchType.LAZY)
and in my BService my findOne function is like below:
#Transactional(readOnly = true)
public B findOne(Long id) {
B b = repository.findOne(id);
for (A a: b.getEntities()) {
a.getChildren().size();
}
return b;
}
but again I'm getting error :(
Try lazy fetch instead
#ManyToMany(mappedBy = "parents", fetch = FetchType.LAZY)

Hibernate fetch strategy for associations

#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?

JPA Annotation issue

I have a Table A , Table AB , TABLE B , Table AB has foreign key references to Table A and Table B. There is a One to One relation between Table A and Table AB.and Many to one between Table B and Table AB.
My question is if i am saving domain for table B, it is saving data in table AB but not in A Please let me know if this is the expected behavior or can i save all data in all other tables just by calling save on Object B
Class A
{
#OneToOne(mappedBy="abpk.a")
#Cascade({ org.hibernate.annotations.CascadeType.ALL,
org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
private AB ab;
}
#Entity
#AssociationOverrides({
#AssociationOverride(name = "abpk.a", joinColumns = #JoinColumn(name = "colA", referencedColumnName = "colA")),
#AssociationOverride(name = "abpk.b", joinColumns = #JoinColumn(name = "Colb", referencedColumnName = "colB")) })
Class AB
{
ABPK abpk = new ABPK();
A a;
B b;
//inner class
class ABPK
{
A a;
B b;
#OneToOne(fetch = FetchType.LAZY)
#Cascade({ org.hibernate.annotations.CascadeType.ALL,
org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
#JoinColumn(name="b")
//Getter for A
GetA();
#ManyToOne(fetch = FetchType.LAZY)
//Getter for B
GetB();
}
}
Class B
{
#OneToMany(fetch = FetchType.LAZY, mappedBy = "abpk.b")
#Cascade({ org.hibernate.annotations.CascadeType.ALL,
org.hibernate.annotations.CascadeType.DELETE_ORPHAN })
private List<AB> abList;
}
That depends on how you have set your cascade policies. If you have set it to CASCADE_TYPE ALL, then theoretically it should persists.
But in any case, it is better to wire up both sides of a relationship when you are trying to persist associations.

Categories