I am trying to use @Transactional
inside @Async
method to track file conversion process as follows:
@Async
@Transactional
public void convertFile(String documentId) {
CustomLog customLog = new CustomLog();
customLog.setStatus("IN_PROGRESS");
mySpringDataRepo.save(customLog);
try {
doConvertFunction(documentId);
} catch (Exception e) {
customLog.setStatus("FAIL");
mySpringDataRepo.save(customLog);
return;
}
customLog.setStatus("SUCCESS");
mySpringDataRepo.save(customLog);
}
I am using the following technologies :
My custom EntityManager configuration:
@Primary
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(EntityManagerFactoryBuilder builder,
DataSource dataSource) {
LocalContainerEntityManagerFactoryBean em = builder.dataSource(dataSource).packages("com.myapp").persistenceUnit("MyEntityManagerFactory").properties(jpaProperties()).build();
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
return em;
}
protected Map<String, Object> jpaProperties() {
Map<String, Object> props = new HashMap<>();
props.put("hibernate.physical_naming_strategy", CamelCaseToUnderscoresNamingStrategy.class.getName());
props.put("hibernate.implicit_naming_strategy", SpringImplicitNamingStrategy.class.getName());
return props;
}
When running the above code on Tomcat 9 It works perfectly fine, but when trying to run it on Websphere the transaction doesn't open and the save method doesn't get executed at all !
I made several tries with no luck as follows:
Using @Transactional(propagation = Propagation.REQUIRES_NEW)
Extracting the save method to a new separate service method which is as follows:
@Service
public class MyService {
@Autowired
private MySpringDataRepo mySpringDataRepo;
@Transactional(propagation = Propagation.REQUIRES_NEW)
public CustomLog save(CustomLog customLog) {
mySpringDataRepo.save(customLog);
return customLog;
}
}
UPDATE:
The only solution worked on Websphere was by creating EntityManager from EntityManagerFactory as follows:
@Service
public class CustomLogService {
@Autowired
private EntityManagerFactory entityManagerFactory;
public Long save(CustomLog customLog) {
EntityManager entityManager = entityManagerFactory.createEntityManager();
entityManager.getTransaction().begin();
entityManager.persist(customLog);
entityManager.flush();
entityManager.getTransaction().commit();
return customLog.getId();
}
public CustomLog find(Long id) {
EntityManager entityManager = entityManagerFactory.createEntityManager();
entityManager.getTransaction().begin();
CustomLog customLog = entityManager.find(CustomLog.class, id);
entityManager.getTransaction().commit();
return customLog;
}
public void update(CustomLog customLog) {
EntityManager entityManager = entityManagerFactory.createEntityManager();
entityManager.getTransaction().begin();
entityManager.merge(customLog);
entityManager.getTransaction().commit();
}
}
and changed my @Async
code to be as follows:
@Async
public void convertFile(String documentId) {
CustomLog customLog = new CustomLog();
customLog.setStatus("IN_PROGRESS");
customLogService.save(customLog);
try {
doConvertFunction(documentId);
} catch (Exception e) {
customLog = customLogService.find(customLog.getId());
customLog.setStatus("FAIL");
customLogService.update(customLog);
return;
}
customLog = customLogService.find(customLog.getId());
customLog.setStatus("SUCCESS");
customLogService.update(customLog);
}
I tried to inject entityManager as follows but it doesn't work only creating entityManager from EntityManagerFactory works:
@PersistenceContext(name = "MyEntityManagerFactory")
private EntityManager entityManager;
Are there any disadvantages/things to consider in this solution because I will be heavily using this method and it returns a new EntityManager instance each time it is invoked?
Update your configuration code to this :
@Configuration
@EnableJpaRepositories(basePackages = { "com.myapp.dao" }, transactionManagerRef = "websphereTxManager")
@EnableTransactionManagement
public class WebSphereDBConfiguration {
@Resource(lookup = "jdbc/sample", name = "java:comp/env/jdbc/sample")
private DataSource dataSource;
@Primary
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean() {
LocalContainerEntityManagerFactoryBean entityManagerFactoryBean = new LocalContainerEntityManagerFactoryBean();
entityManagerFactoryBean.setPackagesToScan("com.myapp.entity");
entityManagerFactoryBean.setJpaVendorAdapter(new HibernateJpaVendorAdapter());
entityManagerFactoryBean.setDataSource(dataSource);
entityManagerFactoryBean.setJpaPropertyMap(jpaProperties());
return entityManagerFactoryBean;
}
@Bean
public EntityManagerFactory entityManagerFactory(LocalContainerEntityManagerFactoryBean entityManagerFactoryBean) {
return entityManagerFactoryBean.getObject();
}
@Bean
public PlatformTransactionManager websphereTxManager(EntityManagerFactory entityManagerFactory) {
JpaTransactionManager transactionManager = new JpaTransactionManager();
transactionManager.setEntityManagerFactory(entityManagerFactory);
return transactionManager;
}
protected Map<String, Object> jpaProperties() {
Map<String, Object> props = new HashMap<>();
props.put("hibernate.physical_naming_strategy", CamelCaseToUnderscoresNamingStrategy.class.getName());
props.put("hibernate.implicit_naming_strategy", SpringImplicitNamingStrategy.class.getName());
return props;
}
}
Use the same code :
@Async
@Transactional(transactionManager = "websphereTxManager")
public void convertFile(String documentId) {
CustomLog customLog = new CustomLog();
customLog.setStatus("IN_PROGRESS");
mySpringDataRepo.save(customLog);
try {
doConvertFunction(documentId);
} catch(Exception e) {
customLog.setStatus("FAIL");
mySpringDataRepo.save(customLog);
return;
}
customLog.setStatus("SUCCESS");
mySpringDataRepo.save(customLog);
}
Few points to be noted:
LocalContainerEntityManagerFactoryBean
.EntityManagerFactory
via injecting LocalContainerEntityManagerFactoryBean
and getting the object via LocalContainerEntityManagerFactoryBean.getObject()
. Also, make that bean as the primary bean for the whole application.JpaTransactionManager
bean via injection and using the EntityManagerFactory
bean.@EnableTransactionManagement
. By use of this annotation, you will tell Spring to use this configuration class to manage your transaction instead of default one.@EnableJpaRepositories
. By this annotation, you will tell Spring which packages to scan containing the JPA repositories and also tell which transaction manager to use via this property transactionManagerRef
by default.@Resource
and set that datasource in the LocalContainerEntityManagerFactoryBean
bean.See if this helps.