I have an entity with an attribute that is a list of children. On its set, I clear the current list and set its new values from database, following the correct guideline for the "all-delete-orphan" problem with hibernate:
#JsonView(Views.Response.class)
#OneToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "uuid")
private Set<Response> responses = new HashSet<>();
public void setResponses(final Set<Response> responses) {
this.responses.clear();
if(responses != null) {
this.responses.addAll(responses);
}
}
By reading some Stack Overflow and other links on this error, I found that this might happen when you assign a new list to responses directly through =. However, if you clear the list first and simply add new values, this should not happen. At some point I even found an article saying I should perform an entityManager.flush() after the clear(). For all I can see and have read, I am following the rules. What am I missing here? Why do I still get the following exception with the code above? I don't know if this information is useful but I am using Java 17.
javax.persistence.PersistenceException: org.hibernate.HibernateException: A collection with cascade="all-delete-orphan" was no longer referenced by the owning entity instance: com.ppro.transaction.model.Processing.acquirerResponses
Related
I have a Spring application and in one of the Controllers lies a POST method that allows a user to create multiple entities at once, since they're all related with one main entity, EncodingResult.
After testing a bit, it seemed that this caused the following error: object references an unsaved transient instance. Looking around a bit, I found that the solution to it was to use CascadeType.ALL on the attributes that have #JoinColumn annotations in this main class, as such:
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "codec", referencedColumnName = "commit_hash", nullable = false)
private Codec codec;
The Codec side is as follows:
(...)
#OneToMany(cascade = CascadeType.ALL, mappedBy = "codec")
private Set<EncodingResult> associatedResults = new HashSet<>();
#Column(name = "commit_hash", nullable = false, unique = true)
// indicates the precise version of the codec
private String commitHash;
(...)
This, however, seems to cause another error: PSQLException: ERROR: duplicate key value violates unique constraint "uk_paitxovhx3j95tuaq9krkk9qh" Detail: Key (commit_hash)=(ec44ee022959410f9596175b9424d9fe1ece9bc8) already exists.
To my understanding, this must be because the CascadeType.PERSIST tries to update the related entities when the parent is updated no matter what (which would make sense, given that in the first POST attempt the system works fine, but in the subsequent ones, which do not save Codec, for example, if there is already one such entity with the given commitHash it doesn't), but I am not sure if this is correct.
Either way, how can I make sure that this operation succeeds? Is it even possible to do what I am trying to do here?
Below is the JSON body being used:
{
"codec": {
"name": "vvcodec",
"commitHash": "ec44ee022959410f9596175b9424d9fe1ece9bc8",
"codecAttrs": "--threads 1",
"preset": ""
},
"video": {
"name": "bowing_cif",
"resolution": "352x288",
"fps": 29.97,
"nFrames": 300,
"format": "y4m",
"uniqueAttrs": "fps29.97_nf300_fy4m_r352x288_nbowing_cif"
},
"encodingConfig": {
"uniqueAttrs": "qp27_nf30_nt8_ca",
"qp": 27,
"nFrames": 30,
"nThreads": 8,
"codecAttrs": ""
},
"ypsnr": 48.2012,
"upsnr": 49.5337,
"vpsnr": 51.1360,
"yuvpsnr": 48.749,
"bitrate": 196.1958,
"time": 0.595,
"energyConsumption": 78.32
}
EDIT: I suppose this will also help - my implementation of the #Service layer for the given method:
public EncodingResult saveEncodingResult(EncodingResult encodingResult) {
var video = encodingResult.getVideo();
var encodingConfig = encodingResult.getEncodingConfig();
var codec = encodingResult.getCodec();
if (videoRepository.findByUniqueAttrs(video.getUniqueAttrs()).isEmpty())
videoRepository.save(video);
if (encodingConfigRepository.findByUniqueAttrs(encodingConfig.getUniqueAttrs()).isEmpty())
encodingConfigRepository.save(encodingConfig);
if (codecRepository.findByCommitHash(codec.getCommitHash()).isEmpty())
codecRepository.save(codec);
return encodingResultRepository.save(encodingResult);
}
Do you receive EncodingResult in your controller method? If so, hibernate has no idea that Codec already exists because you're creating a new Codec object every time (well, spring does, by deserializing json). Upon save, hibernate tries to insert new Codec because of CascadeType.ALL instead of using an existing entity.
Typically, you would fetch an existing entity from the database, update it's state and then save it - that is unless you're creating a new entity.
Second thing, if EncodingResult is a child of Codec it really shouldn't have CascadeType.ALL on that relation - apart from being able to insert new Codec when saving EncodingResult, you're also specifying that REMOVE operation will be cascaded from EncodingResult to Codec.
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.
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,
This is similar, but not identical, to:
Hibernate criteria query on different properties of different objects
I have a SpecChange record, which has a set of ResponsibleIndividuals; these are User records mapped by a hibernate join-table association. I want to create a Criteria query for a SpecChange which has the specified User in its ResponsibleIndividuals set, OR some other condition on the SpecChange.
I'm omitting most of the code for clarity, just showing the relevant annotations:
#Entity
class SpecChange {
#OneToMany
#JoinTable(name = "ri_id_spec_change_id", joinColumns = { #JoinColumn(name = "spec_change_id") }, inverseJoinColumns = #JoinColumn(name = "ri_id"))
#AccessType("field")
public SortedSet<User> getResponsibleIndividuals() { ... }
#Id
#Column(name = "unique_id")
#AccessType("field")
public String getId() { ... }
}
#Entity
class User { ... }
//user does not have a SpecChange association (the association is one-way)
What I want to do:
User currentUser = ...;
Criteria criteria = session.createCriteria(SpecChange.class);
...
criteria.add(Restrictions.disjunction()
.add(Restrictions.eq("responsibleIndividuals", currentUser))
.add(...)
);
criteria.list();
This generates wrong SQL:
select ... from MY_DB.dbo.spec_change this_ ... where ... (this_.unique_id=?)
...and fails:
java.sql.SQLException: Parameter #2 has not been set.
(I omitted another condition in the where clause, hence parameter #2 is the one shown. I am sure that 'currentUser' is not null.)
Note that the restriction references the wrong table: this_, which is SpecChange, not the User table
I tried a dozen different tricks to make it work correctly (including creating an alias, as mentioned in the previous post above). If there is a way to do it using the alias, I wasn't able to determine it.
The following DOES work (but doesn't accomplish what I need, since I can't use it in a disjunction):
criteria.createCriteria("responsibleIndividuals").add(Restrictions.idEq(currentUser.getId()));
[Edit: a workaround for what seems like a bug in Hibernate, using HQL]
select sc
from com.mycompany.SpecChange sc
left join fetch sc.responsibleIndividuals as scResponsibleIndividuals
where scResponsibleIndividuals = :p1 or sc.updUser = :p1
order by sc.updDate desc
This won't work without the alias "scResponsibleIndividuals"