HHH-11587 - Reordering items in List throws a constraint violation

This commit is contained in:
Andrea Boriero 2018-01-31 09:29:02 +00:00
parent f8c1417e3c
commit 39f761b250
8 changed files with 143 additions and 73 deletions

View File

@ -69,6 +69,7 @@ public abstract class AbstractPersistentCollection implements Serializable, Pers
// collections detect changes made via their public interface and mark
// themselves as dirty as a performance optimization
private boolean dirty;
protected boolean elementRemoved;
private Serializable storedSnapshot;
private String sessionFactoryUuid;
@ -105,9 +106,15 @@ public abstract class AbstractPersistentCollection implements Serializable, Pers
return dirty;
}
@Override
public boolean isElementRemoved() {
return elementRemoved;
}
@Override
public final void clearDirty() {
dirty = false;
elementRemoved = false;
}
@Override

View File

@ -307,6 +307,7 @@ public class PersistentBag extends AbstractPersistentCollection implements List
public boolean remove(Object o) {
initialize( true );
if ( bag.remove( o ) ) {
elementRemoved = true;
dirty();
return true;
}
@ -346,6 +347,7 @@ public class PersistentBag extends AbstractPersistentCollection implements List
if ( c.size()>0 ) {
initialize( true );
if ( bag.removeAll( c ) ) {
elementRemoved = true;
dirty();
return true;
}

View File

@ -150,6 +150,7 @@ public class PersistentIdentifierBag extends AbstractPersistentCollection implem
if ( index >= 0 ) {
beforeRemove( index );
values.remove( index );
elementRemoved = true;
dirty();
return true;
}

View File

@ -163,6 +163,7 @@ public class PersistentList extends AbstractPersistentCollection implements List
if ( exists == null ) {
initialize( true );
if ( list.remove( value ) ) {
elementRemoved = true;
dirty();
return true;
}
@ -171,6 +172,7 @@ public class PersistentList extends AbstractPersistentCollection implements List
}
}
else if ( exists ) {
elementRemoved = true;
queueOperation( new SimpleRemove( value ) );
return true;
}
@ -222,6 +224,7 @@ public class PersistentList extends AbstractPersistentCollection implements List
if ( coll.size()>0 ) {
initialize( true );
if ( list.removeAll( coll ) ) {
elementRemoved = true;
dirty();
return true;
}
@ -298,8 +301,10 @@ public class PersistentList extends AbstractPersistentCollection implements List
throw new ArrayIndexOutOfBoundsException( "negative index" );
}
final Object old = isPutQueueEnabled() ? readElementByIndex( index ) : UNKNOWN;
elementRemoved = true;
if ( old == UNKNOWN ) {
write();
dirty();
return list.remove( index );
}
else {

View File

@ -178,6 +178,7 @@ public class PersistentMap extends AbstractPersistentCollection implements Map {
if ( isPutQueueEnabled() ) {
final Object old = readElementByIndex( key );
if ( old != UNKNOWN ) {
elementRemoved = true;
queueOperation( new Remove( key, old ) );
return old;
}
@ -185,6 +186,7 @@ public class PersistentMap extends AbstractPersistentCollection implements Map {
// TODO : safe to interpret "map.remove(key) == null" as non-dirty?
initialize( true );
if ( map.containsKey( key ) ) {
elementRemoved = true;
dirty();
}
return map.remove( key );

View File

@ -206,6 +206,7 @@ public class PersistentSet extends AbstractPersistentCollection implements java.
if ( exists == null ) {
initialize( true );
if ( set.remove( value ) ) {
elementRemoved = true;
dirty();
return true;
}
@ -214,6 +215,7 @@ public class PersistentSet extends AbstractPersistentCollection implements java.
}
}
else if ( exists ) {
elementRemoved = true;
queueOperation( new SimpleRemove( value ) );
return true;
}
@ -266,6 +268,7 @@ public class PersistentSet extends AbstractPersistentCollection implements java.
if ( coll.size() > 0 ) {
initialize( true );
if ( set.removeAll( coll ) ) {
elementRemoved = true;
dirty();
return true;
}

View File

@ -395,7 +395,11 @@ public interface PersistentCollection {
* @return {@code true} if the collection is dirty
*/
boolean isDirty();
default boolean isElementRemoved(){
return false;
}
/**
* Clear the dirty flag, after flushing changes
* to the database.

View File

@ -9,7 +9,9 @@ package org.hibernate.persister.collection;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import org.hibernate.HibernateException;
@ -193,83 +195,51 @@ public class BasicCollectionPersister extends AbstractCollectionPersister {
}
try {
PreparedStatement st = null;
Expectation expectation = Expectations.appropriateExpectation( getUpdateCheckStyle() );
boolean callable = isUpdateCallable();
final Expectation expectation = Expectations.appropriateExpectation( getUpdateCheckStyle() );
final boolean callable = isUpdateCallable();
final int jdbcBatchSizeToUse = session.getConfiguredJdbcBatchSize();
boolean useBatch = expectation.canBeBatched() && jdbcBatchSizeToUse > 1;
Iterator entries = collection.entries( this );
String sql = getSQLUpdateRowString();
int i = 0;
int count = 0;
final Iterator entries = collection.entries( this );
final List elements = new ArrayList();
while ( entries.hasNext() ) {
Object entry = entries.next();
if ( collection.needsUpdating( entry, i, elementType ) ) {
int offset = 1;
elements.add( entries.next() );
}
if ( useBatch ) {
if ( updateBatchKey == null ) {
updateBatchKey = new BasicBatchKey(
getRole() + "#UPDATE",
expectation
);
}
st = session
.getJdbcCoordinator()
.getBatch( updateBatchKey )
.getBatchStatement( sql, callable );
}
else {
st = session
.getJdbcCoordinator()
.getStatementPreparer()
.prepareStatement( sql, callable );
}
try {
offset += expectation.prepare( st );
int loc = writeElement( st, collection.getElement( entry ), offset, session );
if ( hasIdentifier ) {
writeIdentifier( st, collection.getIdentifier( entry, i ), loc, session );
}
else {
loc = writeKey( st, id, loc, session );
if ( hasIndex && !indexContainsFormula ) {
writeIndexToWhere( st, collection.getIndex( entry, i, this ), loc, session );
}
else {
writeElementToWhere( st, collection.getSnapshotElement( entry, i ), loc, session );
}
}
if ( useBatch ) {
session.getJdbcCoordinator()
.getBatch( updateBatchKey )
.addToBatch();
}
else {
expectation.verifyOutcome(
session.getJdbcCoordinator().getResultSetReturn().executeUpdate(
st
), st, -1
);
}
}
catch (SQLException sqle) {
if ( useBatch ) {
session.getJdbcCoordinator().abortBatch();
}
throw sqle;
}
finally {
if ( !useBatch ) {
session.getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release( st );
session.getJdbcCoordinator().afterStatementExecution();
}
}
count++;
final String sql = getSQLUpdateRowString();
int count = 0;
if ( collection.isElementRemoved() ) {
// the update should be done starting from the end to the list
for ( int i = elements.size() - 1; i >= 0; i-- ) {
count = doUpdateRow(
id,
collection,
session,
expectation,
callable,
useBatch,
elements,
sql,
count,
i
);
}
}
else {
for ( int i = 0; i < elements.size(); i++ ) {
count = doUpdateRow(
id,
collection,
session,
expectation,
callable,
useBatch,
elements,
sql,
count,
i
);
}
i++;
}
return count;
}
@ -287,6 +257,82 @@ public class BasicCollectionPersister extends AbstractCollectionPersister {
}
}
private int doUpdateRow(
Serializable id,
PersistentCollection collection,
SharedSessionContractImplementor session,
Expectation expectation, boolean callable, boolean useBatch, List elements, String sql, int count, int i)
throws SQLException {
PreparedStatement st;
Object entry = elements.get( i );
if ( collection.needsUpdating( entry, i, elementType ) ) {
int offset = 1;
if ( useBatch ) {
if ( updateBatchKey == null ) {
updateBatchKey = new BasicBatchKey(
getRole() + "#UPDATE",
expectation
);
}
st = session
.getJdbcCoordinator()
.getBatch( updateBatchKey )
.getBatchStatement( sql, callable );
}
else {
st = session
.getJdbcCoordinator()
.getStatementPreparer()
.prepareStatement( sql, callable );
}
try {
offset += expectation.prepare( st );
int loc = writeElement( st, collection.getElement( entry ), offset, session );
if ( hasIdentifier ) {
writeIdentifier( st, collection.getIdentifier( entry, i ), loc, session );
}
else {
loc = writeKey( st, id, loc, session );
if ( hasIndex && !indexContainsFormula ) {
writeIndexToWhere( st, collection.getIndex( entry, i, this ), loc, session );
}
else {
writeElementToWhere( st, collection.getSnapshotElement( entry, i ), loc, session );
}
}
if ( useBatch ) {
session.getJdbcCoordinator()
.getBatch( updateBatchKey )
.addToBatch();
}
else {
expectation.verifyOutcome(
session.getJdbcCoordinator().getResultSetReturn().executeUpdate(
st
), st, -1
);
}
}
catch (SQLException sqle) {
if ( useBatch ) {
session.getJdbcCoordinator().abortBatch();
}
throw sqle;
}
finally {
if ( !useBatch ) {
session.getJdbcCoordinator().getLogicalConnection().getResourceRegistry().release( st );
session.getJdbcCoordinator().afterStatementExecution();
}
}
count++;
}
return count;
}
public String selectFragment(
Joinable rhs,
String rhsAlias,