HHH-1917 Bulk Delete on the owning side of a ManyToMany relation needs
to delete corresponding rows from the JoinTable Conflicts: hibernate-core/src/test/java/org/hibernate/test/hql/BulkManipulationTest.java
This commit is contained in:
parent
5d280a8041
commit
9c89ef994e
|
@ -25,10 +25,9 @@ package org.hibernate.hql.spi;
|
|||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import org.hibernate.engine.spi.QueryParameters;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.engine.spi.SessionImplementor;
|
||||
|
@ -37,8 +36,12 @@ import org.hibernate.hql.internal.ast.tree.DeleteStatement;
|
|||
import org.hibernate.hql.internal.ast.tree.FromElement;
|
||||
import org.hibernate.internal.util.StringHelper;
|
||||
import org.hibernate.param.ParameterSpecification;
|
||||
import org.hibernate.persister.collection.AbstractCollectionPersister;
|
||||
import org.hibernate.persister.entity.Queryable;
|
||||
import org.hibernate.sql.Delete;
|
||||
import org.hibernate.type.CollectionType;
|
||||
import org.hibernate.type.Type;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
/**
|
||||
* @author Steve Ebersole
|
||||
|
@ -52,7 +55,7 @@ public class TableBasedDeleteHandlerImpl
|
|||
|
||||
private final String idInsertSelect;
|
||||
private final List<ParameterSpecification> idSelectParameterSpecifications;
|
||||
private final String[] deletes;
|
||||
private final List<String> deletes;
|
||||
|
||||
public TableBasedDeleteHandlerImpl(SessionFactoryImplementor factory, HqlSqlWalker walker) {
|
||||
this( factory, walker, null, null );
|
||||
|
@ -75,27 +78,42 @@ public class TableBasedDeleteHandlerImpl
|
|||
this.idSelectParameterSpecifications = processedWhereClause.getIdSelectParameterSpecifications();
|
||||
this.idInsertSelect = generateIdInsertSelect( targetedPersister, bulkTargetAlias, processedWhereClause );
|
||||
log.tracev( "Generated ID-INSERT-SELECT SQL (multi-table delete) : {0}", idInsertSelect );
|
||||
|
||||
final String idSubselect = generateIdSubselect( targetedPersister );
|
||||
deletes = new ArrayList<String>();
|
||||
|
||||
// If many-to-many, delete the FK row in the collection table.
|
||||
for ( Type type : targetedPersister.getPropertyTypes() ) {
|
||||
if ( type.isCollectionType() ) {
|
||||
CollectionType cType = (CollectionType) type;
|
||||
AbstractCollectionPersister cPersister = (AbstractCollectionPersister)factory.getCollectionPersister( cType.getRole() );
|
||||
if ( cPersister.isManyToMany() ) {
|
||||
deletes.add( generateDelete( cPersister.getTableName(),
|
||||
cPersister.getKeyColumnNames(), idSubselect));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
String[] tableNames = targetedPersister.getConstraintOrderedTableNameClosure();
|
||||
String[][] columnNames = targetedPersister.getContraintOrderedTableKeyColumnClosure();
|
||||
String idSubselect = generateIdSubselect( targetedPersister );
|
||||
|
||||
deletes = new String[tableNames.length];
|
||||
for ( int i = tableNames.length - 1; i >= 0; i-- ) {
|
||||
// TODO : an optimization here would be to consider cascade deletes and not gen those delete statements;
|
||||
// the difficulty is the ordering of the tables here vs the cascade attributes on the persisters ->
|
||||
// the table info gotten here should really be self-contained (i.e., a class representation
|
||||
// defining all the needed attributes), then we could then get an array of those
|
||||
final Delete delete = new Delete()
|
||||
.setTableName( tableNames[i] )
|
||||
.setWhere( "(" + StringHelper.join( ", ", columnNames[i] ) + ") IN (" + idSubselect + ")" );
|
||||
if ( factory().getSettings().isCommentsEnabled() ) {
|
||||
delete.setComment( "bulk delete" );
|
||||
}
|
||||
|
||||
deletes[i] = delete.toStatementString();
|
||||
for ( int i = 0; i < tableNames.length; i++ ) {
|
||||
deletes.add( generateDelete( tableNames[i], columnNames[i], idSubselect));
|
||||
}
|
||||
}
|
||||
|
||||
private String generateDelete(String tableName, String[] columnNames, String idSubselect) {
|
||||
// TODO : an optimization here would be to consider cascade deletes and not gen those delete statements;
|
||||
// the difficulty is the ordering of the tables here vs the cascade attributes on the persisters ->
|
||||
// the table info gotten here should really be self-contained (i.e., a class representation
|
||||
// defining all the needed attributes), then we could then get an array of those
|
||||
final Delete delete = new Delete()
|
||||
.setTableName( tableName )
|
||||
.setWhere( "(" + StringHelper.join( ", ", columnNames ) + ") IN (" + idSubselect + ")" );
|
||||
if ( factory().getSettings().isCommentsEnabled() ) {
|
||||
delete.setComment( "bulk delete" );
|
||||
}
|
||||
return delete.toStatementString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Queryable getTargetedQueryable() {
|
||||
|
@ -104,7 +122,7 @@ public class TableBasedDeleteHandlerImpl
|
|||
|
||||
@Override
|
||||
public String[] getSqlStatements() {
|
||||
return deletes;
|
||||
return deletes.toArray( new String[deletes.size()] );
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -23,30 +23,32 @@
|
|||
*/
|
||||
package org.hibernate.test.hql;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
import org.junit.Test;
|
||||
|
||||
import org.hibernate.QueryException;
|
||||
import org.hibernate.Session;
|
||||
import org.hibernate.Transaction;
|
||||
import org.hibernate.dialect.H2Dialect;
|
||||
import org.hibernate.dialect.MySQLDialect;
|
||||
import org.hibernate.exception.ConstraintViolationException;
|
||||
import org.hibernate.id.BulkInsertionCapableIdentifierGenerator;
|
||||
import org.hibernate.id.IdentifierGenerator;
|
||||
import org.hibernate.persister.entity.EntityPersister;
|
||||
import org.hibernate.testing.DialectChecks;
|
||||
import org.hibernate.testing.RequiresDialectFeature;
|
||||
import org.hibernate.testing.SkipLog;
|
||||
import org.hibernate.testing.TestForIssue;
|
||||
import org.hibernate.testing.junit4.BaseCoreFunctionalTestCase;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
import static org.junit.Assert.fail;
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Tests execution of bulk UPDATE/DELETE statements through the new AST parser.
|
||||
|
@ -1187,6 +1189,36 @@ public class BulkManipulationTest extends BaseCoreFunctionalTestCase {
|
|||
t.commit();
|
||||
s.close();
|
||||
}
|
||||
|
||||
@Test
|
||||
@TestForIssue( jiraKey = "HHH-1917" )
|
||||
public void testManyToManyBulkDelete() {
|
||||
Session s = openSession();
|
||||
Transaction t = s.beginTransaction();
|
||||
|
||||
Human friend = new Human();
|
||||
friend.setName( new Name( "Bob", 'B', "Bobbert" ) );
|
||||
s.save( friend );
|
||||
|
||||
Human brett = new Human();
|
||||
brett.setName( new Name( "Brett", 'E', "Meyer" ) );
|
||||
brett.setFriends( new ArrayList() );
|
||||
brett.getFriends().add( friend );
|
||||
s.save( brett );
|
||||
|
||||
s.flush();
|
||||
|
||||
try {
|
||||
s.createQuery( "delete from Human" ).executeUpdate();
|
||||
}
|
||||
catch (ConstraintViolationException cve) {
|
||||
fail("The join table was not cleared prior to the bulk delete.");
|
||||
}
|
||||
finally {
|
||||
t.rollback();
|
||||
s.close();
|
||||
}
|
||||
}
|
||||
|
||||
private class TestData {
|
||||
|
||||
|
|
Loading…
Reference in New Issue