What is the work-around for deleting orphan entities using JPA 2.0 and #OneToMany? - spring

I'm using JPA 2.0, Hibernate 4.1.0.Final, Spring 3.1.1.RELEASE, and Java 1.6. I have this entity with a one-to-many relationship to another entity …
import javax.persistence.CascadeType;
...
#Entity
#Table(name = "classroom")
public class Classroom implements Serializable
{
...
#OneToMany(mappedBy = "classroom", cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REMOVE})
private Set<ClassroomUser> roster;
However, when I update my entity with a different set of ClassroomUser objects
classroom.setRoster(newRoster);
and save the entity, all the previous ClassroomUser objects remain. What is the proper/shortest way to update my entity while removing all the orphan records from the database?
Thanks, - Dave

Use orphanRemoval:
#OneToMany(mappedBy="classroom", cascade={CascadeType.ALL}, orphanRemoval=true)
Whenever an entry is removed from the persistent set, it will get deleted. And this means you need to work with the persistent set. I.e. you are not allowed to replace the set, instead you should do:
classroom.getRoster().clear();
classroom.getRoster().addAll(newRoster);
EXAMPLE how to synchronize persistent set with a user required set:
/**
* Assemble ClassroomUser relations.
* #param classroom Classroom entity. Must be attached persistent or transient. Never null.
* #param userIds Collection of user identifiers. Can be empty. Never null.
*/
private void assembleClassroomUsers(Classroom classroom, Collection<Integer> userIds) {
// Make sure relation set exists (might be null for transient instance)
if (classroom.getUsers() == null) {
classroom.setUsers(new HashSet<ClassroomUser>());
}
// Create working copy of the collection
Collection<Integer> ids = new HashSet<Integer>(userIds);
// Check existing relations and retain or remove them as required
Iterator<ClassroomUser> it = classroom.getUsers().iterator();
while (it.hasNext()) {
Integer userId = it.next().getUser().getId();
if (!ids.remove(userId)) {
it.remove(); // This will be picked by the deleteOrphans=true
}
}
// Create new relations from the remaining set of identifiers
for (Integer userId : ids) {
ClassroomUser classroomUser = new ClassroomUser();
classroomUser.setClassroom(classroom);
// User must not have ClassroomUser relations initialized, otherwise Hibernate
// will get conflicting instructions what to persist and what to drop => error.
// It might be safer to use dummy transient instance...
User dummyUser = new User();
dummyUser.setId(userId);
classroomUser.setUser(dummyUser);
classroom.getUsers().add(classroomUser);
}
}
This approach might seem a little bit complex. You might be able to create something simpler (but probably not too much) with custom equals/hashCode and some Set<E> manipulation methods (e.g. from Guava).

Related

Create a relationship by ID using Neo4J and Spring Data

I have defined a simple node object like this:
#Node("product")
public class Product {
#Id
private String sku;
...
#Relationship(type = "SOLD_BY")
private Set<Shop> shops = new HashSet<>();
...
}
However, my concerns are:
To create (and save) a new Product I must not only create the Product, but also the list of Shop objects. And maybe a Shop object holds an Address object or a list of Employees itself. This means I'm forced to construct a huge list of objects.
Also, maybe the Shop objects are already existing in the database and I don't want to overwrite them by saving the new Product.
By loading a Product, all related Shop objects will be loaded and so on. But I'm just interested in the Product, not in everything connected to it.
So instead I want to do this:
#Relationship(type = "SOLD_BY")
private Set<String> shops = new HashSet<>(); // Just the IDs of the Shops
Is this possible?
Is this a valid approach to solve my problem?

Hibernate OnetoMany with Fetch Lazy giving LazyInitializationException

I am a newbie to Java Persistence API and Hibernate and using Spring JPA repositories for querying in DB. Now I have two entities in Parent <-> Child relationship with Parent entity with #OneToMany and Child entity with #ManyToOne mapping.
Parent Entity:-
#Entity
#Table(name = "PERSONS")
public class Persons {
...
#OneToMany(mappedBy = "person", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
public List<Cards> cards = new ArrayList<Cards>();
...
}
Child Entity:-
#Entity
#Table(name = "CARDS")
public class Cards {
...
#ToString.Exclude
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "PERSON_ID", nullable = false, insertable = false, updatable = false)
public Person person;
...
}
And I am using my PersonsRepository like below :-
#Repository
public interface PersonsRepository extends JpaRepository<Persons, String> {
....
}
Now the fetchType being used in the relationship is LAZY at both the ends. Now whenever I tried to loop over a List and tried to process the cards for each using person.getCards(), it gives me below error:-
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.xxx.abc.Persons.cards, could not initialize proxy - no Session
at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:606)
at org.hibernate.collection.internal.AbstractPersistentCollection.withTemporarySessionIfNeeded(AbstractPersistentCollection.java:218)
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:585)
at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:149)
at org.hibernate.collection.internal.PersistentSet.iterator(PersistentSet.java:188)
at java.util.Spliterators$IteratorSpliterator.estimateSize(Spliterators.java:1821)
at java.util.Spliterator.getExactSizeIfKnown(Spliterator.java:408)
at java.util.stream.AbstractPipeline.copyInto(AbstractPipeline.java:481)
at java.util.stream.AbstractPipeline.wrapAndCopyInto(AbstractPipeline.java:472)
at java.util.stream.ReduceOps$ReduceOp.evaluateSequential(ReduceOps.java:708)
at java.util.stream.AbstractPipeline.evaluate(AbstractPipeline.java:234)
at java.util.stream.ReferencePipeline.collect(ReferencePipeline.java:499)
Now I have found everyone saying that using LAZY is the best approach in Hibernate and it says lot more about the correct design of code as well. I agree the way I have used person.getCards() will not have any open session and that is the reason it is giving me LazyInitializationException but the intent behind this is to save a lot more DB calls.
Assuming I have 1000 persons list, that means I have to make 1000 separate calls to getCards() for each person. That's why if I use the FETCHTYPE.EAGER in Person #OneToMany, what is the performance impact since everything will be fetched eagerly.
Need suggestions about the best practices followed for such kind of problems. TIA.
Edit:-
I have a method in service class where I am using #transactional for that like below:-
#Transactional(readOnly = true)
public void fetchData(Integer param1, Timestamp param2, Timestamp param3, List<String> param4, NavigableMap<Long, List<Cards>> param5) {
List<Persons> validPersons = personRepo.getCardsPerPerson(param2, param3);
if(validPersons != null && !validPersons.isEmpty()) {
// store the cards on the basis of epoch timestamp
prepareTimestampVsCardsMap(validPersons, param4, param5);
}
}
private void prepareTimestampVsCardsMap(List<Persons> validPersons, List<String> uList, NavigableMap<Long, List<Cards>> timestampVsCardsList) {
for(Person person : validPersons) {
Long epoch = order.getOrderTime().getTime();
Set<Cards> cardsPerPerson = person.getCards();
}
}
Also, the query being used in repository for getting the cards associated to a person is using join fetch as below:-
#Query(value = "select p from Person p join fetch Cards c on p.id = c.id WHERE p.orderTime BETWEEN ?1 AND ?2 ORDER BY orderTime ASC")
public List<Person> getCardsPerPerson(Timestamp param1, Timestamp param2);
I am still getting the same above mentioned LazyInitializationException. Can anyone please help.
First of all, it's always better to use FetchType.LAZY instead of FetchType.EAGER. Why? Because you might not need all the data every time. If you want to return a list of Persons and display them somehow, somewhere, do you need to fetch all of their cards as well? If not, then FetchType.LAZY would be the better option, and you would then control how much data you need.
LazyInitializationException usually indicates that you didn't fetch all the data you need while your Session was opened. There are many ways to fetch associated data (none of which is keeping the Session opened while processing request):
1. using join fetch in your JPQL/HQL
#Query("select p from Person p join fetch p.cards where ...")
List<Person> getCardsPerPerson(Timestamp param1, Timestamp param2);
2. if you're using Spring Data, you could use #EntityGraph instead of join fetch
#EntityGraph(attributePaths = { "cards" })
List<Person> getPersons();
That way, every time you call getPersons, it will fetch cards as well. Of course, you couldn't use this one if you have to write #Query.
If you're using Spring Data's naming conventions for some simple queries, then #EntityGraph would be an option for fetching associations.
3. using Criteria API
Again, if you're using Spring Data, this is just a fallback solution in case you end up with MultipleBagFetchException. I will not go into details for this one, but in case you encounter this exception, you'll find solution in Vlad Mihalcea's blog post The best way to fix the Hibernate MultipleBagFetchException.
You are under the misconception that EAGER loading means Hibernate will fetch all data with one statement, this is false. With EAGER as a strategy, the framework will just do every query required to fetch all data for every entity.
Example: If one entity has 2 EAGER relationships, fetching one will result in 3 statements, one to load the entity, one for each of its relationships. If you have 3 entities, you will have 7 statements, the initial statement loading the 3 objects, plus 2 per object.
When your treatment requires everything, there is no real performance impact at the moment. But most applications are not made of one treatment. This means every treatment in your application will load everything which is EAGER, even if not needed. This will effectively slow everything down. You also risk loading all your database in memory if everything is in EAGER.
This is why LAZY is the recommended approach.
As for your LazyInitializationException, it seems in your stack trace that you are using the stream API. It's a wild guess due to missing details, but JPA/Hibernate doesn't handle sharing a session between threads, so if you are using parrallelStream it could cause the problem.

Is double saving a new entity instance with a Spring data 2 JpaRepository correct?

I have two entities in a bi-directional many to many relationship.
A <-> many to many <-> B
I have an endpoint where a client can create an instance of A, and at the same time add some number of B entities to that A, by passing in an array of B entity id keys. Please keep in mind that these B entities already exist in the database. There is no business or software design case for tightly coupling their creation to the creation of A.
So class A looks like this, and B is the same, but with references to A.
#Entity
class A {
#Id
#GeneratedValue
int id;
#ManyToMany
List<B> bs;
String someValue;
int someValue2;
// With some getters and setters omitted for brevity
}
So at first try my endpoint code looks like this.
public A createA(#RequestBody A aToCreate) {
A savedA = aRepository.save(aToCreate);
savedA.getbs().forEach(b -> Service.callWithBValue(b.getImportantValue());
}
And the client would submit a JSON request like this to create a new A which would contain links to B with id 3, and B with id 4.
{
"bs": [{id:3}, {id:10}],
"someValue": "not important",
"someValue2": 1
}
Okay so everything's working fine, I see all the fields deserializing okay, and then I go to save my new A instance using.
aRepository.save(aToCreate);
And that works great... except for the fact that I need all the data associated with the b entity instances, but the A object returned by aRepository.save() has only populated the autofill fields on A, and done nothing with the B entities. They're still just hollow entities who only have their ids set.
Wut.
So I go looking around, and apparently SimpleJpaRepository does this.
#Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
And since the A entity is brand new, it only persists the A entity, but it doesn't merge it so I don't get any of the rich B data. So okay, if I modify my code to take this into account I get this.
public A createA(#RequestBody A aToCreate) {
A savedA = aRepository.save(aRepository.save(aToCreate));
savedA.getbs().forEach(b -> Service.callWithBValue(b.getImportantValue());
}
Which works just fine. The second pass through the repository service it merges instead of persists, so the B relationships get hydrated.
My question is: Is this correct, or is there something else I can do that doesn't look so ineloquent and awful?
To be clear this ONLY matters when creating a brand new instance of A, and once A is in the database, this isn't an issue anymore because the SimpleJpaRepository will flow into the em.merge() line of code. Also I have tried different CascadingType annotations on the relationship but none of them are what I want. Cascading is about persisting the state of the parent entity's view of its children, to its children, but what I want to do is hydrate the child entities on new instance creation, instead of having to make two trips to the database.
In the case of a new A, aToCreate and savedA are the same instance because that is what the JPA spec madates:
https://docs.oracle.com/javaee/6/api/javax/persistence/EntityManager.html#persist(java.lang.Object)
Make an instance managed and persistent.
Spring Data simply returns the same instance so persist/merge can be abstracted into one method.
If the B instances you wish to associate with A are existing entities then you need to fetch a reference to these existing instances and set them on A. You can do this without a database hit by using the T getOne(ID id) method of Spring Data's JpaRepository:
https://docs.spring.io/spring-data/jpa/docs/2.1.4.RELEASE/api/
You can do this in your controller or possibly via a custom deserializer.
This is what I ended up going with. This gives the caller the ability to save and hydrate the instance in one call, and explains what the heck is going on. All my Repository instances now extend this base instance.
public interface BaseRepository<T, ID> extends JpaRepository<T, ID> {
/**
* Saves an instance twice so that it's forced to persist AND then merge. This should only be used for new detached entities that need to be saved, and who also have related entities they want data about hydrated into their object.
*/
#Transactional
default T saveAndHydrate(T save) {
return this.save(this.save(save));
}
}

JPA MERGE failed to update entity field value when this field is a collection(using ElementCollection)

Here we have a Manifest class that includes list of students and teachers, both could be null.
class Manifest{
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "MANIFEST_STUDENT")
List<String> students = new ArrayList<String>();
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "MANIFEST_TEACHER")
List<String> teachers = new ArrayList<String>();;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "MANIFEST_OTHERS")
List<String> others = new ArrayList<String>();;
}
on the UI, there are two multiple select, one for student and one for teacher that let user choose for current manifest.
here is the problem:
When user deselect all students or teachers from the list(meaning remove all students or teachers from current manifest) and click save, unfortunately nothing can be saved, from UI and database it shows that the multiselect chosen looks the SAME as before.
from service layer, the code is simply like this.
manifest.merge();
It seems we must keep at least one student or teacher for the collection field to make the change valid. So what's going on here and what is the solution? BTW, we are on Openjpa.
Kind of resolve the issue, more like a work around:
Before calling merge(), place several condition checkers to make sure the collection fields are not null
public void save(Manifest entity) {
if(entity.getStudents()==null){
entity.setStudents(new ArrayList<String>());
}
if(entity.getTeachers()==null){
entity.setTeachers(new ArrayList<String>());
}
if(entity.getOthers()==null){
entity.setOthers(new ArrayList<String>());
}
entity.merge();
}
Simple as it, it seems the UI returns those collection fields as null even we initiate them as with empty String lists.
cheers.
Initializing a value in a JPA managed class, such as class Manifest, has no bearing on what, or how, JPA will create the class as JPA maps extracted rows to the class. In particular, the result of:
List<String> students = new ArrayList<String>();
is likely to be:
On creation (by JPA) of a new instance, assign an ArrayList<String>() to students.
JPA overwrites students with the data it extracts - the empty ArrayList is dereferenced/lost.
If your code is clearing a list, such as students, use obj.getStudents().clear(). More likely to run into problems if you call obj.setStudents(someEmptyList).
The issue here is how the JPA manager handles empty datasets: as null or as an empty list. The JPA spec (old, not sure about the just released update) doesn't take a position on this point. A relevant article here.
From your comments, it's apparent that OpenJPA may not be respecting a null value for a Collection/List, while it happily manages the necessary changes for when the value is set to an empty list instead. Someone knowing more about OpenJPA than I may be able to help at this stage - meanwhile you've got a workaround.

Can't Persist Field to Aspnet_Users via NHibernate/ActiveRecord

I'm using ActiveRecord with NHibernate on the backend. I've set up a mapping for Users; I can create/retrieve/register users without any issues.
I now want to add an association called Role to my users (many users per role). I've created the appropriate Role class, tables, data, etc. and everything seems to be working on that end as well.
The problem is that when I save a user and associate a Role, that association does not persist to the database.
I've added a RoleId (int16) column to the aspnet_Users table to match the Role table's Id (int16) column. I've tried using Save and SaveAndFlush without success.
Here's some code:
Role superUser = Role.First(r => r.name == "Super User");
User me = User.First(r => r.UserName == myUserName);
me.Role = superUser;
me.Save(); // Or: SaveAndFlush
When debugging, I can see the association on the objects when they're saved (i.e. me.Role is not null and has the right attributes/properties/etc.) However, when I look at the database, the RoleId value for that user is still NULL. (SaveAndFlush doesn't make a difference.)
What am I missing?
I've read somewhere on SO that extending the users table is usually done by adding another table and linking the two by a foreign key; I assume the classes would then use inheritance by composition for the new ExtendedUser class. Assuming I don't want to go that route, why isn't this working? Is it because of the specific ASP.NET MVC stored procedures et. all?
Some relevant mapping:
[ActiveRecord("aspnet_Users", Mutable = false)]
public class User : ActiveRecordLinqBase<User>
{
[PrimaryKey(PrimaryKeyType.Assigned)]
public Guid UserId { get; set; }
// ...
[BelongsTo("RoleId", Cascade = CascadeEnum.SaveUpdate)]
public Role Role { get; set; }
}
[ActiveRecord]
public class Role : ActiveRecordLinqBase<Role>
{
[PrimaryKey]
public int Id { get; set; }
// ...
[HasMany(Inverse = true)]
public IList<User> Users { get; set; }
[Property]
public string Name { get; set; }
}
Edit: mutable="false" - this clearly stands that entity is read only, which is the source of your problem.
Immutable classes, mutable="false", may not be updated or deleted by the application. This allows NHibernate to make some minor
performance optimizations.
Also:
I believe that you need to have cascading defined. You are not saving just the entity itself but also reference to other entity. Use attributes, fluent config or hbml to define this the way you need. Here are the cascading options:
Here is what each cascade option means:
none - do not do any cascades, let the users handles them by
themselves.
save-update - when the object is saved/updated, check the assoications and save/update any object that require it (including
save/update the assoications in many-to-many scenario).
delete - when the object is deleted, delete all the objects in the assoication.
delete-orphan - when the object is deleted, delete all the objects in the assoication. In addition to that, when an object is
removed from the assoication and not assoicated with another object
(orphaned), also delete it.
all - when an object is save/update/delete, check the assoications and save/update/delete all the objects found.
all-delete-orphan - when an object is save/update/delete, check the assoications and save/update/delete all the objects found. In additional to that, when an object is removed from the assoication and not assoicated with another object (orphaned), also delete it.
You may want to read this article.

Resources