JPA/Hibername orphan removal - spring

I am trying to remove orphan entities when my parent entity no longer referencing the child entities. Here is the my entity sample
#CollectionOfElements(fetch=FetchType.LAZY)
#Cascade(value = org.hibernate.annotations.CascadeType.ALL)
private Set<item> items;
I have also tried org.hibernate.annotations.CascadeType.DELETE_ORPHAN but no success.
But if I change my annotation to #OneToMany(mappedBy="foo", orphanRemoval=true) then it is working fine.
Any idea why it is not working with #CollectionOfElements

#CollectionOfElements is used to map collection of basic types or embeddable objects as described here.
I think this isn't your case, so just use #OneToMany.

Related

Spring Data problem - derived delete doesn't work

I have a spring boot application (based off spring-boot-starter-data-jpa. I have an absolute minimum of configuration going on, and only a single table and entity.
I'm using CrudRepository<Long, MyEntity> with a couple of findBy methods which all work. And I have a derived deleteBy method - which doesn't work. The signature is simply:
public interface MyEntityRepository<Long, MyEntity> extends CrudRespository<> {
Long deleteBySystemId(String systemId);
// findBy methods left out
}
The entity is simple, too:
#Entity #Table(name="MyEntityTable")
public class MyEntity {
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
#Column(name="MyEntityPID")
private Long MyEntityPID;
#Column(name="SystemId")
private String systemId;
#Column(name="PersonIdentifier")
private String personIdentifier;
// Getters and setters here, also hashCode & equals.
}
The reason the deleteBy method isn't working is because it seems to only issue a "select" statement to the database, which selects all the MyEntity rows which has a SystemId with the value I specify. Using my mysql global log I have captured the actual, physical sql and issued it manually on the database, and verified that it returns a large number of rows.
So Spring, or rather Hibernate, is trying to select the rows it has to delete, but it never actually issues a DELETE FROM statement.
According to a note on Baeldung this select statement is normal, in the sense that Hibernate will first select all rows that it intends to delete, then issue delete statements for each of them.
Does anyone know why this derived deleteBy method would not be working? I have #TransactionManagementEnabled on my #Configuration, and the method calling is #Transactional. The mysql log shows that spring sets autocommit=0 so it seems like transactions are properly enabled.
I have worked around this issue by manually annotating the derived delete method this way:
public interface MyEntityRepository<Long, MyEntity> extends CrudRespository<> {
#Modifying
#Query("DELETE FROM MyEntity m where m.systemId=:systemId")
Long deleteBySystemId(#Param("systemId") String systemId);
// findBy methods left out
}
This works. Including transactions. But this just shouldn't have to be, I shouldn't need to add that Query annotation.
Here is a person who has the exact same problem as I do. However the Spring developers were quick to wash their hands and write it off as a Hibernate problem so no solution or explanation to be found there.
Oh, for reference I'm using Spring Boot 2.2.9.
tl;dr
It's all in the reference documentation. That's the way JPA works. (Me rubbing hands washing.)
Details
The two methods do two different things: Long deleteBySystemId(String systemId); loads the entity by the given constraints and ends up issuing EntityManager.delete(…) which the persistence provider is about to delay until transaction commits. I.e. code following that call is not guaranteed that the changes have already been synced to the database. That in turn is due to JPA allowing its implementations to actually do just that. Unfortunately that's nothing Spring Data can fix on top of that. (More rubbing, more washing, plus a bit of soap.)
The reference documentation justifies that behavior with the need for the EntityManager (again a JPA abstraction, not something Spring Data has anything to do with) to trigger lifecycle events like #PreDelete etc. which users expect to fire.
The second method declaring a modifying query manually is declaring a query to be executed in the database, which means that entity lifecycles do not fire as the entities do not get materialized upfront.
However the Spring developers were quick to wash their hands and write it off as a Hibernate problem so no solution or explanation to be found there.
There's detailed explanation why it works the way it works in the comments to the ticket. There are solutions provided even. Workarounds and suggestions to bring this up with the part of the stack that has control over this behavior. (Shuts faucet, reaches for a towel.)

How come Spring Data Rest only allows updates from the owner side of ManyToOne/ManyToMany?

I downloaded a sample project from here https://www.baeldung.com/spring-data-rest-relationships
I then ran it and did some test REST calls. As far as I can tell you can only update association from the owner side using SDR. What I mean is
public class Book {
#ManyToOne
#JoinColumn(name = "library_id")
private Library library;
}
and
public class Library {
#OneToMany(mappedBy = "library")
private List<Book> books;
}
You can't actually make post/put calls to /libraries/1/books. Server return 204 but no effect on the db whatsoever.
You can however, make post/put calls to /books/1/library and everything works as intended including keeping the other entity in sync.
Is this normal? It's the same behaviour for #ManyToMany as well. Is there a way to allow for updates from both sides? If I write my own API I can certainly make this the case. Why does SDR not do this?
Is this normal?
In a sense, yes. That's exactly how pure JPA would behave when you added a Book to the collection of Library.books with your current mapping - it would make no changes whatsoever.
My guess is that Spring Data Rest doesn't know (or care) which side of the association is the owner side, and just doesn't go the extra mile to make sure updating the inverse side works as well.
Is there a way to allow for updates from both sides?
A workaround could be to simply pretend both sides of the associations were the owning side, i.e.:
public class Library {
#OneToMany
#JoinColumn(name = "library_id")
private List<Book> books;
}
Be advised that this makes Hibernate treat Library.books and Book.library as two completely separate associations. In some corner cases, your entities may not behave the way you would expect. You have been warned.
Updating bidirectional relations from both sides is very tricky and will potentially cause side effects considering Springs endpoint authorization or Spring Data RESTs BeforeLinkSaveEvent and AfterLinkSaveEvent (https://docs.spring.io/spring-data/rest/docs/current/reference/html/#events).
There has to be a strict parent child relation. I don't think that you can configure the desired behaviour.

Spring Data Neo4j - Indexing and Inheritance

Lets say i have the following data model:
public class A {
#Indexed(indexType = IndexType.FULLTEXT, indexName = "property1")
String property1;
}
public class B extends A {
#Indexed(indexType = IndexType.FULLTEXT, indexName = "property2")
String property2;
}
Can i tell the Spring framework to index property1 of class B under a different index name?
If not, what would you do in such case? I mean, what would you do if you have few classes that all extends the same base class, but in the same time, all the properties that those classes inherit form the base class should be indexed. I can annotate those properties for indexing only in the base class, and it is very limiting. What can i do?
Thanks.
The level attribute in the index definition annotation can be set to Level.INSTANCE. For more help please refer spring-data-neo4j documentation here
Here is an excerpt from the doc :
If a field is declared in a superclass but different indexes for
subclasses are needed, the level attribute declares what will be used
as index. Level.CLASS uses the class where the field was declared and
Level.INSTANCE uses the class that is provided or of the actual entity
instance.
I don't think that's possible. Your property1 will always be indexed in index property1. Being able to specify multiple indexes on a single field would probably fix your issue, but it's currently not possible. A while ago, I've raised an issue for this, but it's not yet implemented.
If you really want a domain (entity) object approach, you could also opt for the domain entity approach. It's not related to Spring or Spring Data Neo4j, but it also does the trick. By manually handling your entities this way, you could also manage the indexes yourself, thus gaining all the flexibility you want.
Just a question, why would you want to specify a different index per subclass?

Spring and Hibernate: DAO Pattern. How to solve LazyInitializationException

I'm wondering: What is the point of FetchType.LAZY in one(many)-to-many using DAO Pattern?
It is basically useless? As soon as you are outside of your DAO (eg. were the actual work is done) you can't fetch the related data as you are not in a hibernate session anymore.
Lets make an Example:
Student and Class. A student takes many classes. he now logs into the system and his Student entity object is retrieved from the system.
application layer -> Service Layer -> DAO
Now the Student wants to see which classes he takes and oops a LazyInitializationException occurs as we are outside of DAO.
What are the options to prevent this? I've like googled hours and not found a solution for this except to actually fetch everything before leaving the DAO which defeats the purpose of lazy in the first place. (Have read about OpenSessionViewFilter but this should work independent of the application layer)
How do you solve this issue in a good way? What are alternative patterns that don't suffer from this?
EDIT:
I get no LazyInitializationException with following settings:
#OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.compound",
cascade = CascadeType.ALL, orphanRemoval = true)
#Fetch(FetchMode.JOIN)
Funny thing is it must be exactly like this.
removing #Fetch -> LazyInitializationException
Even stranger is if I remove orphanRemoval = true, then LazyInitializationException also occurs even with #Fetch. So both of those are required.
Maybe someone could enlighten me why this is the case. Currently I'm tending to ditch hibernate completely as with pure JDBC I would have reached the desired behavior hours ago...
You can always fetch foreign-key relation data without the same ession. Since your session does not exists outside your Application Layer you just fetch it manually in the method where you retrieve the data and set it.
Application Layer:
public List<SchoolClass> getSchoolClassesByStudent(Serializable identifier)
{
List<SchoolClasses> schoolClasses = // get classes by student using criteria or hql
return schoolClasses;
}
Client Layer:
public void loadSchoolClassesByStudent(Student student)
{
student.setSchoolClasses(application.getSchoolClassesByStudent(student.getId()));
}
I myself choosed not to support any collections in my hibernate entities.
I fetch all child relations with very generic methods that my server provides to my client similar to this one.
Edit: Or create some logic (interceptor?) that can check outside the DAO if data is uninitialized before accessing it and initialize it using a generic method.
This would also assume that Hibernate jar's are on the client level, which depends if this is a good idea (Same if the uninitialized data is not set to null).
One way to solve the problem is to use OpenSessionInViewFilter Filter.
<filter>
<filter-name>hibernateSessionFilter</filter-name>
<filter-class>
org.springframework.orm.hibernate3.support.OpenSessionInViewFilter
</filter-class>
</filter>

JPA #ManyToMany Writing On Create But Not Update

My team has two classes, User and Store, related by JPA #ManyToMany annotations. The relevant code is below.
When creating a new User object and setting its stores, life is good. But we're seeing some unexpected behavior when trying to update User objects through our Struts 2 / Spring web UI. (These problems do not occur when running simple automated integration tests through JUnit).
Simply put, when saving a User, its stores collection is not updated -- the database shows no changes, and reloads of the User object in question shows it to have an unchanged set of stores.
The only solution that we have found to this problem -- and we don't like this solution, it makes all of the developers on our team a little queasy -- is to create a new Set of Stores, then do user.setStores(null), then do user.setStores(stores).
We are using the OpenEntityManagerInViewFilter; we are using Hibernate as our JPA provider; we are using Spring's JpaTransactionManager. We don't have any #Transactional annotations in our code -- and adding them breaks the existing code due to proxy behavior described elsewhere.
Any insight anyone might provide as to how we might solve this problem in a non-queasy manner is welcome.
Relevant part of User.java:
#ManyToMany
#JoinTable(name = "company.user_store_access",
joinColumns = #JoinColumn(name = "userid"),
inverseJoinColumns = #JoinColumn(name = "storeid"))
public Set<Store> getStores() {
return stores;
}
Relevant part of Store.java:
#ManyToMany(mappedBy = "stores")
public List<User> getUsers() {
return users;
}
Relevant parts of UserDetailAction.java:
(pass-throughs down a layer or two, and then:)
entity = getEntityManager().merge(entity);
The problem is that you have a bi-directional relation, and in this case there is one entity that controls the other, and from your mapping the controlling one is the Store entity and not the User, so adding a user to a store would work (since the Store is the one who controls) but adding a store to a user will not.
try inverting the situation, by making the User object the one who controls the relation, by putting the #ManyToMany(mappedBy = "users") annotation on the getStores() method and changing the getUsers() one.
Hope it helped.
P.S. the entity that controls the relation is always the one that doesn't have the mappedBy attribute.
I just got stuck to same problem and found solution on another page.
Here is what has worked for me
calling getJpaTemplate().flush() before calling merge has worked for me.
I did not get the reason why it worked.
Please try the following annotation in your User object:
#ManyToMany(cascade = {CascadeType.ALL})

Resources