HHH-8476 Bulk delete doesn't cascade delete on join table
This commit is contained in:
parent
86b2fa4317
commit
3740a5b37b
|
@ -2681,4 +2681,14 @@ public abstract class Dialect implements ConversionContext {
|
|||
public ScrollMode defaultScrollMode() {
|
||||
return ScrollMode.SCROLL_INSENSITIVE;
|
||||
}
|
||||
|
||||
/**
|
||||
* Does this dialect support tuples in subqueries? Ex:
|
||||
* delete from Table1 where (col1, col2) in (select col1, col2 from Table2)
|
||||
*
|
||||
* @return boolean
|
||||
*/
|
||||
public boolean supportsTuplesInSubqueries() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -421,4 +421,9 @@ public class H2Dialect extends Dialect {
|
|||
// see http://groups.google.com/group/h2-database/browse_thread/thread/562d8a49e2dabe99?hl=en
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean supportsTuplesInSubqueries() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -32,12 +32,6 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import antlr.ANTLRException;
|
||||
import antlr.RecognitionException;
|
||||
import antlr.TokenStreamException;
|
||||
import antlr.collections.AST;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.MappingException;
|
||||
import org.hibernate.QueryException;
|
||||
|
@ -52,6 +46,7 @@ import org.hibernate.hql.internal.antlr.HqlSqlTokenTypes;
|
|||
import org.hibernate.hql.internal.antlr.HqlTokenTypes;
|
||||
import org.hibernate.hql.internal.antlr.SqlTokenTypes;
|
||||
import org.hibernate.hql.internal.ast.exec.BasicExecutor;
|
||||
import org.hibernate.hql.internal.ast.exec.DeleteExecutor;
|
||||
import org.hibernate.hql.internal.ast.exec.MultiTableDeleteExecutor;
|
||||
import org.hibernate.hql.internal.ast.exec.MultiTableUpdateExecutor;
|
||||
import org.hibernate.hql.internal.ast.exec.StatementExecutor;
|
||||
|
@ -73,6 +68,12 @@ import org.hibernate.loader.hql.QueryLoader;
|
|||
import org.hibernate.param.ParameterSpecification;
|
||||
import org.hibernate.persister.entity.Queryable;
|
||||
import org.hibernate.type.Type;
|
||||
import org.jboss.logging.Logger;
|
||||
|
||||
import antlr.ANTLRException;
|
||||
import antlr.RecognitionException;
|
||||
import antlr.TokenStreamException;
|
||||
import antlr.collections.AST;
|
||||
|
||||
/**
|
||||
* A QueryTranslator that uses an Antlr-based parser.
|
||||
|
@ -544,7 +545,7 @@ public class QueryTranslatorImpl implements FilterTranslator {
|
|||
return new MultiTableDeleteExecutor( walker );
|
||||
}
|
||||
else {
|
||||
return new BasicExecutor( walker, persister );
|
||||
return new DeleteExecutor( walker, persister );
|
||||
}
|
||||
}
|
||||
else if ( walker.getStatementType() == HqlSqlTokenTypes.UPDATE ) {
|
||||
|
|
|
@ -74,6 +74,11 @@ public class BasicExecutor implements StatementExecutor {
|
|||
}
|
||||
|
||||
public int execute(QueryParameters parameters, SessionImplementor session) throws HibernateException {
|
||||
return doExecute( parameters, session, sql, parameterSpecifications );
|
||||
}
|
||||
|
||||
protected int doExecute(QueryParameters parameters, SessionImplementor session, String sql,
|
||||
List parameterSpecifications) throws HibernateException {
|
||||
BulkOperationCleanupAction action = new BulkOperationCleanupAction( session, persister );
|
||||
if ( session.isEventSource() ) {
|
||||
( (EventSource) session ).getActionQueue().addAction( action );
|
||||
|
@ -88,10 +93,10 @@ public class BasicExecutor implements StatementExecutor {
|
|||
try {
|
||||
try {
|
||||
st = session.getTransactionCoordinator().getJdbcCoordinator().getStatementPreparer().prepareStatement( sql, false );
|
||||
Iterator parameterSpecifications = this.parameterSpecifications.iterator();
|
||||
Iterator paramSpecItr = parameterSpecifications.iterator();
|
||||
int pos = 1;
|
||||
while ( parameterSpecifications.hasNext() ) {
|
||||
final ParameterSpecification paramSpec = ( ParameterSpecification ) parameterSpecifications.next();
|
||||
while ( paramSpecItr.hasNext() ) {
|
||||
final ParameterSpecification paramSpec = (ParameterSpecification) paramSpecItr.next();
|
||||
pos += paramSpec.bind( st, parameters, session, pos );
|
||||
}
|
||||
if ( selection != null ) {
|
||||
|
|
|
@ -0,0 +1,119 @@
|
|||
/*
|
||||
* Hibernate, Relational Persistence for Idiomatic Java
|
||||
*
|
||||
* JBoss, Home of Professional Open Source
|
||||
* Copyright 2013 Red Hat Inc. and/or its affiliates and other contributors
|
||||
* as indicated by the @authors tag. All rights reserved.
|
||||
* See the copyright.txt in the distribution for a
|
||||
* full listing of individual contributors.
|
||||
*
|
||||
* This copyrighted material is made available to anyone wishing to use,
|
||||
* modify, copy, or redistribute it subject to the terms and conditions
|
||||
* of the GNU Lesser General Public License, v. 2.1.
|
||||
* This program is distributed in the hope that it will be useful, but WITHOUT A
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
* You should have received a copy of the GNU Lesser General Public License,
|
||||
* v.2.1 along with this distribution; if not, write to the Free Software
|
||||
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
|
||||
* MA 02110-1301, USA.
|
||||
*/
|
||||
package org.hibernate.hql.internal.ast.exec;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.hibernate.HibernateException;
|
||||
import org.hibernate.dialect.Dialect;
|
||||
import org.hibernate.engine.spi.QueryParameters;
|
||||
import org.hibernate.engine.spi.SessionFactoryImplementor;
|
||||
import org.hibernate.engine.spi.SessionImplementor;
|
||||
import org.hibernate.hql.internal.ast.HqlSqlWalker;
|
||||
import org.hibernate.hql.internal.ast.SqlGenerator;
|
||||
import org.hibernate.hql.internal.ast.tree.DeleteStatement;
|
||||
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;
|
||||
|
||||
import antlr.RecognitionException;
|
||||
import antlr.collections.AST;
|
||||
|
||||
|
||||
/**
|
||||
* Provides deletions in addition to the basic SQL delete statement being executed. Ex: cascading the delete into a
|
||||
* many-to-many join table.
|
||||
*
|
||||
* @author Brett Meyer
|
||||
*/
|
||||
public class DeleteExecutor extends BasicExecutor {
|
||||
|
||||
private static final Logger LOG = Logger.getLogger( DeleteExecutor.class );
|
||||
|
||||
private final List<String> deletes = new ArrayList<String>();
|
||||
|
||||
private List<ParameterSpecification> parameterSpecifications;
|
||||
|
||||
public DeleteExecutor(HqlSqlWalker walker, Queryable persister) {
|
||||
super( walker, persister );
|
||||
|
||||
final SessionFactoryImplementor factory = walker.getSessionFactoryHelper().getFactory();
|
||||
final Dialect dialect = factory.getDialect();
|
||||
|
||||
final DeleteStatement deleteStatement = ( DeleteStatement ) walker.getAST();
|
||||
final AST whereClause = deleteStatement.getWhereClause();
|
||||
|
||||
try {
|
||||
final SqlGenerator gen = new SqlGenerator( factory );
|
||||
gen.whereClause( whereClause );
|
||||
parameterSpecifications = gen.getCollectedParameters();
|
||||
|
||||
// If many-to-many, delete the FK row in the collection table.
|
||||
for ( Type type : persister.getPropertyTypes() ) {
|
||||
if ( type.isCollectionType() ) {
|
||||
final CollectionType cType = (CollectionType) type;
|
||||
final AbstractCollectionPersister cPersister = (AbstractCollectionPersister) factory
|
||||
.getCollectionPersister( cType.getRole() );
|
||||
if ( cPersister.isManyToMany() ) {
|
||||
if (persister.getIdentifierColumnNames().length > 1
|
||||
&& !dialect.supportsTuplesInSubqueries()) {
|
||||
LOG.warn( "This dialect is unable to cascade the delete into the many-to-many join table" +
|
||||
" when the entity has multiple primary keys. Either properly setup cascading on" +
|
||||
" the constraints or manually clear the associations prior to deleting the entities." );
|
||||
}
|
||||
else {
|
||||
final String idSubselectWhere = gen.getSQL().length() > 7 ? gen.getSQL() : "";
|
||||
final String idSubselect = "(select "
|
||||
+ StringHelper.join( ", ", persister.getIdentifierColumnNames() ) + " from "
|
||||
+ persister.getTableName() + idSubselectWhere + ")";
|
||||
final String where = "(" + StringHelper.join( ", ", cPersister.getKeyColumnNames() )
|
||||
+ ") in " + idSubselect;
|
||||
final Delete delete = new Delete().setTableName( cPersister.getTableName() ).setWhere( where );
|
||||
if ( factory.getSettings().isCommentsEnabled() ) {
|
||||
delete.setComment( "delete FKs in join table" );
|
||||
}
|
||||
deletes.add( delete.toStatementString() );
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (RecognitionException e) {
|
||||
throw new HibernateException( "Unable to delete the FKs in the join table!", e );
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public int execute(QueryParameters parameters, SessionImplementor session) throws HibernateException {
|
||||
for (String delete : deletes) {
|
||||
doExecute( parameters, session, delete, parameterSpecifications );
|
||||
}
|
||||
|
||||
// finally, execute the original sql statement
|
||||
return super.execute( parameters, session );
|
||||
}
|
||||
}
|
|
@ -83,6 +83,7 @@ public class TableBasedDeleteHandlerImpl
|
|||
deletes = new ArrayList<String>();
|
||||
|
||||
// If many-to-many, delete the FK row in the collection table.
|
||||
// This partially overlaps with DeleteExecutor, but it instead uses the temp table in the idSubselect.
|
||||
for ( Type type : targetedPersister.getPropertyTypes() ) {
|
||||
if ( type.isCollectionType() ) {
|
||||
CollectionType cType = (CollectionType) type;
|
||||
|
|
|
@ -30,7 +30,9 @@ import static org.junit.Assert.fail;
|
|||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import junit.framework.AssertionFailedError;
|
||||
|
||||
|
@ -1358,7 +1360,18 @@ public class BulkManipulationTest extends BaseCoreFunctionalTestCase {
|
|||
|
||||
s.flush();
|
||||
|
||||
Zoo zoo = new Zoo();
|
||||
Map directors = new HashMap();
|
||||
directors.put( brett.getId().toString(), brett );
|
||||
zoo.setDirectors( directors );
|
||||
s.save( zoo );
|
||||
|
||||
s.flush();
|
||||
|
||||
try {
|
||||
// non-multitable
|
||||
s.createQuery( "delete from Zoo" ).executeUpdate();
|
||||
// multitable (joined subclass)
|
||||
s.createQuery( "delete from Human" ).executeUpdate();
|
||||
}
|
||||
catch (ConstraintViolationException cve) {
|
||||
|
|
Loading…
Reference in New Issue