Cómo configurar múltiples gestores de transacciones con Spring+DBUnit+JUnit
spring-transactions (3)
En una palabra
Mi línea de comandos de la aplicación Java copia datos de una fuente de datos a otra sin usar XA. He configurado dos fuentes de datos separadas y me gustaría una prueba JUnit que pueda retrotraer datos en ambos orígenes de datos. Yo uso DBUnit para cargar datos en la base de datos "fuente", pero no puedo hacer que esto se revierta. Puedo hacer que el origen de datos "objetivo" se revierte.
Mi código
Dada esta configuración ...
<tx:annotation-driven />
<!-- note the default transactionManager name on this one -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSourceA" />
</bean>
<bean id="transactionManagerTarget" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSourceB" />
</bean>
y este código ...
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:resources/spring-context.xml",
"classpath:resources/spring-db.xml"})
@Transactional
@TransactionConfiguration(transactionManager = "transactionManagerTarget", defaultRollback = true)
public class MyIntegrationTest {
@Autowired
private MyService service;
@Autowired
@Qualifier("dataSourceA")
private DataSource dataSourceA;
private IDataSet loadedDataSet;
/**
* Required by DbUnit
*/
@Before
public void setUp() throws Exception {
SybaseInsertIdentityOperation.TRUNCATE_TABLE.execute(getConnection(), getDataSet());
SybaseInsertIdentityOperation.INSERT.execute(getConnection(), getDataSet());
}
/**
* Required by DbUnit
*/
protected IDataSet getDataSet() throws Exception {
loadedDataSet = DbUnitHelper.getDataSetFromFile(getConnection(), "TestData.xml");
return loadedDataSet;
}
/**
* Required by DbUnit
*/
protected IDatabaseConnection getConnection() throws Exception{
return new DatabaseConnection(dataSourceA.getConnection());
}
@Test
public void testSomething() {
// service.doCopyStuff();
}
}
El problema, como yo lo veo, es que @TransactionConfiguration
solo establece el origen de datos objetivo para habilitar una reversión. DBUnit se está dataSourceA
explícitamente y está recogiendo el gestor de transacciones predeterminado llamado transactionManager
(no estoy seguro de cómo) al que no se le indicó que lo retrotraiga.
Pregunta
¿Cómo puedo decirle a los dos gerentes de transacción que se reviertan?
¿Puedo usar un único administrador de transacciones cuando mis fuentes de datos no son compatibles con las transacciones XA?
Nota: La aplicación no requiere un administrador de transacciones en dataSourceA cuando se ejecuta en producción, ya que solo será de solo lectura. Este problema es solo para mis clases de exámenes.
He usado transacciones XA y reversiones en pruebas JUnit usando el código abierto TM Atomikos. Una buena característica es que Atomikos permite el uso de fuentes de datos no compatibles con XA para participar en transacciones XA. Consulte este enlace para ver un ejemplo: http://www.atomikos.com/Documentation/NonXaDataSource
Por otro lado, si XA es una solución decente para sus problemas JUnit es otra historia. ¿Sus pruebas se enfocan mucho en la implementación de la base de datos (Sybase) o es más sobre la lógica de Java? Normalmente configuro bases de datos incrustadas como Apache Derby o HQSQL para pruebas JUnit. Entonces no tengo que preocuparme demasiado por las limpiezas, ya que GC se encargará de eso :)
Una posible solución alternativa sería introducir un bean auxiliar anotado como @Transactional("transactionManagerTarget")
y dejar su prueba anotada como @Transactional("transactionManager")
, configurando ambos con defaultRollback = true
. Su prueba tendría que llamar al bean auxiliar, que a su vez llamaría a su bean de servicio bajo prueba. Esto debería provocar que la transacción alrededor de su servicio se retrotraiga, luego la transacción alrededor de DBUnit.
Aunque es un poco desordenado.
Otros posibles enfoques:
- Al utilizar una base de datos en memoria como H2 en lugar de su base de datos de producción, puede configurar esto para eliminar todos sus datos cuando sea necesario.
- Permita que DBUnit confirme, y tenga una transacción de compensación en su método de desmontaje para borrar los datos.
Use el elemento <qualifier>
dentro de la definición de su administrador de transacciones.
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSourceA" />
<qualifier value="transactionManager" />
</bean>
<bean id="transactionManagerTarget" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSourceB" />
<qualifier value="transactionManagerTarget" />
</bean>
Luego puede referenciar cuál desea usar directamente en la anotación @Transactional
, es decir,
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations={"classpath:resources/spring-context.xml",
"classpath:resources/spring-db.xml"})
@Transactional("transactionManagerTarget")
@TransactionConfiguration(defaultRollback = true)
public class MyIntegrationTest {
...