I developed a typical enterprise application that is responsible for provisioning customer to a 3rd party system. This system has a limitation, that only one thread can work on a certain customer. So we added a simple locking mechanism that consists of #Singleton which contains a Set of customerIds currently in progress. Whenever a new request comes for provisioning, it first checks this Set. If cusotomerId is present, it waits otherwise it adds it to the Set and goes into processing.
Recently it was decided, that this application will be deployed in cluster which means that this locking approach is no longer valid. We came up with a solution to use DB for locking. We created a table with single column that will contain customerIds (it also has a unique constraint). When a new provisioning request comes we start a transaction and try and lock the row with customerId with SELECT FOR UPDATE (if customerId does not exist yet, we insert it). After that we start provisioning customer and when finished, we commit transaction.
Concept works but I have problems with transactions. Currently we have a class CustomerLock with add() and remove() methods that take care of adding and removing customerIds from Set. I wanted to convert this class to a stateless EJB that has bean-managed transactions. add() method would start a transaction and lock the row while remove() method would commit transaction and thus unlocked the row. But it seems that start and end of transaction has to happen in the same method. Is there a way to use the approach I described or do I have to modify the logic so the transaction starts and ends in the same method?
CustomerLock class:
#Stateless
#TransactionManagement(TransactionManagementType.BEAN)
public class CustomerLock {
#Resource
private UserTransaction tx;
public void add(String customerId) throws Exception {
try {
tx.begin();
dsApi.lock()
} catch (Exception e) {
throw e;
}
}
public void remove(String customerId) throws Exception {
try {
tx.commit();
} catch (Exception e) {
throw e
}
}
}
CustomerProvisioner class excerpt:
public abstract class CustomerProvisioner {
...
public void execute(String customerId) {
try {
customerLock.add(customerId);
processing....
customerLock.remove(customerId);
} catch (Exception e) {
logger.error("Error", e);
}
}
...
}
StandardCustomerProvisioner class:
#Stateless
public class StandardCustomerProvisioner extends CustomerProvisioner {
...
public void provision(String customerId) {
// do some business logic
super.execute(customerId);
}
}
As #Gimby noted, you should not mix container-managed and bean-managed transactions. Since your StandardCustomerProvisioner has no annotation like "#TransactionManagement(TransactionManagementType.BEAN)" - it uses container-managed transactions, and REQUIRED by default.
You have 2 options to make it work:
1) To remove "#TransactionManagement(TransactionManagementType.BEAN)" with UserTransaction calls and run CMT
2) Add this annotation ("#TransactionManagement(TransactionManagementType.BEAN)") to StandardCustomerProvisioner and use transaction markup calls from this method, so all the invoked methods use the same transactional context. Markup calls from CustomerLock should be removed anyway.
Related
I am struggling with handling exceptions during transactional methods with Spring #Transactional annotations. Currently, when my Dao insert method throws a unique contraint violation, I want to perform a read instead to get the existing item. However, this fails because the UniqueIdViolation causes the transaction to fail. How can I refactor this to work as intended whilst not doing a dodgy boolean return on the insert?
I have a DAO with an insert method, e.g.:
public class ItemDao {
public void insertItem(Item item) {
// insert into db via jooq
}
public Item fetch(String id) {
// fetch from db
}
...
}
If it violates a unique constraint I have an aspect that handles the DataAccessException and rethrows it as a UniqueIdException.
I have a service method that takes in some information and creates an Item, before returning that item.
If the dao throws a UniqueIdException, I want the service to catch this error and then call itemDao.find() to get the existing Item and return it.
public class MyServiceImpl implements MyService {
#Transactional(isolation = SERIALIZABLE)
public Item createItem(...) {
Item item = new Item(...);
try {
itemDao.insert(item);
return item;
} catch (UniqueIdException ex) {
return itemDao.find(item.getId());
}
}
}
The issue here is when itemDao.insert(item) throws an exception this causes the transaction to fail and therefore itemDao.find() doesn't run.
#Transactional is setup to rollback, by default, only when an unchecked exception is thrown.
Since DataAccessException is unchecked exception your transaction will be rolled backed.
A better strategy will be to first call fetch() if returned null then call insertItem
I have the following application setup:
#SpringBootApplication
#EnableTransactionManagement
public class MyApp extends SpringBootServletInitializer {
...
}
with a class which has the following:
public class DoStaff {
public void doStaffOnAll(List<MyObject> myObjects) {
for (int i=0; i<myObjects.size(); i++) {
try {
doStaffOnSingle(myObjects.get(i), i);
} catch (Exception e) {
e.printStrackTrace();
}
}
}
#Transactional
public void doStaffOnSingle(MyObject myObject, int i) {
repository.save(myObject);
if (i%2==0) {
throw new RuntimeException();
}
}
}
So if I call DoStaff.doStaffOnAll with a list of MyObjects, the code saves all element from the list but also throws a runtime exception for every second element.
Since the doStaffOnSingle has #Transactional annotation, I would expect that every second element will be rolled back.
But if I run this code, every element is saved in the DB successfully. Why is that? What am I doing wrong?
Quoting Spring Documentation:
In proxy mode (which is the default), only external method calls coming in through the proxy are intercepted. This means that self-invocation (in effect, a method within the target object calling another method of the target object) does not lead to an actual transaction at runtime even if the invoked method is marked with #Transactional. Also, the proxy must be fully initialized to provide the expected behavior, so you should not rely on this feature in your initialization code (that is, #PostConstruct).
Move the doStaffOnAll() to a different Spring component, and it'll work.
Or change to aspectj mode.
I would recommend moving the method, and design the code so transaction boundaries are clear and distinct, i.e. all public methods on the class starts a transaction, or no methods on the class starts a transaction.
It should always be very clear where your transaction boundaries are, e.g. in a layered design, you would normally make the #Service layer also be the transaction layer, i.e. any call from a higher layer to the service layer is an atomic transaction.
#Transactional annotation is able to do the magic because of a proxy object.
Since you call the method directly you don't get that magic. In doStaffOnAll method you are directly invoking doStaffOnSingle method. So, nothing of Transactional behaviour gets added.
Try invoking the method using self invocation.
#Service
public class DoStaff {
#Autowired
private DoStaff doStaff;
public void doStaffOnAll(List<MyObject> myObjects) {
for (int i=0; i<myObjects.size(); i++) {
doStaff.doStaffOnSingle(..) // invoke like this
}
}
#Transactional
public void doStaffOnSingle(MyObject myObject, int i) {
}
}
Since the doStaffOnSingle has #Transactional annotation, I would
expect that every second element will be rolled back.
The default Transactional mode will commit everything or nothing. I think you would want to use REQUIRES_NEW Propagation.
Look here for supported propagation types.
https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/transaction/annotation/Propagation.html#REQUIRED
I have a system based on hibernate 4. I have an unique constraint in a table and need to handle it the following way:
try{
getMyService().create(myobj);
}catch(PersistenceException p){
//constraint fails
myobj.setConstraintColumn("new non unique value");
getMyService().create(myobj);//should save it
}
unfortunately I can't change the design of the system so I need just to figure it out this way.
EDIT
I get the following exception:
org.hibernate.AssertionFailure: null id in entry (don't flush the Session after an exception occurs)
The code of create method:
public E create(E entity) {
entityManager.persist(entity);
entityManager.flush();
entityManager.refresh(entity);
return entity;
}
It is not clear where your transaction boundaries are.
When the exception is thrown, you will need to:
1) Ensure that the first transaction is closed (it should be, but not sure - see if you get a nested transaction trying #2 alone)
2) begin a new transaction before you are able to persist/flush again (and subsequently commit that).
I finally figured out the issue. So, let me explain one by one.
First of all, take a look at
Propagation.REQUIRES_NEW does not create a new transaction in Spring with JPA
Then, for example we have a class and method like:
public class MyClass{
#Transactional
public void myMethod(){
....
}
}
So, first of all lets consider that myMethod is in it's own transaction completely, because transactions are AOP based, and it will be committed when appropriate aspect will fire, but only after method completes, throws exception, etc. So we can't partially commit, partially rollback, rollback incompletely,etc. So, we need to do the following:
Start big outer transaction
Start a new nested transaction, try to insert a record.
If nested transaction will fail, it will be rolled back, but the outer one will still be running.
If the first nested transaction failed, then start a new nested transaction and insert a record with new data, which will prevent ConstaintViolationException from being thrown.
So, in this case, we create a class:
public class ServiceHelper{
#Transational(proparation = **Propagation.REQUIRED_NEW**)
public void tryConstraint throws MyConstraintException{
try{
//insert
}catch(ConstraintViolationException e){
throw new MyConstraintException(e);
}catch(Exception ex){
throw new Exception(ex);
}
}
#Transational(proparation = **Propagation.REQUIRED_NEW**)
public void insertWithNoConflict throws Exception {
//Set new data
//insert, if still CVE or anything other, just throw it , or leave it for unchecked exceptions then
}
}
And our service:
public class MyService{
#Autowired
private ServiceHelper serviceHelper;
#Transactional(propagation = **Propagation.REGUIRED_NEW**)
public void createWithCheck(){
try{
serviceHelper.tryConstraint();
}catch(MyConstraintException e){
serviceHelper.insertWithNoConflict();
}
}
}
But there is still a weird situation because I need to use MyService methods for records creation in ServiceHelper, but I can't obtain them there because it will cause circular injections, so I have to get them via services factory like:
MyService service = (MyService)ServicesFactory.getInstance().getBean(MyService.BEAN_ID)
And I don't like it. But this approach works, I checked it today.
We should know 2 things: first of all we can't do anything with a transaction inside a method, we can't start there any new transaction, etc. A transaction context is relevant to method completely, it will still be there before a method ends. And the second thing is required_new doesn't work when we launch the method from the method of the same proxy class.
I am attempting to write to multiple databases using hibernate. I have encapsulated write and read/write sessions within a single session object. However, when I go to save I get a lot of errors that the objects are already associated with another session: "Illegal attempt to associate a collection with two open sessions"
Here is my code:
public class MultiSessionObject implements Session {
private Session writeOnlySession;
private Session readWriteSession;
#Override
public void saveOrUpdate(Object arg0) throws HibernateException {
readWriteSession.saveOrUpdate(arg0);
writeOnlySession.saveOrUpdate(arg0);
}
}
I have tried evicting the object and flushing; however, that causes problems with "Row was updated or deleted by another transaction"... even though both sessions point to different databases.
public class MultiSessionObject implements Session {
private Session writeOnlySession;
private Session readWriteSession;
#Override
public void saveOrUpdate(Object arg0) throws HibernateException {
readWriteSession.saveOrUpdate(arg0);
readWriteSession.flush();
readWriteSession.evict(arg0);
writeOnlySession.saveOrUpdate(arg0);
writeOnlySession.flush();
writeOnlySession.evict(arg0);
}
}
In addition to the above, I have also attempted using the replicate functionality of hibernate. This was also unsuccessful without errors.
Has anyone successfully saved an object to two databases that have the same schema?
The saveOrUpdate tries to reattach a given Entity to the current running Session, so Proxies (LAZY associations) are bound to the Hibernate Session. Try using merge instead of saveOrUpdate, because merge simply copies a detached entity state to a newly retrieved managed entity. This way, the supplied arguments never gets attached to a Session.
Another problem is Transaction Management. If you use Thread-bound Transaction, then you need two explicit transactions if you want to update two DataSources from the same Thread.
Try to set the transaction boundaries explicitly too:
public class MultiSessionObject implements Session {
private Session writeOnlySession;
private Session readWriteSession;
#Override
public void saveOrUpdate(Object arg0) throws HibernateException {
Transaction readWriteSessionTx = null;
try {
readWriteSessionTx = readWriteSession.beginTransaction();
readWriteSession.merge(arg0);
readWriteSessionTx.commit();
} catch (RuntimeException e) {
if ( readWriteSessionTx != null && readWriteSessionTx.isActive() )
readWriteSessionTx.rollback();
throw e;
}
Transaction writeOnlySessionTx = null;
try {
writeOnlySessionTx = writeOnlySession.beginTransaction();
writeOnlySession.merge(arg0);
writeOnlySessionTx.commit();
} catch (RuntimeException e) {
if ( writeOnlySessionTx != null && writeOnlySessionTx.isActive() )
writeOnlySessionTx.rollback();
throw e;
}
}
}
As mentioned in other answers, if you are using Session then you probably need to separate the 2 updates and in two different transactions. The detached instance of entity (after evict) should be able to be reused in the second update operation.
Another approach is to use StatelessSession like this (I tried a simple program so had to handle the transactions. I assume you have to handle the transactions differently)
public static void main(final String[] args) throws Exception {
final StatelessSession session1 = HibernateUtil.getReadOnlySessionFactory().openStatelessSession();
final StatelessSession session2 = HibernateUtil.getReadWriteSessionFactory().openStatelessSession();
try {
Transaction transaction1 = session1.beginTransaction();
Transaction transaction2 = session2.beginTransaction();
ErrorLogEntity entity = (ErrorLogEntity) session1.get(ErrorLogEntity.class, 1);
entity.setArea("test");
session1.update(entity);
session2.update(entity);
transaction1.commit();
transaction2.commit();
System.out.println("Entry details: " + entity);
} finally {
session1.close();
session2.close();
HibernateUtil.getReadOnlySessionFactory().close();
HibernateUtil.getReadWriteSessionFactory().close();
}
}
The issue with StatelessSession is that it does not use any cache and does not support cascading of associated objects. You need to handle that manually.
Yeah,
The problem is exactly what it's telling you. The way to successfully achieve this is to treat it like 2 different things with 2 different commits.
Create a composite Dao. In it you have a
Collection<Dao>
Each of those Dao in the collection is just an instance of your existing code configured for 2 different data sources. Then, in your composite dao, when you call save, you actually independently save to both.
Out-of-band you said you it's best effort. So, that's easy enough. Use spring-retry to create a point cut around your individual dao save methods so that they try a few times. Eventually give up.
public interface Dao<T> {
void save(T type);
}
Create new instances of this using a applicationContext.xml where each instance points to a different database. While you're in there use spring-retry to play a retry point-cut around your save method. Go to the bottom for the application context example.
public class RealDao<T> implements Dao<T> {
#Autowired
private Session session;
#Override
public void save(T type) {
// save to DB here
}
}
The composite
public class CompositeDao<T> implements Dao<T> {
// these instances are actually of type RealDao<T>
private Set<Dao<T>> delegates;
public CompositeDao(Dao ... daos) {
this.delegates = new LinkedHashSet<>(Arrays.asList(daos));
}
#Override
public void save(T stuff) {
for (Dao<T> delegate : delegates) {
try {
delegate.save(stuff);
} catch (Exception e) {
// skip it. Best effort
}
}
}
}
Each 'stuff' is saved in it's own seperate session or not. As the session is on the 'RealDao' instances, then you know that, by the time the first completes it's totally saved or failed. Hibernate might want you to have a different ID for then so that hash/equals are different but I don't think so.
Recently, I've been working with Spring boot + spring data jpa + hibernate. I faced one problem with spring transactions. Here is my service class and two questions:
#Transactional
#Service
class MyService {
#Autowired
private MyRepository myRep;
public void method_A() {
try {
method_C();
.....
method_B();
} catch(Exception e) {}
}
public void method_B() {
Entity e = new Entity();
e.set(...);
myRep.save(e);
}
public void method_C() throws Exception {
....
}
}
1.If method method_C() throws an Exception and I want to catch it and log it, the transaction is not rollbacked in method method_B(), because the Exception does not reach Spring framework. So how should I do in order to catch Exceptions from method_C() and at the same time do not lose capability of method method_B() be rollbacked?
2.Consider new method method_A().
public void method_A() {
for(...) {
...
...
method_B();
}
}
I want invoke method_B() in a loop. If an exception occurs in a method_B() I want transaction of method_B() be rollbacked but method_A() should not exit and the loop should continue excuting. How can I achieve this?
I solved my 2 problems this way: created another #Service class and moved method_B() into it. I've annotated this class as #Transactional. Now the method method_A() looks like this:
public void method_A() {
for(...) {
...
try {
anotherService.method_B();
} catch (Exception e) {
logger.error(...);
}
}
}
If RuntimeException occurs in the method_B() method, the exception is propertly logged, transaction of method_B() is rollbacked and the loop continuous. Thanks everybody for responses.
Instead of throwing exceptions do the following. (return error code).
Update: I read your question after posting. if you call method_b from method_A both are under same transaction. Unfortunately you cannot rollback the method_b changes alone. Spring considers it as one transaction if they are all under one service class. (all methods).
One thing you can try is the following.
request to--> Controller() ---> (spring opens transaction) service_method_a(); (spring closes transaction)
Controller() ---> (spring opens transaction) service_method_c(); (spring closes transaction)
Controller() ---> (spring opens transaction) service_method_b(); (spring closes transaction)
return <--
I hope it makes sense
Each of your methods a,b,c throw exceptions if it likes to be rolledback.
Update:
another approach. This one is much better.
If each of your method are in a different service then you can use the following annotations of the spring to run each of the method in a different transaction boundaries
p v serviceA{
#transactional
method_a(){
serviceb.method_b();
}
}
p v serviceB{
#Transactional(propagation=Propagation.REQUIRED)
method_b(){
}
}
more on it here
Spring transactional story here . Read the points below this article. Those are most important when developing the spring transactional app.