folks,
The method below is part of a transaction using JPA. I'm trying to discover how to add a functionality in this method so when the query within the method throws an exception, all the transaction rolls back.
What could I do in this case? Below is the code:
#Transactional
public void deleteDadosSExecReenvCancelada(Long nuSeqConsecao){
try{
StringBuilder query = new StringBuilder();
query.append(" DELETE FROM sigpc_fnde.s_execucao_financ_gru_reenv where NU_SEQ_EXEC_FINANC_REENV in (SELECT NU_SEQ_EXECUCAO_FINANCEIRA FROM sigpc_fnde.s_exec_financ_reenv where NU_SEQ_CONCESSAO = ?)");
getEntityManager().createNativeQuery(query.toString()).setParameter(1, nuSeqConsecao).executeUpdate() ;
StringBuilder sql = new StringBuilder();
sql.append(" DELETE FROM sigpc_fnde.s_exec_financ_reenv where NU_SEQ_CONCESSAO = ?");
getEntityManager().createNativeQuery(sql.toString()).setParameter(1, nuSeqConsecao).executeUpdate() ;
} catch (Exception e){
e.getCause();
}
}
I would like to know if simply the Annotation #Transactional(rollbackFor=true) guarantees this condition to happen.
#Transactional will detect any unchecked exception thrown in your method and your transaction will be marked as rollback only. I think #Transactional(rollbackFor = Exception.class) is not necessary in your case since all possible exceptions that could be thrown are RuntimeException.
Also, your EntityManager needs to be injected by Spring. If it's not the case, let Spring inject the EntityManager.
An example on how you could let Spring inject the bean:
#RequiredArgsConstructor
class MyService {
...
#PersistenceContext
private final EntityManager entityManager;
...
}
Related
While I have Java Batch jobs that read data, process it and store it in other places in the database, now I need a step to actually remove data from the database. All I need to run is a delete query via JPA.
The chunk based Reader/Processor/Writer pattern does not make sense here. But the Batchlet alternative is giving me a headache either. What did I do?
I created a Batchlet that gets invoked via CDI. At that moment it is easy to inject my JPA EntityManager. What is not easy is to run the update query. Code looks like this:
package ...;
import javax.batch.api.BatchProperty;
import javax.inject.Inject;
import javax.inject.Named;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
#Named("CleanerBatchlet")
public class CleanerBatchlet extends AbstractBatchlet {
public static final Logger log = LogManager.getLogger(CleanerBatchlet.class);
#PersistenceContext(unitName = "...")
private EntityManager entityManager;
#Inject
#BatchProperty(name = "technologyIds")
private String technologyIds;
private void clearQueue(long technologyId) {
//EntityManager entityManager = ...getEntityManager();
//entityManager.getTransaction().begin();
Query q = entityManager.createQuery("delete from Record r where r.technologyId=:technologyId");
q.setParameter("technologyId", technologyId);
int count = q.executeUpdate();
//entityManager.getTransaction().commit();
log.debug("Deleted {} entries from queue {}", count, technologyId);
//entityManager.close();
}
#Override
public String doProcess() throws Exception {
log.debug("doProcess()");
out.println("technologyIds=" + technologyIds);
log.info("technologyIds=" + technologyIds);
try {
String[] parts = technologyIds.split(",");
for (String part: parts) {
long technologyId = Long.parseLong(part);
clearQueue(technologyId);
}
} catch (NullPointerException | NumberFormatException e) {
throw new IllegalStateException("technologyIds must be set to a string of comma-separated numbers.", e);
}
return "COMPLETED";
}
}
As you can see some lines are commented out - these are the ones I am experimenting with.
So if I run the code as-is, I get an exception telling me that the update query requires a transaction. This is regardless of which of the two persistence units in my project I use (one is configured for JTA, the other is not).
javax.persistence.TransactionRequiredException: Executing an update/delete query
It also does not matter whether I uncomment the transaction handling code begin/commit. I still get the same error that a transaction is required to run the update query.
Even when I try to circumvent CDI and JTA completely by creating my own EntityManager via the Persistence API (and close it afterwards, respectively) I do get the very same exception.
So how can I run this delete query or other update queryies from within the batch job?
I'd suggest using plain jdbc to run this delete query, with either auto commit or manual transaction commit.
During the batchlet processing, the incoming transaction is suspended. So the entity manager does not have a transaction context.
Ultimately I made it work by following this tutorial: https://dzone.com/articles/resource-local-vs-jta-transaction-types-and-payara
and going for the Classic RESOURCE_LOCAL Application pattern.
It involves injecting the nonJTA EntityManagerFactory, using that to create the entitymanager and closing it after use. Of course the transaction has to be managed manually but after all now it works.
The essential excerpt of my code looke like this:
#PersistenceUnit(unitName = "...")
private EntityManagerFactory emf;
#Inject
#BatchProperty(name = "technologyIds")
private String technologyIds;
private void clearQueue(long technologyId) {
EntityManager entityManager = emf.createEntityManager();
entityManager.getTransaction().begin();
Query q = entityManager.createQuery("delete from Record r where r.technologyId=:technologyId");
q.setParameter("technologyId", technologyId);
q.executeUpdate();
entityManager.getTransaction().commit();
entityManager.close();
}
I am working on a spring-batch, where after reader and processor, writer is responsible to populate data to DB. Writer is calling Service which internally calls DAO layer. In method insertToDB() if some exception occurs the transaction is not being rolled back. PSB my code.
public class MyWriter{
#Autowired
private MyService myService;
#Override
public void write(List<? extends MyBO> list) {
try{
for(MyBO bo: list){
myService.insert(bo);
}
}
catch(Exception e){
log.error("Cant write to DB")
}
}
public class MyService{
#Autowired
private TableOneDAO tableOneDao;
#Autowired
private TableTwoDAO tableTwoDAO;
#Autowired
private TableThreeDAO tableThreeDAO;
public void insert(MyBO bo){
try{
// do other stuff of processing bo and create entity
MyEntityTableOne myentity1 = getEntityT1(bo);
MyEntityTableTwo myentity2 = getEntityT2(bo);
MyEntityTableThree myentity3 = getEntityT3(bo);
insertToDB(myEntity1,myEntity2,myEntity3);
}
catch(Exception e){
log.error("Error occured.");
throw new MyException("Error Blah blah occured");
}
}
#Transactional(value = "txn1")
public void insertToDB(MyEntityTableOne entity1, MyEntityTableTwo entity2, MyEntityTableThree entity3) {
try{
tableOneDao.insert(entity1);
tableTwoDAO.insert(entity2);
tableThreeDAO.insert(entity3);
}
catch(Exception e){
log.error("Error occured during insert to DB");
throw new MyException("Error Blah blah occured during DB insert");
}
}
The code goes to the catch block, but doesn't rollback records. If some error occurs during insert of Table2 then entry for Table1 is not rolled-back. And if occurs during table3 insertion then table1 and table2 records are not rolled-back.
If I move the #Transactional annotation to insert() method it works fine. What is root cause of this issue. What I have to do if I want to have transaction only on insertToDB() method.
I am trying to make it simple: To support #Transactional spring wraps the implementing class into a so called proxy and surrounds the method call / class with the transactional logic.
Now you are calling the #Transactional annotated method within the same class. Therefore the proxy is not invoked and the transactional does not work. When moving the annotation to your insert method you are invoking the method from outside of the class which means you invoke the method against the proxy.
Thats a limitation of Spring AOP (?) I think.
You can do something like following to achieve what you want:
public class MyService{
#Ressource
private MyService self;
...
self.insertToDB(myEntity1,myEntity2,myEntity3)
Your item writer will be already called in a transaction driven by Spring Batch and that you can configure at the step level by providing the transaction manager and transaction attributes. So there is no need to use #Transactional in the downstream service used by the writer.
You need to remove that annotation from MyService and it should work as expected.
I am trying to do Transactional Rollback in my methods. Intentionally i am making the insert fails to find out . But i don't see its getting rolled back . Please help what i am missing.
#Service
public class ModesService implements IModesService{
ChargeRuleDao chargeRuleDao;
public ModesService(ChargeRuleDao chargeRuleDao){
this.chargeRuleDao = chargeRuleDao;
}
#Override
#Transactional(propagation = Propagation.REQUIRED)
public void process(ChargeRule chargeRule){
chargeRuleDao.deleteShippingChargeAttr(shippingChargeRuleID);
chargeRuleDao.deleteShippingCharge(shippingChargeRuleID);
chargeRuleDao.deleteShippingChargeDest(shippingChargeRuleID);
//Delete
chargeRuleDao.insertShipChargeFeedRule(chargeRule);
}
In DAOImpl class i have methods like below for all deletions and insertion.
#Override
public int deleteShippingChargeAttr(String test) {
MapSqlParameterSource params = new MapSqlParameterSource();
params.addValue("ABC" "ABC", Types.VARCHAR);
return jdbcTemplate.update(DELETE_QUERY, params);
}
You may try #Transactional(rollbackFor = XYZException.class).
XYZException should be an exception which should wrap all the exceptions/exception for which you want to rollback the transaction.
Rollback occurs by default for every unchecked exception. That means you need to throw some type unchecked exception, like for example
throw new NullPointerException();
in your insertShipChargeFeedRule(chargeRule);
more about #Transactional here https://javamondays.com/spring-transactions-explained/
i am getting exception javax.persistence.PersistenceException: org.hibernate.SessionException: Session is closed in return statement, i am using spring JPA.
StringBuilder queryBuilder = new StringBuilder(querystat);
System.out.println("startDate--->" + startDate);
Query query =
getEntityManager().createQuery(queryBuilder.toString());
System.out.println("query.list();--->"
+ query.getResultList().size());
return query.getResultList();
any one have idea on this issue?
Thanks for responses..i have added transactionTemplate in service class its working.below is the working code..
return transactionTemplate
.execute(new TransactionCallback<List<?>>() {
public List<?> doInTransaction(
TransactionStatus status) {
return dao.monthReportQuery(startDate, endDate);
}
});
The public method calling the code needs to be annotated with #Transactional.
Further, the processing of #Transactional needs to be activated. This might or might not be already active. It can be activated using the #EnableTransactionManagement.
The method annotated with #Transactional shall not be called from the same class.
I have two classes:
#Service
#Transaction
class A {
public void method1() {
private B;
try {
save1()
b.method2()
} catch (SqlException e) {
doSomeThing();
}
#Autowired
public setB(){
this.B = B;
}
}
}
#Service
class B {
#Transactional(propagation = Propagation.REQUIRES_NEW, rollbackFor = Exception.class)
public void method2(){
save2()
throw new SqlException();
}
}
I got an SqlException as expected, but also an UnexpectedRollBackException, and the program stops.
I want to know why the data persisted by save2() is not rolled back?
Is it a problem with outer transaction?
UPDATE: I tried catching UnexpectedRollBackException in class A and everything works fine. But I still need some kind of explanation why I get the exception? I suppose the outer transaction should be suspended when the inner transaction begins, so why the rollback is unexpected for the outer transaction?
Thanks.
First of all: you are not injecting the instance of B into class A via spring. i.e. your b is not managed by spring => this leads to the behaviour: Spring is ignoring the #Transactional annotation on method.
Second if you don't want to rollback on SqlException you have to specify noRollbackFor=SqlException.class
UPDATE: after clarification, the call is happening on managed bean. But the problem that expected behaviour - transaction inside of transaction is not supported in general by the transaction management system out of the box. https://docs.spring.io/spring/docs/4.3.11.RELEASE/javadoc-api/org/springframework/transaction/annotation/Propagation.html#REQUIRES_NEW
Unless the details on that system are provided it is impossible to step forward. Out of content the NESTED transaction propagation could be better, then REQUIRES_NEW, because it is making a rollback point for the external transaction and starts new one.
I have a simliar scenario like this and resolved it using propagation = Propagation.NESTED from where the second method is calling. rewrite the code as mentioned below and try.
#Service
class A {
#Transactional(rollbackFor = { HibernateException.class}, propagation = Propagation.NESTED)
public void method1() {
private B;
try {
save1()
b.method2()
} catch (SqlException e) {
doSomeThing();
}
#Autowired
public setB(){
this.B = B;
}
}
}
Note: Transactional is used in method level for class A with propagation nested . Class B shown below.
#Service
class B {
#Transactional(rollbackFor = {Exception.class}, propagation = Propagation.REQUIRED)
public void method2(){
save2()
throw new SqlException();
}
}
This has resolved the issue in my case.