HHH-13050 : Add test with a batch that fails when addToBatch() is called

This commit is contained in:
Gail Badner 2018-11-13 00:07:07 -08:00
parent be0ee006ab
commit a7fccaa377
4 changed files with 457 additions and 248 deletions

View File

@ -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 <http://www.gnu.org/licenses/lgpl-2.1.html>.
*/
/*
* 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<PreparedStatement> 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 );
}
}
}

View File

@ -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<PreparedStatement> 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;
}
}
}

View File

@ -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<PreparedStatement> 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;
}
}
}

View File

@ -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<PreparedStatement> 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;
}
}
}