Spring JPA derived delete query - spring

I am using Spring Data rest with Spring JPA. I have one Spring JPA derived delete query that should deletes the list of items but when I am executing it and noticing the console I found that it is executing select query instead that's very strange situation I have ever come across.
#RepositoryRestResource(collectionResourceRel="revision", path="revision")
interface RevisionRepository extends JpaRepository<Revision, Long> {
List<Revision> deleteByContentId(long contentId)
}
I have even tried using Long instead of List<Revision> doesn't work and also have tried removeByContentId it is also doesn't work either and keeps executing the select query instead delete query.
when I am running this method this is what I got on my console
Hibernate: select revision0_.id as id1_2_, revision0_.body as body2_2_, revision0_.content_id as content_3_2_, revision0_.content_type as content_4_2_, revision0_.date_created as date_cre5_2_, revision0_.file_name as file_nam6_2_, revision0_.folder_id as folder_i7_2_, revision0_.force_ssl as force_ss8_2_, revision0_.is_active as is_activ9_2_, revision0_.lookup as lookup10_2_, revision0_.meta_description as meta_de11_2_, revision0_.meta_keywords as meta_ke12_2_, revision0_.meta_title as meta_ti13_2_, revision0_.nav_item as nav_ite14_2_, revision0_.nav_order as nav_ord15_2_, revision0_.regions_objects as regions16_2_, revision0_.summary as summary17_2_, revision0_.title as title18_2_, revision0_.updated_by as updated19_2_, revision0_.user_id as user_id20_2_ from revisions revision0_ where revision0_.content_id=?
does anyone having any idea why it is behaving strangely?

You need to add #Modifying annotation to your delete method. You will also need to make sure it is executed within a transaction, so you might need to add #Transactional annotation too, if you invoke this method not in a transaction.
Please see an example:
#Modifying
#Transactional
int deleteByFieldName( Long fieldValue );

In latest Spring >=5 and Spring Boot >=2.
#Transactional
int deleteByFieldname( Long fieldValue );
Works fine.
Note 1:
#Modifiyng annotation has absolutely no effect in this. It only work for #Query annotation. Without #Query, it is simply ignored.
Note 2:
In naming convention, that depends on the configured NamingStrategy, the CamelCase might be interpreted and nested entity relation or "_" in field name. So "Fieldname" and "FieldName" mean very different things.
Note 3:
Derived delete queries like this have a really nasty n+1 side effect. They ALWAYS first issue select for the rows then isses delete each row/entity one by one with separate delete stement. No way around this, except using #Query() with manual delete statement. And using #Query then requires the #Modifying for delete query.

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

Spring Boot: check if a given String is unique in the database

In Spring Boot, I need to check if a random String is unique. I suppose that a good way is to use an Entity with an unique column, and than:
if the String exists in the repository, adds it to the repository and return it.
That involses some code that may fail in a multithread environment, because while a thread is checking if a given String exists in the database, another thread can add it to the database at the same time.
Could you give me some hits to solve this problem? Thank you.
You can use pessimistic locking to solve that.
Spring has a #lock annotation with a lock type pessimistic, that might serve your needs. Otherwise you can implement your application in a way where it locks the entity before querying and releases the lock afterwards.
I would start from here: https://en.wikipedia.org/wiki/Record_locking
You can enable transaction locks on query Methods with #Lock annotation.
#Lock(LockModeType.PESSIMISTIC_READ)
public Optional<Person> findById(Long PersonId);

IllegalArgument Exception: Not an entity

I have my spring boot app. in which I have written below code at daoImpl layer.
#Autowired
EntityManager
CriteriaBuilder qb = em.getCriteriaBuilder();
CriteriaQuery<Person> c = qb.createQuery(Person.class);
Root<Person> p = c.from(Person.class);
where "Person" in an #Entity class placed in separate "commons" package.
It's throwing exception "Not an entity.." on second line although it passes the first line.
Can someone help me out with the probable causes. This looks something weird to me since the same entity is been accepted on previous line but not on next line.
Irrespective of you problem generally speaking You can not have a table with name 'Order' because it is a keyword in sql.
Change it to something else and check it will work.
See this if you still want to use any keyword.
I got the solution for this, we should use #EntityScan instead of #ComponentScan to find the package of entity if it's in separate project.

Java Spring, JPA - like expression with wildcard

I am struggling with creation of JPQL statement using Like expression in Java Spring application. My aim is to implement simple case insensitive search function.
My code, which should return a list of companies containing a keyWord in their name looks like this:
List<Company> companies = em.createQuery("select cmp from JI_COMPANIES as companies where UPPER(cmp.name) LIKE :keyWord", Company.class)
.setParameter("keyWord","%" + keyWord.toUpperCase() + "%").getResultList();
return companies;
However, this query only works when my keyWord matches the name of company (Like statement is not used).
When testing the function above, I see in my console message from Hibernate:
Hibernate: select ... /* all my parameters */ ... where company0_.CMPN_NAME=?
It seems that my query is translated to cmpn_name='name' instead of using like expression.
You can look about Hibernate batch processing best pratices and Stateless session.
It was my fault due to not understanding how Spring Data JPA library works.
I have tried to create a custom implementation of myCompanyRepository (myCompanyRepository extends JpaRepository, CompanyRepositoryCustom).
The code which I mentioned above was located in my CompanyRepositoryCustomImpl class which implements CompanyRepositoryCustom interface. However I used method name "findCompaniesByName(String name)". JPA automatically creates a query from this method name and my implementation is not used.
Here is the link for Spring Data JPA reference

Play Framework, Hibernate and too slow insertation

I have two models: User and Base.
User model:
http://pastebin.com/WdLzBkHJ
Base model:
http://pastebin.com/tQrEUaSu
At first I want to put your mind on this notation in Base model:
#org.hibernate.annotations.Entity(dynamicInsert=true,dynamicUpdate=true)
It doesn't work (in SQL Debug shown that Hibernate generated queries with using unnecessary columns which in MySQL are configured as nullable). Tell me please why? What am I doing wrong?
And the main problem is there (method in which user uploads base and string in this base inserts in the MySQL table after parsing):
http://pastebin.com/yG3Mapze
Insertation is VERY SLOW. I have file with 70000 string per line and I can't wait until Hibernate insert this string into DB. Maximum I was waiting for 30 minutes and that wasn't the end.
If I'll use raw queries like that:
DB.execute("INSERT INTO bases (user_id,email,password) VALUES (1,'" + email.replaceAll("'", "\'") + "','" + password.replaceAll("'", "\'") + "')");
instead of
b.save();
After that insertation of 70000 strings to DB completes after ~10-20 seconds.
So I can't understand where is the problem and how to fix it?
Also you can see this code above method declaration:
#NoTransaction
If I uncomment it then I'll recieve this exception:
#689mbad1k
Internal Server Error (500) for request POST /checker/uploadnewbase
JPA error
A JPA error occurred (The JPA context is not initialized. JPA Entity Manager automatically start when one or more classes annotated with the #javax.persistence.Entity annotation are found in the application.):
play.exceptions.JPAException: The JPA context is not initialized. JPA Entity Manager automatically start when one or more classes annotated with the #javax.persistence.Entity annotation are found in the application.
at play.db.jpa.JPA.get(JPA.java:22)
at play.db.jpa.JPA.em(JPA.java:51)
at play.db.jpa.JPQL.em(JPQL.java:16)
at play.db.jpa.JPQL.find(JPQL.java:44)
at models.User.find(User.java)
at controllers.Security.getUser(Security.java:30)
at controllers.GlobalController.userStat(GlobalController.java:21)
at play.mvc.ActionInvoker.invoke(ActionInvoker.java:502)
at play.mvc.ActionInvoker.invokeControllerMethod(ActionInvoker.java:476)
at play.mvc.ActionInvoker.invokeControllerMethod(ActionInvoker.java:471)
at play.mvc.ActionInvoker.handleBefores(ActionInvoker.java:320)
at play.mvc.ActionInvoker.invoke(ActionInvoker.java:140)
at Invocation.HTTP Request(Play!)
But in Play manual we can see that:
"If you want to prevent Play from starting any transaction at all, you can annotate the method with #play.db.jpa.NoTransaction.
To prevent transactions for all methods, you can annotate the Controller-class with #play.db.jpa.NoTransaction."
So I have three problems which I described:
About exception in NoTransaction.
About using dynamicInsert = true.
About improving perfomance for Hibernate like if I'll use raw queries.
The problem is the hibernate session, which must be cleared. Otherwise you get problems with memory and performance. You can found some information in http://docs.jboss.org/hibernate/core/3.3/reference/en/html/batch.html. Unfortunately I don't know how to get the HibernateSession. Perhaps you can get the EntityManager and work with it. But my experiences with Hibernate and Batch are really frustrating, so I would recommend to use your raw-solution.

Resources