I've a particular flow calls in my Spring app.
I've a repository, and I call the method sell() as first call.
#Service
#Transactional
public class ServiceRepositoryImpl implements ServiceRepository {
#Inject
private SellRepository sellRepository;
#Override
public long sell(long id)
....
....
....
Sell sell = new Sell();
...
sellRepository.save(sell);
}
and my SellService:
#Service
public class SellRepositoryImpl implements SellRepository {
#PersistenceContext
private EntityManager entityManager;
#Inject
SellHelperRepository sellHelperRepository;
#Inject
private TransactionTemplate transactionTemplate;
#Override
public <S extends Sell> S save(S sell) throws Exception {
transactionTemplate.execute(new TransactionCallback<S>() {
#Override
public S doInTransaction(TransactionStatus status) {
try {
...
entityManager.persist(sell);
} catch (Throwable e) {
log.error("", e);
status.setRollbackOnly();
}
return vendita;
}
});
sellHelperRepository.createTickets(sell.getId());
return vendita;
}
this is my sellHelperRepository:
#Service
#Transactional
public class SellHelperRepositoryImpl implements SellHelperRepository {
#PersistenceContext
private EntityManager entityManager;
#Override
#Async
public void createTickets(long sellID) {
Sell sell = entityManager.find(Sell.class, sellID);
try{
...
Ticket t = new Ticket();
ticketService.save(t);
}catch(Throwable e){
sell.setStatus("CANCELED");
}
}
and in the end my ticketService:
#Service
#Transactional
public class TicketRepositoryImpl implements TicketRepository {
#PersistenceContext
protected EntityManager entityManager;
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public Biglietto save(Ticket ticket) throws Exception {
entityManager.persist(ticket);
}
The problem is that in this chain of calls when I'm on SellRepositoryImpl, the object sell is not persisted asap I'm out of the transactiontemplate but it should be! So when I enter in SellHelperRepositoryImpl and I try to search the sell it doesn't find it!
This particular structure is needed because these methods are used also from others repository.
I created many junit tests case to check if all works fine; for the rest of tests all works fine, a part for this particular chain of calls.
I think I'm missing something....
Thanks
the problem with your code is that you are using the #Async on the createTickets method , which forces Spring to execute it in the different thread with the "fresh" transaction, so you need to flush/commit first transaction which was opened in the SellRepositoryImpl class.
So you can go in three ways
Remove the #Async from the createTickets method
Change the Transaction Isolation Level from DEFAULT to READ_UNCOMMITTED in SellHelperRepositoryImpl
#Service
#Transactional(isolation=Isolation.READ_UNCOMMITTED)
public class SellHelperRepositoryImpl implements SellHelperRepository {
#PersistenceContext
private EntityManager entityManager;
#Override
#Async
public void createTickets(long sellID) {
Sell sell = entityManager.find(Sell.class, sellID);
try{
...
Ticket t = new Ticket();
ticketService.save(t);
}catch(Throwable e){
sell.setStatus("CANCELED");
}
}
Flush the manually managed transaction in save(S sell), see following code snippet.
#Override
public <S extends Sell> S save(S sell) throws Exception {
transactionTemplate.execute(new TransactionCallback<S>() {
#Override
public S doInTransaction(TransactionStatus status) {
try {
...
entityManager.persist(sell);
entityManager.getTransaction().commit();
status.flush();
} catch (Throwable e) {
log.error("", e);
status.setRollbackOnly();
}
return vendita;
}
});
sellHelperRepository.createTickets(sell.getId());
return vendita;
}
Related
I have a service with a persistence setup using JPA, Hibernate and Guice (if it's useful, I'm not using Spring). This is the first, working version of my code:
public class BookDao {
#Inject
protected Provider<EntityManager> entityManagerProvider;
protected EntityManager getEntityManager() {
return entityManagerProvider.get();
}
#Transactional
public void persist(Book book) {
getEntityManager().persist(book);
}
}
public class MyAppModule extends AbstractModule {
#Override
protected void configure() {
initializePersistence();
}
private void initializePersistence() {
final JpaPersistModule jpaPersistModule = new JpaPersistModule("prod");
jpaPersistModule.properties(new Properties());
install(jpaPersistModule);
}
}
But now I need to configure multiple persistence units. I'm following the advice in this mailing list, and according to them, I should move my module logic to a private module. I did as suggested and created a second version of the same code, the changes are commented below:
#BindingAnnotation
#Retention(RetentionPolicy.RUNTIME)
#Target({ FIELD, PARAMETER, METHOD })
public #interface ProductionDataSource {} // defined this new annotation
public class BookDao {
#Inject
#ProductionDataSource // added the annotation here
protected Provider<EntityManager> entityManagerProvider;
protected EntityManager getEntityManager() {
return entityManagerProvider.get();
}
#Transactional
public void persist(Book book) throws Exception {
getEntityManager().persist(book);
}
}
public class MyAppModule extends PrivateModule { // module is now private
#Override
protected void configure() {
initializePersistence();
// expose the annotated entity manager
Provider<EntityManager> entityManagerProvider = binder().getProvider(EntityManager.class);
bind(EntityManager.class).annotatedWith(ProductionDataSource.class).toProvider(entityManagerProvider);
expose(EntityManager.class).annotatedWith(ProductionDataSource.class);
}
private void initializePersistence() {
JpaPersistModule jpaPersistModule = new JpaPersistModule("prod");
jpaPersistModule.properties(new Properties());
install(jpaPersistModule);
}
}
The newly annotated EntityManager is being correctly injected by Guice and is non-null, but here's the fun part: some of my unit tests started failing, for example:
class BookDaoTest {
private Injector injector;
private BookDao testee;
#BeforeEach
public void setup() {
injector = Guice.createInjector(new MyAppModule());
injector.injectMembers(this);
testee = injector.getInstance(BookDao.class);
}
#Test
public void testPersistBook() throws Exception {
// given
Book newBook = new Book();
assertNull(newBook.getId());
// when
newBook = testee.persist(newBook);
// then
assertNotNull(newBook.getId()); // works in the first version, fails in the second
}
}
In the first version of my code the last line above just works: the entity is persisted and has a new id. However, in the second version of my code (using a PrivateModule and exposing an annotated EntityManager from it) the persist() operation doesn't work anymore, the entity is without an id. What could be the problem? I didn't do any other configuration changes in my environment, and I don't see error messages in the logs. Let me know if you need more details.
It turns out that the problem was the #Transactional annotation. In the first version of my code, Guice automatically adds interceptors for managing the transaction. By doing a debug, I found out that before executing my persist(Book book) method, Guice calls the following method from the com.google.inject.internal.InterceptorStackCallback package:
public Object intercept(Object proxy, Method method, Object[] arguments, MethodProxy methodProxy)
In the second version of my code, when I exposed the persistence unit from a private module the above interceptor was no longer called, leaving my persist operation without transaction handling. This is a known issue and is by design.
As a workaround I had to implement transactions by hand, making my code more verbose. I also had to change the way the entity manager is injected. This solution worked for me:
public class BookDao {
#Inject
#Named(PROD_PERSISTENCE_UNIT_NAME)
private EntityManagerFactory entityManagerFactory;
private EntityManager getEntityManager() {
return entityManagerFactory.createEntityManager();
}
public void persist(Book book) throws Exception {
EntityManager em = getEntityManager();
try {
em.getTransaction().begin();
em.persist(book);
em.getTransaction().commit();
} catch (Exception e) {
em.getTransaction().rollback();
throw e;
} finally {
em.close();
}
}
}
public class MyAppModule extends PrivateModule {
public static final String PROD_PERSISTENCE_UNIT_NAME = "prod";
#Override
protected void configure() {
initializePersistence();
}
private void initializePersistence() {
// persistence unit set to prod DB
final JpaPersistModule jpaPersistModule = new JpaPersistModule(PROD_PERSISTENCE_UNIT_NAME);
// connection properties set to suitable prod values
jpaPersistModule.properties(new Properties());
install(jpaPersistModule);
// expose bindings to entity manager annotated as "prod"
bind(JPAInitializer.class).asEagerSingleton();
bind(PersistService.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME)).to(PersistService.class).asEagerSingleton();
expose(PersistService.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME));
bind(EntityManagerFactory.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME)).toProvider(binder().getProvider(EntityManagerFactory.class));
expose(EntityManagerFactory.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME));
bind(EntityManager.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME)).toProvider(binder().getProvider(EntityManager.class));
expose(EntityManager.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME));
bind(UnitOfWork.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME)).toProvider(binder().getProvider(UnitOfWork.class));
expose(UnitOfWork.class).annotatedWith(named(PROD_PERSISTENCE_UNIT_NAME));
}
}
As a lesson, be very watchful around annotations and other such "magic" that modifies your code under the hood, finding bugs becomes quite difficult.
How can I force a transaction commit in Spring Boot (with Spring Data) while running a method and not after the method ?
I've read here that it should be possible with #Transactional(propagation = Propagation.REQUIRES_NEW) in another class but doesn't work for me.
Any hints? I'm using Spring Boot v1.5.2.RELEASE.
#RunWith(SpringRunner.class)
#SpringBootTest
public class CommitTest {
#Autowired
TestRepo repo;
#Transactional
#Commit
#Test
public void testCommit() {
repo.createPerson();
System.out.println("I want a commit here!");
// ...
System.out.println("Something after the commit...");
}
}
#Repository
public class TestRepo {
#Autowired
private PersonRepository personRepo;
#Transactional(propagation = Propagation.REQUIRES_NEW)
public void createPerson() {
personRepo.save(new Person("test"));
}
}
Use the helper class org.springframework.test.context.transaction.TestTransaction (since Spring 4.1).
Tests are rolled back per default. To really commit one needs to do
// do something before the commit
TestTransaction.flagForCommit(); // need this, otherwise the next line does a rollback
TestTransaction.end();
TestTransaction.start();
// do something in new transaction
An approach would be to inject the TransactionTemplate in the test class, remove the #Transactional and #Commit and modify the test method to something like:
...
public class CommitTest {
#Autowired
TestRepo repo;
#Autowired
TransactionTemplate txTemplate;
#Test
public void testCommit() {
txTemplate.execute(new TransactionCallbackWithoutResult() {
#Override
protected void doInTransactionWithoutResult(TransactionStatus status) {
repo.createPerson();
// ...
}
});
// ...
System.out.println("Something after the commit...");
}
Or
new TransactionCallback<Person>() {
#Override
public Person doInTransaction(TransactionStatus status) {
// ...
return person
}
// ...
});
instead of the TransactionCallbackWithoutResult callback impl if you plan to add assertions to the person object that was just persisted.
Solutions with lambdas.
import java.lang.Runnable;
import java.util.function.Supplier;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.support.TransactionTemplate;
#Autowired
TestRepo repo;
#Autowired
TransactionTemplate txTemplate;
private <T> T doInTransaction(Supplier<T> operation) {
return txTemplate.execute(status -> operation.get());
}
private void doInTransaction(Runnable operation) {
txTemplate.execute(status -> {
operation.run();
return null;
});
}
use as
Person saved = doInTransaction(() -> repo.save(buildPerson(...)));
doInTransaction(() -> repo.delete(person));
I have the following service layout of nested transactions:
#Component
public class Main implements RPCInterface {
#Autowired
private ServiceA serviceA;
#Autowired
private ServiceB serviceB;
#Autowired
private ServiceC serviceC;
#Override
#Transactional (value="txManager", propagation=Propagation.REQUIRED, rollbackFor={ExceptionOne.class, ExceptionTwo.class, ExceptionThree.class})
public void outerMethod() throws ExceptionO {
try {
serviceA.methodA();
serviceB.methodB();
serviceC.methodC();
} catch (ExceptionOne e) {
throw new ExceptionO(e.getMessage, e);
} catch (ExceptionTwo e) {
throw new ExceptionO(e.getMessage, e);
} catch (ExceptionThree e) {
throw new ExceptionO(e.getMessage, e);
}
}
}
#Service
public class ServiceA implements SA {
#Autowired
private ServiceA1 serviceA1;
#Override
public void methodA() {
serviceA1.methodA1();
}
}
#Service
public class ServiceA1 implements SA1 {
#Autowired
private ServiceDBTable1 serviceDBTable1;
#Autowired
private ServiceA1A serviceA1A;
#Transactional
#Override
public void methodA1() {
serviceDBTable4.callToMapper4();
serviceA1A.methodA1A();
}
}
#Service
#Transactional (value="txManager", propagation=Propagation.REQUIRED)
public class ServiceA1A implements SA1A {
#Autowired
private ServiceDBTable2 serviceDBTable2;
#Override
public void methodA1A() {
serviceDBTable1.callToMapper1();
}
}
#Service
public class ServiceB implements SB {
#Autowired
private ServiceDBTable3 serviceDBTable3;
#Override
#Transactional (value="txManager", propagation=Propagation.REQUIRED)
public void methodB() {
serviceDBTable3.callToMapper3();
}
}
#Service
public class ServiceC implements SC {
#Override
public void methodC() throws ExceptionThree {
// code that throws ExceptionThree
}
}
I need to make all the DB calls within ServiceA and ServiceB nested calls to rollback when ServiceC#methodC() throws an exception (or any of them for that matter that throws an exception -- ServiceA or ServiceB).
I tried to make Main#outerMethod transactional with REQUIRED propagation, but it seems like the database commits are not being rolled back. I have even specified the specific classes with rollbackFor but the commits persist. Does anyone know how to fix this issue?
What I did to make it work was to migrate ServiceB.methodB() and ServiceC.methodC() calls to ServiceA.methodA(), and make methodA() #Transactional while throwing all my exceptions from methodA() and rollback based on those three exceptions (my logic actually allowed me to do that):
#Component
public class Main implements RPCInterface {
#Autowired
private ServiceA serviceA;
#Override
public void outerMethod() throws ExceptionO {
try {
serviceA.methodA();
} catch (ExceptionOne e) {
throw new ExceptionO(e.getMessage, e);
} catch (ExceptionTwo e) {
throw new ExceptionO(e.getMessage, e);
} catch (ExceptionThree e) {
throw new ExceptionO(e.getMessage, e);
}
}
}
#Service
public class ServiceA implements SA {
#Autowired
private ServiceA1 serviceA1;
#Autowired
private ServiceB serviceB;
#Autowired
private ServiceC serviceC;
#Override
#Transactional (value="txManager", propagation=Propagation.REQUIRED, rollbackFor={ExceptionOne.class, ExceptionTwo.class, ExceptionThree.class})
public void methodA() throw ExceptionOne, ExceptionTwo, ExceptionThree {
serviceA1.methodA1();
serviceB.methodB();
serviceC.methodC();
}
}
#Service
public class ServiceA1 implements SA1 {
#Autowired
private ServiceDBTable1 serviceDBTable1;
#Autowired
private ServiceA1A serviceA1A;
#Transactional
#Override
public void methodA1() {
serviceDBTable4.callToMapper4();
serviceA1A.methodA1A();
}
}
#Service
#Transactional (value="txManager", propagation=Propagation.REQUIRED)
public class ServiceA1A implements SA1A {
#Autowired
private ServiceDBTable2 serviceDBTable2;
#Override
public void methodA1A() {
serviceDBTable1.callToMapper1();
}
}
#Service
public class ServiceB implements SB {
#Autowired
private ServiceDBTable3 serviceDBTable3;
#Override
#Transactional (value="txManager", propagation=Propagation.REQUIRED)
public void methodB() {
serviceDBTable3.callToMapper3();
}
}
#Service
public class ServiceC implements SC {
#Override
public void methodC() throws ExceptionThree {
// code that throws ExceptionThree
}
}
Since there is no code presented it is hard to know for sure.
However, transactions only work when methods are public. Private methods are not proxied and hence transaction support for them is not there.
Read through Declarative Transations - Spring Docs for more details.
Please post code if you still are struggling for getting better help.
I have a need to persist(insert) a entity to database immediately when the save (or saveAndFlush) code is called.
However although the entity is created in the context it is not persisted in the database immediately.
We are using Spring Boot.
public interface MessageRepository extends JpaRepository<MessageEntity, Long> {
}
In the Service class
#Service
public class TestService {
#Autowired
private MessageRepository messageRepository;
#Transactional
public MessageEntity saveMessage(MessageEntity entity) throws Exception {
entity = messageRepository.saveAndFlush(entity);
return entity;
}
}
Though the entity is created it is not persisted/committed to the database immediately.
We are facing this issue within the Activiti Tasks only.
Any feedback will be appreciated.
This worked.
#Component
public class MessageRepositoryCustomImpl implements MessageRepositoryCustom {
#PersistenceContext
EntityManager entityManager;
#Override
#Transactional(propagation = Propagation.REQUIRES_NEW)
public MessageEntity saveImmediate(MessageEntity entity) {
entityManager.persist(entity);
return entity;
}
}
One way of overcoming this situation is by taking advantage of the REQUIRES_NEW transaction attribute.
In your situation you would have to create a new repository:
public interface MessageRepositoryCustom{
void saveAndFLush(MessageEntity ent);
}
public MessageRepositoryCustomImpl implements MessageRepositoryCustom{
#Autowired
private SessionFactory sessionFactory;
#Transactional(propagation = Propagation.REQUIRES_NEW)
void saveAndFLush(MessageEntity ent){
Session session = sessionFactory.getCurrentSession();
session.persist(ent);
}
}
Then in your service you would use that repository:
#Transactional
public MessageEntity saveMessage(MessageEntity entity) throws Exception {
entity = messageRepositoryCutom.saveAndFlush(entity);
// other processing
return entity;
}
}
Now after the messageRepositoryCutom.saveAndFlush method has finished processing the entity will be physically persisted in the database as it was created in a separate transaction which has been commited.
I work on a project developed by a team mate but we are facing a strange issue that drive me crazy: database is never updated and there's no exception in the logs. Here's the code:
#Service
#Transactional
public class InterventionProjectResultIntegrator implements IInterventionProjectResultIntegrator {
private static final ILogger logger = ComponentLogger.getInstance(InterventionProjectResultIntegrator.class);
private Dao dao;
private String APPLICATION = "APP";
#Autowired
public void setDao(Dao dao){
this.dao = dao;
}
#Override
public void integrateResponse() {
try {
List<ResponseEntity> responseListByStatus = dao.findAllResponseByStatus(Dao.STATUS_EN_COURS, APPLICATION);
for (ResponseEntity response: responseListByStatus ) {
response.setStatus(Dao.STATUS_OK);
dao.mergeResponseEntity(response);
}
} catch (Exception ex) {
logger.error(ex.getMessage(), ex);
throw ex;
}
}
}
As you can see the function is pretty simple:
Getting objects from database
Loop over the objects
Update Each object status
At the end of the loop commit changes
Everything run fine except that the objects are not updated on database and that there is no exception.
The Dao come from a maven dependency that worked fine in another project so I assume that the problem is related to the new one.
I can see following logs in console:
org.springframework.transaction.support.TransactionSynchronizationManager - Retrieved value [org.springframework.orm.jpa.EntityManagerHolder#6dcee890] for key [org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean#713e49c3] bound to thread org.hibernate.event.internal.AbstractSaveEventListener - Persistent instance of: com.domain.ResponseEntity
org.hibernate.event.internal.DefaultMergeEventListener - Ignoring persistent instance
org.hibernate.action.internal.UnresolvedEntityInsertActions - No entity insert actions have non-nullable, transient entity dependencies.
Did you already face similar issue ?
Regards.
[EDIT 1]
As pointed out in comment, I replaced manual transaction handling with #Transactional annotation. See the code updated.
So now I have a new line in the logs, but the same result, object is not save in database.
org.springframework.transaction.interceptor.TransactionAspectSupport - Completing transaction for [com.response.InterventionProjectResultIntegrator.integrateResponse]
As asked the DAO source. This code is not under my responsability, and worked like a charm in another context.
#Repository
public class Dao {
public static final ILogger logger = ComponentLogger.getInstance(Dao.class);
public static final String STATUS_EN_COURS = "PENDING";
public static final String STATUS_OK = "OK";
public static final String STATUS_ERROR = "ERROR";
#PersistenceContext
protected EntityManager entityManager;
public void setEntityManager(EntityManager entityManager) {
this.entityManager = entityManager;
}
public void mergeMvzResponseEntity(ResponseEntity responseEntity) {
if(entityManager != null) {
this.entityManager.merge(responseEntity);
} else {
logger.error("Entity manager not initialized");
}
}
As suggested, I have reworked the source code to use #Transactional annotation, and to let Spring handle transaction stuff:
#Service
#Transactional
public class InterventionProjectResultIntegrator implements IInterventionProjectResultIntegrator {
private static final ILogger logger = ComponentLogger.getInstance(InterventionProjectResultIntegrator.class);
private Dao dao;
private String APPLICATION = "APP";
#Autowired
public void setDao(Dao dao){
this.dao = dao;
}
#Override
public void integrateResponse() {
try {
List<ResponseEntity> responseListByStatus = dao.findAllResponseByStatus(Dao.STATUS_EN_COURS, APPLICATION);
for (ResponseEntity response: responseListByStatus ) {
response.setStatus(Dao.STATUS_OK);
dao.mergeResponseEntity(response);
}
} catch (Exception ex) {
logger.error(ex.getMessage(), ex);
throw ex;
}
}
}
Then added this line to my xml configuration file:
<tx:annotation-driven/>
Now it works like a charm. Thanks to #M. Deinum to have pointed it out.