Spring data: DeleteAll and Insert in same transaction - java

I am trying to achieve below native query logic using hibernate (Spring JPA). But save(Iterable) throws exception and rollback the entire transaction if one of the record fails to persist. Is there any way to do to catch the record error and proceed with insertion on other records.
eg:-
Native Sql Query
set autocommit=false
delete from EMPLOYEE;
insert into EMPLOYEE(id, name, sal) values(2, ‘Roy’, ‘rt’); —-stmt1
insert into EMPLOYEE(id, name, sal) values(2, ‘Joe’, 3000);
commit;
Note: sal column is numeric in EMPLOYEE table. Execution continues eventhough stmt1 failed.
Hibernate (CrudRepository)
#Autowired
CrudRepository employeeRepository;
#Transactional
public void saveToDB(List dataList) {
employeeRepository.deleteAll();
employeeRepository.save(dataList);
}

Anyone else stumbling upon this problem. I managed to get it work with writing own deleteAll Method in RepositoryInterface and setting annotation like this:
#Modifying(flushAutomatically = true)
#Query("delete from MyEntity")
void deleteAllCustom()

Use flush between deleteall and save.

Heey Warriors,finally this works for me.
#Modifying(flushAutomatically = true)
#Transactional
void deleteByProjet(Projet projet);
Good Luck ;)

I just changed from deleteAll to deleteAllInBatch (JpaRepository interface) and it worked.
deleteAllInBatch results in Hibernate registering a prepared statement delete from yourtable right when deleteAllInBatch is called.
With deleteAll, Hibernate
select all your data into memory, as entities
for each entity, it calls EntityManager.delete
each delete generates a EntityDeleteAction which will be called when transaction ends and session flushes. But for whatever reason, the actions for the inserts end up being called before the deletes (maybe the order is unpredictable, I'm not sure).

Related

Spring Data Jpa #Lock annotation with #Transactional

As per java docs for #Lock annotations:
Annotation used to specify the LockModeType to be used when executing the query. It will be evaluated when using Query on a query method or if you derive the query from the method name.
As mentioned above it will be evaluated with #Query annotation or findBySomething..() method.
But as per my finding, when i put #Lock annotation with #Transactional in any method, and get record from db in that transaction method it is acquiring lock on db rows, ideally it should not(correct me if i am wrong):
I verified this with two transactions T1 and T2 as follows:
Starts T1 first, and fetch some records from db and sleep that thread and did not update them.
Now start T2 on other method having same #Lock with pessimistic write and #Transactional annotation and fetch same records and trying to update them, But when it is trying to commit those changes it waits for some time and then throw exception saying PessimsticLock timeout exception
#Transactional
#Lock(LockModeType.PESSIMISTIC_WRITE)
public boolean addInventory(Integer id){
repo.findById(id)// #statement 1
}
also when #statement 1 is called it does not fire "select for update" query instead only fire select query.
Is it true that #Lock can be used with #Transactional and all rows which are fetched under this transaction get lock ?
Using SpringBoot version 2.1.4.RELEASE
I was able to figure out the behavior which I was facing:
#Lock annotation has not effect, if it put over method having #Transactional annotation only , it is working with #Query or findByXyz() method.
In my case, T2 transaction not able to commit because of database which is MySQL and have default transaction isolation level 'Repeatable Read' which do not allow to proceed T2 transaction until T1 commits.
#Lock(LockModeType.PESSIMISTIC_WRITE) //will not have any effect
#Transactional
public Response handleRequest(){
//no lock will acquire
// prior to know about mysql's repeatble read, i was having impression, any call to db in this method will lock the db rows, which is wrong
}
#Lock(LockModeType.PESSIMISTIC_WRITE)// will fire select for update
#Transactional
public findByUserId(){
// any db call from repo layer which have no lock annotations
}
#Lock(LockModeType.PESSIMISTIC_WRITE)// will fire select for update
#Query("select u from user as u where u.id=:id")
#Transactional
public getByUserID(String id){
}

Why my trigger is not working with Hibernate?

I have a Java spring boot project and I use Hibernate in this project. I have a void deleteAll(List<Long> idList) method and its query is #Query(value = "DELETE c FROM Course c WHERE c.id in :idList", nativeQuery = true). I want to use a trigger after every delete operation. I created a trigger in mysql and it works when I try in mysql, but it doesn't work when I call void deleteAll(List<Long> idList) method. How can use my trigger with hibernate?
My trigger defination:
DELIMITER $$
CREATE TRIGGER after_delete_course AFTER DELETE ON course FOR EACH ROW
BEGIN
DELETE tc1 FROM table1_course tc1 WHERE tc1.course_id = OLD.id;
DELETE tc2 FROM table2_course tc2 WHERE tc2.course_id = OLD.id;
DELETE tc3 FROM table3_course tc3 WHERE tc3.course_id = OLD.id;
END$$
DELIMITER ;
SQL generated by hibernate when deleteAll is called :
Hibernate:
DELETE c
FROM
Course c
WHERE
c.id in (
?
)
How do you know it doesn't work? You won't see the delete queries of your trigger being logged in your application, this is happening only on the DBMS side.
If you have an EntityManager that contains the deleted entity or the associations deleted by the trigger, you will have to detach them from the EntityManager to get a consistent view again, as Hibernate does not know what effects DML statements will have on the managed entities. Try using EntityManager.clear after executing this DML statement.
An alternative solution is to use the entity lifecycle annotations provided by JPA.
In this case, you are performing a delete operation. So, you can use the #PreRemove annotation.
You can check some examples on Baeldung

Using Both Query Api And Criteria Api in Hibernate Leads Problems

When I update a row with query API and then retrieve the data with criteria API in the same transaction, I get old value, not the updated value. Why is it like that and how can I solve the problem? I need to get the updated value.
#Service
#Transactional
public class ExampleServiceImpl implements ExampleService {
#Autowired
ExampleRepository exampleRepository;
#Transactional
public void example() {
ExampleEntity entity = (ExampleEntity) sessionFactory.getCurrentSession().createCriteria(ExampleEntity.class).add(Restrictions.eq("id", 190001L)).uniqueResult();
exampleRepository.updateState(190001L, State.CLOSED);
ExampleEntity updatedEntity = (ExampleEntity)sessionFactory.getCurrentSession().createCriteria(ExampleEntity.class).add(Restrictions.eq("id", 190001L)).uniqueResult();
assertEquals(State.CLOSED, updatedEntity.getState());
}
}
#Repository
public class ExampleRepositoryImpl implements ExampleRepository {
public void updateState(Long id, State state) {
String updateScript = "update exampleEntity set state= '%s', " +
"VERSION = VERSION + 1 " +
"where ID = %s;";
updateScript = String.format(updateScript, state, id);
Query sqlQuery = sessionFactory.getCurrentSession().createSQLQuery(updateScript);
sqlQuery.executeUpdate();
}
}
Note: If I delete the first line and don't retrieve entity at the beginning everything works as I expected.
You are mixing native SQL and hibernate. Basically, when you first retrieve the entity, it gets stored in your session EntityManager. You then use plain SQL to update the row in the database, but as far as hibernate is concerned, the entity has not been dirtied because it isn't clever enough to understand how plain SQL relates to the object model. When you retrieve it the second time, it simply gives you the original entity it already has cached in the EntityManager rather than querying the database.
The solution is to simply manually force evict the entity from the EntityManager after the update as follows:
sessionFactory.getCurrentSession().evict(entity);
Or you could simply update the entity you fetched and persist it (best solution IMHO, no superfluous DAO method, and best abstraction away from the database):
ExampleEntity entity = (ExampleEntity) sessionFactory.getCurrentSession().createCriteria(ExampleEntity.class).add(Restrictions.eq("id", 190001L)).uniqueResult();
entity.setState(State.CLOSED);
entity.setVersion(e.getVersion() + 1);
sessionFactory.getCurrentSession().update(entity);
Basically... whichever option you choose, don't mix plain SQL and hibernate queries in the same transaction. Once hibernate has an object loaded, it will return that same entity from its cache until it knows for a fact that it is dirty. It is not clever enough to know that an entity is dirty when plain SQL was used to dirty it. If you have no choice and must use SQL (and this should never be the case in a well designed hibernate model), then call evict to tell hibernate the entity is dirty.
Your transaction is still not committed when you get result - this is the reason you get "old" value.

How do you update a foreign key value directly via Hibernate?

I have a couple of objects that are mapped to tables in a database using Hibernate, BatchTransaction and Transaction. BatchTransaction's table (batch_transactions) has a foreign key reference to transactions, named transaction_id.
In the past I have used a batch runner that used internal calls to run the batch transactions and complete the reference from BatchTransaction to Transaction once the transaction is complete. After a Transaction has been inserted, I just call batchTransaction.setTransaction(txn), so I have a #ManyToOne mapping from BatchTransaction to Transaction.
I am changing the batch runner so that it executes its transactions through a Web service. The ID of the newly inserted Transaction will be returned by the service and I'll want to update transaction_id in BatchTransaction directly (rather than using the setter for the Transaction field on BatchTransaction, which would require me to load the newly inserted item unnecessarily).
It seems like the most logical way to do it is to use SQL rather than Hibernate, but I was wondering if there's a more elegant approach. Any ideas?
Here's the basic mapping.
BatchQuery.java
#Entity
#Table(name = "batch_queries")
public class BatchQuery
{
#ManyToOne
#JoinColumn(name = "query_id")
public Query getQuery()
{
return mQuery;
}
}
Query.java
#Entity
#Table(name = "queries")
public class Query
{
}
The idea is to update the query_id column in batch_queries without setting the "query" property on a BatchQuery object.
Using a direct SQL update, or an HQL update, is certainly feasible.
Not seeing the full problem, it looks to me like you might be making a modification to your domain that's worth documenting in your domain. You may be moving to having a BatchTransaction that has as a member just the TransactionId and not the full transaction.
If in other activities, the BatchTransaction will still be needing to hydrate that Transaction, I'd consider adding a separate mapping for the TransactionId, and having that be the managing mapping (make the Transaction association update and insert false).
If BatchTransaction will no longer be concerned with the full Transaction, just remove that association after adding a the TransactionId field.
As you have writeen, we can use SQL to achieve solution for above problem. But i will suggest not to update the primary keys via SQL.
Now, as you are changing the key, which means you are creating alltogether a new object, for this, you can first delete the existing object, with the previous key, and then try to insert a new object with the updated key(in your case transaction_id)

JPA 1.0 and Hibernate 3.4 generating FOR UPDATE NOWAIT when locking

I am currently working on a Java EJB project being deployed to Weblogic 10.3.3. We are using JPA 1.0 with Hibernate 3.4 as the implementor. We are also using the Oracle10g Dialect.
The issue we are running in to involves the generation of SQL by hibernate when attempting to lock a row for update.
We execute a query:
Query q = entityManager.createNamedQuery("findMyObject");
MyHibernateObject myObject= (MyHibernateObject ) q.getSingleResult();
And then lock that object with:
entityManager.lock(myObject, LockModeType.WRITE);
This act of locking generates the query:
SELECT myObject FROM myTable FOR UPDATE NOWAIT
What I want it to generate is:
SELECT myObject FROM myTable FOR UPDATE
Enabling other threads to query for this object without throwing the exception: org.hibernate.exception.LockAcquisitionException and to just wait their turn or let the EJB transaction timeout.
So knowing all this, can I force Hibernate to generate the SQL without the NOWAIT keyword?
I know that using Hibernate 3.6 and JPA 2.0 will allow this using a pessimistic lock but due to Weblogic only supporting JPA 1.0 our hands are tied.
Ideally I want to avoid writing our own retry and/or timeout mechanism by handling the exception when all I need is to just augment the SQL that Hibernate is generating when the EntityManager creates the lock.
Ok, we are using a workaround until we update to Weblogic 10.3.4
Here it is in case someone else stumbles upon this:
SessionImpl session = (SessionImpl)entityManager.getDelegate();
session.lock(myObject, LockMode.UPGRADE);
This of course breaks from the JPA standard, in that we are exposing hibernates implementation and using the hibernate session.
But it will generate a
SELECT myObject FOR UPDATE
instead of
SELECT myObject FOR UPDATE NOWAIT
Hope this helps someone.
Use below code to skip a locked row. This is the alternate of
select * from student where student id = n for update nowait
findStudnt method throws error if row is already locked. If there is no error call updateStudent method to update student entity else log the error for audit.
#Override
public Student findStudent(final Long studentId) {
TypedQuery<Student> query = getEntityManager().createNamedQuery("from Student s where s.studentId =:studentId", Student.class);
query.setParameter("studentId", studentId);
query.setLockMode(LockModeType.PESSIMISTIC_WRITE);
query.setHint(JAVAX_PERSISTENCE_LOCK_TIMEOUT, ZERO_NUMBER);
return query.getSingleResult();
}
#Override
#Transactional(readOnly = false, propagation = Propagation.REQUIRED)
public void updateStudent(Student student) {
makePersistent(student);
getEntityManager().flush();
}

Categories

Resources