Save This Page
Home » hibernate-distribution-3.3.1.GA-dist » org.hibernate » persister » collection » [javadoc | source]
    1   /*
    2    * Hibernate, Relational Persistence for Idiomatic Java
    3    *
    4    * Copyright (c) 2008, Red Hat Middleware LLC or third-party contributors as
    5    * indicated by the @author tags or express copyright attribution
    6    * statements applied by the authors.  All third-party contributions are
    7    * distributed under license by Red Hat Middleware LLC.
    8    *
    9    * This copyrighted material is made available to anyone wishing to use, modify,
   10    * copy, or redistribute it subject to the terms and conditions of the GNU
   11    * Lesser General Public License, as published by the Free Software Foundation.
   12    *
   13    * This program is distributed in the hope that it will be useful,
   14    * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
   15    * or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU Lesser General Public License
   16    * for more details.
   17    *
   18    * You should have received a copy of the GNU Lesser General Public License
   19    * along with this distribution; if not, write to:
   20    * Free Software Foundation, Inc.
   21    * 51 Franklin Street, Fifth Floor
   22    * Boston, MA  02110-1301  USA
   23    *
   24    */
   25   package org.hibernate.persister.collection;
   26   
   27   import java.io.Serializable;
   28   import java.sql.PreparedStatement;
   29   import java.sql.SQLException;
   30   import java.util.Iterator;
   31   
   32   import org.hibernate.HibernateException;
   33   import org.hibernate.MappingException;
   34   import org.hibernate.jdbc.Expectation;
   35   import org.hibernate.jdbc.Expectations;
   36   import org.hibernate.cache.CacheException;
   37   import org.hibernate.cache.access.CollectionRegionAccessStrategy;
   38   import org.hibernate.cfg.Configuration;
   39   import org.hibernate.collection.PersistentCollection;
   40   import org.hibernate.engine.SessionFactoryImplementor;
   41   import org.hibernate.engine.SessionImplementor;
   42   import org.hibernate.engine.SubselectFetch;
   43   import org.hibernate.exception.JDBCExceptionHelper;
   44   import org.hibernate.loader.collection.BatchingCollectionInitializer;
   45   import org.hibernate.loader.collection.CollectionInitializer;
   46   import org.hibernate.loader.collection.SubselectOneToManyLoader;
   47   import org.hibernate.loader.entity.CollectionElementLoader;
   48   import org.hibernate.mapping.Collection;
   49   import org.hibernate.persister.entity.Joinable;
   50   import org.hibernate.persister.entity.OuterJoinLoadable;
   51   import org.hibernate.pretty.MessageHelper;
   52   import org.hibernate.sql.Update;
   53   import org.hibernate.util.ArrayHelper;
   54   
   55   /**
   56    * Collection persister for one-to-many associations.
   57    *
   58    * @author Gavin King
   59    */
   60   public class OneToManyPersister extends AbstractCollectionPersister {
   61   
   62   	private final boolean cascadeDeleteEnabled;
   63   	private final boolean keyIsNullable;
   64   	private final boolean keyIsUpdateable;
   65   
   66   	protected boolean isRowDeleteEnabled() {
   67   		return keyIsUpdateable && keyIsNullable;
   68   	}
   69   
   70   	protected boolean isRowInsertEnabled() {
   71   		return keyIsUpdateable;
   72   	}
   73   
   74   	public boolean isCascadeDeleteEnabled() {
   75   		return cascadeDeleteEnabled;
   76   	}
   77   
   78   	public OneToManyPersister(
   79   			Collection collection,
   80   			CollectionRegionAccessStrategy cacheAccessStrategy,
   81   			Configuration cfg,
   82   			SessionFactoryImplementor factory) throws MappingException, CacheException {
   83   		super( collection, cacheAccessStrategy, cfg, factory );
   84   		cascadeDeleteEnabled = collection.getKey().isCascadeDeleteEnabled() &&
   85   				factory.getDialect().supportsCascadeDelete();
   86   		keyIsNullable = collection.getKey().isNullable();
   87   		keyIsUpdateable = collection.getKey().isUpdateable();
   88   	}
   89   
   90   	/**
   91   	 * Generate the SQL UPDATE that updates all the foreign keys to null
   92   	 */
   93   	protected String generateDeleteString() {
   94   		
   95   		Update update = new Update( getDialect() )
   96   				.setTableName( qualifiedTableName )
   97   				.addColumns( keyColumnNames, "null" )
   98   				.setPrimaryKeyColumnNames( keyColumnNames );
   99   		
  100   		if ( hasIndex && !indexContainsFormula ) update.addColumns( indexColumnNames, "null" );
  101   		
  102   		if ( hasWhere ) update.setWhere( sqlWhereString );
  103   		
  104   		if ( getFactory().getSettings().isCommentsEnabled() ) {
  105   			update.setComment( "delete one-to-many " + getRole() );
  106   		}
  107   		
  108   		return update.toStatementString();
  109   	}
  110   
  111   	/**
  112   	 * Generate the SQL UPDATE that updates a foreign key to a value
  113   	 */
  114   	protected String generateInsertRowString() {
  115   		
  116   		Update update = new Update( getDialect() )
  117   				.setTableName( qualifiedTableName )
  118   				.addColumns( keyColumnNames );
  119   		
  120   		if ( hasIndex && !indexContainsFormula ) update.addColumns( indexColumnNames );
  121   		
  122   		//identifier collections not supported for 1-to-many
  123   		if ( getFactory().getSettings().isCommentsEnabled() ) {
  124   			update.setComment( "create one-to-many row " + getRole() );
  125   		}
  126   		
  127   		return update.setPrimaryKeyColumnNames( elementColumnNames )
  128   				.toStatementString();
  129   	}
  130   
  131   	/**
  132   	 * Not needed for one-to-many association
  133   	 */
  134   	protected String generateUpdateRowString() {
  135   		return null;
  136   	}
  137   
  138   	/**
  139   	 * Generate the SQL UPDATE that updates a particular row's foreign
  140   	 * key to null
  141   	 */
  142   	protected String generateDeleteRowString() {
  143   		
  144   		Update update = new Update( getDialect() )
  145   				.setTableName( qualifiedTableName )
  146   				.addColumns( keyColumnNames, "null" );
  147   		
  148   		if ( hasIndex && !indexContainsFormula ) update.addColumns( indexColumnNames, "null" );
  149   		
  150   		if ( getFactory().getSettings().isCommentsEnabled() ) {
  151   			update.setComment( "delete one-to-many row " + getRole() );
  152   		}
  153   		
  154   		//use a combination of foreign key columns and pk columns, since
  155   		//the ordering of removal and addition is not guaranteed when
  156   		//a child moves from one parent to another
  157   		String[] rowSelectColumnNames = ArrayHelper.join(keyColumnNames, elementColumnNames);
  158   		return update.setPrimaryKeyColumnNames( rowSelectColumnNames )
  159   				.toStatementString();
  160   	}
  161   
  162   	public boolean consumesEntityAlias() {
  163   		return true;
  164   	}
  165   	public boolean consumesCollectionAlias() {
  166   		return true;
  167   	}
  168   
  169   	public boolean isOneToMany() {
  170   		return true;
  171   	}
  172   
  173   	public boolean isManyToMany() {
  174   		return false;
  175   	}
  176   
  177   	protected int doUpdateRows(Serializable id, PersistentCollection collection, SessionImplementor session)
  178   			throws HibernateException {
  179   
  180   		// we finish all the "removes" first to take care of possible unique
  181   		// constraints and so that we can take better advantage of batching
  182   		
  183   		try {
  184   			int count = 0;
  185   			if ( isRowDeleteEnabled() ) {
  186   				boolean useBatch = true;
  187   				PreparedStatement st = null;
  188   				// update removed rows fks to null
  189   				try {
  190   					int i = 0;
  191   	
  192   					Iterator entries = collection.entries( this );
  193   					int offset = 1;
  194   					Expectation expectation = Expectations.NONE;
  195   					while ( entries.hasNext() ) {
  196   	
  197   						Object entry = entries.next();
  198   						if ( collection.needsUpdating( entry, i, elementType ) ) {  // will still be issued when it used to be null
  199   							if ( st == null ) {
  200   								String sql = getSQLDeleteRowString();
  201   								if ( isDeleteCallable() ) {
  202   									expectation = Expectations.appropriateExpectation( getDeleteCheckStyle() );
  203   									useBatch = expectation.canBeBatched();
  204   									st = useBatch
  205   											? session.getBatcher().prepareBatchCallableStatement( sql )
  206   								            : session.getBatcher().prepareCallableStatement( sql );
  207   									offset += expectation.prepare( st );
  208   								}
  209   								else {
  210   									st = session.getBatcher().prepareBatchStatement( getSQLDeleteRowString() );
  211   								}
  212   							}
  213   							int loc = writeKey( st, id, offset, session );
  214   							writeElementToWhere( st, collection.getSnapshotElement(entry, i), loc, session );
  215   							if ( useBatch ) {
  216   								session.getBatcher().addToBatch( expectation );
  217   							}
  218   							else {
  219   								expectation.verifyOutcome( st.executeUpdate(), st, -1 );
  220   							}
  221   							count++;
  222   						}
  223   						i++;
  224   					}
  225   				}
  226   				catch ( SQLException sqle ) {
  227   					if ( useBatch ) {
  228   						session.getBatcher().abortBatch( sqle );
  229   					}
  230   					throw sqle;
  231   				}
  232   				finally {
  233   					if ( !useBatch ) {
  234   						session.getBatcher().closeStatement( st );
  235   					}
  236   				}
  237   			}
  238   			
  239   			if ( isRowInsertEnabled() ) {
  240   				Expectation expectation = Expectations.appropriateExpectation( getInsertCheckStyle() );
  241   				boolean callable = isInsertCallable();
  242   				boolean useBatch = expectation.canBeBatched();
  243   				String sql = getSQLInsertRowString();
  244   				PreparedStatement st = null;
  245   				// now update all changed or added rows fks
  246   				try {
  247   					int i = 0;
  248   					Iterator entries = collection.entries( this );
  249   					while ( entries.hasNext() ) {
  250   						Object entry = entries.next();
  251   						int offset = 1;
  252   						if ( collection.needsUpdating( entry, i, elementType ) ) {
  253   							if ( useBatch ) {
  254   								if ( st == null ) {
  255   									if ( callable ) {
  256   										st = session.getBatcher().prepareBatchCallableStatement( sql );
  257   									}
  258   									else {
  259   										st = session.getBatcher().prepareBatchStatement( sql );
  260   									}
  261   								}
  262   							}
  263   							else {
  264   								if ( callable ) {
  265   									st = session.getBatcher().prepareCallableStatement( sql );
  266   								}
  267   								else {
  268   									st = session.getBatcher().prepareStatement( sql );
  269   								}
  270   							}
  271   
  272   							offset += expectation.prepare( st );
  273   
  274   							int loc = writeKey( st, id, offset, session );
  275   							if ( hasIndex && !indexContainsFormula ) {
  276   								loc = writeIndexToWhere( st, collection.getIndex( entry, i, this ), loc, session );
  277   							}
  278   
  279   							writeElementToWhere( st, collection.getElement( entry ), loc, session );
  280   
  281   							if ( useBatch ) {
  282   								session.getBatcher().addToBatch( expectation );
  283   							}
  284   							else {
  285   								expectation.verifyOutcome( st.executeUpdate(), st, -1 );
  286   							}
  287   							count++;
  288   						}
  289   						i++;
  290   					}
  291   				}
  292   				catch ( SQLException sqle ) {
  293   					if ( useBatch ) {
  294   						session.getBatcher().abortBatch( sqle );
  295   					}
  296   					throw sqle;
  297   				}
  298   				finally {
  299   					if ( !useBatch ) {
  300   						session.getBatcher().closeStatement( st );
  301   					}
  302   				}
  303   			}
  304   
  305   			return count;
  306   		}
  307   		catch ( SQLException sqle ) {
  308   			throw JDBCExceptionHelper.convert(
  309   					getSQLExceptionConverter(),
  310   					sqle,
  311   					"could not update collection rows: " + 
  312   					MessageHelper.collectionInfoString( this, id, getFactory() ),
  313   					getSQLInsertRowString()
  314   			);
  315   		}
  316   	}
  317   
  318   	public String selectFragment(
  319   	        Joinable rhs,
  320   	        String rhsAlias,
  321   	        String lhsAlias,
  322   	        String entitySuffix,
  323   	        String collectionSuffix,
  324   	        boolean includeCollectionColumns) {
  325   		StringBuffer buf = new StringBuffer();
  326   		if ( includeCollectionColumns ) {
  327   //			buf.append( selectFragment( lhsAlias, "" ) )//ignore suffix for collection columns!
  328   			buf.append( selectFragment( lhsAlias, collectionSuffix ) )
  329   					.append( ", " );
  330   		}
  331   		OuterJoinLoadable ojl = ( OuterJoinLoadable ) getElementPersister();
  332   		return buf.append( ojl.selectFragment( lhsAlias, entitySuffix ) )//use suffix for the entity columns
  333   				.toString();
  334   	}
  335   
  336   	/**
  337   	 * Create the <tt>OneToManyLoader</tt>
  338   	 *
  339   	 * @see org.hibernate.loader.collection.OneToManyLoader
  340   	 */
  341   	protected CollectionInitializer createCollectionInitializer(java.util.Map enabledFilters) throws MappingException {
  342   		return BatchingCollectionInitializer.createBatchingOneToManyInitializer( this, batchSize, getFactory(), enabledFilters );
  343   	}
  344   
  345   	public String fromJoinFragment(String alias,
  346   								   boolean innerJoin,
  347   								   boolean includeSubclasses) {
  348   		return ( ( Joinable ) getElementPersister() ).fromJoinFragment( alias, innerJoin, includeSubclasses );
  349   	}
  350   
  351   	public String whereJoinFragment(String alias,
  352   									boolean innerJoin,
  353   									boolean includeSubclasses) {
  354   		return ( ( Joinable ) getElementPersister() ).whereJoinFragment( alias, innerJoin, includeSubclasses );
  355   	}
  356   
  357   	public String getTableName() {
  358   		return ( ( Joinable ) getElementPersister() ).getTableName();
  359   	}
  360   
  361   	public String filterFragment(String alias) throws MappingException {
  362   		String result = super.filterFragment( alias );
  363   		if ( getElementPersister() instanceof Joinable ) {
  364   			result += ( ( Joinable ) getElementPersister() ).oneToManyFilterFragment( alias );
  365   		}
  366   		return result;
  367   
  368   	}
  369   
  370   	protected CollectionInitializer createSubselectInitializer(SubselectFetch subselect, SessionImplementor session) {
  371   		return new SubselectOneToManyLoader( 
  372   				this,
  373   				subselect.toSubselectString( getCollectionType().getLHSPropertyName() ),
  374   				subselect.getResult(),
  375   				subselect.getQueryParameters(),
  376   				subselect.getNamedParameterLocMap(),
  377   				session.getFactory(),
  378   				session.getEnabledFilters() 
  379   			);
  380   	}
  381   
  382   	public Object getElementByIndex(Serializable key, Object index, SessionImplementor session, Object owner) {
  383   		return new CollectionElementLoader( this, getFactory(), session.getEnabledFilters() )
  384   				.loadElement( session, key, incrementIndexByBase(index) );
  385   	}
  386   
  387   }

Save This Page
Home » hibernate-distribution-3.3.1.GA-dist » org.hibernate » persister » collection » [javadoc | source]