I have some JPA code that is causing me some consternation. There are two objects; a Patient and a PatientUR (identifier) related as follows:
#OneToMany(mappedBy="patient", cascade = CascadeType.ALL, orphanRemoval=true, fetch = FetchType.EAGER)
private List<PatientUR> urs;
#ManyToOne(cascade = CascadeType.DETACH)
#JoinColumn(name="patient_id")
private Patient patient;
I can create a patient and patientUR(s) within a transaction and they are persisted correctly. However, there are occasions (some time later) when I need to retrieve the patientUR(s) from a persisted patient. I use this approach:
// find patient using an initial reference ur
PatientUR myPatientUR = patientURService.find(ur0); // is there something broken here?
Patient myPatient = myPatientUR.getPatient();
// Modify patient if required
// retrieve all UR's
List<PatientUR> myRegisteredURList = myPatient.getUrs();
The issue is that myRegisteredURList always returns an empty list. The patient lookup and ur retrieval happens within a transaction.
Not sure what I am missing here?
Just modified the code with:
List<PatientUR> myRegisteredURList = patientURService.findByPatient(myPatient);
this return correctly - that is searching for PatientUR's by a PatientID rather than by retrieving the PatientUR list from the Patient object.
Related
I am quite a beginner with Spring Data, and I have to code my first difficult query :)
I am making a reservation engine for an event. My data model is composed by:
a RoomType entity defining a possible configuration for a room (e.g. double, triple, quadruple)
a Room entity representing the actual Room
a RoomArrangement entity defining all the possible RoomTypes for a Room (e.g. the room 7 can be configured as Triple or Double room)
a RoomAssignment entity representing the actual configuration chosen for a room after having been reserved
FKs are configured this way
RoomType <--* RoomArrangement *--> Room <-- RoomAssignment
(see code below for Entity definition)
I need to find the Room without an Assignment that has the highest Priority (= nearest to 1) for a given RoomType.
I have configured Spring as below.
RoomType.java
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "room_type_id")
private List<RoomArrangement> roomArrangements;
RoomArrangement.java
#ManyToOne(targetEntity = RoomType.class)
#JoinColumn(name = "room_type_id", nullable = false)
private RoomType roomType;
#ManyToOne(targetEntity = Room.class)
#JoinColumn(name = "room_id", nullable = false)
private Room room;
#Column(name = "priority")
private Integer priority;
Room.java
#OneToMany(fetch = FetchType.LAZY)
#JoinColumn(name = "room_id")
private List<RoomArrangement> roomArrangements;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "room", cascade = CascadeType.ALL)
private RoomAssignment assignment;
RoomArrangementRepository.java
RoomArrangement findFirstByRoomTypeAndRoom_AssignmentIsNullOrderByPriorityAsc(RoomType rt);
The query above is translated to
select
roomarrang0_.id as id1_3_,
roomarrang0_.priority as priority2_3_,
roomarrang0_.room_id as room_id3_3_,
roomarrang0_.room_type_id as room_typ4_3_
from
room_arrangements roomarrang0_
left outer join
rooms room1_ on roomarrang0_.room_id=room1_.id
where
roomarrang0_.room_type_id=9
and
(room1_.id is null)
order by
roomarrang0_.priority asc
limit 1;
The issues are two:
I do not know where the where clause
(room1_.id is null)
comes from
I do not know where the "AndRoom_AssignmentIsNull" clause has gone
Should I "invert" the OneToOne relationship and put the FK on the Room class?
Thanks for your help!
Lorenzo
I've tried to apply some of the suggestions, and "turned" the query on the RoomRepository.
The query came like this:
Room findFirstByRoomArrangements_RoomTypeAndAssignmentIsNullOrderByRoomArrangements_PriorityAsc(RoomType rt);
We come to the same problem:
select
room0_.id as id1_6_,
room0_.room_name as room_nam2_6_
from
rooms room0_
left outer join
room_arrangements roomarrang1_ on room0_.id=roomarrang1_.room_id
where
roomarrang1_.room_type_id=?
and
(room0_.id is null)
order by
roomarrang1_.priority asc
limit ?;
I think the problem lies in the fact that the one-to-one relationship between Room and RoomAssignment is represented on the database with a FK on the room_assignments table to the rooms table.
I will try to put the FK on the rooms table instead of on the room_assignments table and see if something changes.
If you need to find a room without an without an Assignment, shouldn't you be looking that in the Room repository?
You said that you have to find a Room but you are returning a RoomArrangement in a RoomAssignmentRepository. This is quite confusing.
Let's assume you are in right place, that means RoomRepository and as you said:
I need to find the Room without an Assignment that has the highest Priority (= nearest to 1) for a given RoomType.
try to use the following method name
Room findByRoomArrangementRoomTypeAndRoomAssignmentIsNullOrderByPriorityAsc(RoomType rt)
Made it!
In the end, the problem lied in the fact that the one-to-one relationship between Room and RoomAssignment was mapped by an FK from RoomAssignment to Room. Apparently, Spring Data didn't manage this configuration properly: the Assignment is null was translated to room_assignment.room_id = null and, since room_id was an FK to room.id, to room.id = null.
Inverting the FK mapping, the query is translated to
select
room0_.id as id1_6_,
room0_.assignment_id as assignme3_6_,
room0_.room_name as room_nam2_6_
from
rooms room0_
left outer join room_arrangements roomarrang1_ on room0_.id=roomarrang1_.room_id
where
roomarrang1_.room_type_id=?
and (room0_.assignment_id is null)
order by
roomarrang1_.priority asc
limit ?
which correctly returns what I needed.
I have a few entities with lazy one to many relationships (logic omitted for brevity):
#Entity
class A{
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "a_pk", nullable = false)
List<B> blist = new ArrayList<>();
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "a_pk", nullable = false)
List<C> clist = new ArrayList<>();
#Column(name = "natural_identifier", nullable = false)
private String id;
}
#Entity
class B{
}
#Entity
class C{
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "c_pk", nullable = false)
List<D> dlist = new ArrayList<>();
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "c_pk", nullable = false)
List<E> elist = new ArrayList<>();
}
#Entity
class D{
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "d_pk", nullable = false)
List<F> flist = new ArrayList<>();
}
#Entity
class E{
}
#Entity
class F{
}
In some (very rare) case I want to load an instance of A and all of its associations eagerly. The reason for that is that I want to make some modifications on that A instance and it's children as a whole, and then either save or discard them, depending on user input.
If I were to load things as they are needed, I'd have to reattach entities to a new session on user request, but they are being modified, and modifications should not be persisted yet.
So I write something like that:
Session session = sessionFactory.openSession();
s.beginTransaction();
Criteria c = session
.createCriteria(A.class)
.add(Restrictions.eq("id", someValue))
.setFetchMode("blist", SELECT)
.setFetchMode("clist", SELECT)
.createAlias("clist", "c")
.setFetchMode("c.dlist", SELECT)
.setFetchMode("c.elist", SELECT)
.createAlias("c.dlist", "d")
.setFetchMode("d.flist", SELECT);
A a = (A) c.uniqueResult();
session.close(); // line 150
a.getBlist().size(); // line 152 - debug
a.getClist().size(); // line 153 - debug
When I try to access stuff I get an exception on line 152:
org.hibernate.LazyInitializationException:
failed to lazily initialize a collection of role:
A.blist, could not initialize proxy - no Session
If I change fetch strategy to JOIN everywhere in the criteria, I get the same exception, but on line 153 (in other words, the first association gets loaded, but not the others).
EDIT: Alexey Malev suggested that fetch mode is set for an alias; it does seem to be true. With a following criteria:
Criteria c = session
.createCriteria(A.class)
.add(Restrictions.eq("id", someValue))
.setFetchMode("blist", JOIN)
.setFetchMode("clist", JOIN);
I'm getting a different Exception: org.hibernate.loader.MultipleBagFetchException: cannot simultaneously fetch multiple bags on line 149. So, join-fetching is not an option.
The question is, how do I load the whole thing?
Hibernate version 4.2.12.Final
I found a solution to my original problem by using a slightly different approach.
Quoting Hibernate ORM documentation:
Sometimes a proxy or collection needs to be initialized before closing the Session. You can force initialization by calling cat.getSex() or cat.getKittens().size(), for example. However, this can be confusing to readers of the code and it is not convenient for generic code.
The static methods Hibernate.initialize() and Hibernate.isInitialized(), provide the application with a convenient way of working with lazily initialized collections or proxies. Hibernate.initialize(cat) will force the initialization of a proxy, cat, as long as its Session is still open. Hibernate.initialize( cat.getKittens() ) has a similar effect for the collection of kittens.
Simply getting lazy collection (a.getBlist()) does not make it load - I initially made that mistake. If I try to get some data from that collection (get an item, get collection size) it will load. Calling Hibernate.initialize(..) on that collection will do the same.
So, iterating over entity associations, and their respective associations, etc, and explicitly initializing them (eg with Hibernate.initialize()) within session will load everything to be available outside the session once it's closed.
Criteria fetch modes are not used at all with that approach (why won't they work as documented is another question).
It is an obvious case of N+1 problem, but something I can live with.
I think you're using wrong fetch mode. Most likely you need JOIN.
Try this instead:
Criteria c = session
.createCriteria(A.class)
.add(Restrictions.eq("id", someValue))
.setFetchMode("blist", JOIN)
.setFetchMode("clist", JOIN)
.createAlias("clist", "c")
.setFetchMode("c", JOIN)
...//the same for others
Note - I have added fetch mode for alias too. The behavior when you're not able to load any list which has an alias leads to that guess..
For the record I had a similar problem due to the key value setFecthMode(key,...)
I was using table names instead of field names.
I have this object that builds a tree in the database. Each node points to its parent, or is null. I need this relationship to be bi-directional, so each node also knows its children nodes. When a node is deleted IS_ACTIVE gets set to false. How do I modify these annotations such that only children with IS_ACTIVE set to true get loaded?
#Entity
#Table(name = "node")
public class Node {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PARENT_NODE_ID")
private Node parentNode;
#OneToMany(mappedBy = "parentNode", fetch = FetchType.LAZY)
private Set<Node> childrenNodes;
#Column(name = "IS_ACTIVE", nullable = false)
private boolean isActive;
//other fields that shouldn't matter left out.
}
Currently my unit tests are running in the same transaction, so I have to use session.refresh(node) to get the children nodes to load at all, but every time it loads all of the children ignoring my filters and where clause.
What is the correct way to annotate this class so the children only the active children load?
Does it matter if the children are lazy-loaded?
*Please note I have search for answers to this.
As an example, this question seems related, but the solution does not work. I belive it is different because my join is self-referencing... but it might be due to something else I am missing. annotation to filter results of a #OneToMany association
When you say "only children with IS_ACTIVE set to true get loaded", you are defining a business rule and therefore you have to instruct Hibernate to follow it somehow.
Using "session.get(object)" and "session.refresh(object)" you are asking Hibernate to get an Entity, however you did not instruct Hibernate to follow your business rule.
Briefly speaking, there are two ways to solve your issue:
(1): Let Hibernate fetch all "childrenNodes", subsequently you can write another method to return only children with IS_ACTIVE = true. Something like:
public Set<Node> getActiveChildrenNodes(Node n){
Set<Node> result = new HashSet();
for(Node cn : n.getChildrenNodes()){
if(cn.isActive)
result.add(cn);
}
return result;
}
As you may see, this approach may have a bad performance if you have several records in your database.
(2): A better options would be to load only children with IS_ACTIVE = true. So, you can write something like:
public List getActiveChildrenNodes(Node n, Session s){
return = session
.createQuery("FROM Node WHERE Node.id = :pId AND Node.childrenNodes.isActive : pIsActive")
.setParameter("pId", n.getId())
.setParameter("pIsActive", true)
.list();
}
There are several ways to do it, I hope this explanation can help you.
Cheers,
I have two entities: Questionnaire and QuestionnaireTime. Questionnaire's id is a foreign key in QuestionnaireTime. So the relationship in my QuestionnaireTime entity looks like this:
#JoinColumn(name = "questionnaireid", referencedColumnName = "id")
#ManyToOne(cascade = CascadeType.PERSIST)
private Questionnaire questionnaireid;
So what I'm trying to do is to add multiple QuestionnaireTime records for one Questionnaire. If I remove the CascadeType.PERSIST part in my relationship, my persist is not done. And when I use cascade, I get several new records in my main table Questionnaire and that's not what I want.
For example when I want to add three QuestionnaireTime's for a certain Questionnaire, the three records are inserted in my QuestionnaireTime table but also 3+1 records are added in Questionnaire.
If you need more explanation. This is my managed bean, the part that I'm trying to add multiple QuestionnaireTime records in one Questionnaire:
NB - current is my Questionnaire object
else if (current.getType().equals("frequent")) {
int iteration = 1;
currentQuestionnaireTime = new QuestionnaireTime();
if (!selectDateList.isEmpty()) {
for (String insertedDate : selectDateList) {
currentQuestionnaireTime.setId(0);
currentQuestionnaireTime.setQuestionnaireid(current);
getEjbQuestionnaireTimeFacade().create(currentQuestionnaireTime);
iteration++;
}
}
}
try {
getFacade().create(current); // my Questionnaire facade
} catch (EJBException ejbe) {
ejbe.getCause();
}
A few things,
questionnaireid - this is a very bad field name, questionnaire would make sense.
currentQuestionnaireTime.setId(0); - you should not be changing the id of an existing object, instead create a new object
getEjbQuestionnaireTimeFacade().create() - what does this do? If you need the reference to the current, then the current should be persisted first. If you EJB remote? If it is, then either make it local, or ensure you use merge() not persist(), as you new object has a reference to a detached object. Or find the reference in the current persistence context.
We've got an entity model like this (non-related fields omitted for brevity):
#Entity
public class Invoice {
//The other portion of the sum that is invoiced from another payer.
#OneToOne(mappedBy = "parentInvoice", cascade = CascadeType.ALL)
private Invoice otherPayersInvoice;
//The patient's invoice, which is the parent of this other payer's invoice.
#OneToOne(cascade = CascadeType.ALL)
private Invoice parentInvoice;
#ManyToOne
private Payer payer;
}
So, basically we will split invoice's into parts and store the relations within the entities.
The following query works and returns all invoices by payerId:
SELECT i FROM Invoice i WHERE i.payer.id = :payerId
However, when i extend the query to also list all "child" invoices that the payer may have (Invoices invoiced from another payer), I get no results at all and no error or warning anywhere whatsoever.
SELECT i FROM Invoice i WHERE i.payer.id = :payerId OR
(i.parentInvoice IS NOT NULL AND i.parentInvoice.payer.id = :payerId)
What could possibly be the issue here?
i.parentInvoice.payer.id
This is doing an INNER join from Invoice to its parent to its payer, which will filter all rows without a parent or payer.
You need to use an outer-join instead.
SELECT i FROM Invoice i join i.parentInvoice parent join p.payer parentPayer join i.payer payer WHERE payer.id = :payerId OR (parentPayer.id = :payerId)