Hibernate Entity proxy initialization - java

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;
}
}

Related

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

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);
}
...
}

Is it possible to save an entity that references an unsaved proxy?

Background
I have two entities in my project. One is called "Entity" and the other one is a relation. I use those to simulate a relation between objects in my game. So theres some sort of tree/hierarchy in my database... but thats not the case in my game. My entities are looking like this...
#Entity
#Table(name = "entity")
#Access(AccessType.FIELD)
public class EntityPojo implements Serializable {
#Id public long id;
#OneToMany(mappedBy = "owner", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
public Set<RelationshipPojo> relations = new LinkedHashSet<>();
}
#Entity
#Table(name = "relationship")
#Access(AccessType.FIELD)
public class RelationshipPojo {
#Id
public long id;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "entity_id")
public EntityPojo owner;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
public Set<EntityPojo> targets = new LinkedHashSet<>();
}
The Problem
It may happen that one of my ingame objects is being saved while it references an unsaved entity. Imagine this scenario... During my game i create multiple ingame-objects and their entity-pojos.
Then one of them ( the tile ) gets saved, but cant get a proxy of the unsaved object, that causes an ObjectNotFoundException.
var tile = new EntityPojo();
tile.id = 1;
var player = new EntityPojo();
player.id = 2;
var proxy = session.getReference(EntityPojo.class, 2L); // Reference to unsaved player
var inTileRelation = new RelationshipPojo();
inTileRelation.name = "inTile";
inTileRelation.owner = tile;
inTileRelation.targets.add(proxy); // ObjectNotFoundException
tile.relations.add(inTileRelation);
// Tile gets saved before player was saved...
syncDatabase.save(tile);
The Question
Why exactly does it happen and how can i prevent it ? Is it even possible to save a reference to another entity by having a proxy ? Just by having its id that the entity will have in the future ? How could i make that work ?
In SQL i would simply remove the constraints to insert the id itself... is this possible in hibernate ?
As it's stated in the documentation:
Obtain an entity reference without initializing its data
Sometimes referred to as lazy loading, the ability to obtain a reference to an entity without having to load its data is hugely important. The most common case being the need to create an association between an entity and another existing entity.
Book book = new Book();
book.setAuthor( entityManager.getReference( Person.class, personId ) );
The above works on the assumption that the entity is defined to allow lazy loading, generally through use of runtime proxies. In both cases an exception will be thrown later if the given entity does not refer to actual database state when the application attempts to use the returned proxy in any way that requires access to its data.
So, you can use entityManager.getReference only if you know that your database has a record with specified id and you just want establish relationship without actual entity loading.

Prevent Hibernate from loading lazy ManyToOne

I reduced the problem to 3 entities in my model :
Station.
Locations (several per Station).
Groups of location (several per Station).
The Location class:
#Entity
#Table(name = "***")
public class Location {
#ManyToOne
#JoinColumn(name = "s_id")
private Station s;
#ManyToOne
#JoinColumn(name = "g_id")
private GroupOfLocations group;
}
And the class for GroupOfLocation :
#Entity
#Table(name = "***")
public class GroupOfLocation {
#ManyToOne(fetch = FetchType.LAZY) //I do not want the Station to be loaded
#JoinColumn(name = "s_id")
private Station s;
}
When I get a Location by ID:
Its Station is loaded
The Location's group is loaded
The Group's Station is loaded BUT I do not need it.
Problem : The group contains the station but I do not want it to be loaded. I expected that fetch = FetchType.LAZY would prevent the Station from being fully loaded but it does not work.
I have searched on SO and sometimes, the problem comes from a class declared as final but there is no final class in this model.
Any idea?
This is how the entity id searched by ID:
public Location getById(Integer id) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Location> query = cb.createQuery(Location.class);
Root<Location> entity = query.from(Location.class);
Predicate whereClause = cb.equal(entity.get(Location_.id), id);
query.where(whereClause);
return em.createQuery(query).getSingleResult();
}
Any JPQL or Criteria query is going to override the default fetch plan (the one you defined in the entity mapping).
The default query plan is only valid when loading the entity using find or getReference
entityManager.find(GroupOfLocation.class, 1L);
The default #ManyToOne fetch is EAGER but since you haven't issued a join fetch those will be extracted with some additional selects.
The location.group.station won't be fetched with an additional select but a Proxy will still be there.
If the location.station happens to match the location.group.station then Hibernate will use the already loaded location.station, since inside a Hibernate Session object equality matches instance equality too (meaning there can only be one object of an Entity type with a given entity identifier).
So in case both location.group.station and location.station reference the same entity, you will see an initialized Proxy, otherwise the proxy will remain uninitialized.
If the session is closed and the Proxy wasn't initialized you will get a Lazy exception when accessing it.
But you didn't declare fetch = FetchType.LAZY on s & group property of locations.

Hibernate: how to reattach an externalized entity that has an #ElementCollection

Context
Stackoverflow has been invaluable to me over the years, and I wanted to give back by posting the question and the answer for something I spent a significant amount of time on recently.
Background
In my situation, I am storing the serialized JSON of our entities in Memcached. For various reasons, I didn't like the way hibernate's caching (2nd level and query cache) worked, so I decided to write my own caching layer. But this created a problem: there appeared to be no easy way to reattach a previously cached POJO back into a hibernate session, especially if there was an #ElementCollection involved.
Here's an approximate definition of some POJOs I wanted to deserialize from JSON, and reattach to a hibernate session:
#Entity
#Table
public class User extends AbstractModel {
#ElementCollection
#CollectionTable (name = "UserAttribute", joinColumns = { #JoinColumn (name = "UserId") })
#MapKeyColumn (name = "Name")
#Column (name = "Value")
private Map<String, String> attributes = new HashMap<String, String>();
...
}
#Entity
#Table
public class Content extends AbstractModel {
#ManyToOne(cascade = CascadeType.ALL) #JoinColumn (name = "UserId")
private User user;
#ElementCollection
#CollectionTable(name = "ContentAttribute", joinColumns = { #JoinColumn(name = "ContentId") })
#MapKeyColumn(name = "Name")
#Column(name = "Value")
private Map<String, String> attributes = new HashMap<String, String>();
...
}
In my searching for an answer to this question, the closest matches where these:
Is there a way in Hibernate to obtain an entity's loaded PersistentCollection without loading the entire entity object first?
What is the proper way to re-attach detached objects in Hibernate?
Picking up where the posters left off from the links in the question, here's the approximate/relevant code for how I managed to reattach. Below that is an outline of what's going on.
#Repository
public abstract class DaoHibernate<T> implements Dao<T> {
#Override
public T reattach(T entity) {
if (getCurrentSession().contains(entity)) {
return entity;
}
if (entity instanceof User) {
return (T) reattachedUser((User) entity);
}
if (entity instanceof Content) {
Content content = (Content) entity;
User user = content.getUser();
if (!currentSession().contains(user)) {
content.setUser(reattachedUser(user));
}
content.setAttributes(persistentAttributesMap(content.getId(), content.getAttributes(), Content.class);
getCurrentSession().lock(content, LockMode.NONE);
return entity;
}
throw new UnsupportedOperationException("reattach is not supported for entity: " + entity.getClass().getName());
}
private User reattachedUser(User user) {
user.setAttributes(persistentAttributesMap(user.getId(), user.getAttributes(), User.class));
getCurrentSession().lock(user, LockMode.NONE);
return user;
}
#SuppressWarnings ("unchecked")
private Map<String, String> persistentAttributesMap(long id, Map<String, String> attributes, Class clazz) {
SessionFactory sessionFactory = getSessionFactory();
Session currentSession = sessionFactory.getCurrentSession();
String role = clazz.getName() + ".attributes";
CollectionPersister collectionPersister = ((SessionFactoryImplementor) sessionFactory).getCollectionPersister(role);
MapType mapType = (MapType) collectionPersister.getCollectionType();
PersistentMap persistentMap = (PersistentMap) mapType.wrap((SessionImplementor) currentSession, attributes);
persistentMap.setOwner(id);
persistentMap.setSnapshot(id, role, ImmutableMap.copyOf(attributes));
persistentMap.setCurrentSession(null);
return persistentMap;
}
...
}
Walk through
As you can see, we have to ensure we never try to reattach an entity that is already in the current session, or else hibernate will throw an exception. That's why we have to do getCurrentSession().contains(entity) in reattach(). Care must be taken here using contains(), because hibernate will not use entity.hachCode() to lookup the entity, but rather System.identityHashCode(entity), which ensures not only that it is an equivalent instance, but the exact same instance that may already be in the session. In other words, you will have to manage reusing instances appropriately.
As long as associated entities are marked with Cascade.ALL, hibernate should do the right thing. That is, unless you have a hibernate managed collection like our #ElementCollection map of attributes. In this case, we have to manually create a PersistentCollection (PersistentMap, to be precise) and set the right properties on it, as in persistentAttributesMap, or else hibernate will throw an exception. In short, on the PersistentMap, we have to:
Set the owner and snapshot key as the id of the owning entity
Set the snapshot role as the fully qualified entity.property name, as hibernate sees it
Set the snapshot Serializable argument as an immutable copy of the existing collection
Set the session to null so hibernate won't think we're trying to attach it to the existing session twice
To complete the reattachment, call session.lock(entity, LockMode.NONE). At this point, as far as I can tell from my testing, hibernate respects this entity and persists all changes correctly when you call saveOrUpdate().
Caveats
I realize this is not a generic solution for all cases. This was just a quick solution to my specific problem that others can hopefully utilize and improve upon. Software is iterative.

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.

Categories

Resources