package org.hibernate.persister.collection;
import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.Iterator;
import java.util.Set;
import org.hibernate.HibernateException;
import org.hibernate.MappingException;
import org.hibernate.cache.CacheException;
import org.hibernate.cache.spi.access.CollectionDataAccess;
import org.hibernate.collection.spi.PersistentCollection;
import org.hibernate.engine.jdbc.batch.internal.BasicBatchKey;
import org.hibernate.engine.spi.LoadQueryInfluencers;
import org.hibernate.engine.spi.SharedSessionContractImplementor;
import org.hibernate.engine.spi.SubselectFetch;
import org.hibernate.internal.FilterAliasGenerator;
import org.hibernate.internal.util.collections.ArrayHelper;
import org.hibernate.jdbc.Expectation;
import org.hibernate.jdbc.Expectations;
import org.hibernate.loader.collection.BatchingCollectionInitializerBuilder;
import org.hibernate.loader.collection.CollectionInitializer;
import org.hibernate.loader.collection.SubselectOneToManyLoader;
import org.hibernate.loader.entity.CollectionElementLoader;
import org.hibernate.mapping.Collection;
import org.hibernate.persister.entity.Joinable;
import org.hibernate.persister.entity.OuterJoinLoadable;
import org.hibernate.persister.spi.PersisterCreationContext;
import org.hibernate.pretty.MessageHelper;
import org.hibernate.sql.Update;
public class OneToManyPersister extends AbstractCollectionPersister {
private final boolean cascadeDeleteEnabled;
private final boolean keyIsNullable;
private final boolean keyIsUpdateable;
@Override
protected boolean isRowDeleteEnabled() {
return keyIsUpdateable && keyIsNullable;
}
@Override
protected boolean isRowInsertEnabled() {
return keyIsUpdateable;
}
public boolean isCascadeDeleteEnabled() {
return cascadeDeleteEnabled;
}
public OneToManyPersister(
Collection collectionBinding,
CollectionDataAccess cacheAccessStrategy,
PersisterCreationContext creationContext) throws MappingException, CacheException {
super( collectionBinding, cacheAccessStrategy, creationContext );
cascadeDeleteEnabled = collectionBinding.getKey().isCascadeDeleteEnabled()
&& creationContext.getSessionFactory().getDialect().supportsCascadeDelete();
keyIsNullable = collectionBinding.getKey().isNullable();
keyIsUpdateable = collectionBinding.getKey().isUpdateable();
}
@Override
protected String generateDeleteString() {
final Update update = new Update( getDialect() )
.setTableName( qualifiedTableName )
.addColumns( keyColumnNames, "null" )
.addPrimaryKeyColumns( keyColumnNames );
if ( hasIndex && !indexContainsFormula ) {
for ( int i = 0 ; i < indexColumnNames.length ; i++ ) {
if ( indexColumnIsSettable[i] ) {
update.addColumn( indexColumnNames[i], "null" );
}
}
}
if ( hasWhere ) {
update.setWhere( sqlWhereString );
}
if ( getFactory().getSessionFactoryOptions().isCommentsEnabled() ) {
update.setComment( "delete one-to-many " + getRole() );
}
return update.toStatementString();
}
@Override
protected String generateInsertRowString() {
final Update update = new Update( getDialect() )
.setTableName( qualifiedTableName )
.addColumns( keyColumnNames );
if ( hasIndex && !indexContainsFormula ) {
for ( int i = 0 ; i < indexColumnNames.length ; i++ ) {
if ( indexColumnIsSettable[i] ) {
update.addColumn( indexColumnNames[i] );
}
}
}
if ( getFactory().getSessionFactoryOptions().isCommentsEnabled() ) {
update.setComment( "create one-to-many row " + getRole() );
}
return update.addPrimaryKeyColumns( elementColumnNames, elementColumnWriters )
.toStatementString();
}
@Override
protected String generateUpdateRowString() {
final Update update = new Update( getDialect() ).setTableName( qualifiedTableName );
update.addPrimaryKeyColumns( elementColumnNames, elementColumnIsSettable, elementColumnWriters );
if ( hasIdentifier ) {
update.addPrimaryKeyColumns( new String[] {identifierColumnName} );
}
if ( hasIndex && !indexContainsFormula ) {
for ( int i = 0 ; i < indexColumnNames.length ; i++ ) {
if ( indexColumnIsSettable[i] ) {
update.addColumn( indexColumnNames[i] );
}
}
}
return update.toStatementString();
}
@Override
protected String generateDeleteRowString() {
final Update update = new Update( getDialect() )
.setTableName( qualifiedTableName )
.addColumns( keyColumnNames, "null" );
if ( hasIndex && !indexContainsFormula ) {
for ( int i = 0 ; i < indexColumnNames.length ; i++ ) {
if ( indexColumnIsSettable[i] ) {
update.addColumn( indexColumnNames[i], "null" );
}
}
}
if ( getFactory().getSessionFactoryOptions().isCommentsEnabled() ) {
update.setComment( "delete one-to-many row " + getRole() );
}
String[] rowSelectColumnNames = ArrayHelper.join( keyColumnNames, elementColumnNames );
return update.addPrimaryKeyColumns( rowSelectColumnNames )
.toStatementString();
}
@Override
public void recreate(PersistentCollection collection, Serializable id, SharedSessionContractImplementor session)
throws HibernateException {
super.recreate( collection, id, session );
writeIndex( collection, collection.entries( this ), id, true, session );
}
@Override
public void insertRows(PersistentCollection collection, Serializable id, SharedSessionContractImplementor session)
throws HibernateException {
super.insertRows( collection, id, session );
writeIndex( collection, collection.entries( this ), id, true, session );
}
@Override
protected void doProcessQueuedOps(PersistentCollection collection, Serializable id, SharedSessionContractImplementor session)
throws HibernateException {
writeIndex( collection, collection.queuedAdditionIterator(), id, false, session );
}
private void writeIndex(
PersistentCollection collection,
Iterator entries,
Serializable id,
boolean resetIndex,
SharedSessionContractImplementor session) {
if ( isInverse && hasIndex && !indexContainsFormula && ArrayHelper.countTrue( indexColumnIsSettable ) > 0 ) {
try {
if ( entries.hasNext() ) {
int nextIndex = resetIndex ? 0 : getSize( id, session );
Expectation expectation = Expectations.appropriateExpectation( getUpdateCheckStyle() );
while ( entries.hasNext() ) {
final Object entry = entries.next();
if ( entry != null && collection.entryExists( entry, nextIndex ) ) {
int offset = 1;
PreparedStatement st = null;
boolean callable = isUpdateCallable();
boolean useBatch = expectation.canBeBatched();
String sql = getSQLUpdateRowString();
if ( useBatch ) {
if ( recreateBatchKey == null ) {
recreateBatchKey = new BasicBatchKey(
getRole() + "#RECREATE",
expectation
);
}
st = session
.getJdbcCoordinator()
.getBatch( recreateBatchKey )
.getBatchStatement( sql, callable );
}
else {
st = session
.getJdbcCoordinator()
.getStatementPreparer()
.prepareStatement( sql, callable );
}
try {
offset += expectation.prepare( st );
if ( hasIdentifier ) {
offset = writeIdentifier(
st,
collection.getIdentifier( entry, nextIndex ),
offset,
session
);
}
offset = writeIndex(
st,
collection.getIndex( entry, nextIndex, this ),
offset,
session
);
offset = writeElement( st, collection.getElement( entry ), offset, session );
if ( useBatch ) {
session.getJdbcCoordinator()
.getBatch( recreateBatchKey )
.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().getResourceRegistry().release( st );
session.getJdbcCoordinator().afterStatementExecution();
}
}
}
nextIndex++;
}
}
}
catch (SQLException sqle) {
throw sqlExceptionHelper.convert(
sqle,
"could not update collection: " +
MessageHelper.collectionInfoString( this, collection, id, session ),
getSQLUpdateRowString()
);
}
}
}
public boolean consumesEntityAlias() {
return true;
}
public boolean consumesCollectionAlias() {
return true;
}
public boolean isOneToMany() {
return true;
}
@Override
public boolean isManyToMany() {
return false;
}
private BasicBatchKey deleteRowBatchKey;
private BasicBatchKey insertRowBatchKey;
@Override
protected int doUpdateRows(Serializable id, PersistentCollection collection, SharedSessionContractImplementor session) {
try {
int count = 0;
if ( isRowDeleteEnabled() ) {
final Expectation deleteExpectation = Expectations.appropriateExpectation( getDeleteCheckStyle() );
final boolean useBatch = deleteExpectation.canBeBatched();
if ( useBatch && deleteRowBatchKey == null ) {
deleteRowBatchKey = new BasicBatchKey(
getRole() + "#DELETEROW",
deleteExpectation
);
}
final String sql = getSQLDeleteRowString();
PreparedStatement st = null;
try {
int i = 0;
Iterator entries = collection.entries( this );
int offset = 1;
while ( entries.hasNext() ) {
Object entry = entries.next();
if ( collection.needsUpdating(
entry,
i,
elementType
) ) {
if ( useBatch ) {
st = session
.getJdbcCoordinator()
.getBatch( deleteRowBatchKey )
.getBatchStatement( sql, isDeleteCallable() );
}
else {
st = session
.getJdbcCoordinator()
.getStatementPreparer()
.prepareStatement( sql, isDeleteCallable() );
}
int loc = writeKey( st, id, offset, session );
writeElementToWhere( st, collection.getSnapshotElement( entry, i ), loc, session );
if ( useBatch ) {
session
.getJdbcCoordinator()
.getBatch( deleteRowBatchKey )
.addToBatch();
}
else {
deleteExpectation.verifyOutcome(
session.getJdbcCoordinator()
.getResultSetReturn()
.executeUpdate( st ), st, -1
);
}
count++;
}
i++;
}
}
catch (SQLException e) {
if ( useBatch ) {
session.getJdbcCoordinator().abortBatch();
}
throw e;
}
finally {
if ( !useBatch ) {
session.getJdbcCoordinator().getResourceRegistry().release( st );
session.getJdbcCoordinator().afterStatementExecution();
}
}
}
if ( isRowInsertEnabled() ) {
final Expectation insertExpectation = Expectations.appropriateExpectation( getInsertCheckStyle() );
boolean useBatch = insertExpectation.canBeBatched();
boolean callable = isInsertCallable();
if ( useBatch && insertRowBatchKey == null ) {
insertRowBatchKey = new BasicBatchKey(
getRole() + "#INSERTROW",
insertExpectation
);
}
final String sql = getSQLInsertRowString();
PreparedStatement st = null;
try {
int i = 0;
Iterator entries = collection.entries( this );
while ( entries.hasNext() ) {
Object entry = entries.next();
int offset = 1;
if ( collection.needsUpdating( entry, i, elementType ) ) {
if ( useBatch ) {
st = session
.getJdbcCoordinator()
.getBatch( insertRowBatchKey )
.getBatchStatement( sql, callable );
}
else {
st = session
.getJdbcCoordinator()
.getStatementPreparer()
.prepareStatement( sql, callable );
}
offset += insertExpectation.prepare( st );
int loc = writeKey( st, id, offset, session );
if ( hasIndex && !indexContainsFormula ) {
loc = writeIndexToWhere( st, collection.getIndex( entry, i, this ), loc, session );
}
writeElementToWhere( st, collection.getElement( entry ), loc, session );
if ( useBatch ) {
session.getJdbcCoordinator().getBatch( insertRowBatchKey ).addToBatch();
}
else {
insertExpectation.verifyOutcome(
session.getJdbcCoordinator()
.getResultSetReturn()
.executeUpdate( st ), st, -1
);
}
count++;
}
i++;
}
}
catch (SQLException sqle) {
if ( useBatch ) {
session.getJdbcCoordinator().abortBatch();
}
throw sqle;
}
finally {
if ( !useBatch ) {
session.getJdbcCoordinator().getResourceRegistry().release( st );
session.getJdbcCoordinator().afterStatementExecution();
}
}
}
return count;
}
catch (SQLException sqle) {
throw getFactory().getSQLExceptionHelper().convert(
sqle,
"could not update collection rows: " +
MessageHelper.collectionInfoString( this, collection, id, session ),
getSQLInsertRowString()
);
}
}
public String selectFragment(
Joinable rhs,
String rhsAlias,
String lhsAlias,
String entitySuffix,
String collectionSuffix,
boolean includeCollectionColumns) {
StringBuilder buf = new StringBuilder();
if ( includeCollectionColumns ) {
buf.append( selectFragment( lhsAlias, collectionSuffix ) )
.append( ", " );
}
OuterJoinLoadable ojl = (OuterJoinLoadable) getElementPersister();
return buf.append( ojl.selectFragment( lhsAlias, entitySuffix ) )
.toString();
}
@Override
protected CollectionInitializer createCollectionInitializer(LoadQueryInfluencers loadQueryInfluencers)
throws MappingException {
return BatchingCollectionInitializerBuilder.getBuilder( getFactory() )
.createBatchingOneToManyInitializer( this, batchSize, getFactory(), loadQueryInfluencers );
}
@Override
public String fromJoinFragment(String alias, boolean innerJoin, boolean includeSubclasses) {
return ( (Joinable) getElementPersister() ).fromJoinFragment( alias, innerJoin, includeSubclasses );
}
@Override
public String fromJoinFragment(
String alias,
boolean innerJoin,
boolean includeSubclasses,
Set<String> treatAsDeclarations) {
return ( (Joinable) getElementPersister() ).fromJoinFragment(
alias,
innerJoin,
includeSubclasses,
treatAsDeclarations
);
}
@Override
public String whereJoinFragment(String alias, boolean innerJoin, boolean includeSubclasses) {
return ( (Joinable) getElementPersister() ).whereJoinFragment( alias, innerJoin, includeSubclasses );
}
@Override
public String whereJoinFragment(
String alias,
boolean innerJoin,
boolean includeSubclasses,
Set<String> treatAsDeclarations) {
return ( (Joinable) getElementPersister() ).whereJoinFragment(
alias,
innerJoin,
includeSubclasses,
treatAsDeclarations
);
}
@Override
public String getTableName() {
return ( (Joinable) getElementPersister() ).getTableName();
}
@Override
public String filterFragment(String alias) throws MappingException {
String result = super.filterFragment( alias );
if ( getElementPersister() instanceof Joinable ) {
result += ( (Joinable) getElementPersister() ).oneToManyFilterFragment( alias );
}
return result;
}
@Override
protected String filterFragment(String alias, Set<String> treatAsDeclarations) throws MappingException {
String result = super.filterFragment( alias );
if ( getElementPersister() instanceof Joinable ) {
result += ( (Joinable) getElementPersister() ).oneToManyFilterFragment( alias, treatAsDeclarations );
}
return result;
}
@Override
protected CollectionInitializer createSubselectInitializer(SubselectFetch subselect, SharedSessionContractImplementor session) {
return new SubselectOneToManyLoader(
this,
subselect.toSubselectString( getCollectionType().getLHSPropertyName() ),
subselect.getResult(),
subselect.getQueryParameters(),
subselect.getNamedParameterLocMap(),
session.getFactory(),
session.getLoadQueryInfluencers()
);
}
@Override
public Object getElementByIndex(Serializable key, Object index, SharedSessionContractImplementor session, Object owner) {
return new CollectionElementLoader( this, getFactory(), session.getLoadQueryInfluencers() )
.loadElement( session, key, incrementIndexByBase( index ) );
}
@Override
public FilterAliasGenerator getFilterAliasGenerator(String rootAlias) {
return getElementPersister().getFilterAliasGenerator( rootAlias );
}
}