У меня есть два источника данных с двумя схемами в oracle, я выполняю unittest, но он не работает. Я хочу, чтобы если вторая транзакция не удалась, она должна откатить первую транзакцию. Ниже мой код.
package com.test.db;
import static org.junit.Assert.assertEquals;
import java.util.Date;
import javax.sql.DataSource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.transaction.AfterTransaction;
import org.springframework.test.context.transaction.BeforeTransaction;
import org.springframework.transaction.annotation.Transactional;
//@EnableTransactionManagement
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "/META-INF/spring/data-source-context.xml")
public class MultipleDatasourceTests {
private JdbcTemplate jdbcTemplate;
private JdbcTemplate otherJdbcTemplate;
@Autowired
public void setDataSources(@Qualifier("dataSource") DataSource dataSource,
@Qualifier("otherDataSource") DataSource otherDataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
this.otherJdbcTemplate = new JdbcTemplate(otherDataSource);
}
@BeforeTransaction
public void clearData() {
jdbcTemplate.update("delete from T_ORACLE1");
otherJdbcTemplate.update("delete from T_ORACLE1");
}
@AfterTransaction
public void checkPostConditions() {
int count = jdbcTemplate.queryForInt("select count(*) from T_ORACLE1");
// This change was rolled back by the test framework
assertEquals(0, count);
count = otherJdbcTemplate.queryForInt("select count(*) from T_ORACLE1");
// This rolls back as well if the connections are managed together
assertEquals(0, count);
}
/**
* Vanilla test case for two inserts into two data sources. Both should roll
* back.
*
* @throws Exception
*/
@Transactional
@Test
public void testInsertIntoTwoDataSources() throws Exception {
jdbcTemplate.update("delete from T_ORACLE1");
otherJdbcTemplate.update("delete from T_ORACLE2");
int count = jdbcTemplate.update(
"INSERT into T_ORACLE1 (id,name,foo_date) values (?,?,null)", 0,
"foo");
assertEquals(1, count);
count = otherJdbcTemplate
.update(
"INSERT into T_ORACLE2 (id,operation,name,audit_date) values (?,?,?,?)",
1, "INSERT", "foo", new Date());
assertEquals(1, count);
}
/**
* Shows how to check the operation on the inner data source to see if it
* has already been committed, and if it has do something different, instead
* of just hitting a {@link DataIntegrityViolationException}.
*
* @throws Exception
*/
@Transactional
@Test
public void testInsertWithCheckForDuplicates() throws Exception {
int count = jdbcTemplate.update(
"INSERT into T_ORACLE1 (id,name,foo_date) values (?,?,null)", 0,
"foo");
assertEquals(1, count);
count = otherJdbcTemplate.update(
"UPDATE T_ORACLE2 set operation=?, name=?, audit_date=? where id=?",
"UPDATE", "foo", new Date(), 0);
if (count == 0) {
count = otherJdbcTemplate.update(
"INSERT into T_ORACLE2 (id,operation,name,audit_date) values (?,?,?,?)",
0, "INSERT", "foo", new Date());
}
assertEquals(1, count);
}
}
XML
<?xml version = "1.0" encoding = "UTF-8"?>
<beans xmlns = "http://www.springframework.org/schema/beans"
xmlns:tx = "http://www.springframework.org/schema/tx" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xmlns:context = "http://www.springframework.org/schema/context"
xsi:schemaLocation = "http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-2.5.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-2.5.xsd">
<context:annotation-config />
<context:component-scan base-package = "com.test.*"/>
<bean id = "dataSource"
class = "org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name = "driverClassName" value = "oracle.jdbc.driver.OracleDriver" />
<property name = "url" value = "jdbc:oracle:thin:@//localhost:1521/TESTDB1" />
<property name = "username" value = "ORACLE1"/>
<property name = "password" value = "ORACLE1"/>
</bean>
<bean id = "otherDataSource"
class = "org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name = "driverClassName" value = "oracle.jdbc.driver.OracleDriver" />
<property name = "url" value = "jdbc:oracle:thin:@//localhost:1521/TESTDB2" />
<property name = "username" value = "ORACLE2"/>
<property name = "password" value = "ORACLE2"/>
</bean>
<bean id = "transactionManager" class = "com.test.db.MultiTransactionManager">
<property name = "transactionManagers">
<list>
<bean
class = "org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name = "dataSource" ref = "dataSource" />
</bean>
<bean
class = "org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name = "dataSource" ref = "otherDataSource" />
</bean>
</list>
</property>
</bean>
</beans>
package com.test.db;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionException;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
import org.springframework.transaction.support.DefaultTransactionStatus;
public class MultiTransactionManager extends
AbstractPlatformTransactionManager {
private List<PlatformTransactionManager> transactionManagers = new ArrayList<PlatformTransactionManager>();
private ArrayList<PlatformTransactionManager> reversed;
public void setTransactionManagers(
List<PlatformTransactionManager> transactionManagers) {
this.transactionManagers = transactionManagers;
reversed = new ArrayList<PlatformTransactionManager>(
transactionManagers);
Collections.reverse(reversed);
}
@Override
protected void doBegin(Object transaction, TransactionDefinition definition)
throws TransactionException {
@SuppressWarnings("unchecked")
List<DefaultTransactionStatus> list = (List<DefaultTransactionStatus>) transaction;
for (PlatformTransactionManager transactionManager : transactionManagers) {
DefaultTransactionStatus element = (DefaultTransactionStatus) transactionManager
.getTransaction(definition);
list.add(0, element);
}
}
@Override
protected void doCommit(DefaultTransactionStatus status)
throws TransactionException {
@SuppressWarnings("unchecked")
List<DefaultTransactionStatus> list = (List<DefaultTransactionStatus>) status
.getTransaction();
int i = 0;
for (PlatformTransactionManager transactionManager : reversed) {
TransactionStatus local = list.get(i++);
try {
transactionManager.commit(local);
} catch (TransactionException e) {
logger.error("Error in commit", e);
// Rollback will ensue as long as rollbackOnCommitFailure=true
throw e;
}
}
}
@Override
protected Object doGetTransaction() throws TransactionException {
return new ArrayList<DefaultTransactionStatus>();
}
@Override
protected void doRollback(DefaultTransactionStatus status)
throws TransactionException {
@SuppressWarnings("unchecked")
List<DefaultTransactionStatus> list = (List<DefaultTransactionStatus>) status
.getTransaction();
int i = 0;
TransactionException lastException = null;
for (PlatformTransactionManager transactionManager : reversed) {
TransactionStatus local = list.get(i++);
try {
transactionManager.rollback(local);
} catch (TransactionException e) {
// Log exception and try to complete rollback
lastException = e;
logger.error("Error in rollback", e);
}
}
if (lastException!=null) {
throw lastException;
}
}
}
Мой pom.xml
<project xmlns = "http://maven.apache.org/POM/4.0.0" xmlns:xsi = "http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation = "http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.springsource.open</groupId>
<artifactId>spring-best-db-db</artifactId>
<version>2.0.0.CI-SNAPSHOT</version>
<packaging>jar</packaging>
<name>Spring Best Efforts DB-DB</name>
<description><![CDATA[Sample project showing multi DataSource transaction
processing with Spring using best efforts 1PC.
]]> </description>
<properties>
<maven.test.failure.ignore>true</maven.test.failure.ignore>
<spring.framework.version>4.1.4.RELEASE</spring.framework.version>
</properties>
<profiles>
<profile>
<id>strict</id>
<properties>
<maven.test.failure.ignore>false</maven.test.failure.ignore>
</properties>
</profile>
<profile>
<id>fast</id>
<properties>
<maven.test.skip>true</maven.test.skip>
</properties>
</profile>
</profiles>
<dependencies>
<dependency>
<groupId>hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>1.8.0.7</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>1.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.1</version>
<exclusions>
<exclusion>
<groupId>avalon-framework</groupId>
<artifactId>avalon-framework</artifactId>
</exclusion>
<exclusion>
<groupId>logkit</groupId>
<artifactId>logkit</artifactId>
</exclusion>
<exclusion>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<version>10.2.1.6</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.9</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.easymock</groupId>
<artifactId>easymock</artifactId>
<version>2.4</version>
<scope>test</scope>
</dependency>
<!-- Spring Dependencies -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>${spring.framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring.framework.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring.framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>${spring.framework.version}</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>${spring.framework.version}</version>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc8</artifactId>
<version>12.1.0.1</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.github.marcus-nl.btm/btm-spring -->
<dependency>
<groupId>com.github.marcus-nl.btm</groupId>
<artifactId>btm-spring</artifactId>
<version>3.0.0-mk1</version>
</dependency>
<dependency>
<groupId>javax</groupId>
<artifactId>javaee-api</artifactId>
<version>6.0</version>
</dependency>
</dependencies>
</project>
TABLE IN ORACLE1 database
create table T_ORACLE1 (
id integer not null primary key,
name varchar(80),
foo_date timestamp
);
Таблица в базе данных ORACLE2
create table T_ORACLE2 (
id integer not null primary key,
operation varchar(20),
name varchar(80),
audit_date timestamp
);
Я погуглил, чтобы использовать BitronixTransactionManager, но не понял, как настроить для двух источников данных.
Кажется, мы можем использовать спящий режим, но я не хочу использовать спящий режим. Я хочу либо jdbctemplate с запросом sql, либо простой jdbc.
Я получаю ошибку
java.lang.IllegalStateException: не удается активировать синхронизацию транзакций - уже активна в org.springframework.transaction.support.TransactionSynchronizationManager.initSynchronization (TransactionSynchronizationManager.java:270)
Я новичок в распределенной транзакции. Не могли бы вы мне помочь? Можно ли использовать два источника данных, но один менеджер транзакций?
Вы должны использовать источники данных XA, если хотите достичь распределенных транзакций, и менеджер транзакций также должен поддерживать транзакции XA.
Использование диспетчера транзакций Bitronix должно сделать это, но вы также должны использовать источник данных XA: похоже, что реализация на основе Oracle доступна в драйвере Oracle JDBC (см. https://docs.oracle.com/cd/E17904_01/web.1111/e13731/thirdpartytx.htm#WLJTA266).
Вы можете найти пример конфигурации Spring для Bitronix здесь: https://www.snip2code.com/Snippet/652599/Example-distributed-XA-transaction-confi/, просто не забудьте настроить свойства источников данных для использования oracle.jdbc.xa.client.OracleXADataSource вместо PostgreSQL.
Однако обратите внимание, что XA / распределенные транзакции не являются серебряной пулей и не смогут справиться с некоторыми классами проблем (например, сбои сети); вам действительно стоит подумать о возможных альтернативах, прежде чем идти по этому пути.
Я знаю, что мы можем использовать XAdatasouce, но я не хочу использовать jpa, я хочу использовать только JDBC или springtemplate.
Вы можете просто игнорировать использование части, связанной с Hibernate, в указанном файле конфигурации Spring (просто выберите элементы конфигурации источника данных и диспетчера транзакций).
Вы не можете избежать распределенной транзакции? См. stackoverflow.com/a/887426/479159