diff --git a/impl/core/src/main/java/br/gov/frameworkdemoiselle/transaction/Transaction.java b/impl/core/src/main/java/br/gov/frameworkdemoiselle/transaction/Transaction.java index 6a5bdb9..666aa26 100644 --- a/impl/core/src/main/java/br/gov/frameworkdemoiselle/transaction/Transaction.java +++ b/impl/core/src/main/java/br/gov/frameworkdemoiselle/transaction/Transaction.java @@ -51,6 +51,7 @@ public interface Transaction extends Serializable { * Indicates whether the given transaction is still active. * * @return a boolean + * @throws TransactionException if an unexpected error occurs */ boolean isActive(); @@ -58,29 +59,38 @@ public interface Transaction extends Serializable { * Indicates whether the given transaction is already marked to be rolled back. * * @return a boolean + * @throws TransactionException if an unexpected error occurs */ boolean isMarkedRollback(); /** * Create a new transaction and associate it with the current thread. + * + * @throws TransactionException if the transaction can't be started */ void begin(); /** * Complete the transaction associated with the current thread. When this method completes, the thread is no longer * associated with a transaction. + * + * @throws TransactionException if the transaction can't be commited */ void commit(); /** * Roll back the transaction associated with the current thread. When this method completes, the thread is no longer * associated with a transaction. + * + * @throws TransactionException if the transaction can't be rolled back */ void rollback(); /** * Modify the transaction associated with the current thread such that the only possible outcome of the transaction * is to roll back the transaction. + * + * @throws TransactionException if an unexpected error occurs */ void setRollbackOnly(); } diff --git a/impl/core/src/main/java/br/gov/frameworkdemoiselle/transaction/TransactionException.java b/impl/core/src/main/java/br/gov/frameworkdemoiselle/transaction/TransactionException.java new file mode 100644 index 0000000..27c20a4 --- /dev/null +++ b/impl/core/src/main/java/br/gov/frameworkdemoiselle/transaction/TransactionException.java @@ -0,0 +1,64 @@ +/* + * Demoiselle Framework + * Copyright (C) 2010 SERPRO + * ---------------------------------------------------------------------------- + * This file is part of Demoiselle Framework. + * + * Demoiselle Framework is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public License version 3 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public License version 3 + * along with this program; if not, see + * or write to the Free Software Foundation, Inc., 51 Franklin Street, + * Fifth Floor, Boston, MA 02110-1301, USA. + * ---------------------------------------------------------------------------- + * Este arquivo é parte do Framework Demoiselle. + * + * O Framework Demoiselle é um software livre; você pode redistribuí-lo e/ou + * modificá-lo dentro dos termos da GNU LGPL versão 3 como publicada pela Fundação + * do Software Livre (FSF). + * + * Este programa é distribuído na esperança que possa ser útil, mas SEM NENHUMA + * GARANTIA; sem uma garantia implícita de ADEQUAÇÃO a qualquer MERCADO ou + * APLICAÇÃO EM PARTICULAR. Veja a Licença Pública Geral GNU/LGPL em português + * para maiores detalhes. + * + * Você deve ter recebido uma cópia da GNU LGPL versão 3, sob o título + * "LICENCA.txt", junto com esse programa. Se não, acesse + * ou escreva para a Fundação do Software Livre (FSF) Inc., + * 51 Franklin St, Fifth Floor, Boston, MA 02111-1301, USA. + */ +package br.gov.frameworkdemoiselle.transaction; + +import br.gov.frameworkdemoiselle.DemoiselleException; + + +/** + * Represents exceptions that occur during framework managed transactions. A transaction + * exception will be thrown when a transaction can't be started, commited or rolled back. + * + * @author serpro + * + */ +public class TransactionException extends DemoiselleException { + + private static final long serialVersionUID = 1L; + + public TransactionException(String message, Throwable cause) { + super(message, cause); + } + + public TransactionException(Throwable cause) { + super(cause); + } + + public TransactionException(String message) { + super(message); + } +} diff --git a/impl/extension/jdbc/src/main/java/br/gov/frameworkdemoiselle/internal/producer/ConnectionProducer.java b/impl/extension/jdbc/src/main/java/br/gov/frameworkdemoiselle/internal/producer/ConnectionProducer.java index 02bb3c3..c4ae6b2 100644 --- a/impl/extension/jdbc/src/main/java/br/gov/frameworkdemoiselle/internal/producer/ConnectionProducer.java +++ b/impl/extension/jdbc/src/main/java/br/gov/frameworkdemoiselle/internal/producer/ConnectionProducer.java @@ -129,6 +129,7 @@ public class ConnectionProducer implements Serializable { } else { try { connection = producer.create(name).getConnection(); + setTransactionIsolationLevel(connection); disableAutoCommit(connection); cache.put(name, connection); @@ -143,6 +144,14 @@ public class ConnectionProducer implements Serializable { return connection; } + private void setTransactionIsolationLevel(Connection connection) { + try { + connection.setTransactionIsolation(Connection.TRANSACTION_READ_COMMITTED); + } catch (SQLException cause) { + getLogger().debug(getBundle().getString("set-autocommit-failed")); + } + } + private void disableAutoCommit(Connection connection) { try { connection.setAutoCommit(false); diff --git a/impl/extension/jdbc/src/main/java/br/gov/frameworkdemoiselle/transaction/JDBCTransaction.java b/impl/extension/jdbc/src/main/java/br/gov/frameworkdemoiselle/transaction/JDBCTransaction.java index 0bcf0b1..237994a 100644 --- a/impl/extension/jdbc/src/main/java/br/gov/frameworkdemoiselle/transaction/JDBCTransaction.java +++ b/impl/extension/jdbc/src/main/java/br/gov/frameworkdemoiselle/transaction/JDBCTransaction.java @@ -94,7 +94,7 @@ public class JDBCTransaction implements Transaction { status = getProducer().getStatus(connection); status.setActive(false); } catch (Exception cause) { - throw new DemoiselleException(cause); + throw new TransactionException(cause); } } } @@ -112,7 +112,7 @@ public class JDBCTransaction implements Transaction { status = getProducer().getStatus(connection); status.setActive(false); } catch (Exception cause) { - throw new DemoiselleException(cause); + throw new TransactionException(cause); } } } diff --git a/impl/extension/jpa/src/main/java/br/gov/frameworkdemoiselle/transaction/JPATransaction.java b/impl/extension/jpa/src/main/java/br/gov/frameworkdemoiselle/transaction/JPATransaction.java index 546f181..c954abd 100644 --- a/impl/extension/jpa/src/main/java/br/gov/frameworkdemoiselle/transaction/JPATransaction.java +++ b/impl/extension/jpa/src/main/java/br/gov/frameworkdemoiselle/transaction/JPATransaction.java @@ -46,6 +46,8 @@ import javax.persistence.EntityTransaction; import br.gov.frameworkdemoiselle.annotation.Priority; import br.gov.frameworkdemoiselle.internal.producer.EntityManagerProducer; import br.gov.frameworkdemoiselle.util.Beans; +import br.gov.frameworkdemoiselle.util.NameQualifier; +import br.gov.frameworkdemoiselle.util.ResourceBundle; /** * Represents the strategy destinated to manage JPA transactions. @@ -59,6 +61,8 @@ public class JPATransaction implements Transaction { private static final long serialVersionUID = 1L; private EntityManagerProducer producer; + + private ResourceBundle bundle; private EntityManagerProducer getProducer() { if (producer == null) { @@ -67,6 +71,14 @@ public class JPATransaction implements Transaction { return producer; } + + private ResourceBundle getBundle() { + if (bundle==null) { + bundle = Beans.getReference(ResourceBundle.class , new NameQualifier("demoiselle-jpa-bundle")); + } + + return bundle; + } public Collection getDelegate() { return getProducer().getCache().values(); @@ -75,23 +87,58 @@ public class JPATransaction implements Transaction { @Override public void begin() { EntityTransaction transaction; - for (EntityManager entityManager : getDelegate()) { - transaction = entityManager.getTransaction(); - - if (!transaction.isActive()) { - transaction.begin(); + + try { + for (EntityManager entityManager : getDelegate()) { + transaction = entityManager.getTransaction(); + + if (!transaction.isActive()) { + transaction.begin(); + } } } + catch(Exception e) { + /* + Precisamos marcar para rollback todos os EntityManagers que conseguimos iniciar + antes da exceção ser disparada. + */ + setRollbackOnly(); + + throw new TransactionException(e); + } } @Override public void commit() { EntityTransaction transaction; - for (EntityManager entityManager : getDelegate()) { - transaction = entityManager.getTransaction(); - - if (transaction.isActive()) { - transaction.commit(); + + int commitedEntityManagers = 0; + try { + for (EntityManager entityManager : getDelegate()) { + transaction = entityManager.getTransaction(); + + if (transaction.isActive()) { + transaction.commit(); + commitedEntityManagers++; + } + } + } + catch(Exception e) { + /* + Precisamos marcar para rollback todos os EntityManagers que conseguimos iniciar + antes da exceção ser disparada. + */ + setRollbackOnly(); + + /* + Esse erro pode ser bastante problemático, pois EntityManagers já encerrados com commit + não podem ser revertidos. Por isso anexamos uma mensagem recomendando ao usuário que considere o uso de JTA em sua aplicação. + */ + if (commitedEntityManagers>0) { + throw new TransactionException(getBundle().getString("partial-rollback-problem"),e); + } + else { + throw new TransactionException(e); } } } @@ -99,25 +146,37 @@ public class JPATransaction implements Transaction { @Override public void rollback() { EntityTransaction transaction; - for (EntityManager entityManager : getDelegate()) { - transaction = entityManager.getTransaction(); - - if (transaction.isActive()) { - transaction.rollback(); + + try { + for (EntityManager entityManager : getDelegate()) { + transaction = entityManager.getTransaction(); + + if (transaction.isActive()) { + transaction.rollback(); + } } } + catch(Exception e) { + throw new TransactionException(e); + } } @Override public void setRollbackOnly() { EntityTransaction transaction; - for (EntityManager entityManager : getDelegate()) { - transaction = entityManager.getTransaction(); - - if (transaction.isActive()) { - transaction.setRollbackOnly(); + + try { + for (EntityManager entityManager : getDelegate()) { + transaction = entityManager.getTransaction(); + + if (transaction.isActive()) { + transaction.setRollbackOnly(); + } } } + catch(Exception e) { + throw new TransactionException(e); + } } @Override @@ -125,14 +184,19 @@ public class JPATransaction implements Transaction { boolean active = false; EntityTransaction transaction; - for (EntityManager entityManager : getDelegate()) { - transaction = entityManager.getTransaction(); - - if (transaction.isActive()) { - active = true; - break; + try { + for (EntityManager entityManager : getDelegate()) { + transaction = entityManager.getTransaction(); + + if (transaction.isActive()) { + active = true; + break; + } } } + catch (Exception e) { + throw new TransactionException(e); + } return active; } @@ -142,14 +206,19 @@ public class JPATransaction implements Transaction { boolean rollbackOnly = false; EntityTransaction transaction; - for (EntityManager entityManager : getDelegate()) { - transaction = entityManager.getTransaction(); - - if (transaction.isActive() && transaction.getRollbackOnly()) { - rollbackOnly = true; - break; + try { + for (EntityManager entityManager : getDelegate()) { + transaction = entityManager.getTransaction(); + + if (transaction.isActive() && transaction.getRollbackOnly()) { + rollbackOnly = true; + break; + } } } + catch(Exception e) { + throw new TransactionException(e); + } return rollbackOnly; } diff --git a/impl/extension/jpa/src/main/resources/demoiselle-jpa-bundle.properties b/impl/extension/jpa/src/main/resources/demoiselle-jpa-bundle.properties index 1d5cce2..2afd0b9 100644 --- a/impl/extension/jpa/src/main/resources/demoiselle-jpa-bundle.properties +++ b/impl/extension/jpa/src/main/resources/demoiselle-jpa-bundle.properties @@ -47,4 +47,5 @@ malformed-jpql=Consulta JPQL mal formada para pagina\u00E7\u00E3o de dados. invalid-scope-for-entity-manager=O escopo especificado para o Entity Manager \u00E9 inv\u00E1lido. Por favor informe um dos escopos v\u00E1lidos para a propriedade frameworkdemoiselle.persistence.entitymanager.scope\: request, session, view, conversation, application entity-manager-scope-not-defined=N\u00E3o foi poss\u00EDvel ler o escopo configurado para o Entity Manager, usando o escopo padr\u00E3o [{0}] passivable-scope-without-optimistic-lock=Um Entity Manager armazenado no escopo [{0}] suporta apenas trava otimista com vers\u00E3o. Use o tipo adequado ou remova a trava. (veja [LockModeType.OPTIMISTIC_FORCE_INCREMENT]) -defining-entity-manager-scope=Definindo escopo [{0}] para produtor de Entity Manager \ No newline at end of file +defining-entity-manager-scope=Definindo escopo [{0}] para produtor de Entity Manager +partial-rollback-problem=N\u00E3o foi poss\u00EDvel reverter a transa\u00E7\u00E3o para todos os EntityManager's configurados, favor considerar o uso de transa\u00E7\u00F5es JTA em uma aplica\u00E7\u00E3o com m\u00FAltiplos EntityManager's diff --git a/impl/extension/jpa/src/test/java/transaction/interceptor/JPATransactionTest.java b/impl/extension/jpa/src/test/java/transaction/interceptor/JPATransactionTest.java index f03e131..b86368f 100644 --- a/impl/extension/jpa/src/test/java/transaction/interceptor/JPATransactionTest.java +++ b/impl/extension/jpa/src/test/java/transaction/interceptor/JPATransactionTest.java @@ -8,6 +8,8 @@ import static junit.framework.Assert.assertTrue; import javax.inject.Inject; import javax.persistence.EntityManager; +import junit.framework.Assert; + import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit.Arquillian; import org.jboss.shrinkwrap.api.spec.WebArchive; @@ -18,6 +20,7 @@ import org.junit.runner.RunWith; import test.Tests; import br.gov.frameworkdemoiselle.annotation.Name; import br.gov.frameworkdemoiselle.transaction.TransactionContext; +import br.gov.frameworkdemoiselle.transaction.TransactionException; @RunWith(Arquillian.class) public class JPATransactionTest { @@ -74,6 +77,20 @@ public class JPATransactionTest { assertEquals("desc-1", entity1.getDescription()); assertEquals("desc-2", entity2.getDescription()); } + + @Test + public void commitWithException() { + tb.commitWithException(); + + try { + tb.commitWithException(); + Assert.fail(); + } + catch(TransactionException te) { + te.printStackTrace(); + //success + } + } @Test public void rollbackWithSuccess() { diff --git a/impl/extension/jpa/src/test/java/transaction/interceptor/TransactionalBusiness.java b/impl/extension/jpa/src/test/java/transaction/interceptor/TransactionalBusiness.java index c99f3cd..2ea45e9 100644 --- a/impl/extension/jpa/src/test/java/transaction/interceptor/TransactionalBusiness.java +++ b/impl/extension/jpa/src/test/java/transaction/interceptor/TransactionalBusiness.java @@ -42,6 +42,15 @@ public class TransactionalBusiness { em1.persist(entity1); em2.persist(entity2); } + + @Transactional + public void commitWithException() { + MyEntity1 entity1 = new MyEntity1(); + entity1.setId(createId("id-1")); + entity1.setDescription("desc-1"); + + em1.persist(entity1); + } @Transactional public void rollbackWithSuccess() throws Exception { diff --git a/impl/extension/jpa/src/test/java/transaction/manual/JPATransactionTest.java b/impl/extension/jpa/src/test/java/transaction/manual/JPATransactionTest.java index fd93d97..be941d8 100644 --- a/impl/extension/jpa/src/test/java/transaction/manual/JPATransactionTest.java +++ b/impl/extension/jpa/src/test/java/transaction/manual/JPATransactionTest.java @@ -10,6 +10,8 @@ import javax.inject.Inject; import javax.persistence.EntityManager; import javax.persistence.TransactionRequiredException; +import junit.framework.Assert; + import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit.Arquillian; import org.jboss.shrinkwrap.api.spec.WebArchive; @@ -21,6 +23,7 @@ import br.gov.frameworkdemoiselle.annotation.Name; import br.gov.frameworkdemoiselle.transaction.JPATransaction; import br.gov.frameworkdemoiselle.transaction.Transaction; import br.gov.frameworkdemoiselle.transaction.TransactionContext; +import br.gov.frameworkdemoiselle.transaction.TransactionException; import br.gov.frameworkdemoiselle.util.Beans; import br.gov.frameworkdemoiselle.util.NameQualifier; @@ -81,6 +84,42 @@ public class JPATransactionTest { assertEquals("desc-1", persisted1.getDescription()); assertEquals("desc-2", persisted2.getDescription()); } + + @Test + public void commitWithException() { + Transaction transaction = transactionContext.getCurrentTransaction(); + + MyEntity1 entity1 = new MyEntity1(); + entity1.setId(createId("id-7")); + entity1.setDescription("desc-7"); + + assertFalse(transaction.isActive()); + transaction.begin(); + assertTrue(transaction.isActive()); + + em1.persist(entity1); + transaction.commit(); + em1.clear(); + + entity1 = new MyEntity1(); + entity1.setId(createId("id-7")); + entity1.setDescription("desc-7"); + + assertFalse(transaction.isActive()); + transaction.begin(); + assertTrue(transaction.isActive()); + + em1.persist(entity1); + + try { + transaction.commit(); + Assert.fail(); + } + catch(TransactionException te) { + te.printStackTrace(); + //success + } + } @Test(expected = TransactionRequiredException.class) public void checkNoTransactionAutomaticallyLoaded() { diff --git a/impl/extension/jta/src/main/java/br/gov/frameworkdemoiselle/transaction/JTATransaction.java b/impl/extension/jta/src/main/java/br/gov/frameworkdemoiselle/transaction/JTATransaction.java index c24099b..c8f2d6c 100644 --- a/impl/extension/jta/src/main/java/br/gov/frameworkdemoiselle/transaction/JTATransaction.java +++ b/impl/extension/jta/src/main/java/br/gov/frameworkdemoiselle/transaction/JTATransaction.java @@ -77,7 +77,7 @@ public class JTATransaction implements Transaction { return getDelegate().getStatus() != STATUS_NO_TRANSACTION; } catch (SystemException cause) { - throw new DemoiselleException(cause); + throw new TransactionException(cause); } } @@ -91,7 +91,7 @@ public class JTATransaction implements Transaction { || getDelegate().getStatus() == STATUS_ROLLEDBACK; } catch (SystemException cause) { - throw new DemoiselleException(cause); + throw new TransactionException(cause); } } @@ -104,7 +104,7 @@ public class JTATransaction implements Transaction { getDelegate().begin(); } catch (Exception cause) { - throw new DemoiselleException(cause); + throw new TransactionException(cause); } } @@ -117,7 +117,7 @@ public class JTATransaction implements Transaction { getDelegate().commit(); } catch (Exception cause) { - throw new DemoiselleException(cause); + throw new TransactionException(cause); } } @@ -130,7 +130,7 @@ public class JTATransaction implements Transaction { getDelegate().rollback(); } catch (SystemException cause) { - throw new DemoiselleException(cause); + throw new TransactionException(cause); } } @@ -143,7 +143,7 @@ public class JTATransaction implements Transaction { getDelegate().setRollbackOnly(); } catch (SystemException cause) { - throw new DemoiselleException(cause); + throw new TransactionException(cause); } } } diff --git a/impl/extension/jta/src/test/java/jtatransaction/interceptor/InterceptorJTATransactionTest.java b/impl/extension/jta/src/test/java/jtatransaction/interceptor/InterceptorJTATransactionTest.java index 9c66e23..52be7f8 100644 --- a/impl/extension/jta/src/test/java/jtatransaction/interceptor/InterceptorJTATransactionTest.java +++ b/impl/extension/jta/src/test/java/jtatransaction/interceptor/InterceptorJTATransactionTest.java @@ -8,6 +8,8 @@ import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.TransactionRequiredException; +import junit.framework.Assert; + import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit.Arquillian; import org.jboss.shrinkwrap.api.spec.WebArchive; @@ -19,6 +21,7 @@ import test.Tests; import br.gov.frameworkdemoiselle.DemoiselleException; import br.gov.frameworkdemoiselle.transaction.JTATransaction; import br.gov.frameworkdemoiselle.transaction.TransactionContext; +import br.gov.frameworkdemoiselle.transaction.TransactionException; import br.gov.frameworkdemoiselle.util.Beans; @RunWith(Arquillian.class) @@ -79,6 +82,23 @@ public class InterceptorJTATransactionTest { assertFalse(transactionContext.getCurrentTransaction().isActive()); } + + @Test + public void commitWithException() { + + TransactionalBusiness business = Beans.getReference(TransactionalBusiness.class); + + business.commitWithException(); + + try { + business.commitWithException(); + Assert.fail(); + } + catch(TransactionException te) { + te.printStackTrace(); + //sucess + } + } @Test(expected = TransactionRequiredException.class) public void checkNoTransactionAutomaticallyLoaded() { diff --git a/impl/extension/jta/src/test/java/jtatransaction/interceptor/TransactionalBusiness.java b/impl/extension/jta/src/test/java/jtatransaction/interceptor/TransactionalBusiness.java index 70640c3..764ab5b 100644 --- a/impl/extension/jta/src/test/java/jtatransaction/interceptor/TransactionalBusiness.java +++ b/impl/extension/jta/src/test/java/jtatransaction/interceptor/TransactionalBusiness.java @@ -35,6 +35,16 @@ public class TransactionalBusiness { em1.flush(); em2.flush(); } + + @Transactional + public void commitWithException() { + MyEntity1 entity1 = new MyEntity1(); + entity1.setId(createId("id-1")); + entity1.setDescription("desc-1"); + + em1.joinTransaction(); + em1.persist(entity1); + } public void checkNoTransactionAutomaticallyLoaded() { MyEntity1 entity = new MyEntity1(); diff --git a/impl/extension/jta/src/test/java/jtatransaction/manual/ManualJTATransactionTest.java b/impl/extension/jta/src/test/java/jtatransaction/manual/ManualJTATransactionTest.java index 3024066..1caf9a7 100644 --- a/impl/extension/jta/src/test/java/jtatransaction/manual/ManualJTATransactionTest.java +++ b/impl/extension/jta/src/test/java/jtatransaction/manual/ManualJTATransactionTest.java @@ -9,6 +9,8 @@ import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import javax.persistence.TransactionRequiredException; +import junit.framework.Assert; + import org.jboss.arquillian.container.test.api.Deployment; import org.jboss.arquillian.junit.Arquillian; import org.jboss.shrinkwrap.api.spec.WebArchive; @@ -20,6 +22,7 @@ import test.Tests; import br.gov.frameworkdemoiselle.transaction.JTATransaction; import br.gov.frameworkdemoiselle.transaction.Transaction; import br.gov.frameworkdemoiselle.transaction.TransactionContext; +import br.gov.frameworkdemoiselle.transaction.TransactionException; import br.gov.frameworkdemoiselle.util.Beans; @RunWith(Arquillian.class) @@ -97,6 +100,47 @@ public class ManualJTATransactionTest { assertEquals("desc-1", persisted1.getDescription()); assertEquals("desc-2", persisted2.getDescription()); } + + @Test + public void commitWithException() { + Transaction transaction = transactionContext.getCurrentTransaction(); + + MyEntity1 entity1 = new MyEntity1(); + entity1.setId(createId("id-1")); + entity1.setDescription("desc-1"); + + assertFalse(transaction.isActive()); + + transaction.begin(); + assertTrue(transaction.isActive()); + + em1.joinTransaction(); + + em1.persist(entity1); + + transaction.commit(); + em1.clear(); + + entity1 = new MyEntity1(); + entity1.setId(createId("id-1")); + entity1.setDescription("desc-1"); + + assertFalse(transaction.isActive()); + transaction.begin(); + assertTrue(transaction.isActive()); + + em1.joinTransaction(); + em1.persist(entity1); + + try { + transaction.commit(); + Assert.fail(); + } + catch(TransactionException e) { + e.printStackTrace(); + //success + } + } @Test(expected = TransactionRequiredException.class) public void checkNoTransactionAutomaticallyLoaded() { -- libgit2 0.21.2