Does Hibernate always load associated object even if it can be null? - java

I have a question regarding Hibernate.
I have two objects with a Many-to-One relationship:
Eg:
Object 1:
public class Person {
#Basic
#Column(length = 50)
protected String name;
#NotFound(action=NotFoundAction.IGNORE)
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "groupCode", referencedColumnName = "code", updatable=false)
protected Group group;
...all the getters and setters...
}
Object 2:
public class Group {
#Id
#Basic
#Column(length = 3, nullable = false)
protected String code;
#Basic
#Column(length = 30, nullable = false)
protected String groupName;
#Basic
#Column(precision = 15, scale = 0)
protected long exampleFieldId;
...rest of code....
}
I have tried to make this example as simple as possible. My issue is that the associated object (Group) on Person can be null. Currently, Hibernate loads up an instance of Group when I load up a particular Person and throws an exception because it cannot set exampleFieldId to be null (as it is a primitive type).
I can stop this error by changing long to be Long however, I would have thought that the Group object on Person should be null and thus no Group object loaded in the first place?
Does anyone know if Hibernate loads the associated object regardless of it being allowed to be null, or have I missed some important annotation?
Thanks

As mentioned by Firo:
have you disabled lazy loading and set fetchmnode to join because
NHibernate has to fetch them to decide if it should nullify it or not
and it can not decide that only with an id
This seems to be the same issue you are experiencing even though it is in NHibernate. Worth checking though!
Why does Hibernate attempt to load "not-found=ignore" associations?
Edit: It could be that you're missing #Fetch(FetchMode.JOIN).

I have finally worked out what was going on here, and the issue was not with Hibernate.
Deep in the code I had a UserType that converted a null String into a empty String, meaning that the groupCode would never actually be null. Hence Hibernate assumes that there is a child object to load.
Adding the annotation #Type(type="org.hibernate.type.StringType") above the groupCode avoided this issue.
Interestingly I had misunderstood the use of
#NotFound(action=NotFoundAction.IGNORE).
I had thought it was used to solve the issue I described above, but it actually defines what to do if groupCode is set but there’s no corresponding Group; not what to do when groupCode is null.
Others may fall for that too.

Related

Bidirectional OneToMany JPA mapping with eager fetch on both sides worked [duplicate]

This question already has answers here:
Problem with LazyInitializationException
(2 answers)
Closed 4 months ago.
I have 3 tables in the DB and 3 JPA entities respectively in Java application.
#Data
#Entity
public class Fraud {
#Id
#Column(name = "id")
private Integer id;
#Column(name = "fraud_type")
private String fraudType;
#Column(name = "fraud_value")
private String fraudValue;
#OneToMany(mappedBy = "fraud", fetch = FetchType.EAGER)
private List<FraudActionEntity> fraudActions;
}
#Data
#Entity
public class FraudActionEntity {
#Id
#Column(name = "id")
private Integer id;
#ManyToOne
#JoinColumn(name = "fraud_id")
private Fraud fraud;
#ManyToOne
#JoinColumn(name = "action_id")
private Action action;
#Column(name = "enabled")
private Boolean enabled;
}
#Data
#Entity
public class Action {
#Id
#Column(name = "id")
private Integer id;
#Column(name = "attribute_key")
private String attributeKey;
#Column(name = "attribute_value")
private String attributeValue;
}
#Repository
public interface FraudRepository extends JpaRepository<Fraud, Integer> {
public Fraud findByFraudTypeAndFraudValue(String fraudType, String fraudValue);
}
My use case
On a certain type of fraud, I want to traverse all the actions that triggers from that type of fraud and act on them.
Access code
Fraud fraud = fraudRepository.findByFraudTypeAndFraudValue("Type", "Value");
log.info(fraud.getFraudActions().get(0).getAction());
When I above code runs, everything works OK. I get the fraud and fraudActions associations as well, without getting any error.
I was under the impression that as both entities Fraud and FraudActionEntity are fetching each other eagerly, so it should give some error like cyclic fetch/infinite fetch loop, but it didn't!
Why did it work? And when exactly will give it error like cyclic fetch error OR infinite fetch loop error? And if it does give a cyclic fetch error, can we fix it using lazy fetch at #ManyToOne side as given below:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "fraud_id")
private Fraud fraud;
Update: A simple and very effective work-around towards the LazyInitializationException is to annotate your method with #Transactional annotation. This will create and maintain the transaction while the method is being executed, thereby allowing your code to make the necessary calls to the DB's lazy init objects. Learn more about it here.
The return type of your JPA repository method should be a List of the Entity object, since the result could be more than one row (that is probably why you are getting the null of the fraud variable).
Regarding the Fetch strategy, you could use Eager on that particular association or maybe other strategies. One possible solution would be to make a second query in case you need the lazy-loaded FraudAction list of objects.
Also, as a side-note avoid using lombok data annotation, and always make sure that you have a NoArgsConstructor in your Entity/DTO classes (in your case #Data adds that by accident since it includes #RequiredArgsConstructor and you do not have any final variables.

Why does hibernate need to save the parent when saving the child and cause a OptimisticLockException even if there no change to the parent?

We are trying to save many child in a short amount of time and hibernate keep giving OptimisticLockException.
Here a simple exemple of that case:
University
id
name
audit_version
Student
id
name
university_id
audit_version
Where university_id can be null.
The java object look like:
#Entity
#Table(name = "university")
#DynamicUpdate
#Data
#Accessors(chain = true)
#EqualsAndHashCode(callSuper = true)
public class University {
#Id
#SequenceGenerator(name = "university_id_sequence_generator", sequenceName = "university_id_sequence", allocationSize = 1)
#GeneratedValue(strategy = SEQUENCE, generator = "university_id_sequence_generator")
#EqualsAndHashCode.Exclude
private Long id;
#Column(name = "name")
private String name;
#Version
#Column(name = "audit_version")
#EqualsAndHashCode.Exclude
private Long auditVersion;
#OptimisticLock(excluded = true)
#OneToMany(mappedBy = "student")
#ToString.Exclude
private List<Student> student;
}
#Entity
#Table(name = "student")
#DynamicUpdate
#Data
#Accessors(chain = true)
#EqualsAndHashCode(callSuper = true)
public class Student {
#Id
#SequenceGenerator(name = "student_id_sequence_generator", sequenceName = "student_id_sequence", allocationSize = 1)
#GeneratedValue(strategy = SEQUENCE, generator = "student_id_sequence_generator")
#EqualsAndHashCode.Exclude
private Long id;
#Column(name = "name")
private String name;
#Version
#Column(name = "audit_version")
#EqualsAndHashCode.Exclude
private Long auditVersion;
#OptimisticLock(excluded = true)
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "university_id")
#ToString.Exclude
private University university;
}
It seem when we assign university and then save Student, if we do more than 4 in a short amount of time we will get the OptimisticLockException.
It seem hibernate is creating update version on the University table even though the University didn't change at the db level.
UPDATE: code that save the student
Optional<University> universityInDB = universidyRepository.findById(universtityId);
universityInDB.ifPresent(university -> student.setUniversity(university);
Optional<Student> optionalExistingStudent = studentRepository.findById(student);
if (optionalExistingStudent.isPresent()) {
Student existingStudent = optionalExistingStudent.get();
if (!student.equals(existingStudent)) {
copyContentProperties(student, existingStudent);
studentToReturn = studentRepository.save(existingStudent);
} else {
studentToReturn = existingStudent;
}
} else {
studentToReturn = studentRepository.save(student);
}
private static final String[] IGNORE_PROPERTIES = {"id", "createdOn", "updatedOn", "auditVersion"};
public void copyContentProperties(Object source, Object target) {
BeanUtils.copyProperties(source, target, Arrays.asList(IGNORE_PROPERTIES)));
}
We tried the following
#OptimisticLock(excluded = true)
Doesn't work, still give the optimistic lock exception.
#JoinColumn(name = "university_id", updatable=false)
only work on a update since we don't save on the update
#JoinColumn(name = "university_id", insertable=false)
work but don't save the relation and university_id is always null
Change the Cascade behaviour.
The only one value that seem to made sense was Cascade.DETACH, but give a org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing.
Other solution we though of but are not sure what to pick
Give the client a 409 (Conflict) error
After the 409 the client must retry his post.
for a object sent via the queue the queue will retry that entry
later.
We don't want our client to manage this error
Retry after a OptimisticLockException
It's not clean since when the entry come from the queue we already doing it but might be the best solution so far.
Make the parent owner of the relationship
This might be fine if there are not a big number of relation, but we have case that might go in the 100 even in the 1000, which
will
make the object to big to be sent on a queue or via a Rest call.
Pessimistic Lock
Our whole db is currently in optimisticLocking
and we managed to prevent these case of optimisticLocking so far, we
don't want to change our whole locking strategy just because of this
case. Maybe force pessimistic locking for that subset of the model
but I haven't look if it can be done.
It does NOT need it unless you need it.
Do this:
University universityProxy = universidyRepository.getOne(universityId);
student.setUniversity(universityProxy);
In order to assign a University you don't have to load a University entity into the context. Because technically, you just need to save a student record with a proper foreign key (university_id). So when you have a university_id, you can create a Hibernate proxy using the repository method getOne().
Explanation
Hibernate is pretty complex under the hood. **When you load an entity to the context, it creates a snapshot copy of its fields and keeps track if you change any of it**. It does much more... So I guess this solution is the simplest one and it should help (unless you change the `university` object somewhere else in the scope of the same session). It's hard to say when other parts are hidden.
Potential issues
wrong #OneToMany mapping
#OneToMany(mappedBy = "student") // should be (mappedBy = "university")
#ToString.Exclude
private List<Student> student;
the collection should be initialized. Hibernate uses it's own impls of collections, and you should not set fields manually. Only call methods like add() or remove(), or clear()
private List<Student> student; // should be ... = new ArrayList<>();
*overall some places are not clear, like studentRepository.findById(student);. So if you want to have a correct answer it's better to be clear in your question.
If you enable your query logs from Hibernate, it would be worthwhile to see the queries that your ORM is performing. You'll likely realize that your ORM is doing too much.
In your application properties or config file enable hibernate.show_sql=true
I wouldn't be surprised if your single update to a Student becomes an update to a University which becomes an update to all of its containing Students. Everything gets a version bump.
ORM and entity mappings are for strategically retrieving data. They should not be used to actually define object relationships.
You'll want to visit strategies and design your entities based on how they are used in their REST endpoints.
You specified in your question that you are trying to save a Student but you're noticing that the University also gets updated along with every Student update.
Likely there would never be a time when a Student should ever update a University
Keep your entities lean!
You can structure your entity in such a way that supports this unidirectional relationship. I removed some of the annotation just to demonstrate the structure. You will want to keep in mind that when creating entities, you are writing them for how they are retrieved...
public class University {
#Id
private Long id;
private String name;
private Long auditVersion;
#OneToMany
private List<Student> student;
}
public class Student {
#Id
private Long id;
private String name;
private Long auditVersion;
private Long universityId;
}
This will ensure that updates to the student remains targeted and clean. You are simply assigning a university id to the student therefore establishing that relationship.
You typically want to respect LockExceptions. Retrying upon a LockException is simply bullying your database into submission and will cause more headaches as your application scales.
You always have the option to work with lean entities and create custom response or message objects that would zip the results together.
ORMs are not to be used to create shortcuts
The performance consequence of a SELECT on an indexed/foreign key is roughly the same cost of grabbing them joined... you only introduce a little extra network latency. A second trip to the database is not always a bad idea. (Often times, this is exactly how Hibernate fetches your entities)
You won't have to write queries, but you will still need to understand the retrieval and update strategies.
You're sacrificing database performance and introducing complexity for a convenient .getChild() method. You'll find that you resolve more performance/locking issues by removing annotations, not adding them.

javax.persistence.EntityNotFoundException: Unable to find object with id X

I'm trying to persist an object, after that i want to add 2 lists to that object and then update it (Since i can't persist the object with the lists).
All beign done inside a loop, the first iteration works just fine, from the second i get a EntityNotFoundException saying that the ID wasn't found to do the update.
private Foo foo;
private FooDao dao;
for(int i = 0 ; i<10 ; i++){
foo = new Foo();
foo.setVar(i);
dao.save(foo);
generateLists(); //creates a new list every interaction
foo.setCatList(catList);
foo.setBarList(barList);
dao.update(foo);
}
If i remove the Lists and the update it works fine.
The object:
#Entity
public class Foo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
private Integer id;
#Basic(optional = false)
#NotNull
private String var;
#OneToMany(cascade = CascadeType.ALL)
private List<Bar> barList;
#OneToMany(cascade = CascadeType.ALL)
private List<Cat> catList;
//Getters and Setters
}
The DAO methods:
public void save(Foo foo) {
this.manager.joinTransaction();
this.manager.persist(foo);
//this.manager.flush(); Tried this, but didn't work
}
public void update(Foo foo) {
this.manager.joinTransaction();
this.manager.merge(foo);
}
The error:
ERROR [org.jboss.as.ejb3] (EJB default - 1) javax.ejb.EJBTransactionRolledbackException: Unable to find foo with id 4
ERROR [org.jboss.as.ejb3.invocation] (EJB default - 1) JBAS014134: EJB Invocation failed on component FooDao for method public void FooDao.atualiza(Foo): javax.ejb.EJBTransactionRolledbackException: Unable to find Foo with id 4
ps: Using this generic approach to simplify, if needed i'll post my solution(or the mess that i call solution)
Because you are adding same lists again and again.Since you have OneToMany the second transaction says you already persisted that list.
An workaround would to change relation to ManyToMany
Adding #NotFound(action = NotFoundAction.IGNORE) with #ManyToOne or #OneToOne is helpful because ..ToOne is FetchType.EAGER by default
I believe problem is with the transactions obtain the transaction and commit within the save method in your dAO.
this.manager.getTransaction().commit();
If an error comes it is because there is no active transaction it is why the flush fails. Be sure you are within a transaction, consider use EntityTransaction to begin , commit and end the transaction around your DAO methods.
If you don't have an active TX you won't be able to write entities to the database, and when you try to merge something the entity won't be found then this is the reason for what you are getting EntityNotFoundException
--- UPDATE
When you do the merge, what is the ID that the Foo object have ? I believe is not the correct one , or at least the last id inserted in the database, so what I suggest is find first and then merge.
Use
<T> T find(java.lang.Class<T> entityClass,
java.lang.Object primaryKey)
Using the id, and verify the instance have the correct key
I got similar issue. Fixed by updating #ManyToOne annotation to:
#NotNull
#JoinColumn(nullable = false)
#ManyToOne(optional = false, fetch = FetchType.LAZY)
private ParentEntityClass parent;
added: nullable = false, optional = false, fetch = FetchType.LAZY and, in addition to these - #NotNull (javax.validation.constraints.NotNull - probably not necessary, just to prevent null-ables :) )

Hibernate upgrade from 3 to 4: Criteria transaction ceases to work

I have recently upgraded Spring 2.5 to 3.2 and Hibernate 3 to 4.2.8 in a general upgrade of a web application. Most things are working now, but there is one Criteria transaction that is not working and has me puzzled. The new version returns no result (but no errors), while the old one retrieved properly the requested value.
The code is the same one in the old and new versions, and I have verified that the argument that reaches it is the same. Here is the Java code:
Criteria criteria = sessionFactory.getCurrentSession().createCriteria(ViewingResource.class);
criteria.createCriteria("viewings","currentViewings");
criteria.add(Property.forName("currentViewings.id").eq(viewingId));
ViewingResource result = (ViewingResource)criteria.uniqueResult();
ViewingResource is my entity, which is defined as follows:
#Entity
#DiscriminatorValue("viewing")
public class ViewingResource extends AbstractInformationResource {
private static final long serialVersionUID = -4569093742552159052L;
#OneToOne(targetEntity = Attribute.class, fetch = FetchType.LAZY)
#JoinColumn
private Attribute primaryAttribute;
#OneToMany(targetEntity = Viewing.class, cascade = {CascadeType.PERSIST, CascadeType.MERGE}, orphanRemoval=true)
#Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE})
#JoinTable(name = "informationresource_viewings")
#OrderBy("sort")
private Set<ResourceViewing> viewings;
public Set<ResourceViewing> getViewings() {
return viewings;
}
public Attribute getPrimaryAttribute() {
return primaryAttribute;
}
}
As for the abstract class it extends:
#Entity
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(
name = "type",
discriminatorType = DiscriminatorType.STRING
)
#Table(name = "informationresource")
abstract public class AbstractInformationResource extends PersistentEntity<String> {
private static final long serialVersionUID = 8709376067232042462L;
#Id #GeneratedValue(generator="system-uuid")
#GenericGenerator(name="system-uuid", strategy = "uuid")
private String id;
#Column(nullable = false)
private String name;
#Column(nullable = false)
private int sort;
public String getId() {
return id;
}
public String getName() {
return name;
}
public int getSort() {
return sort;
}
}
And the original PersistentEntity is just an extension of Serializable with an id and no annotations.
I enabled Hibernate logs and found the problem may be in the way annotations work between in Hibernate 3 and 4, for the Hibernate generated SQL strings differ in this way:
Hibernate 3:
select
... (maps to all columns)
from
informationresource this_
inner join
informationresource_viewings viewings3_
on this_.id=viewings3_.informationresource_id
inner join
Viewing currentvie1_
on viewings3_.viewings_id=currentvie1_.id
where
this_.type in (
'viewing', 'directory'
)
and currentvie1_.id=?
Whereas in Hibernate 4, the generated SQL performs no joins:
select
... (maps to all columns, except type, attributeType and fieldName)
from
informationresource this_,
informationresource_viewings viewings3_,
Viewing currentvie1_
where
this_.id=viewings3_.informationresource_id
and viewings3_.viewings_id=currentvie1_.id
and this_.type='viewing'
and currentvie1_.id=?
Any hints that may help me advance with this issue? My current guess is that maybe I skipped some annotation definition that has been changed or modified since Hibernate 3, but so far I haven't been able to find anything illegal in the way I declare them - and my attempts to modify the #Join have been unsuccessful so far.
EDIT. After toying with this for some time, I have found that the issue may be related to the #DiscriminatorColumn of the abstract class. I have found that the problem lies that my type for this kind of request is never 'viewing', but 'directory'. In the old generated SQL I had both types generated:
this_.type in (
'viewing', 'directory'
)
But in the new sql this is constrained to 'viewing':
and this_.type='viewing'
I have changed in the new SQL this line, and it returns the right values that I need. The column type has only those two values, 'viewing' and 'directory'. So my question now is how to make Criteria to keep asking for the types there instead of forcing 'viewing' type.
Finally I found the solution, thanks to the hint I appointed in the EDIT block.
The solution came from establishing a formula in the base class:
#DiscriminatorFormula("case when type in ('viewing','directory') then 1 else 2 end")
And then changing in viewing resource the discriminator value annotation:
#DiscriminatorValue("1")
I really don't know why in Hibernate 3 I got all the values in the discrimination, and in Hibernate 4 the value was only this one, since the code had not changed at all. So if anyone in the future sees some similar behavior, maybe this trick can help you.

Trying to persist an entity with #ManyToOne mapping

I'm trying to persist a User that has a mapping #ManyToOne with UserStatus
but when I do the code below, the hibernate throws PropertyValueException
user.setStatus(new UserStatus(1));
em.persist(user); // ou session.saveAndUpdate(user);
to work I have to do this way:
user.setStatus(em.getReference(UserStatus.class, 1));
em.persist(user); // ou session.saveAndUpdate(user);
I know the first way is possible, but what I don't know is whether I need to configure or call another method (I've already tried saveAndUpdate from Session and still the same)
Does anyone have any idea?
The error message is:
not-null property references a null or transient value
the mapping
#ManyToOne(optional = false)
#JoinColumn(name = "user_status_id", nullable = false)
public UserStatus getStatus() {
return status;
}
This error means "you are referencing a null (not persisted) object" and you have to choice: remove nullable or set #Cascade so UserStatus will per persisted when you do em.persist(user)
#ManyToOne(optional = false)
#JoinColumn(name = "user_status_id", nullable = false)
#Cascade(cascade=CascadeType.ALL)
public UserStatus getStatus() {
return status;
}
EDIT: After various test, using getReference() is the right way to proceed because new UserStatus(1) go for error and should be substituted as getReference(UserStatus.class,ID) to return a proxied instance of UserStatus. Proxied object doesn't hit on database, so SELECT is avoided and the only field setted on UserStatus proxy is the ID, necessary to resolve #ManyToOne relation!
Some useful answer: When to use EntityManager.find() vs EntityManager.getReference()What is the difference between EntityManager.find() and EntityManger.getReference()?

Categories