I need some clarification regarding Spring transation.
In ClassA I am creating a declaritive transaction. From ClassA I am calling method1() of ClassB.
ClassB is having #Transactional(propagation = Propagation.REQUIRED) annotation in classs level.
In method1() I am doing a save operation.
My question is, when that data will be committed ? Will it be committed while control will come out from
method1()? or in ClassA where we are committing the transaction manually ?.
Code:
ClassA
class ClassA {
void myMethod() {
TransactionStatus status = null;
DefaultTransactionDefinition def =
new DefaultTransactionDefinition(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
def.setTimeout(contentFileProcessingConfigBean.getFileCopyTransactionTimeout());
status = transactionManager.getTransaction(def);
b.method1();
if (status != null) {
transactionManager.commit(status);
}
}
}
ClassB
#Transactional(propagation = Propagation.REQUIRED)
class ClassB {
void method1() {
//doing save operation
}
}
Probably after B.method1() finishes the aspect which is wrapping the call to #Transactional (visible in the stack trace) will commit the transaction. However this will depend on:
Are A and B are both Spring beans?
Is the same transactionManager bean used by transactions in A and B?
Maybe if A is written using TransactionTemplate instead of plain TransactionManager object.
Mixing the manual and declarative transaction management is not recommended as you have to guess. I guess it will come to transactionManager which you are not showing.
Related
In Spring, beans are encapsulated by a proxy object and when called from outer object the proxy object's method is also called. By using this trick transaction management is performed in proxy object's methods.
But when you are calling a method in the same bean and if you want the called method to be run in another transaction with the caller method it is not possible. Since the method calls in the same bean does not pass through the proxy object which performs transaction operations.
In order to solve this, self-injection of the bean inside itself is proposed.
like this
#Service
public class MyService{
#Autowired
public MyService myService;
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void method1(){
method2();// runs in the same transaction with method1
myService.method2();// runs in separate transaction from method1
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void method2(){
}
}
I want to ask whether self-injection leads to memory leakage.
Outer bean MyService includes injected myService and myService should include attribute of type MyService, which should include attribute of type MyService ....
The service is not copied, it is only a reference. But I'd be wary of such a design: it creates a circular reference and the object graph is impossible to create at once. You first need to create the incomplete service, then set its field. Furthermore, it is kind of confusing and surprising to find such a dependency to self
I'd propose a different solution:
#Component
public class WithTransaction {
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void requiresNew(final Runnable runnable) {
runnable.run();
}
}
#Service
public class MyService{
private final WithTransaction withTransaction;
#Autowired
public MyService(final WithTransaction withTransaction) {
this.withTransaction = withTransaction;
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void method1(){
method2();// runs in the same transaction with method1
withTransaction.requiresNew(this::method2); // runs in separate transaction from method1
}
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void method2(){
}
}
And if you can avoid it: don't call any public methods from your service on the service itself. Split the service into smaller components so that each "public" call must go through the proxy.
Given
class CallingClass {
#Autowired SomeService someService;
doit() {
someService.readOnlyCall();
}
}
#Transactional(readOnly = true)
class SomeService {
#Autowired AnotherService anotherService;
readOnlyCall() {
// logic which may then do...
anotherService.writeCall();
}
}
class AnotherService {
#Transactional // not readonly
writeCall() {
// save some entity
}
}
Will the entity be committed to the database when the writeCall method exits or when the readOnlyCall method exits?
And how can you modify it so it commits after the writeCall method?
The entity will not be committed to the database in both method exits. It is because
Your SomeService class has read-only (which you have specify). It will set FlushMode.NEVER which to prevent commit the transaction
Your SomeService class and AnotherService class has REQUIRED propagation type(which is by default and you have not specify other type for both class). It will create a new transaction if doesn't exist but will use current transaction if there is.
In this case the SomeService class begin transaction with read-only and AnotherService class will participates in the existing read-only transaction. Therefore, no entity will be committed.
To make it commits after the writeCall method, just add setting on writeCall method as below:
#Transactional (readOnly = false, propagation =Propagation.REQUIRES_NEW) // not readonly
writeCall() {
// save some entity
}
These settings have precedence for writeCall method. It will suspend current read-only transaction and create a new read/write transaction. Therefore, it will commits after the writeCall method complete transaction.
I have a stateless bean that observes an event and saves a record:
#Stateless
public class Manager {
#Inject
Repository repository;
Manager() {}
#Inject
Manager(Repository repository) {
this.repository = repository;
}
public void EventHandler(#Observes MyEvent myEvent) {
save(event.obj);
}
private save(Object object) {
repository.add(object);
}
}
My repository is like this:
#Stateless
public class Repository {
#PersistenceContext
EntityManager em;
Repository() {}
public void add(Object object) {
em.persist(object);
// em.flush(); <---
}
}
It doesn’t work unless I uncomment the line
The thing I don’t understand is why do I need to flush! Shouldn’t the transaction commit automatically?
Could it be that I have an EJB container and the cdi transaction started by Observes never actually ends but do something weird? Or it ends but doesn’t commit because he doesn’t know about EJB?
persist() is not meant to insert the row immediately, but rather at flush time, which is usually immediately before transaction commit.
However, if this is not what you are asking, but the row is not inserted even after the transaction has ended, it may be that flush mode for that transaction is set to manual.
I have 3 Spring Components calling first->second->third.
If I only have #Transactional annotation on first and third will transaction be propagated properly?
#Component public class C1 {
#Autowired C2 c2;
#Transactional public method1() {
...
c2.method2();
}
}
#Component public class C2 {
#Autowired C3 c3;
public method2() {
...
c3.method3();
}
}
#Component public class C3 {
#Transactional public method3() {
...
}
}
Yes, Transaction support is thread-bound. When method2() is executing, it does so in the same Thread and therefore has access to the current Transaction. The same goes for method3().
#Transactional works as below
if its marked on any method then we can specify an attribute called propagation whose value could be Required by default or RequiredNew.
sample is
#Transactional(readOnly = true,propagation=Propagation.REQUIRES_NEW)
public void somemethod(){
anotherMethod();
}
if the attribute is just "Required" which is there by default then it maintains a single transaction for whole propogation of all methods no matter whether you declare #transactional in each method or not.
so all methods execution comes under same transaction and if any rollback happens in last method it affects til the method called.
if the attribute is set to "RequiredNew" then each method runs in their own tranactions so rollback in one method does not rollback other methods tranactions.
hope you are clear now
I am using JPA with Spring. If I were to let Spring handle the transactions, then this is what my Service layer would look like assuming the EntityManager has been properly injected into the DAOs:
MyService {
#Transactional
public void myMethod() {
myDaoA.doSomething();
myDaoB.doSomething();
}
}
However, if I were to do transactions manually, I have to make sure to pass that instance of EntityManager into each of the DAOs within a transaction. Any idea how can this be better refactored? I fee ugly doing new MyDaoA(em) or passing em into each DAO method like doSomething(em).
MyService {
private EntityManagerFactory emf;
public void myMethod() {
EntityManager em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
MyDaoA myDaoA = new MyDaoA(em);
MyDaoB myDaoB = new MyDaoB(em);
try {
tx.begin();
myDaoA.doSomething();
myDaoB.doSomething();
tx.commit();
} catch(Exception e) {
tx.rollback();
}
}
}
However, if I were to do transactions
manually, I have to make sure to pass
that instance of EntityManager into
each of the DAOs within a transaction.
This is where you are wrong. From the Spring Reference, JPA section:
The main problem with such a DAO is
that it always creates a new
EntityManager through the factory. You
can avoid this by requesting a
transactional EntityManager (also
called "shared EntityManager" because
it is a shared, thread-safe proxy for
the actual transactional
EntityManager) to be injected instead
of the factory:
public class ProductDaoImpl implements ProductDao {
#PersistenceContext
private EntityManager em;
public Collection loadProductsByCategory(String category) {
Query query = em.createQuery(
"from Product as p where p.category = :category");
query.setParameter("category", category);
return query.getResultList();
}
}
The #PersistenceContext annotation has
an optional attribute type, which
defaults to
PersistenceContextType.TRANSACTION.
This default is what you need to
receive a shared EntityManager proxy.
add this to your spring config
<bean p:entityManagerFactory-ref="emf" class='org.springframework.orm.jpa.support.SharedEntityManagerBean' />
now you can #Autowired EntityManager inside your dao
for the transaction management, since you already using spring, and #Transactional annotation, i assume you already have one transaction manager declared in your spring.xml
so using spring's transaction management
as
transactionStatus = platformTransactionManager.getTransaction(new DefaultTransactionDefinition());
// do your work here
platformTransactionManager.commit(transactionStatus );
Shot in the dark a bit I guess, but do you know you can do:
TransactionInterceptor.currentTransactionStatus().setRollbackOnly();
That usually eliminates the majority of cases where you would want/need to use programmatic transactions in a system that otherwise has declarative transactions.