Reputation: 21
I am trying to use Narayana JTA (standalone) for integration tests using Spring 6, Hibernate 6 and Narayana 7 jars. There are two XA datasources for 2 different oracle databases. I have an integration test which calls a service method which tries to create entities using two repositories in these two databases. The transaction is marked around this service method. So if there is some database error e.g. 'Unique key constraint violated' in one of the 2 databases; then the whole transaction should be rolled back. Meaning, if there is an error in creating one entity then the other entity in other database should not be created. But in my case this does not happen; the other entity gets saved to the other database. If I use Atomikos then this works perfectly - whole transaction is rolled back on error. I want to make it work with Narayana JTA as it is close to the actual project setup which uses wildfly. Any pointers/guidance please? Here is my configuration:
spring-context.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:p="https://www.springframework.org/schema/p"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:jee="http://www.springframework.org/schema/jee"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/jee
http://www.springframework.org/schema/jee/spring-jee.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context.xsd">
<bean id ="jpaConfig" abstract="true">
<property name="jpaProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.OracleDialect</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.jdbc.batch_size">100</prop>
<prop key="hibernate.current_session_context_class">jta</prop>
<prop key="hibernate.transaction.jta.platform">org.hibernate.engine.transaction.jta.platform.internal.JBossStandAloneJtaPlatform</prop>
<prop key="hibernate.connection.handling_mode">DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT</prop>
</props>
</property>
</bean>
<!-- XA DataSource 1 -->
<bean id="firstDS"
class="oracle.jdbc.xa.client.OracleXADataSource">
<property name="URL" value="<url-for-firstDS>" />
<property name="user" value="user" />
<property name="password" value="pass" />
</bean>
<!-- XA DataSource 2 -->
<bean id="secondDS"
class="oracle.jdbc.xa.client.OracleXADataSource">
<property name="URL" value="<url-for-secondDS>" />
<property name="user" value="user" />
<property name="password" value="pass" />
</bean>
<bean id="firstDS_entityManagerFactoryBean" parent="jpaConfig"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
<property name="jtaDataSource" ref="firstDS" />
<property name="packagesToScan" value="com.trials.jpa.pojo" />
<property name="jpaVendorAdapter" ref="jpaVendorAdapter"/>
</bean>
<bean id="secondDS_entityManagerFactoryBean" parent="jpaConfig"
class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" >
<property name="jtaDataSource" ref="secondDS" />
<property name="packagesToScan" value="com.trials.jpa.pojo" />
<property name="jpaVendorAdapter" ref="jpaVendorAdapter"/>
</bean>
<!-- Global JTA Transaction Manager -->
<bean id="transactionManager" class="org.springframework.transaction.jta.JtaTransactionManager">
<property name="transactionManager">
<bean class="com.arjuna.ats.internal.jta.transaction.arjunacore.TransactionManagerImple" />
</property>
<property name="userTransaction">
<bean class="com.arjuna.ats.internal.jta.transaction.arjunacore.UserTransactionImple" />
</property>
</bean>
<!-- Enable annotation-driven transaction management -->
<tx:annotation-driven transaction-manager="transactionManager"/>
<!-- Component scanning for @Transactional support -->
<context:component-scan base-package="com.trials.jpa.service"/>
<!-- Other beans and configurations -->
<bean id="employeeDAO" class="com.trials.jpa.dao.EmployeeDAOImpl" parent="parentDAOHibernateImpl"/>
<bean id="departmentDAO" class="com.trials.jpa.dao.DepartmentDAOImpl" parent="parentDAOHibernateImpl"/>
<bean id="abstactHibernateDAO" class="com.trials.dao.AbstractHibernateDAO" abstract="true">
<property name="firstDS_SessionFactory" ref="firstDS_entityManagerFactoryBean"/>
<property name="secondDS_SessionFactory" ref="secondDS_entityManagerFactoryBean"/>
</bean>
<bean id="parentDAOHibernateImpl" class="com.trials.dao.ParentDAOHibernateImpl" abstract="true" parent="abstactHibernateDAO"/>
<bean id="employeeService" class="com.trials.jpa.service.EmployeeServiceImpl">
<property name="employeeDAO" ref="employeeDAO"/>
</bean>
<bean id="departmentService" class="com.trials.jpa.service.DepartmentServiceImpl">
<property name="employeeDAO" ref="employeeDAO"/>
<property name="departmentDAO" ref="departmentDAO"/>
</bean>
</beans>
Here is java code:
Entity in the first database
@Entity
@Table(name="Employee")
public class Employee implements Serializable
{
private static final long serialVersionUID = 5836022075789949386L;
@Id
//@GeneratedValue(generator = "ADD_ID", strategy = GenerationType.SEQUENCE)
//@SequenceGenerator(name = "ADD_ID", sequenceName = "orclseq",allocationSize=1)
@Column(name="ID", unique=true, nullable=false, precision=10, scale=0)
private long id=1;
@Column(name="FIRST_NAME", length=255, nullable=true, unique=false, insertable = true, updatable = true)
private String firstName;
@Column(name="LAST_NAME", length=255, nullable=true, unique=false, insertable = true, updatable = true)
private String lastName;
@Column(name="SALARY", nullable=true, unique=false, insertable = true, updatable = true)
private long salary;
// ...
}
Entity in the second database
@Entity
@Table(name="Department")
public class Department implements Serializable
{
private static final long serialVersionUID = 139133657785770125L;
@Id
@GeneratedValue(generator = "id_seq", strategy = GenerationType.SEQUENCE)
@SequenceGenerator(name = "id_seq", sequenceName = "id_seq",allocationSize=1)
private long id=1;
@Column(name="name", length=100, nullable=true, unique=false, insertable = true, updatable = true)
private String name;
@Column(name="location", length=100, nullable=true, unique=false, insertable = true, updatable = true)
private String location;
// ...
}
DAO /repositories EmployeeDAO
public class EmployeeDAOImpl extends ParentDAOHibernateImpl<Employee, Long> implements EmployeeDAO
{
@Override
public Employee createEmployee(Employee e)
{
firstDS_SessionFactory.getCurrentSession().persist(e);
return e;
}
// ...
}
DepartmentDAO
public class DepartmentDAOImpl extends ParentDAOHibernateImpl<Department, Long> implements DepartmentDAO
{
@Override
public Department createDepartment(Department d)
{
secondDS_SessionFactory.getCurrentSession().persist(d);
return d;
}
// ...
}
Service
public class EmployeeServiceImpl implements EmployeeService
{
EmployeeDAO employeeDAO;
DepartmentDAO departmentDAO;
// getter, setters
@Override
@Transactional
public Map<String, Long> createEmployeeAndDepartment(Employee e, Department d) throws Exception
{
Map<String, Long> map = new HashMap<String, Long>(2);
Employee e1 = employeeDAO.createEmployee(e);
Department d1 = departmentDAO.createDepartment(d);
map.put("Employee", e1.getId());
map.put("Department", d1.getId());
return map;
}
}
Spring test
@ExtendWith(SpringExtension.class)
@ContextConfiguration(
locations = { "classpath*:/spring-context.xml"})
public class EmployeeServiceTest
{
@Autowired
EmployeeService employeeService;
@Autowired
DepartmentService departmentService;
@Test
void test_createEntities() throws Exception
{
Map<String, Long> idMap = null;
idMap = employeeService.createEmployeeAndDepartment(new Employee("test firstname", "test lastname", 100000), new Department("new", "pune"));
// should throw exception and department entity should not be saved in database as there is exception while creating Employee entity.
System.out.println(idMap);
}
}
gradle dependencies:
dependencies {
api "org.hibernate:hibernate-core:6.5.2.Final"
api "org.springframework:spring-context:6.1.13"
api "org.springframework:spring-context-support:6.1.13"
api "org.springframework:spring-jdbc:6.1.13"
api "org.springframework:spring-jms:6.1.13"
api "org.springframework:spring-orm:6.1.13"
api "org.springframework:spring-test:6.1.13"
api "org.aspectj:aspectjweaver:1.9.4"
api "ch.qos.logback:logback-classic:1.5.3"
api "org.junit.jupiter:junit-jupiter:5.10.1"
api "org.mockito:mockito-core:5.10.0"
api "com.oracle.database.jdbc:ojdbc8:19.19.0.0"
api ('org.jboss:jboss-common-core:2.2.22.GA'){
exclude module: 'jboss-logging-spi'
}
integrationTestImplementation "org.jboss.narayana.jta:narayana-jta:7.0.2.Final"
integrationTestImplementation sourceSets.main.output
integrationTestImplementation sourceSets.test.output
}
Upvotes: 0
Views: 99