Unidirectional #OneToMany association fails equality test in JPA - java

I have set up a unidirectional OneToMany relationship like the example in section 2.10.5.1 of the JPA 2.1 spec:
#Entity
public class Client implements Serializable {
...
#OneToMany
private List<ServiceOrder> activeServiceOrders;
public void setActiveServiceOrders( List<ServiceOrder> activeServiceOrders ) {
this.activeServiceOrders = activeServiceOrders;
}
public List<ServiceOrder> getActiveServiceOrders() {
return activeServiceOrders;
}
}
The ServiceOrder class implements hashCode and equals using its auto-generated long id. They were implemented by Eclipse.
public class ServiceOrder implements Serializable {
#TableGenerator( name = "generator_serviceOrder", table = "SEQUENCE_TABLE", pkColumnName = "SEQ_NAME", valueColumnName = "LAST_VALUE_GEN", pkColumnValue = "SERVICE_ORDER_SEQ", allocationSize = 1, initialValue = 0 )
#Id
#GeneratedValue( strategy = GenerationType.TABLE, generator = "generator_serviceOrder" )
private long id;
...
#Override
public boolean equals( Object obj ) {
if ( this == obj )
return true;
if ( obj == null )
return false;
if ( getClass() != obj.getClass() )
return false;
ServiceOrder other = (ServiceOrder ) obj;
if ( id != other.id )
return false;
return true;
}
...
}
Tables are all auto-generated as expected. Then, when I want to establish the relationship I do:
...
Client client = entityManager.find(...);
ServiceOrder so = entityManager.find(...);
client.getActiveServiceOrders().add( so );
...
Everything is fine until now, transaction commits successfully. Problem starts when I try to remove the relationship (in another transaction, another moment):
...
Client sameClient = entityManager.find(...);
ServiceOrder sameSo = entityManager.find(...);
log.info(sameClient.getActiveServiceOrders().size()); // "1", OK
log.info(sameClient.getActiveServiceOrders().contains(so)); // "false". Why?
sameClient.getActiveServiceOrders().remove(so); // does nothing, returns false
...
I debugged and discovered that the following is failing in ServiceOrder.equals():
...
if ( getClass() != obj.getClass() ) // different probably because JPA (Hibernate) proxies one of the objects
return false; // returns
...
I found two temporary solutions:
Remove ServiceOrder equals() and hashCode();
or
Make the relationship bidirectional (and of course update both sides every add/remove);
I don't understand this behavior. Why the difference in treatment if the relationship is uni or bi-directional? Also, if I get these entities in the context of the same transaction, how would fail the first equals test:
if ( this == obj )
return true;
I'm using JPA 2.1 (Wildfly 8.1.0).
Best Regards and thank you in advance.
Renan

You should override equals and hashCode but you should never use the ID for hash code unless you make the hashCode immutable and use the ID only when it's not null for equality.
Otherwise, prior to saving an Entity with the ID being null which is to be assigned during the flush time when you add a Transient entity to a collection, the moment it gets persisted and the ID is generated the equals/hashCode contract is going to broken.
Hibernate best practices suggest using a business key for object equality/hashCode.
So quoting the reference documentation:
The general contract is: if you want to store an object in a List, Map
or a Set then it is a requirement that equals and hashCode are
implemented so they obey the standard contract as specified in the
documentation.
To avoid this problem we recommend using the "semi"-unique attributes
of your persistent class to implement equals() (and hashCode()).
Basically you should think of your database identifier as not having
business meaning at all (remember, surrogate identifier attributes and
automatically generated values are recommended anyway). The database
identifier property should only be an object identifier, and basically
should be used by Hibernate only. Of course, you may also use the
database identifier as a convenient read-only handle, e.g. to build
links in web applications.
Instead of using the database identifier for the equality
comparison, you should use a set of properties for equals() that
identify your individual objects. For example, if you have an "Item"
class and it has a "name" String and "created" Date, I can use both to
implement a good equals() method. No need to use the persistent
identifier, the so-called "business key" is much better. It's a
natural key, but this time there is nothing wrong with using it!

Don't override the equals and hashCode. Hibernate has its own implementation to find out the objects, and that's why you don't get the expected result.
This article explains more:
https://community.jboss.org/wiki/EqualsandHashCode?_sscc=t

Related

Hibernate Envers wrong modified flag on list

I use Envers to audit my data and sometimes the value of the _MOD is incorrect. It stays at 0 instead of 1 when I am adding an element in my list. But it happens only in a specific case.
My entity:
#Entity
#Table(name = "PERSONNE")
#Audited(withModifiedFlag = true)
public class PersonEntity {
#Id
#Column(name = "ID_PERSONNE")
private Long id;
#Column(name = "NAME", length = 100)
private String name;
#Audited( withModifiedFlag = true, modifiedColumnName = "SERVICES_MOD")
private Set<PersonneServiceEntity> services = new HashSet<>(); // Entity with attributs, gettters, setters and envers annotations...
#Audited( withModifiedFlag = true, modifiedColumnName = "OPT_INS_MOD")
private Set<OptinEntity> optIns = new HashSet<>();// Entity with attributs, gettters, setters and envers annotations...
// more fields
// + getters, setteurs, equals, tostring
my service:
// personFromDB is retrieve via an Id
private void update(PersonEntity personFromRequest, PersonEntity personFromDB) {
personFromDB.setName(personFromRequest.getName());
updateServices(personFromRequest, personFromDB); // add new support to the list
updateOptins(personFromRequest, personFromDB); // add new services to the list
personDao.saveAndFlush(personFromDB);
}
This is were the magic happens: When I am updating name, services and optIns. Values in my database are all correct, my entity is correctly persisted, except one envers's column: OPT_INS_MOD ( OPT_INS_MOD == 0).
But if I am not updating the name ( line commented ) then everything is correctly persisted including all _MOD values ( OPT_INS_MOD == 1 and SERVICES_MOD ).
And finally if I am switching updateSupport(personFromRequest, personFromDB) and updateServices(personFromRequest, personFromDB), in this case OPT_INS_MOD is correct but not SERVICES_MOD.
My guess is that there is a problem when Envers is getting all modified fields. Because it does not make any sense to me.
Any ideas? I am using Envers version 4.3.11.Final
I'm not sure this will help you because it doesn't sound like the same problem but I've noticed a weirdness with modified flags and collections.
I get my entities back from the front end converted from JSON back to POJOs. In order to keep from having a transient object error from Hibernate, I need to reset the value in the #Id field (which was never sent to the FE). This works fine for 1-1 entities.
On collections, I found that if I create a new instance of the collection class and fill it with refreshed entities from the old collection and then assign that new collection to the old attribute, the modified flag is set to true.
However, if I fill a new collection with refreshed entities, clear() the old collection, then add all the items in the new collection, modified flag will be false unless there were actual changes to the collection.

Is it a good practice to make a DAO class Comparable type

This is my sample mapping in hibernate
class ApplnDoc {
AdmAppln admAppln;
// getters and setters
}
class AdmAppln {
Set<Student> student;
// getters and setters
}
class Student {
int id;
String registerNo;
AdmAppln admAppln;
// getters and setters
}
In ApplnDoc table we are storing images of all candidates. AdmAppln is for storing admission details, Student is for storing student details. Even if AdmAppln is having a Set of Student, only one record of Student will be present for a particular AdmAppln id (under one AdmAppln only one Student).
Now I want to write few data from to these tables, into an Excel file, whose records must be sorted in the order of registerNo (if it is present), otherwise using id of the Student. We are using XSSFWorkbook class under org.apache.poi.xssf.usermodel package for doing operations on Excel sheet. Here I found a way to sort the excel sheet, but I tried and found a way in code itself using Comparable interface.
This is what I did in ApplnDoc class
public int compareTo(ApplnDoc otherData) {
if(new ArrayList<Student>(this.admAppln.getStudents()).get(0).getRegisterNo() != null &&
!new ArrayList<Student>(this.admAppln.getStudents()).get(0).getRegisterNo().isEmpty() &&
new ArrayList<Student>(otherData.admAppln.getStudents()).get(0).getRegisterNo() != null &&
!new ArrayList<Student>(otherData.admAppln.getStudents()).get(0).getRegisterNo().isEmpty()) {
return new ArrayList<Student>(this.admAppln.getStudents()).get(0).getRegisterNo()
.compareTo
(new ArrayList<Student>(otherData.admAppln.getStudents()).get(0).getRegisterNo());
} else {
return new ArrayList<Student>(this.admAppln.getStudents()).get(0).getId() -
new ArrayList<Student>(otherData.admAppln.getStudents()).get(0).getId();
}
}
Since there is no get() method in Set interface the only way to get Student's registerNo from AdmAppln was to convert it to a list. Then I sorted the list and then it was iterated to generate the excel file.
Is the above mentioned comparison mechanism a proper one or is there a better way? Why I am asking this question because when the Hibernate session is closed and in my compareTo if I'm accessing the child table columns, then I will be getting Invocation exception.
There are some thing worth discussing here:
1-
Even if AdmAppln is having a Set of Student, only one record of
Student will be present for a particular AdmAppln
Why?
is this something you have no control over or is there any particular reason to keep a set where is not needed? (also im assuming a #OneToMany instead of a #OneToOne mapping)
2-
This lead to the child object beig lazy fetched (N.B this is an assumption since you didn't post relevant code about mappings or how you fetch the entity from db).
This means that you have to either switch to eager fetching in the entity (unrecommended) or specify it when fetching the entities
3-
Also please refactor that compareTo and use variables
public int compareTo(ApplnDoc otherData) {
Student thisStudent = new ArrayList<>(this.admAppln.getStudents()).get(0);
Student otherStudent = new ArrayList<>(otherData.admAppln.getStudents()).get(0);
if(thisStudent.getRegisterNo() != null &&
!thisStudent.getRegisterNo().isEmpty() &&
otherStudent.getRegisterNo() != null &&
!otherStudent.getRegisterNo().isEmpty()) {
return thisStudent.getRegisterNo().compareTo(otherStudent.getRegisterNo());
} else {
return thisStudent.getId() - otherStudent.getId();
}
}
While nothing wrong with that comparison mechanism (except the NullPointer if you have an empty Set of student) you should use the database ordering when querying.
If you still want to compare that way you just have to make sure you have everything you need fetched before closing the session.
You need to load the entire object tree before closing the session else you will get Exception. By the way you can always sort records with the query itself.

Hibernate Envers : get only changed fields

How can i get only modified fields from audited entity?
When i use
AuditQuery query = getAuditReader().createQuery().forEntitiesAtRevision(MyEntity.class, revisionNumber).getResultList()
I get all fields; but i want to get only fields modified?
Without Modified Flags Feature
If you are not using the Modified Flags feature on the #Audited annotation, the only way to obtain that an audited property changed from revision X to revision Y is to actually fetch both revisions and then compare the actual field values between the two object instances yourself.
With Modified Flags Feature
Assuming you are using the Modified Flags feature on the #Audited annotation, presently the only way is to fetch the revision numbers for a given entity instance and using those revisions and prior knowledge of the audited columns, use the Envers Query API to ask whether a property changed for that revision.
Obviously this approach is not ideal as it does impose some prior knowledge on the user code's part to know the fields that are audited in order to get the desired result.
List<Number> revisions = reader.getRevisions( MyEntity.class, myEntityId );
for ( Number revisionNumber : revisions ) {
for ( String propertyName : propertyNamesToCheckList ) {
final Long hits = reader.createQuery()
.forRevisionsOfEntity( MyEntity.class, false, false )
.add( AuditEntity.id().eq( myEntityId ) )
.add( AuditEntity.revisionNumber().eq( revisionNumber ) )
.add( AuditEntity.propertyName( propertyName ).hasChanged() )
.addProjection( AuditEntity.id().count() )
.getSingleResult();
if ( hits == 1 ) {
// propertyName changed at revisionNumber
}
else {
// propertyName didn't change at revisionNumber
}
}
}
Modified Flags Property Changes Queries
In Hibernate Envers 6.0, we are introducing a new query that combines forRevisionsOfEntity with the modified flags query mechanism to obtain not only the revised instances for a given entity class type and primary key, but also a list of fields that were modified at each revision.
The following pseudo code gives an example of the future API:
List results = reader.forRevisionsOfEntityWithChanges( MyEntity.class false )
.add( AuditEntity.id().eq( entityId ) )
.getResultList();
Object previousEntity = null;
for ( Object row : results ) {
Object[] rowArray = (Object[]) row;
final MyEntity entity = rowArray[0];
final RevisionType revisionType = (RevisionType) rowArray[2];
final Set<String> propertiesChanged = (Set<String>) rowArray[3];
for ( String propertyName : propertiesChanged ) {
// using the property name here you know
// 1. that the property changed in this revision (no compare needed)
// 2. Can get old/new values easily from previousEntity and entity
}
}
This feature may be expanded upon or changed as it is going to be considered experimental, but it is something that users have asked for and we at least intend to deliver a first pass at this functionality based on modified flags.
We haven't decided if or how we'd support this for non-modified flags at the moment, so again the only choice there will presently be a brute force bean comparison.
Fore more details on this feature see HHH-8058.

Spring JPA/Hibernate Composite key generation in PrePersist, Custom Generator or Service Layer?

I have a legacy database and I'm developing a Spring MVC application with JPA/Hibernate. My problem comes with the generation of the composite primary keys. An example of primary key is composed like this:
Serial, Year, OrderID, LineId
LineId will be generated based on the max(LineId) for each tuple of Serial, Year and LineId.
I've thought about the following ways:
PrePersist Listener: It means the listener would have to access repositories and even maybe have references to other entities in order to get the next id. EDIT: Hibernate Docs say: A callback method must not invoke EntityManager or Query methods!. https://docs.jboss.org/hibernate/orm/4.0/hem/en-US/html/listeners.html#d0e3013
Custom Generator: I haven't found a single example that shows how to access the entity's instance to retrieve the properties I need to do a proper select.
Service Layer: Would be just too verbose.
Overriding the Spring's Data's JPA Repository save() method implmentation: In this case, Here we could access the entity's instance properties.
What is the correct way to achieve this purpose? Thanks
What I have often done to support this is to use a domain-driven design technique where I control this at the time I associate an OrderLine to the Order.
public class Order {
private List<OrderLine> lines;
// don't allow the external world to modify the lines collection.
// forces them to use domain driven API exposed below.
public List<OrderLine> getLines() {
return Collections.unmodifiableList( lines );
}
// avoid allowing external sources to set the lines collection
// Hibernate can set this despite the method being private.
private void setLines(List<OrderLine> lines) {
this.lines = lines;
}
public OrderLine addLine(String serial, Integer year) {
final OrderLine line = new OrderLine( this, serial, year );
lines.add( line );
return line;
}
public void removeLine(Integer lineId) {
lines.removeIf( l -> l.getId().getLineId().equals( lineId ) );
}
}
public OrderLine {
public OrderLine() {
}
OrderLine(Order order, String serial, Integer year) {
this.id = new OrderLineId( order.getLines().size() + 1, serial, year, order.getId() );
}
}
The only code which ever calls the special OrderLine constructor is called from Order and you make sure that you always delegate the addition and removal of OrderLine entities through the aggregate root, Order.
This also implies you only ever need to expose an Order repository and you manipulate the lines associated with an Order only through an Order and never directly.

Grails: Checking whether a detached object is in an attached Set

The session of my application contains a user objects which has a settings objects which contains an attribute "effectiveOrganisation". The settings objects is loaded eagerly and since the Hibernate Session is per request, the user object in the session is detached from the Hibernate Session.
I want to check wheter the "effectiveOrganisation" is in the Set of an attached object:
<g:if test="${session.user.settings.effectiveOrganisation in
documentInstance.downloadingOrganisations}">
But the result of this test is always false. Maybe this is because the organisation in the session and the organisation of the documentInstance are not identical objects. I implemented equals and hashCode in the Organisation class but it didn't help.
I tried the following test in a controller:
def org = session.user.settings.effectiveOrganisation
doc.downloadingOrganisations.each{
if(it.equals(org))
println("equals works")
}
if(! doc.downloadingOrganisations.contains(org))
println("contains doesn't work")
The surprising result is:
equals works
contains doesn't work
equals and hashCode looks as follows:
boolean equals(o) {
if (this.is(o)) return true;
if (getClass() != o.class) return false;
Organisation that = (Organisation) o;
if (name != that.name) return false;
if (selfInspecting != that.selfInspecting) return false;
return true;
}
int hashCode() {
int result;
result = (name != null ? name.hashCode() : 0);
result = 31 * result + (selfInspecting != null ? selfInspecting.hashCode() : 0);
return result;
}
How can I check wheter an object from the session is contained in the set of an attached object?
It looks like your hashcode computation is probably the issue. Hashcode is usually a lot cheaper to calculate than equals, so it's compared first. If there's a collision and two different objects generate the same hashcode, then equals() is checked. But if two objects have different hashcodes then according to the hashcode/equals contract they are assumed to be different objects.
The instances in the collection are proxies - is that affecting the hashcode calculation?
Check the class of the instances. The hash code is probably not the issue, but the objects are most likely hibernate proxies which is the issue.
Check if equals() is being called during contains()
Also, changing this g:if to
g:if test="${session.user.settings.id in
    documentInstance.downloadingOrganisations*.id}"
May fix it.

Categories