Entity id exists but is appears as null inside equals() method (Hibernate) - java

I have three classes: WorkPosition, Employee and EmployeeCode. Employee represents a person who works somewhere, employee can have many (work)positions at work, and employee's codes represent the employee (one or more codes). For each WorkPosition, a default EmployeeCode (field defaultCode) must be assigned, if there are any codes for the employee, that shows which code represents the employee in this position.
Employee -> WorkPosition is a one-to-many relationship
Employee -> EmployeeCode is a one-to-many relationship
EmployeeCode -> WorkPosition is a one-to-many relationship
WorkPosition class:
#Entity
#Table(name = "work_position")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class WorkPosition{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequence_generator")
#SequenceGenerator(name = "sequence_generator")
private Long id;
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#NotNull
private Employee employee;
#ManyToOne(fetch = FetchType.LAZY)
private EmployeeCode defaultCode;
// other fields, getters, setters, equals and hash ...
Employee class:
#Entity
#Table(name = "employee")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class Employee{
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequence_generator")
#SequenceGenerator(name = "sequence_generator")
private Long id;
#OneToMany(mappedBy = "employee", fetch = FetchType.LAZY)
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Set<EmployeeCode> employeeCodes;
#OneToMany(mappedBy = "employee", fetch = FetchType.LAZY)
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Set<WorkPosition> workPositions;
// other fields, getters, setters, equals and hash ...
EmployeeCode class:
#Entity
#Table(name = "employee_code")
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
public class EmployeeCode {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequence_generator")
#SequenceGenerator(name = "sequence_generator")
private Long id;
#ManyToOne(optional = false, fetch = FetchType.LAZY)
#NotNull
private Employee employee;
#OneToMany(mappedBy = "defaultCode", fetch = FetchType.LAZY)
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Set<WorkPosition> defaultCodes;
public boolean equals(Object o) {
if (this == o) {
return true;
} else if (!(o instanceof EmployeeCode)) {
return false;
} else {
return this.id != null && this.id.equals(((EmployeeCode)o).id);
}
}
// other fields, getters, setters, hash ...
So, in my example, the only difference between one Employee's WorkPositions is the defaultCode, that may differ between WorkPositions.
I have a form, where I can manipulate all data related to a WorkPosition. For example, I can change the defaultCode of the WorkPosition and/or delete an EmployeeCode. When I save the form, I must check if an EmployeeCode was deleted that was set as defaultCode for any of the WorkPositions related to the saved WorkPosition. If so, I reassign it, otherwise I wouldn't be able to delete the EmployeeCode as I would get a ConstraintViolationException as the WorkPosition would be still referencing the EmployeeCode I wish to delete.
Let's say I have an Employee with two EmployeeCodes (EC1 and EC2) and two WorkPositions (WP1 and WP2). DefaultCode of WP1 is EC1 and defaultCode of WP2 is EC2. I save the form of WP1, but I do not delete anything. To check if the defaultCode (EC2) of a related WorkPosition (WP2) still exists, I loop over all the remaining codes (savedWorkPosition.getEmployeeCodes() where savedWorkPosition equals to WP1) and check whether it still contains the defaultCode (relatedWorkPosition.getDefaultCode() where relatedWorkPosition is queried from db and it references EC2).
newDefaultCode = savedWorkPosition.getEmployeeCodes() // [EC1, EC2]
.stream()
.filter(code -> code.equals(relatedWorkPosition.getDefaultCode()))
.findFirst()
.orElseGet(() -> ...);
However, the equals() (look at the EmployeeCode class above) returns false. When I debugged the equals method, I found out that the id of the parameter object (EC2) is null. When I log out the id in the filter call before the equals, I get the correct id. I could do .filter(code -> code.getId().equals(relatedWorkPosition.getDefaultCode().getId())) and it works, but this seems wrong. Why is the id in the equals method null?
I think it might be to do something with the state of the entity in the persistance context and Hibernate does something I do not understand. I used some help from this answer to log out the state of the entities:
entityManager.contains(relatedWorkPosition.getDefaultCode()) returns true
entityManagerFactory.getPersistenceUnitUtil().getIdentifier(relatedWorkPosition.getDefaultCode()) returns correct id.
entityManager.contains(<any code in savedWorkPosition.getEmployeeCodes()>) returns false
entityManagerFactory.getPersistenceUnitUtil().getIdentifier(<any code in savedWorkPosition.getEmployeeCodes()>) returns correct id.

Why is the id in the equals method null?
I'll provide my answer based on what you said previously (because, I've experienced something like this by myself):
However, the equals() (look at the EmployeeCode class above) returns false. When I debugged the equals method, I found out that the id of the parameter object (EC2) is null. When I log out the id in the filter call before the equals, I get the correct id. I could do .filter(code -> code.getId().equals(relatedWorkPosition.getDefaultCode().getId())) and it works, but this seems wrong ...
The problem is the combination of using #ManyToOne(fetch=lazy) and your current implementation of the equals in the class EmployeeCode ... When you declare a ManyToOne relationship as lazy, and you load the entity that contains/wraps such relationship, hibernate does not load the relationship or entity, instead it injects a proxy class that extends from your Entity class ... the proxy class acts an interceptor and loads the real entity data from the persistence layer only when one its declared methods is called ...
Here is the tricky part: the library used for such proxy creation, creates an exact copy of the intercepted entity, which includes the same instance variables that you declared in your entity Class (all of them initialized with default JAVA values) ... When you pass such proxy to an equals method (to be fair, it could be any method) and the method's logic accesses instance variables in the provided arguments, you will be accessing the dummy variables of the proxy and not the ones you want/expect. That's the reason why you're seeing that weird behaviour on your equals implementation ...
To fix this and avoid bugs, as a rule of thumb, I recommend to replace the use of instance variables and call the getter and setter methods on the provided arguments instead ... in your case, it will be something like this:
public boolean equals(Object o) {
if (this == o) {
return true;
} else if (!(o instanceof EmployeeCode)) {
return false;
} else {
return this.id != null
&& this.id.equals(((EmployeeCode)o).getId());
}
}
You may wonder why:
this.id != null && this.id.equals(((EmployeeCode)o).getId());
and not:
this.getId() != null
&& this.getId().equals(((EmployeeCode)o).getId());
The reason is simple: assumming that the java object, on which the equals method is called, is a proxy/lazy entity ... when you invoke such method, the proxy's logic loads the real entity and call the real equals method on it ... A symbolic representation of proxy EmployeeCode class could look like this (beware, it's not the real implemention, is just an example to understand better the concept):
class EmployeeCodeProxy extends EmployeeCode {
// same instance variables as EmployeeCode ...
// the entity to be loaded ...
private EmployeeCode $entity;
....
public boolean equals(Object o) {
if (this.$entity == null) {
this.$entity = loadEntityFromPersistenceLayer();
}
return this.$entity.equals(o);
}
...
}

Related

Spring repository saveAll inserting duplicate rows for mapped entity

I am trying to insert a list of entities which have one to one relation to another entity. It is possible that the one to one mapped object would be same for many parent entity. I am expecting that the same child entity is referred in foreign keys of parent, but actually duplicate rows are getting created. Here are my Entities.
#Builder
#Entity
public class PaymentInfoType1 {
#Id
Long id;
LocalDate date;
#Column(precision = 15, scale = 2)
BigDecimal amount;
String reference;
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "account", referencedColumnName = "id")
Account account;
}
#Builder
#Entity
#EqualsAndHashCode(onlyExplicitlyIncluded = true)
public class Account {
#Id
Long id;
#EqualsAndHashCode.Include
String name;
#EqualsAndHashCode.Include
String accountId;
}
I am creating a list of PaymentInfoType1 based on the information received from a different system. Each PaymentInfoType1 get created along with its Account, which could have exactly the same info but different objects in realtime.
When i do:
PaymentInfoType1 first = // Created with some logic
Account account1 = // name = sample & accountId = 123
first.setAccount(account1);
PaymentInfoType1 second = // Created with some logic
Account account2 = // name = sample & accountId = 123
second.setAccount(account2);
// Both the above its own account object but the field have exactly same values.
List<PaymentInfoType1> list = List.of(first, second);
repo.saveAll(list);
I was expecting that there will be two rows in PaymentInfoType1 table and one in Account, but found that Account also has two rows. Looks like Equals and HashCode does not have any effect in this case.
How can handle this to not insert duplicate rows when the mapping objects are similar by equals/hashcode.
JPA does nothing with #EqualsAndHashcode (that just generates class methods equals and hashCode).
JPA identifies entities by entity id annotated with #Id (or #EmebeddedId) and this id is also something that can be implemented and checked - and usually also generated (like some db sequence) - in the database level.
If you want to use Account identified by name and accountId on JPA side you need to use #EmbeddedId and #Embeddable and get rid of #Id. This would be something like:
#Embeddable
public class AccountId {
String name;
String accountId; // maybe needs renaming...
}
and then in the Account:
#EmbeddedId
AccountId accountId;
See this for example

Duplicate embeddable objects inserted

I have an Entity including a collection of Embeddable objects as follows:
#Entity
#EntityListeners(AuditingEntityListener.class)
#Table(name = "as")
public class A {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ElementCollection
private Set<B> bs;
public B getB(String name) {
for(B b : bs)
if(b.getName().equals(name))
return b;
return null;
}
public void addB(B b) {
if(!bs.add(b))
throw new IllegalArgumentException("Duplicate ......");
}
....
}
#Embeddable
public class B {
#Column(nullable = false)
private String name;
#Temporal(TemporalType.TIMESTAMP)
#Column(nullable = false)
private Date creationTimestamp;
}
I'm using Spring Data to load and save my entity as follows:
Optional<A> a = aRepository.findById(aId);
B b = a.getB(...);
if(b == null) {
b = new B(...);
a.addB(b);
}
aRepository.save(a);
The code above is in a method annotatted with #Transactional.
When the method returns, I can see 3 duplicate embeddable objects in my database instead of one.
Any idea?
EDIT:
After a long debugging, I can confirm that Hibernate only inserts only one row for the single instance I add. However, when I return the created object from my REST controller, at some point the Jackson object mapper is involved to serialize my object before sending it back to the client, and here happens the two remaining INSERTs... I never saw that before... any help would be appreciated
More information:
The last 2 INSERTs are done when the SessionRepository commits the sessions changes - I precise that I use Spring Session. If that can help...
Issue happens here bs.add(b). Set adds an object would check if the object exists in the set. But in your case object b would always not the same as existing object because of creationTimestamp. creationTimestamp would be different from existing object. Because you new object, the time would be current datatime. So bs.add(b) would always be true.
Try to remove creationTimestamp and rerun your code to verify this.
You need to override equals() and hashCode() of B

Hibernate Entity proxy initialization

I'm having a problem with a Hibernate entity that does not get initialised.
It seems that it's still returning a not initialised proxy...
If I take a look at my debug info I would expect my entity to be initialised.
But it looks like the following:
entity = {SomeEntity_$$_jvst47c_1e#9192}"SomeEntityImpl#1f3d4adb[id=1,version=0]"
handler = {org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer#9196}
interfaces = {java.lang.Class[2]#9197}
constructed = true
persistentClass = {java.lang.Class#3605}"class SomeEntityImpl"
getIdentifierMethod = null
setIdentifierMethod = null
overridesEquals = true
componentIdType = null
replacement = null
entityName = {java.lang.String#9198}"SomeEntityImpl"
id = {java.lang.Long#9199}"1"
target = {SomeEntityImpl#9200}"SomeEntityImpl#1f3d4adb[guid=<null>,id=1,version=0]"
initialized = true
readOnly = true
unwrap = false
session = {org.hibernate.internal.SessionImpl#6878}"SessionImpl(PersistenceContext[entityKeys=[EntityKey[EntityReferenceImpl#2], EntityKey[SomeEntityImpl#1], EntityKey[...
readOnlyBeforeAttachedToSession = null
sessionFactoryUuid = null
allowLoadOutsideTransaction = false
Notice that my Hibernate POJO still only contains a handlereven after doing an explicit initialisation...
In my debug view, I can see the 'real' property values (not displayed above) when I expand the target node.
What I'm doing:
EntityReferenceImpl entityReference = findEntityReference(session);
SomeEntity entity = null;
if (entityReference != null) {
// initialize association using a left outer join
HibernateUtil.initialize(entityReference.getSomeEntity());
entity = entityReference.getSomeEntity();
}
return entity;
Notice the HibernateUtil.initialize call!
SomeEntity mapping:
public class SomeEntityImpl extends AbstractEntity implements SomeEntity {
#OneToMany(mappedBy = "someEntity", fetch = FetchType.EAGER, targetEntity = EntityReferenceImpl.class, orphanRemoval = true)
#Cascade(CascadeType.ALL)
#Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
private Set<EntityReference> entityReferences = new HashSet<>();
#Target(EntityName.class)
#Embedded
private Name name;
#Target(EntityAddress.class)
#Embedded
private Address address;
...
}
EntityReferenceImpl mapping:
public class EntityReferenceImpl extends AbstractEntity implements EntityReference {
#ManyToOne(optional = true, fetch = FetchType.LAZY, targetEntity = SomeEntityImpl.class)
#JoinColumn(name = "entity_id")
private SomeEntity someEntity;
...
}
So what is the side effect: When the POJO later comes with updated properties I'm still having the same structure (as mentioned above) and I can see the updated properties under the target node.
But when I'm trying to update the entity using session.merge() or session.update()or session.saveOrUpdate(), Hibernate does not detect the 'dirty' properties and does not invoke an update query to the database.
Does anyone have some clues about this weird behavior? I have tried everything what I can but without any results.
All help is very welcome!!
Entity in your debug window looks like properly initialized.
When you have some entity that may be proxied by hibernate, this entity is stored inside proxy object even after being properly initialized. After initialisation proxy object itself doesn't disappear...
public class EntityReferenceImpl extends AbstractEntity implements EntityReference {
#ManyToOne(fetch = FetchType.LAZY, ...)
private SomeEntity someEntity;
...
In your example you have EntityReferenceImpl entity which has #ManyToOne(LAZY) to SomeEntity entity.
When hibernate loads EntityReferenceImpl it fills all fields from resultSet values but someEntity field is set to proxy object.
This proxy objects looks like this:
class SomeEntity_$$_javassist_3 extends SomeEntity implements HibernateProxy {
+ firstname = NULL;
+ lastname = NULL;
+ age = 0;
+ handler; //of type: JavassistLazyInitializer
getFirstname() {
handler.invoke(..., Method thisMethod, Method proceed, args);
}
getLastName() {...}
}
Your SomeEntity class has (for example) methods getFirstName() etc, but javassist generated class simply extends your SomeEntity and has few new bytecode-generated methods like c7getFirstName() etc.
And most important - proxy class has new field: handler of type JavassistLazyInitializer.
Lets see how JavassistLazyInitializer looks like:
JavassistLazyInitializer {
+ target; //holds SomeEntity object
invoke(..., Method thisMethod, Method proceed, args) {
if (target == null) {
target = initialize(); // calls sessionImpl.immediateLoad
}
return thisMethod.invoke( target, args );
}
}
So when you look into your proxy object - it has your fields like firstname, lastname etc.
When you initialize this proxy, SomeEntity is loaded into target field. Your firstname, lastname fields on proxy objects are null as before - proxy doesn't use them, but real data is in SomeEntity object held by target field.
This is how proxy is implemented in hibernate.
You may ask - why such solution? Such design comes from polymorphism issues. If SomeEntity would be abstract parent class with 2 subclasses EntityA and EntityB hibernate has no problem - someEntity field holds proxy (generated) class extending SomeEntity but having concrete EntityA or EntityB inside target field.
However there are some pitfalls with this solution and polymorphism. Your someEntity field will be instance of SomeEntity but never instance of EntityA nor instance of EntityB.
Hibernate uses Proxies to intercept calls to LAZY entities. That structure you see in debug is how a Proxy looks like.
You don't need to call HibernateUtil.initialize, but simply use "fetch joins" to load all entities you are interested in a single query.
If the entity is attached to the current Session, the dirty checking mechanism will automatically translate all entity state transitions to database DML statements.
Session.update is meant to re-attach detached entities (entities that were loaded in a Session that's been closed).
Session.merge is for copying the entity state onto an already loaded entity (which is loaded on the fly, if not loaded previously).
Check if you have enabled transactions, as otherwise you can only select entities. For persist/merge and dirty checking updates you must use transactions (use Java EE or Spring #Transactional support).
The post https://forum.hibernate.org/viewtopic.php?f=1&t=997278 was useful for me. As the getter methods in Entity Model Objects were marked as final, Javaassist wasn't able to override the method and thus change it's value. My Entity Objects were like this -
#Entity
#Table(name = "COUNTRIES")
public class Country {
#Id
private Long id;
#Column(nullable = false)
private String name;
private final getId() {
return id;
}
private final getName() {
return name;
}
}

Referential integrity with One to One using hibernate

I'm having two tables -
Foo { foo_id, name }
Foo_properties { fp_id, foo_id, phoneNumber}
Now I want to map this in my object model using hibernate..
I need a foo_id in Foo_properties because i want to maintain referential integrity and want to add ON DELETE CASCADE constraint.
so I mapped the relation in the following way -
#Entity
public class Foo{
#Id
private long foo_id;
private String name;
#OneToOne(mappedBy = "foo")
private FooProperties fooProperties;
}
#Entity
public class FooProperties{
#Id
private long fp_id;
private String phoneNumber;
#OneToOne
#JoinColumn(name = "foo_id", nullable = false)
private Foo foo;
}
Now since the owning side is FooProperties class, I'm facing following issues -
If I set the new instance of FooProperties to Foo the existing FooProperties still remains in DB and hibernate doesn't delete that instance, e.g.
Foo foo = entityManager.find(Foo.class, fooId);
foo.setFooProperties(new FooProperties("xxx-xxx-xxx"));
entityManager.merge(foo);
This results into the new row in FooProperties table along with the existing one. Now I don't understand how I can change my mapping to so I can have above code (or variant of it) working for all scenarios, that means I need Foo as a owning side and foo_id in FooProperties. Is there any way to define the mapping like this?
NOTE: I already asked question based on this but I think I wasn't clear in previous question so asked this another one.
You were already told to use orphanRemoval = true or CascadeType.DELETE_ORPHAN. However, due to casuistics in interpretation of JPA Specification it wouldn't work as expected for one-to-one relationships (HHH-5559).
You can achieve a proper behaviour of orphanRemoval with the following trick:
#Entity
public class Foo{
#OneToMany(mappedBy = "foo", orphanRemoval = true)
private List<FooProperties> fooProperties;
public FooProperties getFooProperties() {
if (fooProperties == null || fooProperties.isEmpty()) return null;
else return fooProperties.get(0);
}
public void setFooProperties(FooProperties newFooProperties) {
if (fooProperties == null) fooProperties = new ArrayList<FooProperties>();
else fooProperties.clear();
if (newFooProperties != null)
fooProperties.add(newFooProperties);
}
...
}
#Entity
public class FooProperties{
#ManyToOne
#JoinColumn(name = "foo_id", nullable = false)
private Foo foo;
...
}
Or even this, if you don't need FooPropeties.foo:
#Entity
public class Foo{
#OneToMany(orphanRemoval = true)
#JoinColumn(name = "foo_id", nullable = false)
private List<FooProperties> fooProperties;
// getter/setter as above
...
}
Bar is the owner of the association (as indicated by the mappedBy on the inverse side) and thus the cascade has to be set there.
Edit:
To invert that, this might help.
There are 2 options for you to choose from, since you don't want to change your mapping :
Do it via your service layer logic. I think you have a similar question already.
Use the Hibernate annotation #Cascade(org.hibernate.annotations.CascadeType.DELETE_ORPHAN) on the Foo side of the relationship. However this is explicitly Hibenate and JPA 2 doesn't include support for the same.
I think instead of calling merge on the entity, if you directly call update on session object then hibernate will first delete the existing row and then it will add the new one. I implemented the same, but, in my case I used xml for mapping the entity. I hope this will help you.

hibernate circular foreign key

I have to test some code I have not myself written. It is an integration test: the application is running continuously on a server and my tests run against it.
The tests are Selenium tests, they launch a browser, execute some JavaScript inside it to simulate user actions and checks if the database is correctly updated. I have to restore the database to its initial state after each.
To do this, I use Spring annotations and Hibernate via DAO's I have not myself written.
The problem is that there are circular foreign keys. An object of class A has a OneToMany relationship with objects of type B, and there is also a ManyToOne association with the same class. I try to delete an object of type A and all its associated B's in the same transaction, but it doesn't work because Hibernate tries to set "defaultB" to null before deleting the object of type A. It is completely unnecessary to nullify it, although it makes sense to do it once the referred object of type B is deleted.
I (naively) thought that because the 2 operations were executed in the same transaction, deleting the object "a" of type A referring to (and referenced by) the object "b" of class B and deleting b at the same time would be no problem. However, I was plain wrong. It there is way to do this without changing the DB model (which I haven't written)?
Update 1: I don't understand why, when I execute mySession.delete(B), Hibernate tries to nullify a key it knows as non-nullable...any thoughts about this?
Update 2: there is a one-to-many relationship from class C to class B. Hibernate also tries to nullify the "c_id" field in the table corresponding to B, when I delete the C object that has this c_id. And that even though I delete the object of class B before its "parent". I know Hibernate reorders the queries and adds some stuff of its own, but I don't get the point of reordering queries that are already in the correct order to make them fail.
Here are (relevant parts of) the classes:
#Entity
#Table(name = "A")
public class A {
private Set<B> bs;
private B defaultB;
#OneToMany(mappedBy = "a", fetch = LAZY)
public Set<B> getBs() {
return bs;
}
public void setBs(Set<B> bs) {
this.bs = bs;
}
#ManyToOne(fetch = LAZY, optional = false)
#JoinColumn(name = "default_b_id", nullable = false)
public B getDefaultB(){
return defaultB;
}
public void setDefaultB(B defaultB) {
this.defaultB = defaultB;
}
}
#Entity
#Table(name = "B")
public class B {
private a;
#ManyToOne(fetch = LAZY, optional = false)
#JoinColumn(name = "A_id", nullable = false)
public A getA() {
return a;
}
public void setA(A a) {
this.a = a;
}
}
I try to delete an object of type A and all its associated B's in the same transaction
You should cascade the REMOVE operation for this if you don't want to have to remove all Bs manually. I would try the following (using cascade on both associations):
#Entity
#Table(name = "A")
public class A {
private Set<B> bs;
private B defaultB;
#OneToMany(mappedBy = "a", fetch = LAZY, cascade=CascadeType.REMOVE)
public Set<B> getBs() {
return bs;
}
public void setBs(Set<B> bs) {
this.bs = bs;
}
#ManyToOne(fetch = LAZY, optional = false, cascade=CascadeType.REMOVE)
#JoinColumn(name = "default_b_id", nullable = false)
public Strategy getDefaultB(){
return defaultB;
}
public void setDefaultB(B defaultB) {
this.defaultB = defaultB;
}
}
I cannot change these annotations. BTW, I do remove all associated B's manually, it's just that the queries Hibernate issues don't do what I want.
Ok... But then my guess is that you're not updating correctly both sides of the bidirectional association before remove the entities. This is typically done in defensive programming methods like this (in A):
public removeFromBs(B b) {
b.setA(null);
this.getBs().remove(b);
}
I assume that you want to delete a, but Hibernate does not allow it because b still refer to it?
Since you meta-model do not specify cascade delete, you need to "break" the link of b to a before deleting a. So do b.setA(null) before deleting a.

Categories

Resources