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
Related
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);
}
...
}
How can I pass the test? (was working before migrate my code to use repositories). The bs are stored in the database after save, but the object are not updated. What I have to do to achieve it?
Given these classes:
#Entity
public class A {
#Id
private String id;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "aId")
private Set<B> bs= new HashSet<B>();
...
}
#Entity
public class B {
#Id
#GeneratedValue
private int id;
private String aId;
private String foo;
...
}
And Repository:
#RepositoryDefinition(domainClass = A.class, idClass = String.class)
public interface ARepository {
...
void save(A a);
...
}
This test fail:
// "a" saved and flushed
B b = new B();
b.setAId(a.getId());
a.getBs().add(b);
ARepository.save(a);
assertTrue(b.getId() > 0);
repository.save() does persist (if the provided argument is transient) or merge (otherwise).
Since a is not transient, merge is performed, meaning that there is no persist operation that could be cascaded to bs.
You either have to save b explicitly or add b to a new a before the a is saved, so that persist is cascaded properly.
Probably, the reason is that B object is not in persisted state yet. As soon as it will be saved - you shouldn't get errors.
Should look like this:
// "a" saved and flushed
B b = new B();
BRepository.save(b)
b.setAId(a.getId());
a.getBs().add(b);
ARepository.save(a);
assertTrue(b.getId() > 0);
Also could you please provide stacktrace? Would be really helpful.
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;
}
}
How do I implement one-many relationship in ORMLite Android?
please find the example
public class A {
private String name;
#DatabaseField (foreign = true, foreignAutoRefresh = true, columnName = "A")
private B b;
#DatabaseField(columnName = "author")
private String authorName;
}
public class B {
#DatabaseField(generatedId = true, columnName = "id")
private long id;
#DatabaseField(columnName = "name")
private String name;
#ForeignCollectionField
Collection<A> aees;
}
B has collection of A. I am calling dao.create(b);
Now i create dao of b, since b has all the data. But the table B has only created with data, A is never created. Kindly could some one help?
Now i create dao of b, since b has all the data. But the table B has only created with data, A is never created. Kindly could some one help?
Right. You need to create the A items using:
for (A a : b.aees) {
aDao.create(a);
}
ORMLite does not automatically create those for you.
You might take a look a the source code of the foreign-collection example program.
You have to override the DAO of the B class, so when an object B is created or updated, the objects in the collection to be also updated.
Take a look at this question: Collections in ORMLite.
i was facing the same problem. My json was like:
{
"parent":{
"name":"ABC",
"children":[
{"childName":"1"},
{"childName":"2"},
{"childName":"3"}
]
}
}
i resolved the issue like this:
Parent parent = new Parent();
parent.setName("ABC");
while(get children one by one from json data)
{
childrenArray.add(new Child(its Name));
}
parentDAO.create(parent);
for(Child child : childrenArray)
{
child.setParent(parent);
childDAO.create(child);
}
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.