Reputation: 530
I want to perform transaction across database using JTA (using atomikos) configuration.
I have below code which I wanted to perform in one transaction. However when I run the application, it saves entityObject1 and update eventObject2 and doesnt rollback when an exception is thrown when i run l.intValue() statement. below is all code that I am using with configuration for JTA.
Am i missing anything? Could anyone please help.
public void testJTATRansaction() {
service1.saveEvent1(eventObject1);
service2.updateEvent2(eventObject2);
}
saveEvent1 method in service1:
@Transactional(propagation=Propagation.REQUIRED, rollbackFor = Exception.class)
public int saveEvent1(Object eventObject1) {
return repository1.save(eventObject1);
}
updateEvent2 method in service2:
@Transactional(propagation=Propagation.REQUIRED, rollbackFor = Exception.class)
public int updateEvent2(Object eventObject2) {
int i = l.intValue(); //l is null object, to throw error
return repository2.updateEvent2(eventObject2);
}
I am using default save method from repository1 (JPARepository save method).
updateEvent2 method in repository2 class:
@Modifying
@Transactional(propagation=Propagation.REQUIRED, rollbackFor = Exception.class)
@Query(UPDATE_EVENTS)
public int updateEvent2(
@Param(value = "eventObject2") Object eventObject2);
I am using spring boot application class to initialise my application:
@SpringBootApplication
@ComponentScan("com.cbc.event")
@EnableTransactionManagement
public class RatingDaemonApplication {
public static void main(String[] args) throws Exception {
SpringApplication.run(RatingDaemonApplication.class);
}
}
I have below JTA configuration:
@Configuration
@ComponentScan
@EnableTransactionManagement
public class JTATransactionConfig {
@Bean
public JpaVendorAdapter jpaVendorAdapter() {
HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
hibernateJpaVendorAdapter.setShowSql(true);
hibernateJpaVendorAdapter.setGenerateDdl(true);
hibernateJpaVendorAdapter.setDatabase(Database.MYSQL);
return hibernateJpaVendorAdapter;
}
@Bean(name = "userTransaction")
public UserTransaction userTransaction() throws Throwable {
UserTransactionImp userTransactionImp = new UserTransactionImp();
userTransactionImp.setTransactionTimeout(10000);
return userTransactionImp;
}
@Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
public TransactionManager atomikosTransactionManager() throws Throwable {
UserTransactionManager userTransactionManager = new UserTransactionManager();
userTransactionManager.setForceShutdown(false);
AtomikosJtaPlatform.transactionManager = userTransactionManager;
return userTransactionManager;
}
@Bean(name = "transactionManager")
@DependsOn({ "userTransaction", "atomikosTransactionManager" })
public PlatformTransactionManager transactionManager() throws Throwable {
UserTransaction userTransaction = userTransaction();
AtomikosJtaPlatform.transaction = userTransaction;
TransactionManager atomikosTransactionManager = atomikosTransactionManager();
return new JtaTransactionManager(userTransaction, atomikosTransactionManager);
}
}
and datasource configuration is:
@Configuration
@DependsOn("transactionManager")
@PropertySource({"classpath:application.properties"})
@EnableJpaRepositories(basePackages = {"com.cbc.repository"},
transactionManagerRef="transactionManager", entityManagerFactoryRef = "entityMF")
public class dataSourceConfiguration {
@Autowired
Environment env;
@Autowired
JpaVendorAdapter jpaVendorAdapter;
public DataSource eventsDS() {
AtomikosDataSourceBean xaDS = new AtomikosDataSourceBean();
xaDS.setXaDataSourceClassName(env.getProperty(DRIVER_CLASS_NAME));
xaDS.setXaDataSource(getMysqlXADataSource());
xaDS.setUniqueResourceName("DS");
xaDS.setMaxPoolSize(3);
return xaDS;
}
private MysqlXADataSource getMysqlXADataSource() {
MysqlXADataSource ds = new MysqlXADataSource();
ds.setPinGlobalTxToPhysicalConnection(true);
ds.setURL(env.getProperty(URL));
ds.setUser(env.getProperty(USER));
ds.setPassword(env.getProperty(PASSWORD));
return ds;
}
@Bean(name="entityMF")
public LocalContainerEntityManagerFactoryBean importedEventsEntityMF() {
Map<String, Object> properties = new HashMap<>();
properties.put("hibernate.transaction.jta.platform", AtomikosJtaPlatform.class.getName());
properties.put("javax.persistence.transactionType", "JTA");
LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();
entityManager.setJtaDataSource(eventsDS());
entityManager.setJpaVendorAdapter(jpaVendorAdapter);
entityManager.setPackagesToScan("com.cbc.events");
entityManager.setPersistenceUnitName("persistenceUnit");
entityManager.setJpaPropertyMap(properties);
return entityManager;
}
}
I have below AtomikosJtaPlatform class
public class AtomikosJtaPlatform extends AbstractJtaPlatform {
private static final long serialVersionUID = 1L;
static TransactionManager transactionManager;
static UserTransaction transaction;
@Override
protected TransactionManager locateTransactionManager() {
return transactionManager;
}
@Override
protected UserTransaction locateUserTransaction() {
return transaction;
}
}
Upvotes: 1
Views: 2890
Reputation: 1
JTA transaction manager will only work if you use JNDI. JTA tx manager listens to Datasource and bring under a transaction only if the datasource bean is in Java/Web container and not in app. container.
Either you need to use JNDI for JTA to work or start using JPA transaction manager. JTA transaction manager is mainly used in Distributed Transaction and is prone to transaction rollback failures.
Upvotes: 0
Reputation: 41
Using h2 datasource,the distributed transaction is success. But use mysql datasource,it is tested fail. (1) First doubt the atomikos do not support MysqlXADataSource good. (2) second think the JPA and hibernate is not support JTA so good.
Then I tink use jdbc.
@Configuration
public class ArticleConfigure {
@ConfigurationProperties("second.datasource")
@Bean(name="articleDataSourceProperties")
public DataSourceProperties secondDataSourceProperties() {
return new DataSourceProperties();
}
//@Bean(name = "articleDataSource")
@Bean(name = "articleDataSource")
public DataSource articleDataSource() {
MysqlXADataSource mdatasource = new MysqlXADataSource();
mdatasource.setUrl(secondDataSourceProperties().getUrl());
mdatasource.setUser(secondDataSourceProperties().getUsername());
mdatasource.setPassword(secondDataSourceProperties().getPassword());
/*JdbcDataSource h2XaDataSource = new JdbcDataSource();
h2XaDataSource.setURL(secondDataSourceProperties().getUrl());*/
//atomikos datasource configure
com.atomikos.jdbc.AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mdatasource);
xaDataSource.setMaxPoolSize(30);
xaDataSource.setUniqueResourceName("axds1");
return xaDataSource;
}
@Bean(name = "twojdbcTemplate")
public JdbcTemplate twojdbcTemplate() {
return new JdbcTemplate(articleDataSource());
}
}
TransactionConfig.
@Configuration
@EnableTransactionManagement
@ComponentScan(basePackages="cn.crazychain")
public class TransactionConfig {
@Bean(name = "userTransaction")
public UserTransaction userTransaction() throws Throwable {
UserTransactionImp userTransactionImp = new UserTransactionImp();
userTransactionImp.setTransactionTimeout(10000);
//return new BitronixTransactionManager();
return userTransactionImp;
}
@Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")
//@Bean(name = "atomikosTransactionManager")
public TransactionManager atomikosTransactionManager() throws Throwable {
UserTransactionManager userTransactionManager = new UserTransactionManager();
userTransactionManager.setForceShutdown(false);
//return TransactionManagerServices.getTransactionManager();
return userTransactionManager;
}
@Bean(name = "customerJtaTransactionManager")
@DependsOn({ "userTransaction", "atomikosTransactionManager" })
public PlatformTransactionManager transactionManager() throws Throwable {
UserTransaction userTransaction = userTransaction();
TransactionManager atomikosTransactionManager = atomikosTransactionManager();
return new JtaTransactionManager(userTransaction, atomikosTransactionManager);
}
}
Whether there is bug in hibernate jpa whith ax. The conclusion is that JTA works fine with jdbc and AtomikosDataSourceBean. The origin reference open source project is https://github.com/fabiomaffioletti/mul-at.git The whole source code of mine is https://github.com/lxiaodao/crazychain.
Upvotes: 1
Reputation: 26
This is from spring documentation
When the propagation setting is PROPAGATION_REQUIRED, a logical transaction scope is created for each method upon which the setting is applied. Each such logical transaction scope can determine rollback-only status individually, with an outer transaction scope being logically independent from the inner transaction scope. Of course, in case of standard PROPAGATION_REQUIRED behavior, all these scopes will be mapped to the same physical transaction. So a rollback-only marker set in the inner transaction scope does affect the outer transaction's chance to actually commit (as you would expect it to).
However, in the case where an inner transaction scope sets the rollback-only marker, the outer transaction has not decided on the rollback itself, and so the rollback (silently triggered by the inner transaction scope) is unexpected. A corresponding UnexpectedRollbackException is thrown at that point. This is expected behavior so that the caller of a transaction can never be misled to assume that a commit was performed when it really was not. So if an inner transaction (of which the outer caller is not aware) silently marks a transaction as rollback-only, the outer caller still calls commit. The outer caller needs to receive an UnexpectedRollbackException to indicate clearly that a rollback was performed instead.
Try changing the method declarations as below and give it a go
public int saveEvent1(Object eventObject1) throws UnexpectedRollbackException
public int updateEvent2(Object eventObject2) throws UnexpectedRollbackException
To avoid such things Its a good idea to have a separate method in one of those service classes or a completely different service class , and call both repository operations in one go , with transaction annotation
Also when you have the service methods annotated with transaction annotation then you dont need to annotate you repository methods , the more annotations you have related to transactions more complex it is to resolve issue.
Upvotes: 0