executeandfetchtable java oracle junit transactions sql2o

java - executeandfetchtable - sql2o github



Transacción compartida entre diferentes conexiones de OracleDB (7)

Si su problema solo necesita ser "resuelto" (p. Ej., No es una "mejor práctica"), independientemente del rendimiento, para que las pruebas se completen en orden, intente configurar:

config.setMaximumPoolSize(1);

Es posible que deba establecer un tiempo de espera más alto, ya que las pruebas en la cola de prueba esperarán su turno y podrían expirar. Por lo general, no sugiero soluciones como esta, pero su configuración no es óptima, generará condiciones de carrera y pérdida de datos. Sin embargo, buena suerte con las pruebas.

Después de varios días para investigar sobre el problema, decidí enviar esta pregunta porque aparentemente no tiene sentido lo que está sucediendo.

El caso

Mi computadora está configurada con una base de datos Oracle Express local. Tengo un proyecto JAVA con varias pruebas JUnit que extienden una clase principal (sé que no es una "mejor práctica") que abre una conexión OJDBC (utilizando un conjunto de conexiones estáticas de Hikari de 10 conexiones) en el método @Before y Retroceda en el @Después.

public class BaseLocalRollbackableConnectorTest { private static Logger logger = LoggerFactory.getLogger(BaseLocalRollbackableConnectorTest.class); protected Connection connection; @Before public void setup() throws SQLException{ logger.debug("Getting connection and setting autocommit to FALSE"); connection = StaticConnectionPool.getPooledConnection(); } @After public void teardown() throws SQLException{ logger.debug("Rollback connection"); connection.rollback(); logger.debug("Close connection"); connection.close(); }

StacicConnectionPool

public class StaticConnectionPool { private static HikariDataSource ds; private static final Logger log = LoggerFactory.getLogger(StaticConnectionPool.class); public static Connection getPooledConnection() throws SQLException { if (ds == null) { log.debug("Initializing ConnectionPool"); HikariConfig config = new HikariConfig(); config.setMaximumPoolSize(10); config.setDataSourceClassName("oracle.jdbc.pool.OracleDataSource"); config.addDataSourceProperty("url", "jdbc:oracle:thin:@localhost:1521:XE"); config.addDataSourceProperty("user", "MyUser"); config.addDataSourceProperty("password", "MyPsw"); config.setAutoCommit(false); ds = new HikariDataSource(config); } return ds.getConnection(); }

}

Este proyecto tiene cientos de pruebas (no en paralelo) que utilizan esta conexión (en localhost) para ejecutar consultas (insertar / actualizar y seleccionar) usando Sql2o pero la transacción y el cierre de la conexión se gestionan solo externamente (según la prueba anterior). La base de datos está completamente vacía para tener pruebas de ACID.

Entonces el resultado esperado es insertar algo en DB, hace las aserciones y luego retroceder. de esta forma, la segunda prueba no encontrará ningún dato agregado por la prueba anterior para mantener el nivel de aislamiento.

El problema Ejecutar todas las pruebas juntas (secuencialmente), el 90% de las veces funcionan correctamente. el 10% de una o dos pruebas, al azar, fallan, porque hay datos sucios en la base de datos (duplicados, por ejemplo, únicos) en pruebas anteriores. mirando los registros, las reversiones de pruebas anteriores se realizaron correctamente. De hecho, si reviso la base de datos, está vacía) Si ejecuto estas pruebas en un servidor con mayor rendimiento pero con el mismo JDK, el mismo Oracle DB XE, esta proporción de fallas se incrementa al 50%.

Esto es muy extraño y no tengo ni idea porque las conexiones son diferentes entre las pruebas y se llama al rollback cada vez. El nivel de aislamiento de JDBC se LEA COMPROMETIDO, por lo que incluso si usamos la misma conexión, esto no debería crear ningún problema incluso con la misma conexión. Entonces mi pregunta es: ¿Por qué sucede? ¿Tiene alguna idea? ¿Es la reversión de JDBC sincrónica, como sé o podría haber algunos casos en los que puede avanzar aunque no se haya completado por completo?

Estos son mis parámetros principales de DB: procesa 100 sesiones 172 transacciones 189


Intente configurar la auditoría en todas las declaraciones en Oracle. Luego encuentra sesiones que viven simultáneamente. Creo que existe el problema en las pruebas. La reversión de JDBC es sincrónica. Commit se puede configurar como commit nowait pero no creo que lo hagas especial en tus pruebas.

También preste atención en dml paralelo. En una tabla en la misma transacción, no puede hacer el dml paralelo + cualquier otro dml sin confirmar porque obtiene Ora-12838.

¿Tienes una transacción autónoma? La lógica empresarial en las pruebas puede retrotraerlas manualmente y durante las pruebas la transacción autónoma es como otra sesión y no se ve ninguna confirmación desde la sesión principal.


No estoy seguro de si esto lo solucionará, pero podrías intentarlo:

public class BaseLocalRollbackableConnectorTest { private static Logger logger = LoggerFactory.getLogger(BaseLocalRollbackableConnectorTest.class); protected Connection connection; private Savepoint savepoint; @Before public void setup() throws SQLException{ logger.debug("Getting connection and setting autocommit to FALSE"); connection = StaticConnectionPool.getPooledConnection(); savepoint = connection.setSavepoint(); } @After public void teardown() throws SQLException{ logger.debug("Rollback connection"); connection.rollback(savepoint); logger.debug("Close connection"); connection.close(); while (!connection.isClosed()) { try { Thread.sleep(500); } catch (InterruptedException ie) {} } }

En realidad, hay dos "soluciones" allí: bucle después del cierre para asegurarse de que la conexión esté cerrada antes de regresar al grupo. En segundo lugar, cree un punto de guardado antes de la prueba y restáurelo después.


Como todas las otras respuestas han señalado, es difícil decir qué falla con la información proporcionada. Además, incluso si logra encontrar el problema actual mediante auditoría , no significa que sus pruebas estén libres de errores de datos.

Pero aquí hay una alternativa: porque ya tiene un esquema de base de datos en blanco, puede exportarlo a un archivo SQL. Luego, antes de cada prueba:

  1. Suelta el esquema
  2. Vuelva a crear el esquema de nuevo
  3. Alimente los datos de la muestra (si es necesario)

Ahorraría mucho tiempo en la depuración, asegúrese de que la base de datos esté en su estado original cada vez que ejecute las pruebas. Todo esto se puede hacer en un script.

Nota: Oracle Enterprise tiene la función de flashback para admitir su tipo de operación. Además, si puede administrar Hibernate y los "me gusta", existen otras bases de datos en memoria (como HSQLDB ) que puede utilizar para aumentar la velocidad de prueba y mantener la coherencia en su conjunto de datos.

EDITAR: Parece inverosímil, pero por las dudas: connection.rollback() solo tiene efecto si no se llama a commit () antes.


Después de todas las confirmaciones de tus respuestas de que no estoy enojado con las reversiones y el comportamiento de las transacciones en las pruebas unitarias, revisé profundamente todas las consultas y todas las causas posibles y, afortunadamente (sí, furtivamente ... aunque me avergüence, me arrepiento gratis) todo funciona como se espera (Transacciones, Antes, Después, etc.).

Hay algunas consultas que obtienen el resultado de algunas vistas complejas (y radicalmente profundas configuradas en la capa DAO) para identificar la información de una sola fila. Esta vista se basa en el MAX of a TIMESTAMP con el fin de identificar lo último de un evento en particular (en la vida real, los eventos se producen después de varios meses).

Al hacer la preparación de la base de datos para continuar con las pruebas unitarias, estos eventos se agregan secuencialmente en cada prueba. En algunos casos, cuando estas consultas de inserción en la misma transacción son particularmente rápidas, se agregan más eventos relacionados con el mismo objeto en el mismo milisegundo (el TIMESTAMP se agrega manualmente usando un JODA DateTime) y el MAX de una fecha, devuelve dos o más valores Por esta razón, se explica el hecho de que en las computadoras / servidores más eficientes, esto sucedía con más frecuencia que las más lentas. Esta vista se usa en más pruebas y, según la prueba, el error es diferente y aleatorio (valor NULL agregado como clave principal, clave principal duplicada, etc.).

Por ejemplo: en la siguiente consulta INSERT SELECT es evidente este error:

INSERT INTO TABLE1 (ID,COL1,COL2,COL3) SELECT :myId, T.VAL1, T.VAL2, T.VAL3 FROM MyView v JOIN Table2 t on t.ID = v.ID WHERE ........

el parámetro myId se agrega luego como parámetro Sql2o

MyView es

SELECT ID, MAX(MDATE) FROM TABLEV WHERE.... GROUP BY ...

Cuando la vista devuelve al menos 2 resultados debido a la misma fecha máxima, falla porque la identificación es fija (generada por una secuencia al principio pero almacenada usando el parámetro por segunda vez). Esto genera la restricción PK violada.

Este es solo un caso pero me vuelve loco a mí (y a mis colegas) debido a este comportamiento aleatorio ...

Agregar un descanso de 1 milisegundo entre esos eventos insertar, es fijo. ahora estamos trabajando para encontrar una solución diferente, aunque este caso (un usuario que interactúa dos veces en el mismo milisegundo) no puede suceder en el sistema de producción, ¡pero lo importante es que no ocurre ninguna magia como de costumbre!

Ahora puedes insultarme :)


Puedes hacer una cosa aumentar el no. de conexiones en el tamaño máximo de la agrupación y revertir la operación en el mismo lugar donde se cometió la operación en lugar de usarla en la declaración posterior. Espero que funcione


Me he encontrado con el mismo problema hace 2-3 años (he pasado mucho tiempo para aclarar esto). El problema es que @Before y @After no siempre son realmente secuenciales. [Puede probar esto iniciando el proceso en la depuración y colocar algunos puntos de interrupción en los métodos anotados.

Editar: No estaba lo suficientemente claro como señaló Tonio . El orden de @Before y @After se garantiza en términos de ejecución antes de la prueba y luego. El problema era en mi caso que a veces el @Before y el @ After estaban en mal estado.

Esperado:

@Before -> test1 () -> @After -> @Before -> @ test2 () -> @ After

Pero a veces experimenté el siguiente orden:

@Before -> test1 () -> @Before -> @After -> @ test2 () -> @ After

No estoy seguro si es un error o no. En el momento en que cavé en la profundidad de la misma y parecía algo así como (¿procesador?) Magia relacionada con la programación. La solución a ese problema fue en nuestro caso ejecutar las pruebas en un único hilo y llamar manualmente los procesos de inicio y limpieza ... Algo como esto:

public class BaseLocalRollbackableConnectorTest { private static Logger logger = LoggerFactory.getLogger(BaseLocalRollbackableConnectorTest.class); protected Connection connection; public void setup() throws SQLException{ logger.debug("Getting connection and setting autocommit to FALSE"); connection = StaticConnectionPool.getPooledConnection(); } public void teardown() throws SQLException{ logger.debug("Rollback connection"); connection.rollback(); logger.debug("Close connection"); connection.close(); } @Test public void test() throws Exception{ try{ setup(); //test }catch(Exception e){ //making sure that the teardown will run even if the test is failing teardown(); throw e; } teardown(); } }

No lo he probado, pero una solución mucho más elegante podría ser sincronizar los métodos @Before y @After en el mismo objeto. Por favor, actualízame si tienes el chanse para intentarlo. :)

Espero que también solucione tu problema.