HHH-5315:

- work units executed both in the before tx completion process and in the synchronization
- each test class has its own H2 database
- tests in a JTA environment

git-svn-id: https://svn.jboss.org/repos/hibernate/core/trunk@19928 1b8cb986-b30d-0410-93ca-fae66ebed9b2
This commit is contained in:
Adam Warski 2010-07-10 12:48:19 +00:00
parent 8ff871941d
commit b50d8268d5
8 changed files with 194 additions and 7 deletions

View File

@ -118,6 +118,12 @@
<artifactId>javassist</artifactId> <artifactId>javassist</artifactId>
<scope>test</scope> <scope>test</scope>
</dependency> </dependency>
<!-- For JTA tests -->
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-testing</artifactId>
<scope>test</scope>
</dependency>
</dependencies> </dependencies>
<dependencyManagement> <dependencyManagement>
@ -137,6 +143,11 @@
<artifactId>hibernate-entitymanager</artifactId> <artifactId>hibernate-entitymanager</artifactId>
<version>${version}</version> <version>${version}</version>
</dependency> </dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-testing</artifactId>
<version>${version}</version>
</dependency>
<dependency> <dependency>
<groupId>org.hibernate</groupId> <groupId>org.hibernate</groupId>
<artifactId>hibernate-tools</artifactId> <artifactId>hibernate-tools</artifactId>

View File

@ -37,11 +37,14 @@ import org.hibernate.envers.tools.Pair;
import org.hibernate.FlushMode; import org.hibernate.FlushMode;
import org.hibernate.Session; import org.hibernate.Session;
import javax.transaction.Synchronization;
/** /**
* @author Adam Warski (adam at warski dot org) * @author Adam Warski (adam at warski dot org)
*/ */
public class AuditProcess implements BeforeTransactionCompletionProcess { public class AuditProcess implements BeforeTransactionCompletionProcess, Synchronization {
private final RevisionInfoGenerator revisionInfoGenerator; private final RevisionInfoGenerator revisionInfoGenerator;
private final SessionImplementor session;
private final LinkedList<AuditWorkUnit> workUnits; private final LinkedList<AuditWorkUnit> workUnits;
private final Queue<AuditWorkUnit> undoQueue; private final Queue<AuditWorkUnit> undoQueue;
@ -49,8 +52,9 @@ public class AuditProcess implements BeforeTransactionCompletionProcess {
private Object revisionData; private Object revisionData;
public AuditProcess(RevisionInfoGenerator revisionInfoGenerator) { public AuditProcess(RevisionInfoGenerator revisionInfoGenerator, SessionImplementor session) {
this.revisionInfoGenerator = revisionInfoGenerator; this.revisionInfoGenerator = revisionInfoGenerator;
this.session = session;
workUnits = new LinkedList<AuditWorkUnit>(); workUnits = new LinkedList<AuditWorkUnit>();
undoQueue = new LinkedList<AuditWorkUnit>(); undoQueue = new LinkedList<AuditWorkUnit>();
@ -153,4 +157,12 @@ public class AuditProcess implements BeforeTransactionCompletionProcess {
session.flush(); session.flush();
} }
} }
// Synchronization methods
public void beforeCompletion() {
doBeforeTransactionCompletion(session);
}
public void afterCompletion(int status) { }
} }

View File

@ -52,10 +52,22 @@ public class AuditProcessManager {
AuditProcess auditProcess = auditProcesses.get(transaction); AuditProcess auditProcess = auditProcesses.get(transaction);
if (auditProcess == null) { if (auditProcess == null) {
// No worries about registering a transaction twice - a transaction is single thread // No worries about registering a transaction twice - a transaction is single thread
auditProcess = new AuditProcess(revisionInfoGenerator); auditProcess = new AuditProcess(revisionInfoGenerator, session);
auditProcesses.put(transaction, auditProcess); auditProcesses.put(transaction, auditProcess);
/*
* HHH-5315: the process must be both a BeforeTransactionCompletionProcess and a TX Synchronization.
*
* In a resource-local tx env, the process is called after the flush, and populates the audit tables.
* Also, any exceptions that occur during that are propagated (if a Synchronization was used, the exceptions
* would be eaten).
*
* In a JTA env, the before transaction completion is called before the flush, so not all changes are yet
* written. However, Synchronization-s do propagate exceptions, so they can be safely used.
*/
session.getActionQueue().registerProcess(auditProcess); session.getActionQueue().registerProcess(auditProcess);
session.getTransaction().registerSynchronization(auditProcess);
session.getActionQueue().registerProcess(new AfterTransactionCompletionProcess() { session.getActionQueue().registerProcess(new AfterTransactionCompletionProcess() {
public void doAfterTransactionCompletion(boolean success, SessionImplementor session) { public void doAfterTransactionCompletion(boolean success, SessionImplementor session) {
auditProcesses.remove(transaction); auditProcesses.remove(transaction);

View File

@ -27,9 +27,13 @@ import java.io.IOException;
import javax.persistence.EntityManager; import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory; import javax.persistence.EntityManagerFactory;
import org.hibernate.cfg.Environment;
import org.hibernate.ejb.AvailableSettings;
import org.hibernate.envers.AuditReader; import org.hibernate.envers.AuditReader;
import org.hibernate.envers.AuditReaderFactory; import org.hibernate.envers.AuditReaderFactory;
import org.hibernate.envers.event.AuditEventListener; import org.hibernate.envers.event.AuditEventListener;
import org.hibernate.testing.tm.ConnectionProviderImpl;
import org.hibernate.testing.tm.TransactionManagerLookupImpl;
import org.testng.annotations.*; import org.testng.annotations.*;
import org.hibernate.ejb.Ejb3Configuration; import org.hibernate.ejb.Ejb3Configuration;
@ -89,11 +93,15 @@ public abstract class AbstractEntityTest {
initListeners(); initListeners();
} }
cfg.configure("hibernate.test.cfg.xml");
if (auditStrategy != null && !"".equals(auditStrategy)) { if (auditStrategy != null && !"".equals(auditStrategy)) {
cfg.setProperty("org.hibernate.envers.audit_strategy", auditStrategy); cfg.setProperty("org.hibernate.envers.audit_strategy", auditStrategy);
} }
cfg.configure("hibernate.test.cfg.xml"); // Separate database for each test class
cfg.setProperty("hibernate.connection.url", "jdbc:h2:mem:" + this.getClass().getName());
configure(cfg); configure(cfg);
emf = cfg.buildEntityManagerFactory(); emf = cfg.buildEntityManagerFactory();
@ -117,4 +125,10 @@ public abstract class AbstractEntityTest {
public Ejb3Configuration getCfg() { public Ejb3Configuration getCfg() {
return cfg; return cfg;
} }
protected void addJTAConfig(Ejb3Configuration cfg) {
cfg.setProperty("connection.provider_class", ConnectionProviderImpl.class.getName());
cfg.setProperty(Environment.TRANSACTION_MANAGER_STRATEGY, TransactionManagerLookupImpl.class.getName());
cfg.setProperty(AvailableSettings.TRANSACTION_TYPE, "JTA");
}
} }

View File

@ -0,0 +1,74 @@
/*
* Hibernate, Relational Persistence for Idiomatic Java
*
* Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
* indicated by the @author tags or express copyright attribution
* statements applied by the authors. All third-party contributions are
* distributed under license by Red Hat Middleware LLC.
*
* This copyrighted material is made available to anyone wishing to use, modify,
* copy, or redistribute it subject to the terms and conditions of the GNU
* Lesser General Public License, 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 Lesser General Public License
* for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this distribution; if not, write to:
* Free Software Foundation, Inc.
* 51 Franklin Street, Fifth Floor
* Boston, MA 02110-1301 USA
*/
package org.hibernate.envers.test.integration.jta;
import org.hibernate.ejb.Ejb3Configuration;
import org.hibernate.envers.test.AbstractEntityTest;
import org.hibernate.envers.test.entities.StrTestEntity;
import org.hibernate.envers.test.integration.reventity.ExceptionListenerRevEntity;
import org.hibernate.testing.tm.SimpleJtaTransactionManagerImpl;
import org.testng.annotations.Test;
import javax.persistence.EntityManager;
/**
* Same as {@link org.hibernate.envers.test.integration.reventity.ExceptionListener}, but in a JTA environment.
* @author Adam Warski (adam at warski dot org)
*/
public class JtaExceptionListener extends AbstractEntityTest {
public void configure(Ejb3Configuration cfg) {
cfg.addAnnotatedClass(StrTestEntity.class);
cfg.addAnnotatedClass(ExceptionListenerRevEntity.class);
addJTAConfig(cfg);
}
@Test(expectedExceptions = RuntimeException.class)
public void testTransactionRollback() throws Exception {
SimpleJtaTransactionManagerImpl.getInstance().begin();
// Trying to persist an entity - however the listener should throw an exception, so the entity
// shouldn't be persisted
newEntityManager();
EntityManager em = getEntityManager();
em.getTransaction().begin();
StrTestEntity te = new StrTestEntity("x");
em.persist(te);
SimpleJtaTransactionManagerImpl.getInstance().commit();
}
@Test(dependsOnMethods = "testTransactionRollback")
public void testDataNotPersisted() throws Exception {
SimpleJtaTransactionManagerImpl.getInstance().begin();
// Checking if the entity became persisted
newEntityManager();
EntityManager em = getEntityManager();
Long count = (Long) em.createQuery("select count(s) from StrTestEntity s where s.str = 'x'").getSingleResult();
assert count == 0l;
SimpleJtaTransactionManagerImpl.getInstance().commit();
}
}

View File

@ -0,0 +1,63 @@
package org.hibernate.envers.test.integration.jta;
import org.hibernate.ejb.Ejb3Configuration;
import org.hibernate.envers.test.AbstractEntityTest;
import org.hibernate.envers.test.entities.IntTestEntity;
import org.hibernate.testing.tm.SimpleJtaTransactionManagerImpl;
import org.testng.annotations.Test;
import javax.persistence.EntityManager;
import java.util.Arrays;
/**
* Same as {@link org.hibernate.envers.test.integration.basic.Simple}, but in a JTA environment.
* @author Adam Warski (adam at warski dot org)
*/
public class JtaTransaction extends AbstractEntityTest {
private Integer id1;
public void configure(Ejb3Configuration cfg) {
cfg.addAnnotatedClass(IntTestEntity.class);
addJTAConfig(cfg);
}
@Test
public void initData() throws Exception {
SimpleJtaTransactionManagerImpl.getInstance().begin();
newEntityManager();
EntityManager em = getEntityManager();
em.joinTransaction();
IntTestEntity ite = new IntTestEntity(10);
em.persist(ite);
id1 = ite.getId();
SimpleJtaTransactionManagerImpl.getInstance().commit();
//
SimpleJtaTransactionManagerImpl.getInstance().begin();
newEntityManager();
em = getEntityManager();
ite = em.find(IntTestEntity.class, id1);
ite.setNumber(20);
SimpleJtaTransactionManagerImpl.getInstance().commit();
}
@Test(dependsOnMethods = "initData")
public void testRevisionsCounts() throws Exception {
assert Arrays.asList(1, 2).equals(getAuditReader().getRevisions(IntTestEntity.class, id1));
}
@Test(dependsOnMethods = "initData")
public void testHistoryOfId1() {
IntTestEntity ver1 = new IntTestEntity(10, id1);
IntTestEntity ver2 = new IntTestEntity(20, id1);
assert getAuditReader().find(IntTestEntity.class, id1, 1).equals(ver1);
assert getAuditReader().find(IntTestEntity.class, id1, 2).equals(ver2);
}
}

View File

@ -12,10 +12,10 @@
<property name="format_sql">true</property> <property name="format_sql">true</property>
<property name="dialect">org.hibernate.dialect.H2Dialect</property> <property name="dialect">org.hibernate.dialect.H2Dialect</property>
<property name="connection.url">jdbc:h2:mem:envers</property>
<property name="connection.driver_class">org.h2.Driver</property> <property name="connection.driver_class">org.h2.Driver</property>
<property name="connection.username">sa</property> <property name="connection.username">sa</property>
<property name="connection.password"></property> <property name="connection.password"></property>
<!-- The connection URL is set in AbstractEntityTest -->
<!--<property name="dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>--> <!--<property name="dialect">org.hibernate.dialect.MySQL5InnoDBDialect</property>-->
<!--<property name="connection.url">jdbc:mysql:///hibernate_tests?useUnicode=true&amp;characterEncoding=UTF-8</property>--> <!--<property name="connection.url">jdbc:mysql:///hibernate_tests?useUnicode=true&amp;characterEncoding=UTF-8</property>-->

View File

@ -38,6 +38,7 @@
<package name="org.hibernate.envers.test.integration.interfaces.hbm.propertiesAudited2.subclass" /> <package name="org.hibernate.envers.test.integration.interfaces.hbm.propertiesAudited2.subclass" />
<package name="org.hibernate.envers.test.integration.interfaces.hbm.propertiesAudited2.joined" /> <package name="org.hibernate.envers.test.integration.interfaces.hbm.propertiesAudited2.joined" />
<package name="org.hibernate.envers.test.integration.interfaces.hbm.propertiesAudited2.union" /> <package name="org.hibernate.envers.test.integration.interfaces.hbm.propertiesAudited2.union" />
<package name="org.hibernate.envers.test.integration.jta" />
<package name="org.hibernate.envers.test.integration.manytomany" /> <package name="org.hibernate.envers.test.integration.manytomany" />
<package name="org.hibernate.envers.test.integration.manytomany.biowned" /> <package name="org.hibernate.envers.test.integration.manytomany.biowned" />
<package name="org.hibernate.envers.test.integration.manytomany.inverseToSuperclass" /> <package name="org.hibernate.envers.test.integration.manytomany.inverseToSuperclass" />