How to fetch object using the List Association in Hibernate - java

I have a class A with setters and getters
class A{
private int id;
private List<B> b;
}
I want to fetch the A object based on the B objects. So suppose I have a list of B objects I want to get the A objects that contain the B.
So i decided to use in
Here is the code
Criteria criteria = session.createCriteria(A.class).setCacheable(false);
criteria.add(Restrictions.in("b",{list of B})).list();
This gives an error java.sql.SQLException: No value specified for parameter 1
How can I get object A using B

You should join the B's first:
Criteria criteria = session.createCriteria(A.class)
.setCacheable(false)
.setFetchMode("bList", FetchMode.JOIN)
.createAlias("bList", "b");
and then use an IN for the ids of b:
criteria.add(Restrictions.in("b.id",{list of B})).list();
Set your fetch mode accordingly whether you want the list of B's also apart from A.

Related

Update Entity Relationship via UPDATE .. SET Query

I have two entities:
class A {
#OneToMany(mappedBy = "a")
List<B> bs;
// getter/ setter
}
class B {
#ManyToOne
A a;
// getter/ setter
}
To delete one b, I first need to invalidate that relationship.
"Traditionally" I would do something like that:
A a = em.getReference(A.class, entityIdA)
B b = em.getReference(B.class, entityIdB);
a.getBs().remove(b);
b.setA(null);
em.remove(b);
This is not very performant, if the List of a's is getting large (a few hundreds in my case).
I know I can also use JPQL to create an update query.
something like this:
Query q = em.createQuery("UPDATE B b SET b.a = NULL");
q.executeUpdate();
Question: What would be the corresponding JPQL query to remove one b from a's list of bs?
In short:
How to translate
a.getBs().remove(b);
into a JPQL query?
EDIT: the mentioned update query translates to
UPDATE B SET A_ID = ? WHERE (ID = ?)
bind => [null, 2]
Tables look like this:
A
ID
B
ID A_ID
From the comments and from this question, changing the owning side of the relationship is sufficient.
Therefore, to do
a.getBs().remove(b);
as an jpql query, one can do
"UPDATE B b SET b.a = NULL"
This will release the bidirectional relationship between a and b.
Note that you might need to clear the L2 cache or close the EntitiyManagerFactory for this to take effect.
factory.getCache().evictAll();

Hibernate/JPA, save a new entity while only setting id on #OneToOne association

I have two entities,
class A { #OneToOne B b; }
class B { ... lots of properties and associations ... }
When I create new A() and then save, i'd like to only set the id of b.
So new A().setB(new B().setId(123)).
Then save that and have the database persist it.
I do not really need to or want to fetch the entire B first from the database, to populate an instance of A.
I remember this used to work, but when I am testing it is not.
I have tried Cascade All as well.
B b = (B) hibernateSession.byId(B.class).getReference(b.getId());
a.setB(b);
hibernateSession.load(...) // can also be used as it does the same.
The JPA equivalent is :
entitymanager.getReference(B.class, id)
Below code should help.It will fetch B only when its accessed.
class A {
#OneToOne(fetch = FetchType.LAZY) B b;
}

hibernate order by nested entity field with possible null values

I've stumbled upon a problem with Hibernate. I've 2 entities - let's say A and B like so (Entity/Table annotations ommited):
class A {
#ManyToOne
#JoinColumn(name = "b_id")
private B b;
}
class B {
#Column(name = "name")
private String name;
}
Now, I'm trying to query all A entities and ordering them by name field of B's entity like so:
SELECT q FROM A AS q ORDER BY q.b.name asc nulls last
The problem is, there are rows in A's table having null foreign-key (b is null) - in result the aforementioned query returns only rows that don't contain null in b field, and I'd like to have them all.
I guess hibernate joins the table without using LEFT JOIN (OUTER JOIN?) resulting in null values being skipped.
Is there any way to change this behaviour? It would be great, if I could solve it by using annotations in entity classes, because the query-generating mechanism is pretty locked up.
You can use CriteriaBuilder and set alias on entityRoot
Root<A> entityRoot = criteriaQuery.from(A);
entityRoot.join("b", JoinType.LEFT).alias("b");
criteriaQuery.select(entityRoot)
.orderBy(criteriaBuilder.asc(entityRoot.get("b").get("name"))
;
you can use criteria query for this but you will have to create session while using that, it is simpler to access database using criteria:
Criteria criteria = session.createCriteria(A.class)
//create alias of your other class to provide ordering according to foriegn key
criteria.createAlias("foreignkey","keyin table A(eg..b)");
criteria.addOrder(Order.asc(b.name));
List list = criteria.getlist();
hope this helps

Specifying criteria for child table in One To Many Relationship

I have two entity objects (A and B) that have a One-To-Many relationship. I am using JPA (Hibernate) to join these tables and query them for a specific result set, but the criteria I specify for the child table (B) are not applied when fetching my results. This is how I have defined the query:
CriteriaBuilder builder = entityManager.getCriteriaBuilder();
CriteriaQuery<A> query = builder.createQuery(A.class);
Root<A> a = query.from(A.class);
Join<A, B> abJoined = a.join(A_.b);
query.distinct(true)
.where(builder.and(
builder.equal(a.get(A_.id), id),
builder.equal(a.get(A_.active), 1),
builder.equal(a.get(A_.location), 1011),
builder.equal(a.get(A_.status), "Pending"),
builder.equal(abJoined.get(B_.status), "Not Moved"),
builder.greaterThan(abJoined.get(B_.detailId), 0)
));
When I call entityManager.createQuery(query).getResultList(); I get one instance of entity 'A', but when I try to access 'B' through 'A' a.getB() the two criteria that I had specified for abJoined are not applied and I get all instances of 'B' that are joined to 'A'. Is there something more I need to do to get these criteria applied? If the criteria cannot be applied, is there a recommended method for removing the corresponding instances of 'B' from the result set?
I can provide more code or other details if necessary.
The query is used to select which A entities must be returned by the query. But the A entities will never be partial entities containing only a subset of their Bs. They will always be complete instances of A, reflecting the state of what A is in the database, and which B this A is related to.
BTW, even if this was possible (it's not, and explicitely forbidden by the JPA spec), your query would at least have to load the Bs as well, using a fetch join. Otherwise only the state of A is returned by the query, and the linked Bs are loaded lazily.
But in JPA, for a #OneToMany relationship, Query is fired first on the master, with all the Criteria and fetches just master records.
Later Individually the query is fired, BUT BUT BUT......the B table criteria will not contain all the conditions, but only one condition i.e. just the primary key VALUE of master records fetched in i.e just with one condition "b.A_id=[primarykeyfetched from master]"
SOLUTION is simple
Make an Independent class, in which we require the fields of #OneToMany relationship
STEP 1
public class AB {
private A a;
private B b; //#OneToMany with A in the Entity class definitions
public AB(){}
public AB(A a, B b){ ///Very Important to have this constructor
this.a=a;
this.b=b;
}
getter setter....
}
STEP 2
CriteriaQuery<AB> q = criteriaBuilder.createQuery(AB.class);
Root<A> fromA = criteriaQuery.from(A.class);
List<Predicate> predicates = new ArrayList<Predicate>();
Join<A,B> fromB = fromA.join("b", JoinType.INNER);/*"b",fieldName of B in entity Class A*/
criteriaQuery.select(criteriaBuilder.construct(AB.class,A,B));//looks AB's 2 param constr.
predicates.add(criteriaBuilder.equal(fromA.get("name"), filter.getName()));
predicates.add(criteriaBuilder.equal(fromB.get("salary"), filter.getSalary()));
..........
List<AB> ABDataList = typedQuery.getResultList();
for(AB eachABData :ABDataList){
....put in ur objects
obj.setXXX(eachABData.getA().getXXX());
obj.setYYY(eachABData.getB().getYYY());
}
This will give all the results applying all the criteria to master table A, and comparing the primary key of Table B instead of foreign key.

Limit the size of a collection in JPA

Say I have an entity like this
#Entity
Class A{
//fields
#Onetomany
Set<B> b; //
}
Now, how do I limit the number of 'B's in the collection in such a way that, when there is a new entry in the collection, the oldest one is removed, some thing like removeEldestEntry we have in a LinkedHashMap.
I am using MySQL 5.5 DB with Hibernate. Thanks in advance.
EDIT
My goal is not to have more than N number of entries in that table at any point of time.
One solution I have is to use a Set and schedule a job to remove the older entries. But I find it dirty. I am looking for a cleaner solution.
I would use the code to manually enforce this rule. The main idea is that the collection B should be well encapsulated such that client only can change its content by a public method (i.e addB()) . Simply ensure this rule inside this method (addB()) to ensure that the number of entries inside the collection B cannot larger than a value.
A:
#Entity
public class A {
public static int MAX_NUM_B = 4;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
private Set<B> b= new LinkedHashSet<B>();
public void addB(B b) {
if (this.b.size() == MAX_NUM_B) {
Iterator<B> it = this.b.iterator();
it.next();
it.remove();
}
this.b.add(b);
}
public Set<B> getB() {
return Collections.unmodifiableSet(this.b);
}
}
B:
#Entity
public class B{
#ManyToOne
private A a;
}
Main points:
A should be the owner of the relationship.
In A , do not simply return B as client can bypass the checking logic implemented in addB(B b) and change its content freely.Instead , return an unmodifiable view of B .
In #OneToMany , set orphanRemovalto true to tell JPA to remove the B 's DB records after its corresponding instances are removed from the B collection.
There is one API provided by Apache Commons Collection. Here you can use the class CircularFifoBuffer for your reference of the same problem you have, if you want example shown as below that you can achive that
Buffer buf = new CircularFifoBuffer(4);
buf.add("A");
buf.add("B");
buf.add("C");
buf.add("D"); //ABCD
buf.add("E"); //BCDE
I think you will have to do it manually.
One solution that comes to mind is using #PrePersist and #PreUpdate event listeners in entity A.
Within the method annotated with above annotations , you check if size of Set<B> , if it is above the max limit, delete the oldest B entries(which may be tracked by a created_time timestamp property of B)

Categories

Resources