diff --git a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/BaseExceptionHandlingTest.java b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/BaseExceptionHandlingTest.java new file mode 100644 index 0000000000..2c6b4520f8 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/BaseExceptionHandlingTest.java @@ -0,0 +1,69 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.exceptionhandling; + +import java.util.Arrays; +import java.util.Map; + +import org.hibernate.cfg.AvailableSettings; + +import org.hibernate.testing.junit4.CustomParameterized; +import org.junit.runner.RunWith; +import org.junit.runners.Parameterized; + +@RunWith(CustomParameterized.class) +public abstract class BaseExceptionHandlingTest extends BaseJpaOrNativeBootstrapFunctionalTestCase { + + private static final String EXCEPTION_HANDLING_PROPERTY = + AvailableSettings.NATIVE_EXCEPTION_HANDLING_51_COMPLIANCE; + + public enum ExceptionHandlingSetting { + DEFAULT, + TRUE, + FALSE + } + + @Parameterized.Parameters(name = "Bootstrap={0}, ExceptionHandlingSetting={1}") + public static Iterable parameters() { + return Arrays.asList( new Object[][] { + { BootstrapMethod.JPA, ExceptionHandlingSetting.DEFAULT, ExceptionExpectations.jpa() }, + { BootstrapMethod.JPA, ExceptionHandlingSetting.TRUE, ExceptionExpectations.jpa() }, + { BootstrapMethod.JPA, ExceptionHandlingSetting.FALSE, ExceptionExpectations.jpa() }, + { BootstrapMethod.NATIVE, ExceptionHandlingSetting.DEFAULT, ExceptionExpectations.nativePost52() }, + { BootstrapMethod.NATIVE, ExceptionHandlingSetting.TRUE, ExceptionExpectations.nativePre52() }, + { BootstrapMethod.NATIVE, ExceptionHandlingSetting.FALSE, ExceptionExpectations.nativePost52() } + } ); + } + + private final ExceptionHandlingSetting exceptionHandlingSetting; + + protected final ExceptionExpectations exceptionExpectations; + + protected BaseExceptionHandlingTest( + BootstrapMethod bootstrapMethod, + ExceptionHandlingSetting exceptionHandlingSetting, + ExceptionExpectations exceptionExpectations) { + super( bootstrapMethod ); + this.exceptionHandlingSetting = exceptionHandlingSetting; + this.exceptionExpectations = exceptionExpectations; + } + + @Override + protected void configure(Map properties) { + switch ( exceptionHandlingSetting ) { + case DEFAULT: + // Keep the default + break; + case TRUE: + properties.put( EXCEPTION_HANDLING_PROPERTY, "true" ); + break; + case FALSE: + properties.put( EXCEPTION_HANDLING_PROPERTY, "false" ); + break; + } + } +} \ No newline at end of file diff --git a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/BaseJpaOrNativeBootstrapFunctionalTestCase.java b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/BaseJpaOrNativeBootstrapFunctionalTestCase.java new file mode 100644 index 0000000000..732cd4c0d6 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/BaseJpaOrNativeBootstrapFunctionalTestCase.java @@ -0,0 +1,374 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.exceptionhandling; + +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import javax.persistence.EntityManager; +import javax.persistence.SharedCacheMode; +import javax.persistence.ValidationMode; +import javax.persistence.spi.PersistenceUnitTransactionType; + +import org.hibernate.HibernateException; +import org.hibernate.Session; +import org.hibernate.boot.registry.BootstrapServiceRegistry; +import org.hibernate.boot.registry.BootstrapServiceRegistryBuilder; +import org.hibernate.boot.registry.StandardServiceRegistryBuilder; +import org.hibernate.boot.registry.internal.StandardServiceRegistryImpl; +import org.hibernate.bytecode.enhance.spi.EnhancementContext; +import org.hibernate.cfg.Configuration; +import org.hibernate.cfg.Environment; +import org.hibernate.dialect.Dialect; +import org.hibernate.engine.spi.SessionFactoryImplementor; +import org.hibernate.engine.spi.SessionImplementor; +import org.hibernate.internal.util.config.ConfigurationHelper; +import org.hibernate.jpa.AvailableSettings; +import org.hibernate.jpa.HibernatePersistenceProvider; +import org.hibernate.jpa.boot.spi.Bootstrap; +import org.hibernate.jpa.boot.spi.PersistenceUnitDescriptor; +import org.hibernate.resource.transaction.spi.TransactionCoordinator; + +import org.hibernate.testing.AfterClassOnce; +import org.hibernate.testing.BeforeClassOnce; +import org.hibernate.testing.cache.CachingRegionFactory; +import org.hibernate.testing.junit4.BaseUnitTestCase; +import org.junit.After; + +import static org.junit.Assert.fail; + +/** + * A base class for all functional tests. + */ +public abstract class BaseJpaOrNativeBootstrapFunctionalTestCase extends BaseUnitTestCase { + + // IMPL NOTE : Here we use @Before and @After (instead of @BeforeClassOnce and @AfterClassOnce like we do in + // BaseCoreFunctionalTestCase) because the old HEM test methodology was to create an EMF for each test method. + + private static final Dialect dialect = Dialect.getDialect(); + + public enum BootstrapMethod { + JPA, + NATIVE + } + + private final BootstrapMethod bootstrapMethod; + + private StandardServiceRegistryImpl serviceRegistry; + private SessionFactoryImplementor sessionFactory; + + private Session session; + + protected Dialect getDialect() { + return dialect; + } + + protected SessionFactoryImplementor sessionFactory() { + return sessionFactory; + } + + protected StandardServiceRegistryImpl serviceRegistry() { + return serviceRegistry; + } + + protected Session openSession() throws HibernateException { + session = sessionFactory().openSession(); + return session; + } + + protected EntityManager openEntityManager() throws HibernateException { + return openSession().unwrap( EntityManager.class ); + } + + protected BaseJpaOrNativeBootstrapFunctionalTestCase(BootstrapMethod bootstrapMethod) { + this.bootstrapMethod = bootstrapMethod; + } + + @BeforeClassOnce + @SuppressWarnings( {"UnusedDeclaration"}) + public void buildSessionOrEntityManagerFactory() { + switch ( bootstrapMethod ) { + case JPA: + buildEntityManagerFactory(); + break; + case NATIVE: + buildSessionFactory(); + break; + } + + afterSessionOrEntityManagerFactoryBuilt(); + } + + private void buildEntityManagerFactory() { + log.trace( "Building EntityManagerFactory" ); + + Properties properties = buildProperties(); + ArrayList classes = new ArrayList(); + + classes.addAll( Arrays.asList( getAnnotatedClasses() ) ); + properties.put( AvailableSettings.LOADED_CLASSES, classes ); + + sessionFactory = Bootstrap.getEntityManagerFactoryBuilder( + buildPersistenceUnitDescriptor(), + properties + ).build().unwrap( SessionFactoryImplementor.class ); + + serviceRegistry = (StandardServiceRegistryImpl) sessionFactory.getServiceRegistry() + .getParentServiceRegistry(); + } + + private void buildSessionFactory() { + // for now, build the configuration to get all the property settings + Configuration configuration = new Configuration(); + configuration.setProperties( buildProperties() ); + + Class[] annotatedClasses = getAnnotatedClasses(); + if ( annotatedClasses != null ) { + for ( Class annotatedClass : annotatedClasses ) { + configuration.addAnnotatedClass( annotatedClass ); + } + } + + BootstrapServiceRegistry bootRegistry = buildBootstrapServiceRegistry(); + serviceRegistry = buildServiceRegistry( bootRegistry, configuration ); + sessionFactory = ( SessionFactoryImplementor ) configuration.buildSessionFactory( serviceRegistry ); + + afterSessionOrEntityManagerFactoryBuilt(); + } + + + private PersistenceUnitDescriptor buildPersistenceUnitDescriptor() { + return new TestingPersistenceUnitDescriptorImpl( getClass().getSimpleName() ); + } + + public static class TestingPersistenceUnitDescriptorImpl implements PersistenceUnitDescriptor { + private final String name; + + public TestingPersistenceUnitDescriptorImpl(String name) { + this.name = name; + } + + @Override + public URL getPersistenceUnitRootUrl() { + return null; + } + + @Override + public String getName() { + return name; + } + + @Override + public String getProviderClassName() { + return HibernatePersistenceProvider.class.getName(); + } + + @Override + public boolean isUseQuotedIdentifiers() { + return false; + } + + @Override + public boolean isExcludeUnlistedClasses() { + return false; + } + + @Override + public PersistenceUnitTransactionType getTransactionType() { + return null; + } + + @Override + public ValidationMode getValidationMode() { + return null; + } + + @Override + public SharedCacheMode getSharedCacheMode() { + return null; + } + + @Override + public List getManagedClassNames() { + return null; + } + + @Override + public List getMappingFileNames() { + return null; + } + + @Override + public List getJarFileUrls() { + return null; + } + + @Override + public Object getNonJtaDataSource() { + return null; + } + + @Override + public Object getJtaDataSource() { + return null; + } + + @Override + public Properties getProperties() { + return null; + } + + @Override + public ClassLoader getClassLoader() { + return null; + } + + @Override + public ClassLoader getTempClassLoader() { + return null; + } + + @Override + public void pushClassTransformer(EnhancementContext enhancementContext) { + } + } + + private BootstrapServiceRegistry buildBootstrapServiceRegistry() { + final BootstrapServiceRegistryBuilder builder = new BootstrapServiceRegistryBuilder(); + builder.applyClassLoader( getClass().getClassLoader() ); + prepareBootstrapRegistryBuilder( builder ); + return builder.build(); + } + + protected void prepareBootstrapRegistryBuilder(BootstrapServiceRegistryBuilder builder) { + } + + private StandardServiceRegistryImpl buildServiceRegistry(BootstrapServiceRegistry bootRegistry, Configuration configuration) { + Properties properties = new Properties(); + properties.putAll( configuration.getProperties() ); + Environment.verifyProperties( properties ); + ConfigurationHelper.resolvePlaceHolders( properties ); + + StandardServiceRegistryBuilder cfgRegistryBuilder = configuration.getStandardServiceRegistryBuilder(); + + StandardServiceRegistryBuilder registryBuilder = new StandardServiceRegistryBuilder( bootRegistry, cfgRegistryBuilder.getAggregatedCfgXml() ) + .applySettings( properties ); + + return (StandardServiceRegistryImpl) registryBuilder.build(); + } + + private Properties buildProperties() { + Properties properties = Environment.getProperties(); + + properties.put( org.hibernate.cfg.AvailableSettings.CACHE_REGION_FACTORY, CachingRegionFactory.class.getName() ); + for ( Map.Entry entry : getCachedClasses().entrySet() ) { + properties.put( AvailableSettings.CLASS_CACHE_PREFIX + "." + entry.getKey().getName(), entry.getValue() ); + } + for ( Map.Entry entry : getCachedCollections().entrySet() ) { + properties.put( AvailableSettings.COLLECTION_CACHE_PREFIX + "." + entry.getKey(), entry.getValue() ); + } + + configure( properties ); + + if ( createSchema() ) { + properties.put( org.hibernate.cfg.AvailableSettings.HBM2DDL_AUTO, "create-drop" ); + } + properties.put( org.hibernate.cfg.AvailableSettings.USE_NEW_ID_GENERATOR_MAPPINGS, "true" ); + properties.put( org.hibernate.cfg.AvailableSettings.DIALECT, getDialect().getClass().getName() ); + + return properties; + } + + protected void configure(Map properties) { + } + + protected static final Class[] NO_CLASSES = new Class[0]; + + protected Class[] getAnnotatedClasses() { + return NO_CLASSES; + } + + public Map getCachedClasses() { + return new HashMap<>(); + } + + public Map getCachedCollections() { + return new HashMap<>(); + } + + protected void afterSessionOrEntityManagerFactoryBuilt() { + } + + protected boolean createSchema() { + return true; + } + + @After + public final void afterTest() throws Exception { + completeStrayTransaction(); + + cleanupSession(); + + } + + @AfterClassOnce + @SuppressWarnings( {"UnusedDeclaration"}) + protected void releaseSessionFactory() { + if ( sessionFactory == null ) { + return; + } + sessionFactory.close(); + sessionFactory = null; + if ( serviceRegistry != null ) { + if ( serviceRegistry.isActive() ) { + try { + serviceRegistry.destroy(); + } + catch (Exception ignore) { + } + fail( "StandardServiceRegistry was not closed down as expected" ); + } + } + serviceRegistry=null; + } + + private void completeStrayTransaction() { + if ( session == null ) { + // nothing to do + return; + } + + if ( ( (SessionImplementor) session ).isClosed() ) { + // nothing to do + return; + } + + if ( !session.isConnected() ) { + // nothing to do + return; + } + + final TransactionCoordinator.TransactionDriver tdc = + ( (SessionImplementor) session ).getTransactionCoordinator().getTransactionDriverControl(); + + if ( tdc.getStatus().canRollback() ) { + session.getTransaction().rollback(); + } + session.close(); + } + + private void cleanupSession() { + if ( session != null && ! ( (SessionImplementor) session ).isClosed() ) { + session.close(); + } + session = null; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ConstraintViolationExceptionHandlingTest.java b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ConstraintViolationExceptionHandlingTest.java new file mode 100644 index 0000000000..02675330e1 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ConstraintViolationExceptionHandlingTest.java @@ -0,0 +1,180 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.exceptionhandling; + +import javax.persistence.Column; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.Session; +import org.hibernate.Transaction; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.junit.Assert.fail; + +@TestForIssue(jiraKey = "HHH-12666") +public class ConstraintViolationExceptionHandlingTest extends BaseExceptionHandlingTest { + + public ConstraintViolationExceptionHandlingTest(BootstrapMethod bootstrapMethod, + ExceptionHandlingSetting exceptionHandlingSetting, + ExceptionExpectations exceptionExpectations) { + super( bootstrapMethod, exceptionHandlingSetting, exceptionExpectations ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + A.class, + AInfo.class + }; + } + + @Test + public void testConstraintViolationOnSave() { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + AInfo aInfo = new AInfo(); + aInfo.uniqueString = "unique"; + s.persist( aInfo ); + s.flush(); + s.clear(); + try { + AInfo anotherAInfo = new AInfo(); + anotherAInfo.uniqueString = "unique"; + s.save( anotherAInfo ); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected) { + exceptionExpectations.onConstraintViolationOnSaveAndSaveOrUpdate( expected ); + } + finally { + tx.rollback(); + s.close(); + } + } + + @Test + public void testConstraintViolationOnSaveOrUpdate() { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + AInfo aInfo = new AInfo(); + aInfo.uniqueString = "unique"; + s.persist( aInfo ); + s.flush(); + s.clear(); + try { + AInfo anotherAInfo = new AInfo(); + anotherAInfo.uniqueString = "unique"; + s.saveOrUpdate( anotherAInfo ); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected) { + exceptionExpectations.onConstraintViolationOnSaveAndSaveOrUpdate( expected ); + } + finally { + tx.rollback(); + s.close(); + } + } + + @Test + public void testConstraintViolationOnPersist() { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + AInfo aInfo = new AInfo(); + aInfo.uniqueString = "unique"; + s.persist( aInfo ); + s.flush(); + s.clear(); + try { + AInfo anotherAInfo = new AInfo(); + anotherAInfo.uniqueString = "unique"; + s.persist( anotherAInfo ); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected) { + exceptionExpectations.onConstraintViolationOnPersistAndMergeAndFlush( expected ); + } + finally { + tx.rollback(); + s.close(); + } + } + + @Test + public void testConstraintViolationOnMerge() { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + AInfo aInfo = new AInfo(); + aInfo.uniqueString = "unique"; + s.persist( aInfo ); + s.flush(); + s.clear(); + try { + AInfo anotherAInfo = new AInfo(); + anotherAInfo.uniqueString = "unique"; + s.merge( anotherAInfo ); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected) { + exceptionExpectations.onConstraintViolationOnPersistAndMergeAndFlush( expected ); + } + finally { + tx.rollback(); + s.close(); + } + } + + @Test + public void testConstraintViolationUpdateFlush() { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + AInfo aInfo = new AInfo(); + aInfo.uniqueString = "unique"; + s.persist( aInfo ); + AInfo aInfo1 = new AInfo(); + s.persist( aInfo1 ); + s.flush(); + s.clear(); + try { + aInfo1 = s.get( AInfo.class, aInfo1.id ); + aInfo1.uniqueString = "unique"; + s.flush(); + } + catch (RuntimeException expected) { + exceptionExpectations.onConstraintViolationOnPersistAndMergeAndFlush( expected ); + } + finally { + tx.rollback(); + s.close(); + } + } + + @Entity(name = "A") + public static class A { + @Id + private long id; + + @ManyToOne(optional = false) + private AInfo aInfo; + } + + @Entity(name = "AInfo") + public static class AInfo { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private long id; + + @Column(unique = true) + private String uniqueString; + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ExceptionExpectations.java b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ExceptionExpectations.java new file mode 100644 index 0000000000..84d1dc66f3 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/ExceptionExpectations.java @@ -0,0 +1,108 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.exceptionhandling; + +import java.sql.SQLException; +import javax.persistence.PersistenceException; + +import org.hibernate.TransientObjectException; +import org.hibernate.exception.ConstraintViolationException; + +import static org.hamcrest.CoreMatchers.instanceOf; +import static org.junit.Assert.assertThat; + +interface ExceptionExpectations { + + static ExceptionExpectations jpa() { + return new ExceptionExpectations() { + @Override + public void onConstraintViolationOnSaveAndSaveOrUpdate(RuntimeException e) { + assertThat( e, instanceOf( PersistenceException.class ) ); + } + + @Override + public void onConstraintViolationOnPersistAndMergeAndFlush(RuntimeException e) { + assertThat( e, instanceOf( PersistenceException.class ) ); + assertThat( e.getCause(), instanceOf( ConstraintViolationException.class ) ); + assertThat( e.getCause().getCause(), instanceOf( SQLException.class ) ); + } + + @Override + public void onTransientObjectOnSaveAndSaveOrUpdate(RuntimeException e) { + assertThat( e, instanceOf( TransientObjectException.class ) ); + } + + @Override + public void onTransientObjectOnPersistAndMergeAndFlush(RuntimeException e) { + assertThat( e, instanceOf( IllegalStateException.class ) ); + assertThat( e.getCause(), instanceOf( TransientObjectException.class ) ); + } + }; + } + + static ExceptionExpectations nativePre52() { + return new ExceptionExpectations() { + @Override + public void onConstraintViolationOnSaveAndSaveOrUpdate(RuntimeException e) { + assertThat( e, instanceOf( ConstraintViolationException.class ) ); + assertThat( e.getCause(), instanceOf( SQLException.class ) ); + } + + @Override + public void onConstraintViolationOnPersistAndMergeAndFlush(RuntimeException e) { + assertThat( e, instanceOf( ConstraintViolationException.class ) ); + assertThat( e.getCause(), instanceOf( SQLException.class ) ); + } + + @Override + public void onTransientObjectOnSaveAndSaveOrUpdate(RuntimeException e) { + assertThat( e, instanceOf( TransientObjectException.class ) ); + } + + @Override + public void onTransientObjectOnPersistAndMergeAndFlush(RuntimeException e) { + assertThat( e, instanceOf( TransientObjectException.class ) ); + } + }; + } + + static ExceptionExpectations nativePost52() { + return new ExceptionExpectations() { + @Override + public void onConstraintViolationOnSaveAndSaveOrUpdate(RuntimeException e) { + assertThat( e, instanceOf( ConstraintViolationException.class ) ); + assertThat( e.getCause(), instanceOf( SQLException.class ) ); + } + + @Override + public void onConstraintViolationOnPersistAndMergeAndFlush(RuntimeException e) { + assertThat( e, instanceOf( PersistenceException.class ) ); + assertThat( e.getCause(), instanceOf( ConstraintViolationException.class ) ); + assertThat( e.getCause().getCause(), instanceOf( SQLException.class ) ); + } + + @Override + public void onTransientObjectOnSaveAndSaveOrUpdate(RuntimeException e) { + assertThat( e, instanceOf( TransientObjectException.class ) ); + } + + @Override + public void onTransientObjectOnPersistAndMergeAndFlush(RuntimeException e) { + assertThat( e, instanceOf( IllegalStateException.class ) ); + assertThat( e.getCause(), instanceOf( TransientObjectException.class ) ); + } + }; + } + + void onConstraintViolationOnSaveAndSaveOrUpdate(RuntimeException e); + + void onConstraintViolationOnPersistAndMergeAndFlush(RuntimeException e); + + void onTransientObjectOnSaveAndSaveOrUpdate(RuntimeException e); + + void onTransientObjectOnPersistAndMergeAndFlush(RuntimeException e); +} diff --git a/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/TransientObjectExceptionHandlingTest.java b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/TransientObjectExceptionHandlingTest.java new file mode 100644 index 0000000000..c5a5c56a95 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/test/exceptionhandling/TransientObjectExceptionHandlingTest.java @@ -0,0 +1,155 @@ +/* + * Hibernate, Relational Persistence for Idiomatic Java + * + * License: GNU Lesser General Public License (LGPL), version 2.1 or later. + * See the lgpl.txt file in the root directory or . + */ +package org.hibernate.test.exceptionhandling; + +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.Id; +import javax.persistence.ManyToOne; + +import org.hibernate.Session; +import org.hibernate.Transaction; + +import org.hibernate.testing.TestForIssue; +import org.junit.Test; + +import static org.junit.Assert.fail; + +@TestForIssue(jiraKey = "HHH-12666") +public class TransientObjectExceptionHandlingTest extends BaseExceptionHandlingTest { + + public TransientObjectExceptionHandlingTest(BootstrapMethod bootstrapMethod, + ExceptionHandlingSetting exceptionHandlingSetting, + ExceptionExpectations exceptionExpectations) { + super( bootstrapMethod, exceptionHandlingSetting, exceptionExpectations ); + } + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { + A.class, + AInfo.class + }; + } + + @Test + public void testSave() { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + A a = new A(); + a.id = 1; + a.aInfo = new AInfo(); + try { + s.save( a ); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected) { + exceptionExpectations.onTransientObjectOnSaveAndSaveOrUpdate( expected ); + } + finally { + tx.rollback(); + s.close(); + } + } + + @Test + public void testSaveOrUpdate() { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + A a = new A(); + a.id = 1; + a.aInfo = new AInfo(); + try { + s.saveOrUpdate( a ); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected) { + exceptionExpectations.onTransientObjectOnSaveAndSaveOrUpdate( expected ); + } + finally { + tx.rollback(); + s.close(); + } + } + + @Test + public void testPersist() { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + A a = new A(); + a.id = 1; + a.aInfo = new AInfo(); + try { + s.persist( a ); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected) { + exceptionExpectations.onTransientObjectOnPersistAndMergeAndFlush( expected ); + } + finally { + tx.rollback(); + s.close(); + } + } + + @Test + public void testMerge() { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + A a = new A(); + a.id = 1; + a.aInfo = new AInfo(); + try { + s.merge( a ); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected) { + exceptionExpectations.onTransientObjectOnPersistAndMergeAndFlush( expected ); + } + finally { + tx.rollback(); + s.close(); + } + } + + @Test + public void testUpdateFlush() { + Session s = openSession(); + Transaction tx = s.beginTransaction(); + A a = new A(); + a.id = 1; + a.aInfo = new AInfo(); + try { + s.update( a ); + s.flush(); + fail( "should have thrown an exception" ); + } + catch (RuntimeException expected) { + exceptionExpectations.onTransientObjectOnPersistAndMergeAndFlush( expected ); + } + finally { + tx.rollback(); + s.close(); + } + } + + @Entity(name = "A") + public static class A { + @Id + private long id; + + @ManyToOne(optional = false) + private AInfo aInfo; + } + + @Entity(name = "AInfo") + public static class AInfo { + @Id + @GeneratedValue + private long id; + } +}