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:
Brett Meyer 2013-01-15 13:28:14 -05:00 committed by Brett Meyer
parent 5d280a8041
commit 9c89ef994e
2 changed files with 76 additions and 26 deletions

View File

@ -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

View File

@ -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 {