From a7fccaa377b306b60102c868dc69b750c4d37ba8 Mon Sep 17 00:00:00 2001 From: Gail Badner Date: Tue, 13 Nov 2018 00:07:07 -0800 Subject: [PATCH] HHH-13050 : Add test with a batch that fails when addToBatch() is called --- .../JtaWithStatementsBatchTest.java | 248 ------------------ .../batch/AbstractJtaBatchTest.java | 140 ++++++++++ .../batch/JtaWithFailingBatchTest.java | 170 ++++++++++++ .../batch/JtaWithStatementsBatchTest.java | 147 +++++++++++ 4 files changed, 457 insertions(+), 248 deletions(-) delete mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/JtaWithStatementsBatchTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/AbstractJtaBatchTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/JtaWithFailingBatchTest.java create mode 100644 hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/JtaWithStatementsBatchTest.java diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/JtaWithStatementsBatchTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/JtaWithStatementsBatchTest.java deleted file mode 100644 index 224b3d7932..0000000000 --- a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/JtaWithStatementsBatchTest.java +++ /dev/null @@ -1,248 +0,0 @@ -/* - * 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 . - */ - -/* - * 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 http://www.gnu.org/licenses/lgpl-2.1.html - */ -package org.hibernate.jpa.test.transaction; - -import java.sql.PreparedStatement; -import java.sql.SQLException; -import java.util.ArrayList; -import java.util.List; -import java.util.Map; -import javax.persistence.Entity; -import javax.persistence.EntityManager; -import javax.persistence.EntityTransaction; -import javax.persistence.FlushModeType; -import javax.persistence.GeneratedValue; -import javax.persistence.GenerationType; -import javax.persistence.Id; - -import org.hibernate.annotations.GenericGenerator; -import org.hibernate.annotations.Parameter; -import org.hibernate.cfg.AvailableSettings; -import org.hibernate.engine.jdbc.batch.internal.AbstractBatchImpl; -import org.hibernate.engine.jdbc.batch.internal.BatchBuilderImpl; -import org.hibernate.engine.jdbc.batch.internal.BatchBuilderInitiator; -import org.hibernate.engine.jdbc.batch.internal.BatchingBatch; -import org.hibernate.engine.jdbc.batch.spi.Batch; -import org.hibernate.engine.jdbc.batch.spi.BatchKey; -import org.hibernate.engine.jdbc.spi.JdbcCoordinator; -import org.hibernate.internal.CoreMessageLogger; -import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; - -import org.hibernate.testing.DialectChecks; -import org.hibernate.testing.RequiresDialectFeature; -import org.hibernate.testing.TestForIssue; -import org.hibernate.testing.jta.TestingJtaBootstrap; -import org.hibernate.testing.logger.LoggerInspectionRule; -import org.hibernate.testing.logger.Triggerable; -import org.junit.Before; -import org.junit.Rule; -import org.junit.Test; - -import org.jboss.logging.Logger; - -import static org.hamcrest.core.Is.is; -import static org.hamcrest.core.IsNot.not; -import static org.junit.Assert.assertFalse; -import static org.junit.Assert.assertThat; -import static org.junit.Assert.fail; - -/** - * @author Andrea Boriero - */ -@TestForIssue(jiraKey = "HHH-13050") -@RequiresDialectFeature(DialectChecks.SupportsIdentityColumns.class) -public class JtaWithStatementsBatchTest extends BaseEntityManagerFunctionalTestCase { - - @Rule - public LoggerInspectionRule logInspection = new LoggerInspectionRule( - Logger.getMessageLogger( CoreMessageLogger.class, AbstractBatchImpl.class.getName() ) - ); - - private Triggerable triggerable; - - @Before - public void setUp() { - triggerable = logInspection.watchForLogMessages( - "HHH000352: Unable to release batch statement..." ); - triggerable.reset(); - } - - @Override - protected Class[] getAnnotatedClasses() { - return new Class[] { Comment.class, EventLog.class }; - } - - @Override - protected void addConfigOptions(Map options) { - super.addConfigOptions( options ); - TestingJtaBootstrap.prepare( options ); - options.put( BatchBuilderInitiator.BUILDER, TestBatchBuilder.class.getName() ); - - options.put( AvailableSettings.JPA_TRANSACTION_TYPE, "JTA" ); - options.put( AvailableSettings.STATEMENT_BATCH_SIZE, "50" ); - } - - @Test - public void testPersist() { - EntityManager em = createEntityManager(); - EntityTransaction transaction = null; - try { - transaction = em.getTransaction(); - transaction.begin(); - - em.setFlushMode( FlushModeType.AUTO ); - - // Persist entity with non-generated id - EventLog eventLog1 = new EventLog(); - eventLog1.setMessage( "Foo1" ); - em.persist( eventLog1 ); - - // Persist entity with non-generated id - EventLog eventLog2 = new EventLog(); - eventLog2.setMessage( "Foo2" ); - em.persist( eventLog2 ); - - Comment comment = new Comment(); - comment.setMessage( "Bar" ); - em.persist( comment ); - - transaction.commit(); - } - finally { - assertThat( statements.size(), not( 0 ) ); - assertThat( numberOfStatementsAfterReleasing, is( 0 ) ); - statements.forEach( statement -> { - try { - assertThat( statement.isClosed(), is( true ) ); - } - catch (SQLException e) { - fail( e.getMessage() ); - } - } ); - if ( transaction != null && transaction.isActive() ) { - transaction.rollback(); - } - - em.close(); - } - - assertFalse( triggerable.wasTriggered() ); - - em = createEntityManager(); - - try { - transaction = em.getTransaction(); - transaction.begin(); - Integer savedComments - = em.createQuery( "from Comment" ).getResultList().size(); - assertThat( savedComments, is( 1 ) ); - - Integer savedEventLogs - = em.createQuery( "from EventLog" ).getResultList().size(); - assertThat( savedEventLogs, is( 2 ) ); - } - finally { - if ( transaction != null && transaction.isActive() ) { - transaction.rollback(); - } - em.close(); - } - } - - @Entity(name = "Comment") - public static class Comment { - private Long id; - private String message; - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - } - - @Entity(name = "EventLog") - public static class EventLog { - private Long id; - private String message; - - @Id - @GeneratedValue(generator = "eventLogIdGenerator") - @GenericGenerator(name = "eventLogIdGenerator", strategy = "org.hibernate.id.enhanced.TableGenerator", parameters = { - @Parameter(name = "table_name", value = "primaryKeyPools"), - @Parameter(name = "segment_value", value = "eventLog"), - @Parameter(name = "optimizer", value = "pooled"), - @Parameter(name = "increment_size", value = "500"), - @Parameter(name = "initial_value", value = "1") - }) - public Long getId() { - return id; - } - - public void setId(Long id) { - this.id = id; - } - - public String getMessage() { - return message; - } - - public void setMessage(String message) { - this.message = message; - } - } - - private static int numberOfStatementsAfterReleasing; - private static List statements = new ArrayList<>(); - - public static class TestBatch extends BatchingBatch { - - public TestBatch(BatchKey key, JdbcCoordinator jdbcCoordinator, int batchSize) { - super( key, jdbcCoordinator, batchSize ); - } - - protected void releaseStatements() { - statements.addAll( getStatements().values() ); - super.releaseStatements(); - numberOfStatementsAfterReleasing += getStatements().size(); - } - } - - public static class TestBatchBuilder extends BatchBuilderImpl { - private int jdbcBatchSize; - - @Override - public void setJdbcBatchSize(int jdbcBatchSize) { - this.jdbcBatchSize = jdbcBatchSize; - } - - @Override - public Batch buildBatch(BatchKey key, JdbcCoordinator jdbcCoordinator) { - return new TestBatch( key, jdbcCoordinator, jdbcBatchSize ); - } - } - -} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/AbstractJtaBatchTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/AbstractJtaBatchTest.java new file mode 100644 index 0000000000..062d5f3a4d --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/AbstractJtaBatchTest.java @@ -0,0 +1,140 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.jpa.test.transaction.batch; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.List; +import java.util.Map; +import javax.persistence.Entity; +import javax.persistence.GeneratedValue; +import javax.persistence.GenerationType; +import javax.persistence.Id; + +import org.hibernate.annotations.GenericGenerator; +import org.hibernate.annotations.Parameter; +import org.hibernate.cfg.AvailableSettings; +import org.hibernate.engine.jdbc.batch.internal.AbstractBatchImpl; +import org.hibernate.engine.jdbc.batch.internal.BatchBuilderInitiator; +import org.hibernate.internal.CoreMessageLogger; +import org.hibernate.jpa.test.BaseEntityManagerFunctionalTestCase; + +import org.hibernate.testing.jta.TestingJtaBootstrap; +import org.hibernate.testing.logger.LoggerInspectionRule; +import org.hibernate.testing.logger.Triggerable; +import org.junit.Before; +import org.junit.Rule; + +import org.jboss.logging.Logger; + +import static org.hamcrest.core.Is.is; +import static org.junit.Assert.assertThat; +import static org.junit.Assert.fail; + +/** + * @author Andrea Boriero + */ +public abstract class AbstractJtaBatchTest extends BaseEntityManagerFunctionalTestCase { + + @Rule + public LoggerInspectionRule logInspection = new LoggerInspectionRule( + Logger.getMessageLogger( CoreMessageLogger.class, AbstractBatchImpl.class.getName() ) + ); + + protected Triggerable triggerable; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Comment.class, EventLog.class }; + } + + @Override + protected void addConfigOptions(Map options) { + super.addConfigOptions( options ); + TestingJtaBootstrap.prepare( options ); + options.put( BatchBuilderInitiator.BUILDER, getBatchBuilderClassName() ); + options.put( AvailableSettings.JPA_TRANSACTION_COMPLIANCE, "true" ); + options.put( AvailableSettings.JPA_TRANSACTION_TYPE, "JTA" ); + options.put( AvailableSettings.STATEMENT_BATCH_SIZE, "50" ); + } + + @Before + public void setUp() { + triggerable = logInspection.watchForLogMessages( + "HHH000352: Unable to release batch statement..." ); + triggerable.reset(); + } + + protected void assertAllStatementsAreClosed(List statements) { + statements.forEach( statement -> { + try { + assertThat( "A PreparedStatement has not been closed", statement.isClosed(), is( true ) ); + } + catch (SQLException e) { + fail( e.getMessage() ); + } + } ); + } + + protected abstract String getBatchBuilderClassName(); + + @Entity(name = "Comment") + public static class Comment { + private Long id; + private String message; + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + } + + @Entity(name = "EventLog") + public static class EventLog { + private Long id; + private String message; + + @Id + @GeneratedValue(generator = "eventLogIdGenerator") + @GenericGenerator(name = "eventLogIdGenerator", strategy = "org.hibernate.id.enhanced.TableGenerator", parameters = { + @Parameter(name = "table_name", value = "primaryKeyPools"), + @Parameter(name = "segment_value", value = "eventLog"), + @Parameter(name = "optimizer", value = "pooled"), + @Parameter(name = "increment_size", value = "500"), + @Parameter(name = "initial_value", value = "1") + }) + public Long getId() { + return id; + } + + public void setId(Long id) { + this.id = id; + } + + public String getMessage() { + return message; + } + + public void setMessage(String message) { + this.message = message; + } + } + +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/JtaWithFailingBatchTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/JtaWithFailingBatchTest.java new file mode 100644 index 0000000000..ee38b55626 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/JtaWithFailingBatchTest.java @@ -0,0 +1,170 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.jpa.test.transaction.batch; + +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.ArrayList; +import java.util.List; +import javax.persistence.EntityManager; +import javax.persistence.FlushModeType; +import javax.transaction.Status; +import javax.transaction.TransactionManager; + +import org.hibernate.engine.jdbc.batch.internal.BatchBuilderImpl; +import org.hibernate.engine.jdbc.batch.internal.BatchingBatch; +import org.hibernate.engine.jdbc.batch.spi.Batch; +import org.hibernate.engine.jdbc.batch.spi.BatchKey; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; + +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jta.TestingJtaPlatformImpl; +import org.junit.Test; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNot.not; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; + +/** + * @author Gail Badner + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-13050") +@RequiresDialectFeature(DialectChecks.SupportsIdentityColumns.class) +public class JtaWithFailingBatchTest extends AbstractJtaBatchTest { + + private static TestBatch testBatch; + + @Override + protected Class[] getAnnotatedClasses() { + return new Class[] { Comment.class, EventLog.class }; + } + + @Test + public void testAllStatementsAreClosedInCaseOfBatchExecutionFailure() throws Exception { + TransactionManager transactionManager = TestingJtaPlatformImpl.INSTANCE.getTransactionManager(); + EntityManager em = createEntityManager(); + try { + transactionManager.begin(); + + em.setFlushMode( FlushModeType.AUTO ); + + // Persist entity with non-generated id + EventLog eventLog1 = new EventLog(); + eventLog1.setMessage( "Foo1" ); + em.persist( eventLog1 ); + + // Persist entity with non-generated id + EventLog eventLog2 = new EventLog(); + eventLog2.setMessage( "Foo2" ); + em.persist( eventLog2 ); + + Comment comment = new Comment(); + comment.setMessage( "Bar" ); + + try { + em.persist( comment ); + transactionManager.commit(); + } + catch (Exception expected) { + //expected + if ( transactionManager.getStatus() == Status.STATUS_ACTIVE ) { + transactionManager.rollback(); + } + } + + assertThat( + "AbstractBatchImpl#releaseStatements() has not been callled", + testBatch.calledReleaseStatements, + is( true ) + ); + assertAllStatementsAreClosed( testBatch.createdStatements ); + assertStatementsListIsCleared(); + } + finally { + + em.close(); + } + + assertFalse( "HHH000352: Unable to release batch statement... has been thrown", triggerable.wasTriggered() ); + } + + private void assertStatementsListIsCleared() { + assertThat( testBatch.createdStatements.size(), not( 0 ) ); + assertThat( + "Not all PreparedStatements have been released", + testBatch.numberOfStatementsAfterReleasing, + is( 0 ) + ); + } + + public static class TestBatch extends BatchingBatch { + private int numberOfStatementsAfterReleasing; + private List createdStatements = new ArrayList<>(); + private boolean calledReleaseStatements; + + private String currentStatementSql; + + public TestBatch(BatchKey key, JdbcCoordinator jdbcCoordinator, int batchSize) { + super( key, jdbcCoordinator, batchSize ); + } + + @Override + public PreparedStatement getBatchStatement(String sql, boolean callable) { + currentStatementSql = sql; + PreparedStatement batchStatement = super.getBatchStatement( sql, callable ); + createdStatements.add( batchStatement ); + return batchStatement; + } + + @Override + public void addToBatch() { + // Implementations really should call abortBatch() before throwing an exception. + // Purposely skipping the call to abortBatch() to ensure that Hibernate works properly when + // a legacy implementation does not call abortBatch(). + throw sqlExceptionHelper().convert( + new SQLException( "fake SQLException" ), + "could not perform addBatch", + currentStatementSql + ); + } + + @Override + protected void releaseStatements() { + super.releaseStatements(); + calledReleaseStatements = true; + numberOfStatementsAfterReleasing += getStatements().size(); + } + } + + @Override + protected String getBatchBuilderClassName() { + return TestBatchBuilder.class.getName(); + } + + public static class TestBatchBuilder extends BatchBuilderImpl { + private int jdbcBatchSize; + + @Override + public void setJdbcBatchSize(int jdbcBatchSize) { + this.jdbcBatchSize = jdbcBatchSize; + } + + @Override + public Batch buildBatch(BatchKey key, JdbcCoordinator jdbcCoordinator) { + return buildBatchTest( key, jdbcCoordinator, jdbcBatchSize ); + } + + protected BatchingBatch buildBatchTest(BatchKey key, JdbcCoordinator jdbcCoordinator, int jdbcBatchSize) { + testBatch = new TestBatch( key, jdbcCoordinator, jdbcBatchSize ); + return testBatch; + } + } +} diff --git a/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/JtaWithStatementsBatchTest.java b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/JtaWithStatementsBatchTest.java new file mode 100644 index 0000000000..da0459e800 --- /dev/null +++ b/hibernate-core/src/test/java/org/hibernate/jpa/test/transaction/batch/JtaWithStatementsBatchTest.java @@ -0,0 +1,147 @@ +/* + * 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 http://www.gnu.org/licenses/lgpl-2.1.html + */ +package org.hibernate.jpa.test.transaction.batch; + +import java.sql.PreparedStatement; +import java.util.ArrayList; +import java.util.List; +import javax.persistence.EntityManager; +import javax.persistence.FlushModeType; +import javax.transaction.Status; +import javax.transaction.TransactionManager; + +import org.hibernate.engine.jdbc.batch.internal.BatchBuilderImpl; +import org.hibernate.engine.jdbc.batch.internal.BatchingBatch; +import org.hibernate.engine.jdbc.batch.spi.Batch; +import org.hibernate.engine.jdbc.batch.spi.BatchKey; +import org.hibernate.engine.jdbc.spi.JdbcCoordinator; + +import org.hibernate.testing.DialectChecks; +import org.hibernate.testing.RequiresDialectFeature; +import org.hibernate.testing.TestForIssue; +import org.hibernate.testing.jta.TestingJtaPlatformImpl; +import org.junit.Test; + +import static org.hamcrest.core.Is.is; +import static org.hamcrest.core.IsNot.not; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertThat; + +/** + * @author Andrea Boriero + */ +@TestForIssue(jiraKey = "HHH-13050") +@RequiresDialectFeature(DialectChecks.SupportsIdentityColumns.class) +public class JtaWithStatementsBatchTest extends AbstractJtaBatchTest { + + private static TestBatch testBatch; + + @Test + public void testUnableToReleaseStatementMessageIsNotLogged() + throws Exception { + TransactionManager transactionManager = TestingJtaPlatformImpl.INSTANCE.getTransactionManager(); + EntityManager em = createEntityManager(); + try { + transactionManager.begin(); + + em.setFlushMode( FlushModeType.AUTO ); + + // Persist entity with non-generated id + EventLog eventLog1 = new EventLog(); + eventLog1.setMessage( "Foo1" ); + em.persist( eventLog1 ); + + // Persist entity with non-generated id + EventLog eventLog2 = new EventLog(); + eventLog2.setMessage( "Foo2" ); + em.persist( eventLog2 ); + + Comment comment = new Comment(); + comment.setMessage( "Bar" ); + em.persist( comment ); + + transactionManager.commit(); + assertStatementsListIsCleared(); + assertAllStatementsAreClosed( testBatch.createtdStatements ); + } + finally { + if ( transactionManager.getStatus() == Status.STATUS_ACTIVE ) { + transactionManager.rollback(); + } + em.close(); + } + + assertFalse( "HHH000352: Unable to release batch statement... has been thrown", triggerable.wasTriggered() ); + + em = createEntityManager(); + + try { + transactionManager.begin(); + Integer savedComments + = em.createQuery( "from Comment" ).getResultList().size(); + assertThat( savedComments, is( 1 ) ); + + Integer savedEventLogs + = em.createQuery( "from EventLog" ).getResultList().size(); + assertThat( savedEventLogs, is( 2 ) ); + } + finally { + if ( transactionManager.getStatus() == Status.STATUS_ACTIVE ) { + transactionManager.rollback(); + } + em.close(); + } + } + + private void assertStatementsListIsCleared() { + assertThat( testBatch.createtdStatements.size(), not( 0 ) ); + assertThat( + "Not all PreparedStatements have been released", + testBatch.numberOfStatementsAfterReleasing, + is( 0 ) + ); + } + + public static class TestBatch extends BatchingBatch { + private int numberOfStatementsAfterReleasing; + private List createtdStatements = new ArrayList<>(); + + public TestBatch(BatchKey key, JdbcCoordinator jdbcCoordinator, int batchSize) { + super( key, jdbcCoordinator, batchSize ); + } + + protected void releaseStatements() { + createtdStatements.addAll( getStatements().values() ); + super.releaseStatements(); + numberOfStatementsAfterReleasing += getStatements().size(); + } + } + + @Override + protected String getBatchBuilderClassName() { + return TestBatchBuilder.class.getName(); + } + + public static class TestBatchBuilder extends BatchBuilderImpl { + private int jdbcBatchSize; + + @Override + public void setJdbcBatchSize(int jdbcBatchSize) { + this.jdbcBatchSize = jdbcBatchSize; + } + + @Override + public Batch buildBatch(BatchKey key, JdbcCoordinator jdbcCoordinator) { + return buildBatchTest( key, jdbcCoordinator, jdbcBatchSize ); + } + + protected BatchingBatch buildBatchTest(BatchKey key, JdbcCoordinator jdbcCoordinator, int jdbcBatchSize) { + testBatch = new TestBatch( key, jdbcCoordinator, jdbcBatchSize ); + return testBatch; + } + } +}