How to force Hibernate to remove orphans before update - java

Let's say I have following model structure:
#Entity
#Table(....)
public class AnnotationGroup{
...
private List<AnnotationOption> options;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, orphanRemoval = true)
#JoinColumn(name = "annotation_group_id", nullable = false)
public List<AnnotationOption> getOptions() {
return options;
}
}
#Entity
#Table(...)
public class AnnotationOption {
private Long id;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Override
public Long getId() {
return id;
}
}
At the moment I have group1 with AnnotationOptions opt1 opt2 and opt3
Then I want to replace all option with only one option opt1
Additionally I have constraint in database:
CONSTRAINT "UQ_ANNOTATION_OPTION_name_annotation_group_id" UNIQUE (annotation_option_name, annotation_group_id)
And this one fires:
Caused by: org.postgresql.util.PSQLException: ERROR: duplicate key value violates unique constraint "UQ_ANNOTATION_OPTION_name_annotation_group_id"
Detail: Key (name, annotation_group_id)=(opt1, 3) already exists.
Actually isuue that hibernate removes orphans after update.
Can you suggest something t resolve issue?

There are so many things that are wrong in this example:
EAGER fetching on the #OneToManycollection is almost always a bad idea.
Unidirectional collections are also bad, use the bidirectional one.
If you get this exception, most likely you cleared all the elements and re-added back the ones that you want to be retained.
The best way to fix it is to explicitly merge the existing set of children with the incoming ones so that:
New child entities are being added to the collection.
The child entities that are no longer needed are removed.
The child entities matching the business key (annotation_group_name, study_id) are updated with the incoming data.

According to Hibernate documentation hibernate perform in the following order to preserve foreign-key constraint:
Inserts, in the order they were performed
Updates
Deletion of collection elements
Insertion of collection elements
Deletes, in the order they were performed
For your special need you should manually flush the transaction to force the deletion in database before.

Related

EntityNotFoundException when removing from OneToMany collection

I have two entities connected with a bidirectional OneToMany/ManyToOne relationship.
#Entity
class One {
#OneToMany(mappedBy = "one", orphanRemoval = true, fetch = FetchType.EAGER)
private Set<Many> manies;
// ...
}
#Entity
class Many {
#ManyToOne
#JoinColumn(name = "one_id", nullable = false)
private One one;
// ...
}
When I want to remove a Many instance, I remove it from its One's manies Set and delete it from the database. If I take the same One instance and save it again (because I changed anything, it doesn't have to be related to the relationship), I get an exception:
javax.persistence.EntityNotFoundException: Unable to find test.Many with id 12345
The ID (in this example 12345) is the ID of the just removed entity. Note that removal of the entity 12345 succeeded: The transaction commits successfully and the row is removed from the database.
Adding and removing instances is done with Spring Data repositories. Removing looks more or less like this:
one.getManies().remove(manyToRemove);
oneDao.save(one);
manyDao.delete(manyToRemove);
I debugged a little and found out that the Set is a Hibernate PersistentSet which contains a field storedSnapshot. This in turn is another Set that still references the removed entity. I have no idea why this reference is not removed from the snapshot but I suspect this is the problem: I think Hibernate tries to remove the entity a second time because it's in the snapshot but not in the actual collection. I searched for quite a while but I didn't encounter others with a similar problem.
I use Hibernate 4.2.2 and Spring Data 1.6.0.
Am I doing something inherently wrong? How can I fix this?
I'm having the same problem.
A workaround is to replace the entire collection.
Set<Many> maniesBkp = one.getManies(); // Instance of PersistentSet
maniesBkp.remove(manyToRemove);
Set<Many> manies = new HashSet<Many>();
manies.addAll(maniesBkp);
one.setManies(manies);
...
manyDao.delete(manyToRemove);
...
oneDao.save(one);
As your #OneToMany relation has orphanRemoval = true, you don't have to explicitly remove the child element from the database - just remove it from the collection and save the parent element.
Try add CascadeType.REMOVE and orphanRemoval
#OneToMany(mappedBy = "one", orphanRemoval = true, fetch = FetchType.EAGER, cascade = CascadeType.REMOVE, orphanRemoval = true)
Than perform delete following
one.getManies().remove(manyToRemove);
oneDao.save(one);
Edited:
I have created POC, look on the UserRepositoryIntegrationTest.departmentTestCase (code)

Hibernate value is not deleting from the databse

#Entity
public class EUser {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#ManyToMany(fetch=FetchType.EAGER)
private List<UserRole> roles;
}
when doing the following action
EUser approveUser = (EUser) userService.getOne(2);
approveUser.getRoles().clear();
userService.update(approveUser);
System.out.println(approveUser.getRoles().size());
it says the size is zero but when i go the db in the EUser_UserRole table i see the value still present. How to solve this??
also in the EUser_UserRole it says
This table does not contain a unique column. Grid edit, checkbox, Edit, Copy and Delete features are not available
how can i delete add edit delete manually??
Cascading is indeed a way to let Hibernate do the removal and if I see the posted code, that is most likely what is asked for. But since the question is about manually deleting while cascading is more automatic deletion, I have to add the suggestions to:
use EntityManager.remove()
invoke a JPQL delete query
Which more fit more the description of "manual" deletion.
se CascadeType. Reference
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<UserRole> roles;
Update :
orphanRemoval attribute can use JPA 2.x version. You have to find out the deleted UserRole data by comparing old rolesList and new rolesList`
orphanRemoval attribute does not support in ManyToMany mapping.
Do not use a cascade for ManyToMany relationships. This can result in an undesired rippling deletion over a wide entity cluster easier than one might hope.
If you want to clear the relationship (delete rows from the join table) for a single user to their roles, you will need to clear the relationship fields on both sides, meaning clearing the List of UserRole in EUser and removing the current EUser from the lists in the respective UserRole instances.
EDIT:
You are not deleting any entities from the database when clearing the lists of related entities. The only result will be that some rows in the join table will be deleted and after the next fetch/refresh, your EUser and UserRole instances will no longer be related.
If you want to remove the UserRoles DB entries, you can do so after removing the relationships to

Hiberate query not returning newly added records from same transaction

My Entity is like
#Entity
#Table(name = "Item")
public class Item implements Serializable {
#Id
#GeneratedValue
#Column(name = "ID")
private long id;
#JoinColumn(name = "PARENT_ID")
#JsonIgnore
private Item parent;
}
I am doing 3 things in a single transaction
Persist some items using EntityManager
hibernate query "from item where id in newIdList"
hibernate query "from item where parent=parentid"
In First step after persisting new items I do entityManager.flush(); and flush mode here is commit.
In second step I do given hibernate query. Here I get the proper result but in third step when I do hibernate query it returns me the results. But this result does not contain the newly persisted query.
I think the problem is due to parentId condition. As per requirements I cannot change the condition. Is there any way we can solve this problem?
#JoinColumn does not establish a relationship from Item > Parent.
You need to annotate this relationship with the relevant association mapping, #OneToOne, #ManyToMany, #OneToMany, #ManyToOne etc.
Try 3rd step after commit... This is not direct solution to your problem but just give a try..

Hibernate One to One mapping not updating the child table

I have following Associated classes with one to one mapping.
#Entity
public class EmployeeEntity
{
#Id
private String id;
private String name;
#OneToOne(mappedBy = "employeeEntity", fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#Fetch(FetchMode.SELECT)
#JoinColumn(name = "empid")
private AddressEntity addressEntity;
...
...
getters & setters
}
#Entity
public class AddressEntity
{
#Id
#Column(unique=true, nullable=false)
#GeneratedValue(generator="gen")
#GenericGenerator(name="gen", strategy="foreign", parameters=#Parameter(name="property", value="employeeEntity"))
private String empId;
#OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private EmployeeEntity employeeEntity;
...
getters & setters
}
I am using postgres and having tables (employeeentity, addressentity) with following foriegn key constraint on addressentity table:
Foreign-key constraints:
"fkakhilesh" FOREIGN KEY (empid) REFERENCES employeeentity(id) ON DELETE CASCADE
I have following requirements with different REST calls:
POST Rest call - should create an employee with address.
POST Rest call - should create an employee without address.
GET Rest call - should retrieve an employeee. Address should also come if it exist.
PUT Rest call - should update an employee and address (if address exists).
PUT Rest call - should update an employee and address (when address is passed and it already exists in addressentity table for empid)
PUT Rest call - should update an employee and create the address (when address is passed and it does not exists in addressentity table for empid)
I am able to perform operations 1 to 5 without any issues.
The main problem is in 6 and following questions come to my mind:
1. when i do "getSession().update(object)" , I get hibernate's StaleStateException: Batch update returned unexpected row count from update [0]; actual row count: 0; expected: 1.
is this not possible with "update" if address does not exists? can't I create a new address during update?
do i need to change my ServiceImpl call to "getSession().merge(object) ? is this case can only be handled by calling "merge" ? how it impacts performance?
If i do merge, i get hibernate's IdentifierGenerationException: attempted to assign id from null one-to-one property.
Am i missing something here?
this can be solved by changing hibernate mapping? or somethin related to cascade.
what is the importance of #GeneratedValue(generator="gen") here? why is #parameter used in #GenericGenerator
I am new to hibernate and trying to get into the depth of hibernate mapping.
Also, I would be delighted if you could suggest me on the design as what should be the best way to handle this.
I got the fix for this. This one-one mapping is somewhat tricky and not simple as i thought initially.
I have used bidirectional one to one mapping, so it is important to call the setters of both EmployeeEntity and AddressEntity to set each other during update. for example:
employeeEntity.setAddressEntity(addressEntity) and addressEntity.setEmpoyeeEntity(empoyeeEntity) has to explicitly called.
setting alone employeeEntity.setAddressEntity(addressEntity) will not work.
Always use integer Id and use .getSession.saveOrUpdate(entity); for save or update.
In the One to One Mapping you should mention constrained=true on the child. It makes Child Id the same as Parent Id.
Use these lines for child id. I don't know Java attributes syntax.
<generator class="foreign">
<param name="property">employeeEntity</param>
</generator>
Also remove Fetch type and Cascade.All from child. I think the default fetch mode is Select which is fine. Cascade is usally used for the Parent part which is responsible for the parent-child relation.

How to define many-to-many to itself in JPA?

I'm trying to define this SQL schema in JPA:
TABLE event (id INT)
TABLE chain (predecessor INT, successor INT)
In other words, every event has a number of successors, which are events themselves. I'm trying to do it this way in JPA:
#Entity
public class Event {
#Id Integer id;
#ManyToMany(cascade = CascadeType.PERSIST)
#JoinTable(
name = "chain",
joinColumns = #JoinColumn(name = "successor"),
inverseJoinColumns = #JoinColumn(name = "predecessor")
)
private Collection<Event> predecessors;
}
#Entity
public class Chain {
#Id Integer id;
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "predecessor")
private Event predecessor;
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "successor")
private Event successor;
}
Is it correct?
Normally one would not both define a ManyToMany with a JoinTable and then also separately define the join table as its own Entity. Join tables aren't Entities normally, they're just join tables and the provider manages them under the hood. You're creating a lot of headaches for yourself as far as properly maintaining in memory state of the application when you change one or the other. (Which is necessary if, for example, you want to use L2 caching.)
So, either one works fine, combined, they are sort of oddsauce. Usually if you defined Chain as an entity, you would just have a list of Chain on the Event. Not also redefine it as a JoinTable on Event. Does that make sense?
(and as it is currently strictly defined, it will break if you try to make changes through the collection on Event unless that ID is a database generated sequence.)
Edit: something like this -
#Entity
public class Event {
#Id Integer id;
#OneToMany(cascade = CascadeType.PERSIST, mappedBy="successor")
private Collection<Chain> predecessorChains;
}
What you wrote originally can be made to work as long as you realize that the Collection<Event> predecessors is inherently read only and will get fubared if you try to L2 cache it. The fact that you put a CascadeType on it makes one thing that you wanted to be able to add and remove Events to/from that, which will explode when hibernate tries to execute illegal SQL.
If you use #ManyToMany, you don't need Chain entity (otherwise, if you need Chain entity, for example, to store additional data associated with the relathionship, you need to declare two one-to-many relationships between Event and Chain).

Categories

Resources