How to explicit execute begin (commit, rollback) transaction with PlatformTransactionManager? - spring

I'm using Spring PlatformTransactionManager with jTds and Sybase ASE 16.
I cannot use AutoCommit because it will SET CHAINED ON and the database I'm working has triggers with unchained procedures.
So my connection has SET CHAINED OFF (AutoCommit=false).
In this scenario, SYBASE ASE requires explicit begin transaction, commit transaction or rollback transaction. If not, it will completely ignore any transaction.
I'm looking for a way to tell the PlatformTransactionManager to execute begin transaction, commit transaction or rollback transation. Is it possible?
Update -> My configuration:
spring.sybase.url=jdbc:jtds:sybase://hostname:6000/db;autoCommit=false;appName=integration
spring.sybase.username=myuser
spring.sybase.password=mypass
spring.sybase.driver-class-name=net.sourceforge.jtds.jdbc.Driver
spring.sybase.hikari.pool-name=datasource-pool
spring.sybase.hikari.connection-timeout=3000
spring.sybase.hikari.maximum-pool-size=5
spring.sybase.hikari.minimum-idle=0
spring.sybase.hikari.transaction-isolation=TRANSACTION_READ_COMMITTED
spring.sybase.hikari.auto-commit=false
spring.sybase.hikari.connection-test-query=SELECT 1
Bean Configuration
#Configuration
#EnableTransactionManagement
public class SybaseDataSourceConfig {
public static final String SYBASE_JDBC_TEMPL = "sybaseJdbcTempl";
public static final String SYBASE_DS = "sybaseDs";
public static final String SYBASE_DS_PROPERTIES = "sybaseDsProp";
public static final String SYBASE_TRAN_MANAGER = "sybaseTM";
#Bean(name = SYBASE_DS_PROPERTIES)
#ConfigurationProperties("spring.sybase")
public DataSourceProperties sybaseDataSourceProperties() {
return new DataSourceProperties();
}
#Bean(name = SYBASE_DS)
#ConfigurationProperties("spring.sybase.hikari")
public HikariDataSource sybaseDataSource(#Qualifier(SYBASE_DS_PROPERTIES) final DataSourceProperties sybaseDataSourceProperties) {
return sybaseDataSourceProperties.initializeDataSourceBuilder().type(HikariDataSource.class).build();
}
#Bean(name = SYBASE_JDBC_TEMPL)
public JdbcTemplate sybaseJdbcTemplate(#Qualifier(SYBASE_DS) final HikariDataSource sybaseDataSource) {
return new JdbcTemplate(sybaseDataSource);
}
#Bean(name = SYBASE_TRAN_MANAGER)
public PlatformTransactionManager transactionManager(#Qualifier(SYBASE_DS) final DataSource sybaseDataSource) {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(sybaseDataSource);
return dataSourceTransactionManager;
}
}

Related

Is there a way to stop Spring from rollbacking every transaction in a test method and do a Rollback after it executes?

I'm trying to write some integration tests for an app that I write as a practise. I have a test class with #Transactional annotation on it, to stop tests from committing any changes to db. I've noticed that rollback happens for every method of JPA repository I use. When I save an entity and then try to find a values for mentioned entity I get an empty List since that entity does not exists in DB. So do I have to change my approach or is there a way to wrap all of test method contents into a single transaction that will be rolledback after test method is done running?
Service that is tested:
#Service
public class FridgeServiceImpl implements FridgeService {
private final FridgeRepository fridgeRepository;
public FridgeServiceImpl(FridgeRepository fridgeRepository) {
this.fridgeRepository = fridgeRepository;
}
#Override
public Fridge save(Fridge fridge) {
return fridgeRepository.save(fridge);
}
#Override
public List<IngredientInFridge> getAllInFridge(Long id) {
return fridgeRepository.findAllInFridge(id);
}
#Override
public List<IngredientInFridge> getAllToExpireAfterDate(LocalDate date) {
return fridgeRepository.findAllWithExpirationDateAfter(date);
}
Repository:
#Repository
#Transactional
public interface FridgeRepository extends JpaRepository<Fridge, Long> {
#Query(value = "select i from Fridge f join f.fridgeContents i where i.id.fridgeId = :fridgeId")
List<IngredientInFridge> findAllInFridge(#Param("fridgeId") Long fridgeId);
#Query(value = "select i from IngredientInFridge i inner join IngredientGrammage ig on i.ingredient.id = ig.ingredient.id where i.fridge.id = :fridgeId " +
"and ig.recipe.id = :recipeId")
Set<IngredientInFridge> getIngredientsAvailableForRecipe(#Param("recipeId") Long recipeId, #Param("fridgeId") Long fridgeId);
#Query(value = "select i from IngredientInFridge i where i.expirationDate <= :date")
List<IngredientInFridge> findAllWithExpirationDateAfter(#Param("date") LocalDate date);
Test class:
#ExtendWith(SpringExtension.class)
#ContextConfiguration(classes = {TestConfig.class})
#Transactional
class FridgeServiceImplTest {
#Autowired
FridgeService fridgeService;
Ingredient ingredient;
Fridge fridge;
IngredientInFridge ingredientInFridge;
#BeforeEach
public void setUp() {
ingredient = new Ingredient(100, "Chicken", UnitOfMeasure.G);
fridge = new Fridge(new HashSet<>());
ingredientInFridge = new IngredientInFridge(ingredient, fridge, 50, LocalDate.of(2020, 12, 30));
fridge.addToFridge(ingredientInFridge);
}
#Test
void getAllInFridge_ThenReturn_1() {
//given
var savedFridge = fridgeService.save(fridge); //save an entity
//when
var ingredientsInFridge = fridgeService.getAllInFridge(savedFridge.getId()); //empty List since fridgeService.save(fridge) was rolledback.
//then
Assertions.assertEquals(1, savedFridge.getFridgeContents().size());
Assertions.assertEquals(1, ingredientsInFridge.size()); // failed assertion
}
Test config:
#Configuration
#EnableJpaRepositories(value = "com.calculate.calories.repository")
#PropertySource("classpath:applicationTest.properties")
#ComponentScan(basePackages = {"com.calculate.calories.service", "com.calculate.calories.repository"})
#EntityScan("com.calculate.calories.model")
public class TestConfig {
#Autowired
private Environment environment;
#Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory() {
LocalContainerEntityManagerFactoryBean em
= new LocalContainerEntityManagerFactoryBean();
em.setDataSource(dataSource());
em.setPackagesToScan("com.calculate.calories.model");
JpaVendorAdapter vendorAdapter = new HibernateJpaVendorAdapter();
em.setJpaVendorAdapter(vendorAdapter);
em.setJpaProperties(additionalProperties());
return em;
}
#Bean
public DataSource dataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(Objects.requireNonNull(environment.getProperty("spring.datasource.driver-class-name")));
dataSource.setUrl(Objects.requireNonNull(environment.getProperty("spring.datasource.url")));
dataSource.setUsername(Objects.requireNonNull(environment.getProperty("spring.datasource.username")));
dataSource.setPassword(Objects.requireNonNull(environment.getProperty("spring.datasource.password")));
return dataSource;
}
#Bean
public PlatformTransactionManager transactionManager() {
//JpaTransactionManager transactionManager = new JpaTransactionManager();
//transactionManager.setEntityManagerFactory(entityManagerFactory().getObject());
return new DataSourceTransactionManager(dataSource());
}
#Bean
public PersistenceExceptionTranslationPostProcessor exceptionTranslation() {
return new PersistenceExceptionTranslationPostProcessor();
}
Properties additionalProperties() {
Properties properties = new Properties();
properties.setProperty("hibernate.hbm2ddl.auto", "none");
properties.setProperty("hibernate.dialect", "org.hibernate.dialect.MySQL5Dialect");
return properties;
}
I tried to use JpaTransactionManager instead of DataSourceTransactionManager but if I do so, there are no rollbacks for the tests and db is flooded with duplicate data. I've also attempted changing
#Transactional from class level to method level but it changed nothing.
I've seen this post: Does #Transactional annotation on test methods rollback every transaction in the annotated method before it gets to the end?
But when I try to use the EntityManager.flush() after saving it throws javax.persistence.TransactionRequiredException: no transaction is in progress

How to make use of spring declarative transactions along with EntityManagerFactory?

I have defined the following configuration in my application
#Bean
#Primary
#ConfigurationProperties(prefix="database1")
public DataSource rameshDS()
{
return DataSourceBuilder.create().build();
}
#Primary
#Bean(name = "rameshEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean rameshEntityManagerFactory(EntityManagerFactoryBuilder builder) {
return builder
.dataSource(rameshDS())
.packages("com.nandagiri.entities")
.build();
}
#Primary
#Bean(name = "rameshTransactionManager")
public PlatformTransactionManager rameshTransactionManager(
final #Qualifier("rameshEntityManagerFactory") LocalContainerEntityManagerFactoryBean rameshEntityManagerFactory) {
return new JpaTransactionManager(rameshEntityManagerFactory.getObject());
}
If I try to insert data into one of the tables in the following way, the data is not persisted. But if I uncomment the lines to explicitly begin/commit transactions then it is working fine. But, I wanted to use declarative way of transactions. How to achieve that?
#Autowired
#Qualifier("rameshEntityManagerFactory")
EntityManagerFactory rameshEntity;
#Override
#Transactional(value = "rameshTransactionManager")
public void storeInfo(Ramesh ramesh)
{
EntityManager em = rameshEntity.createEntityManager();
//em.getTransaction().begin();
em.persist(ramesh);
//em.getTransaction().commit();
}
If I persist the entities with repository interface it is working absolutely fine without any issues. please find the code below.
#Repository
public interface RameshRepository extends JpaRepository<Ramesh, String>
{
}
#Transactional(transactionManager = "rameshTransactionManager", propagation = Propagation.REQUIRED)
public void saveRameshs()
{
saveRamesh1();
saveRamesh2();
}
#Transactional
public void saveRamesh1()
{
Ramesh ramesh = new Ramesh();
ramesh.setId("8");
ramesh.setFname("jagadeesh");
ramesh.setLname("K");
repository.save(ramesh);
}
#Transactional
public void saveRamesh2()
{
Ramesh ramesh = new Ramesh();
ramesh.setId("9");
ramesh.setFname("jagadeesh123");
ramesh.setLname("k123");
repository.save(ramesh);
//int x = 5/0;
}

Bitronix - JMS and JDBC - Message is dequeued on Exception

I'm trying to integrate Bitronix Transaction Manager in my Spring boot project to manage jdbc and jms transaction together. I have two databases and one ActiveMQ broker for jms. I've got connect the databases in the same transaction but when I tried to include JMS, It seems not to work.
This is my Bitronix Transaction Manager configuration:
#Configuration
#EnableTransactionManagement
public class BitronixJtaConfiguration {
private static final Logger log = LoggerFactory.getLogger(BitronixJtaConfiguration.class);
#Value("${bitronix.tm.serverId}")
private String serverId;
#Value("${bitronix.tm.journal.disk.logPart1Filename:}")
private String logPart1Filename;
#Value("${bitronix.tm.journal.disk.logPart2Filename:}")
private String logPart2Filename;
#Bean
public bitronix.tm.Configuration transactionManagerServices() {
bitronix.tm.Configuration configuration = TransactionManagerServices.getConfiguration();
configuration.setServerId(serverId);
if ("".equals(logPart1Filename) && "".equals(logPart2Filename)) {
configuration.setJournal(null);
log.info("Disable journal for testing.");
} else {
configuration.setLogPart1Filename(logPart1Filename);
configuration.setLogPart2Filename(logPart2Filename);
}
return configuration;
}
#Bean
public TransactionManager transactionManager() {
return TransactionManagerServices.getTransactionManager();
}
#Bean
public UserTransaction userTransaction() {
return TransactionManagerServices.getTransactionManager();
}
#Bean
public PlatformTransactionManager platformTransactionManager() {
UserTransaction userTransaction = userTransaction();
TransactionManager transactionManager = transactionManager();
return new JtaTransactionManager(userTransaction, transactionManager);
}
}
This is one of my database configuration class:
#Configuration
#EnableTransactionManagement
public class TransportationPlanDBConfig {
private static final Logger LOGGER = LoggerFactory.getLogger("ppalfile");
#Value("${tp.jdbc.driverClassName}")
private String driverClassName;
#Value("${tp.jdbc.username}")
private String username;
#Value("${tp.jdbc.url}")
private String url;
#Value("${tp.jdbc.password}")
private String password;
#Value("${tp.c3p0.max_size}")
private int c3p0MaxSize;
#Value("${tp.c3p0.min_size}")
private int c3p0MinSize;
#Value("${tp.c3p0.unreturned_connection_timeout}")
private int c3p0UnreturnedConnectionTimeout;
#Value("${tp.c3p0.acquire_increment}")
private int c3p0AcquireIncrement;
#Value("${tp.c3p0.max_idle_time}")
private int c3p0MaxIdleTime;
public TransportationPlanDBConfig() {
// Empty constructor
}
#Bean(name = "tpds", destroyMethod = "close")
public DataSource dataSource() {
LOGGER.debug("Creating Transportation plan DS");
PoolingDataSource poolingDataSource = new PoolingDataSource();
poolingDataSource.setClassName(driverClassName);
poolingDataSource.setUniqueName("tpds");
Properties props = new Properties();
props.put("url", url);
props.put("user", username);
props.put("password", password);
poolingDataSource.setDriverProperties(props);
poolingDataSource.setAllowLocalTransactions(true);
poolingDataSource.setMaxPoolSize(c3p0MaxSize);
poolingDataSource.init();
return poolingDataSource;
}
#Bean(name = "tpJdbcTemplate")
JdbcTemplate jdbcTemplate(#Qualifier("tpds") DataSource dataSource) {
LOGGER.debug("Creating JdbcTemplate transport plan");
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
LOGGER.debug(" JdbcTemplate Transport Plan created ");
return jdbcTemplate;
}
}
My ActiveMQ configuration class:
#Configuration
#EnableTransactionManagement
public class ActivesMQsConfiguration {
#Bean
public ConnectionFactory jmsConnectionFactoryLocal() {
PoolingConnectionFactory btmPoolingConnectionFactory = new PoolingConnectionFactory();
btmPoolingConnectionFactory.setClassName("org.apache.activemq.ActiveMQXAConnectionFactory");
btmPoolingConnectionFactory.setUniqueName("AMQLocal");
btmPoolingConnectionFactory.setMinPoolSize(1);
btmPoolingConnectionFactory.setMaxPoolSize(5);
btmPoolingConnectionFactory.setAllowLocalTransactions(true);
btmPoolingConnectionFactory.setUser("admin");
btmPoolingConnectionFactory.setPassword("admin");
btmPoolingConnectionFactory.getDriverProperties().setProperty("brokerURL", "tcp://localhost:61616");
btmPoolingConnectionFactory.init();
return btmPoolingConnectionFactory;
}
#Bean
public JmsListenerContainerFactory<?> jmsListenerContainerFactoryLocal(
#Qualifier("jmsConnectionFactoryLocal") ConnectionFactory connectionFactory,
DefaultJmsListenerContainerFactoryConfigurer configurer) {
DefaultJmsListenerContainerFactory factory = new DefaultJmsListenerContainerFactory();
factory.setConnectionFactory(connectionFactory);
factory.setSessionTransacted(true);
configurer.configure(factory, connectionFactory);
return factory;
}
}
My JMS Listener implemetation:
#Component
#Transactional
public class ContactTransactionReceiver {
private int mensajesConsumer2 = 0;
#Autowired
#Qualifier("versionJdbcTemplate")
private JdbcTemplate versionJdbcTemplate;
#Autowired
#Qualifier("tpJdbcTemplate")
private JdbcTemplate tpjdbcTemplate;
#Autowired
private VersionsConfDao versionsConfDao;
#Autowired
private TrainDao trainDao;
#Transactional(rollbackFor=Exception.class)
#JmsListener(destination = "Consumer.consumer2.VirtualTopic.TopicPrueba")
public void receiveMessageFromContacts2(Message message) throws Exception {
mensajesConsumer2++;
TextMessage txtMessage = (TextMessage) message;
System.out.println("Segundo consumer:" + txtMessage.getText() + " recibidos:" + mensajesConsumer2);
VersionsConf versionsconf = new VersionsConf("V" + mensajesConsumer2, "V" + mensajesConsumer2, false,new Timestamp(1L), 1);
VersionsConf versionsResult = versionsConfDao.insertUpdate(versionJdbcTemplate, versionsconf);
if (mensajesConsumer2 == 2) {
throw new Exception();
}
Train train = new Train("101"+mensajesConsumer2, 1L, 2L, false, true, "atp");
Train trainResult = trainDao.insertUpdate(tpjdbcTemplate, train);
if (mensajesConsumer2 == 3) {
throw new Exception();
}
}
}
Based on my listener implementation, as I understood Bitronix functionality:
On first incoming message: Must insert one row in each database and dequeue the message. -> This works fine.
On second and third incoming message: Must insert 0 rows due to the exception and keep the message in the queue. -> No rows inserted but the message is dequeued.
Moreover, I'd like to add that It logs the following during the execution:
[main] bitronix.tm.recovery.Recoverer: recovery committed 0 dangling transaction(s) and rolled back 0 aborted transaction(s) on 4 resource(s) [AMQLocal, vds, AMQRemote, tpds]
So, I understood that both brokers and both data bases are registered. But when the listener process the second message (It throws an exception), and It logs:
WARN 5740 [Session Task-1] bitronix.tm.twopc.Preparer : executing transaction with 0 enlisted resource
Any idea about the problem??
You can find the full code on: https://github.com/PedroRamirezTOR/spring-jms-jdbc-integration.git
Thanks!
First, the recovery committed 0 dangling transaction(s) and rolled back 0 aborted transaction(s) on 4 resource(s) message is going to appear every now and then and it is perfectly normal. You can ignore it as long as the committed and rolled back counters are at zero.
The executing transaction with 0 enlisted resource log looks like the real deal.
I highly suspect a problem with your Spring setup. I'm no Spring expert by any mean, but the DefaultJmsListenerContainerFactory should have a reference to your Spring PlatformTransactionManager instance, so that it knows it has to work transactionally, so you should call factory.setTransactionManager(PlatformTransactionManager).
This should at least move you to the next step.

springboot jta transaction rollback is not working

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;
}
}
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.
Transaction propagation
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.
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.
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.

createQuery is not valid without active transaction or invalid state, the connection object is closed

I have the following Java config file:
#Configuration
#ComponentScan(basePackages="com.blahblah")
#EnableWebMvc
#EnableTransactionManagement
#Import({ SecurityConfig.class })
public class MvcConfiguration extends WebMvcConfigurerAdapter{
#Bean
public ViewResolver getViewResolver(){
InternalResourceViewResolver resolver = new InternalResourceViewResolver();
resolver.setPrefix("/WEB-INF/views/");
resolver.setSuffix(".jsp");
return resolver;
}
#Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/resources/**").addResourceLocations("/resources/");
}
#Bean
public DataSource getDataSource() {
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("net.sourceforge.jtds.jdbc.Driver");
dataSource.setUrl("jdbc:jtds:sqlserver://localhost:1433/VacationDB;instance=SQLEXPRESS");
dataSource.setUsername("bavarezul13");
dataSource.setPassword("Scholl1313.");
return dataSource;
}
#Bean
public LocalSessionFactoryBean sessionFactory() {
LocalSessionFactoryBean sessionFactory = new LocalSessionFactoryBean();
sessionFactory.setDataSource(getDataSource());
sessionFactory.setConfigLocation(new ClassPathResource("hibernate.cfg.xml"));
return sessionFactory;
}
#Bean(name = "transactionManager")
#Autowired
public HibernateTransactionManager transactionManager(SessionFactory sessionFactory) {
HibernateTransactionManager txManager = new HibernateTransactionManager(sessionFactory);
return txManager;
}
}
My DAO implementation is annotated #Transactional and I have a method like this:
#Repository(value="VacationDAOImplHibernate")
#Transactional
public class VacationDAOImplHibernate implements VacationDAO{
#Autowired
SessionFactory sessionFactory;
#Override
public VacationHibernate getVacation(int id) {
Session session = sessionFactory.getCurrentSession();
//Transaction tx = session.beginTransaction();
String hql = "from VacationHibernate v where v.id = :id";
#SuppressWarnings("unchecked")
List<VacationHibernate> listVacationHibernate = session.createQuery(hql).setParameter("id", id).list();
VacationHibernate vh = listVacationHibernate.get(0);
//tx.commit();
session.close();
return vh;
}
}
If I create a transaction and commit it, everything works normally. If not, I get the error:java.sql.SQLException: Invalid state, the Connection object is closed if I delete the following line from hibernate.cfg.xml: <property name="current_session_context_class">thread</property> or org.hibernate.HibernateException: createQuery is not valid without active transaction with that line in hibenrate.cfg.xml.
It wouldn't have been a problem to begin and commit the transaction by myself, but Transaction tx = session.beginTransaction(); takes 6 seconds!!!(I have no idea why)
Marius, you may also try setting up connection pooling in your application to improve database connection retrieval time, for example we use DBCP2 for connection pooling as the given example below
#Bean
public DataSource dataSource() {
final org.apache.commons.dbcp2.BasicDataSource dataSource = new org.apache.commons.dbcp2.BasicDataSource();
dataSource.setDriverClassName("org.postgresql.Driver");
dataSource.setUrl("jdbc:postgresql://localhost:5432/myproject-db");
dataSource.setUsername("username");
dataSource.setPassword("password");
dataSource.setInitialSize(2 * 5);
dataSource.setMaxTotal(2 * 50);
dataSource.setLogAbandoned(true);
dataSource.setMaxIdle(2);
dataSource.setMaxConnLifetimeMillis(3 * 60 * 1000); // Milliseconds (3 minutes)
dataSource.setPoolPreparedStatements(true);
return dataSource;
}
I found a solution to my errors: I deleted name="current_session_context_class">thread</property> in hibernate.cg.xml, so I had the error: java.sql.SQLException: Invalid state, the Connection object is closed as I said in my question.
Then I deleted session.close(); and the error went away.
Everything works now and Spring is managing the transaction but it also takes a lot of time to open it.
I suspect it might be a problem with the driver
<dependency>
<groupId>net.sourceforge.jtds</groupId>
<artifactId>jtds</artifactId>
<version>1.3.1</version>
</dependency>

Resources