I'm doing an insert + some updates inside a Transaction using JDBCTemplate.
The thing is that I have to set primary key programmatically which I read from a table which keeps track of Primary keys.
public void insert(final COrder cOrder) {
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
public void doInTransactionWithoutResult(TransactionStatus status) {
Long cOrderId = insertOrder(cOrder);
insertOrderLines(cOrder, cOrderId);
insertCOrderTaxes(cOrder, cOrderId);
updateMStorage(cOrder);
insertMTransactions(cOrder);
}
});
}
public Long insertOrder(COrder o) {
Long cOrderId = getNextId("C_ORDER");
o.setcOrderId(cOrderId);
...
}
//the above insert methods here
and finally getNextId() which gets the next id.
public synchronized Long getNextId(final String tableName) {
final IdHolder idHolder = new IdHolder(-1l);
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
public void doInTransactionWithoutResult(TransactionStatus status) {
Long nextId = jdbcTemplate.queryForObject("SELECT CURRENTNEXT FROM AD_SEQUENCE WHERE NAME=?",
new String[] { tableName }, Long.class);
jdbcTemplate.update("UPDATE AD_SEQUENCE SET CURRENTNEXT = CURRENTNEXT + 1 WHERE NAME=?",
new Object[] { tableName });
idHolder.setId(nextId);
}
});
return idHolder.getId();
}
Basically I want all these insertions/updates done all or none, but this getNextId() I need to commit regardless of the outer transaction(because of reserving the next primary key).
My question is, is propagation PROPAGATION_REQUIRES_NEW the right one for Transaction which runs in method getNextId()?
Yes. If you use PROPAGATION.REQUIRES_NEW the current transaction, if any, will be paused and a new one will be created. Once the execution of that transaction completes, it will be committed if no error occurs and the outer transaction will be resumed. Regardless of what happens next, the inner transaction is committed and is now independent.
In your code above, make sure that your transactionTemplate has been configured with the right propagation mode, that is:
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
Related
What is the difference between method with #Transactional(propagation = Propagation.SUPPORTS) vs having no #Transactional annotation at all?
#Transactional(propagation = Propagation.SUPPORTS)
public void MyMethod()
vs
public void MyMethod()
Wouldn't the one without the annotation also use a transaction if one is already open, otherwise continue without any transaction?
There is a small difference. suppose we have two methods a() and b(), and a() is going to call b(). a itself is transnational and its propagation level is Propagation.REQUIRED but b one time is annotated with #Transactional(propagation = Propagation.SUPPORTS) and one time is not with annotation.
case 1:
#Transactional
public void a() {
for (int i = 0; i < 10; i++) {
try {
productRepository.b(i, "product " + i);
} catch (RuntimeException ex){
// do nothing
}
}
}
public void b(int id, String name) {
if(id > 5)
throw new RuntimeException();
String sql = "INSERT INTO test_table VALUES(?, ?)";
template.update(sql, id, name);
}
in case 1 we have aspect() -> a() -> b() and you are able to prevent RuntimeException to reach aspect and aspect inspects the transaction to see whether it is marked to rollback, so aspect consider this transaction successful and you can see we have this result in our database
0,product 0
1,product 1
2,product 2
3,product 3
4,product 4
5,product 5
even though multiple exceptions were thrown but we were able to commit the operations have been done so far.
now consider case 2:
#Transactional
public void a() {
for (int i = 0; i < 10; i++) {
try {
productRepository.b(i, "product " + i);
} catch (RuntimeException ex){
// do nothing
}
}
}
#Transactional(propagation = Propagation.SUPPORTS)
public void b(int id, String name) {
if(id > 5)
throw new RuntimeException();
String sql = "INSERT INTO test_table VALUES(?, ?)";
template.update(sql, id, name);
}
with propagation = Propagation.SUPPORTS if a transaction already exists it's going to use it. so if you throw an exception in b it is going to mark the same transaction to rollback and even if you use the try/catch block to prevent the RuntimeException to reach aspect in a() the transaction is already marked to rollback in b and you see no result in your database. aspect() -> a() -> aspect() -> b()
credit goes to Laurentiu Spilca, see this and read the comment section
From your link, it states that Propagation.SUPPORTS might have impact on
synchronization:
SUPPORTS is slightly different from no transaction at all, as it defines a transaction scope that synchronization will apply for. As a consequence, the same resources (JDBC Connection, Hibernate Session, etc) will be shared for the entire specified scope
Yes one without any annotation will use existing transaction if one is already open but what if there is not any won't you want to revert the changes if your transaction fails.
Propagation has also other attribute has well in case you method is not dependant of parent transaction and runs in individual transaction scope, probably you can go for REQUIRES_NEW this way you can have independent transaction for your method.
I use Spring + Hibernate + JPA
I need to handle the list of customers by inserting their orders.
Here is the Unit of work :
for(Customer customer: CustomerList) {
List<Order> orderList = customer.getOrders();
for(Order order: OrderList) {
//1. Insert order into ORDER table
//If insert fails due to Duplicate key then no rollback and I follow steps 2 & 3.
//If insert fails due to any reason except duplicate key then rollback all the previous transactions
//2. select the order record (If any issue during selection then rollbackall the previous transactions)
//3. Update the order If status of the order is different from that in DB (If any issue during update then rollback all the previous transactions)
}
// 4. Update Customer record into CUSTOMER table (If any issue during update then rollback all the previous transactions)
}
Commit is required when all the orders and customer db processes are ok.
Insert order
1.a If duplicate order do not roll back. But select that order from table and update if the status of the order is different in the req compared to the one in db
1.b If any other error during inserting of ORDER then roll back
1.c If no error then proceed inserting orders of the particular Customer
Once orders of the particular Customer is done, then update Customer table
Loop continues..
While handling point 1, 2 and 3 If everything is ok, then commit is required.
If any issues in the middle then all transactions are rolled back
Controller --> Facade layer --> Service --> Repository/Dao
Facade:
#Autowired
MyServiceBean serviceBean;
#Transactional(noRollbackFor = {EntityExistsException.class, PersistException.class, ConstraintViolationException.class, DataIntegrityViolationException.class})
#override
public void facadeMethod(MyReq req) {
List<Customer> custList = req.getCustomers():
for(Customer customer: CustList) {
List<Order> orderList = customer.getOrders();
for(Order order: orderList) {
String dbAction = "";
try {
dbAction = serviceBean.insertOrder(order);
} catch(Exception e) {
// log exception and roll back completely
}
if("handleDupl".equalsTo(dbAction) {
serviceBean.handleDuplOrder(order);
}
}
myService.updateCustomer(customer);
}
}
Service:
#Autowired
MyRepository repo;
#Transactional(propagation = propagation.REQUIRES_NEW)
#override
public String inserOrder() {
String dbAction = "";
try {
repo.insertOrderDao(order);
} catch(all duplicate key exceptions like entityExist, persist, ConstraintVioaltion, DataIntegrity e) {
dbAction = "handleDuplOrder";
} catch(all other Exception except duplicate key e) {
// roll back and throw exception to Facade layer
}
return dbAction;
}
#Transactional(propagation = propagation.REQUIRES_NEW)
#override
public void handleDuplOrder(Order order) {
try {
repo.selectOrderDao(order);
repo.updateOrder(order);
} catch(Exception e) {
// roll back and throw exception to Facade layer
}
}
Repository:
#PersistentContext(unitNmae = "MY_SHCEMA")
EntityManager entityManager;
#Override
public void insertOrderDao(Order order) {
entityManager.persist(order);
entityManager.flush();
}
Problem:
When I send req with One customer who has single order, where the order is duplicate, I see PersistException is caught inside Service method and when it exists from Service method it also throws TransactionSystemException(nested exception is RollbackException: Transaction is marked as rollback only, could not commit JPA transaction) is thrown to Facade layer irrespective of the how I suppress the exception in inner transaction.
Kindly advice If I can achieve Unit of work commit or rollback in this way.
Expected:
I want Spring's #Transactional to ignore Duplicate key exceptions by not rolling back and not affecting the next transaction.
Your outer transaction still fail because you throw ApplicationDuplOrderException() inside your service.
You should setup your services like below:
#Transactional
#Override
public void facadeMethod(MyReq req) {
List<Customer> custList = req.getCustomers():
for(Customer customer: CustList) {
List<Order> orderList = customer.getOrders();
for(Order order: orderList) {
try {
myService.insertOrder(order);
} catch(Exception e) {
// log exception and roll back completely
throw e; // important, you must rethrow
}
}
myService.updateCustomer(customer);
}
}
#Transactional(propagation = propagation.REQUIRES_NEW)
#Override
public void inserOrder() {
try {
repo.insertOrderDao(order);
} catch(all duplicate key exceptions like entityExist, persist, ConstraintVioaltion, DataIntegrity e) {
log.error(xxx); // instead of throwing
} catch(all other Exception except duplicate key e) {
throw e;
}
}
I have a situation in Spring where I am writing data to some external source,
Now before writing the data to the external source,
i take a lock
read the object
perform some operation
write the oject back. and unlock the object.
Below piece of code explaing roughly how i do it.
//Code begins here
Lock lck = new ReentrantLock();
public void manipulateData(){
lck.lock();
//Object obj = read the data
//modify it
Write(obj)
lck.unlock();
}
//Code End here
Now in a multi-threaded environment what currently happens is after write call I am calling unlock but my transaction is not committed until my function execution completes. However since I am calling unlock. Other thread gets the lock and read the data which is actually in correct.
So I want something like the lock should be obtained by other thread only when the transaction commit.
Also I cannot use programmatic transaction.
You can consider extracting the code that reads the object and modifies it to a different method (annotated with #Transactional). The lock should then be taken just before invoking the method and released after the method returns. Something like this:
#Autowired
private Dao dao;
public void manipulateData(){
lck.lock();
dao.update(myObj);
lck.unlock();
}
class Dao {
#Transactional
public void update(MyObject obj) {
//read and modify
}
}
You can implement an Aspect (AOP) similar to this:
First create a proprietary Transactional similar to this:
#Target({ ElementType.METHOD })
#Retention(RetentionPolicy.RUNTIME)
public #interface LockingTransactional {
}
Then the aspect's interesting code should be similar to this:
...
private final Lock lck = new ReentrantLock();
...
#Around("#annotation(<FULL-PACKAGE-NAME-HERE>.LockingTransactional)")
public Object intercept(ProceedingJoinPoint pjp) throws Throwable {
try {
lck.lock();
TransactionStatus status = createTransaction(); // create the transaction "manually"
Object result;
try {
result = pjp.proceed();
} catch (Throwable t) {
txManager.rollback(status);
throw t;
}
txManager.commit(status);
return result;
} finally {
lck.unlock();
}
}
...
private TransactionStatus createTransaction() {
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
def.setIsolationLevel(<YOUR-LEVEL-HERE>);
def.setPropagationBehavior(<YOUR-PROPAGATION-HERE>);
return txManager.getTransaction(definition);
}
...
Is that possible to perform commit in the method that is marked as Spring's #Transactional?
#PersistenceContext
private EntityManager em;
#Transactional(propagation = Propagation.REQUIRED)
public void saveMembersWithMultipleCommits(List<Member> members)
throws HibernateException
{
Iterator<Member> it = members.iterator();
while (it.hasNext())
{
while (it.hasNext())
{
Member wsBean = it.next();
em.persist(wsBean); // overall commit will be made after method exit
log.info("Webservices record " + wsBean + " saved. " + i++);
}
}
}
I would like to have commit to DB after say each 500 items. Is that possible with aforementioned context?
No, you need to do it programatically using, for instance, the TransactionTemplate API. Read more here.
It would look something like
while (it.hasNext())
{
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
protected void doInTransactionWithoutResult(TransactionStatus status) {
int counter = 0;
while (it.hasNext() && counter++ < 500) {
Member wsBean = it.next();
em.persist(wsBean);
log.info("Webservices record " + wsBean + " saved. " + i++);
}
}
);
}
Your question suggests that you have misplaced your transaction boundary.
You can move the persist call into a private method and make that method transactional instead of the outer one. This method could accept 500 members at a time and then will commit when it exits.
If you are looking forward to committing transactionally inside your other transaction, you might need to use #Transactional (propagation = Propagation.REQUIRES_NEW)
Alternate strategy is you create a method in DAO and mark it #Transactional. This method will do bulk update(for eg 500 nos). So you can have a method with code
#Transactional
public void mybatchUpdateMethod(){
StatelessSession session = this.hibernateTemplate.getSessionFactory()
.openStatelessSession();
Transaction transaction = null;
Long entryCounter = 0L;
PreparedStatement batchUpdate = null;
try {
transaction = session.beginTransaction();
batchUpdate = session.connection().prepareStatement(insertSql);
for (BatchSnapshotEntry entry : entries) {
entry.addEntry(batchUpdate);
batchUpdate.addBatch();
if (++entryCounter == 500) {
// Reached limit for uncommitted entries, so commit
batchUpdate.executeBatch();
}
}
batchUpdate.executeBatch();
batchUpdate.close();
batchUpdate = null;
}
catch (HibernateException ex) {
transaction.rollback();
transaction = null;
}
}
Every time you call this method, it will commit after 500 inserts/updates
I have spring junit test consisting of two sequential transactions which are propagated as REQUIRES_NEW:
public class ContractServiceTest extends AbstractIntegrationTest {
#Autowired
private PersistenceManagerHibernate persistenceManagerHibernate;
#Autowired
private ContractService contractService;
#Autowired
private EntityChangeService entityChangeService;
#Resource
private AddServiceService addService;
#Autowired
private ReferenceBookService refService;
#Autowired
private PropertyService propertyService;
#Autowired
private HibernateTransactionManager transactionManager;
#Test
public void testContractDeletes() {
Long contractId = 1L;
final Contract contract = createTestDetachedContract(contractId, PropertyServiceTest.createManaged(propertyService, refService), refService);
ensureContractCreated(contract);
deleteTransactional(contract);
Assert.assertEquals(1, entityChangeService.findByPaginationOrderByUpdateDate(Contract.class.getName(), contract.getId().toString(), null, 0, 30).size());
}
#Test
#Ignore
public void testContractCreates() {
Long contractId = 1L;
final Contract contract = createTestDetachedContract(contractId, PropertyServiceTest.createManaged(propertyService, refService), refService);
ensureContractDeleted(contract);
createContractTransactional(contract);
Assert.assertEquals(1, entityChangeService.findByPaginationOrderByUpdateDate(Contract.class.getName(), contract.getId().toString(), null, 0, 30).size());
}
private void ensureContractCreated(Contract contract) {
if (persistenceManagerHibernate.isCreated(Contract.class, contract.getId())) {
return;
}
createContractTransactional(contract);
}
private void deleteTransactional(final Contract contract) {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setPropagationBehavior(Propagation.REQUIRES_NEW.value());
transactionTemplate.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
try {
contractService.delete(contract);
} catch (Exception e) {
toString();
}
return null;
}
});
}
private void createContractTransactional(final Contract contract) {
TransactionTemplate transactionTemplate2 = new TransactionTemplate(transactionManager);
transactionTemplate2.setPropagationBehavior(Propagation.REQUIRES_NEW.value());
transactionTemplate2.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
contractService.create(contract);
return null;
}
});
}
private void ensureContractDeleted(final Contract contract) {
if (!persistenceManagerHibernate.isCreated(Contract.class, contract.getId())) {
return;
}
deleteTransactional(contract);
}
public static Contract createTestDetachedContract(Long contractId, Property property, ReferenceBookService refService) {
Contract contract1 = new Contract();
contract1.setId(contractId);
contract1.setName("test name");
contract1.setProperty(property);
contract1.setNumber("10");
contract1.setType(refService.get(ContractType.class, 1L));
contract1.setStatus(refService.get(ContractStatus.class, 1L));
contract1.setCreated(new Date());
contract1.setCurrencyRate(new BigDecimal(10));
contract1.setInitialSum(new BigDecimal(10));
contract1.setSum(new BigDecimal(10));
return contract1;
}
}
Test freezes at commiting of transaction with insert sql statement, which is:
private void createContractTransactional(final Contract contract) {
TransactionTemplate transactionTemplate2 = new TransactionTemplate(transactionManager);
transactionTemplate2.setPropagationBehavior(Propagation.REQUIRES_NEW.value());
transactionTemplate2.execute(new TransactionCallback() {
public Object doInTransaction(TransactionStatus status) {
contractService.create(contract);
return null;
}
});
}
Why does that happening(debugger stops at some oracle code without source code provided) and how to write spring junit test with two sequential transactions correctly?
It sounds like the test is creating a deadlock on the Contract table in your database. The root cause of this is most likely the use of the REQUIRES_NEW propagation level, as detailed in this question. The important part is this:
PROPAGATION_REQUIRES_NEW starts a new, independent "inner" transaction for the given scope. This transaction will be committed or rolled back completely independent from the outer transaction, having its own isolation scope, its own set of locks, etc. The outer transaction will get suspended at the beginning of the inner one, and resumed once the inner one has completed
The createContractTransactional method is trying to insert into the Contract table but something earlier in the test must be holding a lock on it, I'm guessing it's the call to persistenceManagerHibernate.isCreated(Contract.class, contract.getId()). Whatever the cause, you have two independent transactions that are locking on the same table, i.e. a deadlock.
Try setting the propagation level the transactions in your test to REQUIRED, which is the default setting. This creates a new transaction if there isn't one already, otherwise the current transaction is used. That should make your test execute in a single transaction and so you shouldn't get a deadlock. Once that is working then you may want to read the spring documentation on its propagation levels to make sure that REQUIRED is the right level for your needs.