/* * JBoss, Home of Professional Open Source. * Copyright 2008, Red Hat Middleware LLC, and individual contributors * as indicated by the @author tags. See the copyright.txt file in the * distribution for a full listing of individual contributors. * * This is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation; either version 2.1 of * the License, or (at your option) any later version. * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY 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 along with this software; if not, write to the Free * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA * 02110-1301 USA, or see the FSF site: http://www.fsf.org. */ package org.jboss.ejb.plugins.cmp.jdbc; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.util.ArrayList; import java.util.Collection; import java.util.Iterator; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Collections; import javax.ejb.EJBException; import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCFieldBridge; import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCCMPFieldBridge; import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCCMRFieldBridge; import org.jboss.ejb.plugins.cmp.jdbc.bridge.JDBCEntityBridge; import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCFunctionMappingMetaData; import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCReadAheadMetaData; import org.jboss.logging.Logger; import org.jboss.deployment.DeploymentException; /** * Loads relations for a particular entity from a relation table. * * @author <a href="mailto:dain@daingroup.com">Dain Sundstrom</a> * @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a> * @version $Revision: 81030 $ */ public final class JDBCLoadRelationCommand { private final JDBCStoreManager manager; private final JDBCEntityBridge entity; private final Logger log; public JDBCLoadRelationCommand(JDBCStoreManager manager) { this.manager = manager; this.entity = (JDBCEntityBridge) manager.getEntityBridge(); // Create the Log log = Logger.getLogger( this.getClass().getName() + "." + manager.getMetaData().getName()); } public Collection execute(JDBCCMRFieldBridge cmrField, Object pk) { JDBCCMRFieldBridge relatedCMRField = (JDBCCMRFieldBridge) cmrField.getRelatedCMRField(); // get the read ahead cahces ReadAheadCache readAheadCache = manager.getReadAheadCache(); ReadAheadCache relatedReadAheadCache = cmrField.getRelatedManager().getReadAheadCache(); // get the finder results associated with this context, if it exists ReadAheadCache.EntityReadAheadInfo info = readAheadCache.getEntityReadAheadInfo(pk); List loadKeys = info.getLoadKeys(); Connection con = null; PreparedStatement ps = null; ResultSet rs = null; try { // generate the sql boolean[] preloadMask = getPreloadMask(cmrField); String sql = getSQL(cmrField, preloadMask, loadKeys.size()); // create the statement if(log.isDebugEnabled()) log.debug("load relation SQL: " + sql); // get the connection con = cmrField.getDataSource().getConnection(); ps = con.prepareStatement(sql.toString()); // Set the fetch size of the statement if(entity.getFetchSize() > 0) { ps.setFetchSize(entity.getFetchSize()); } // get the load fields JDBCCMPFieldBridge[] myKeyFields = getMyKeyFields(cmrField); JDBCCMPFieldBridge[] relatedKeyFields = getRelatedKeyFields(cmrField); // set the parameters int paramIndex = 1; for(int i = 0; i < loadKeys.size(); i++) { Object key = loadKeys.get(i); for(int j = 0; j < myKeyFields.length; ++j) paramIndex = myKeyFields[j].setPrimaryKeyParameters(ps, paramIndex, key); } // execute statement rs = ps.executeQuery(); // initialize the results map Map resultsMap = new HashMap(loadKeys.size()); for(int i = 0; i < loadKeys.size(); ++i) { resultsMap.put(loadKeys.get(i), new ArrayList()); } // load the results Object[] ref = new Object[1]; while(rs.next()) { // reset the column index for this row int index = 1; // ref must be reset to null before each load ref[0] = null; // if we are loading more then one entity, load the pk from the row Object loadedPk = pk; if(loadKeys.size() > 1) { // load the pk for(int i = 0; i < myKeyFields.length; ++i) { index = myKeyFields[i].loadPrimaryKeyResults(rs, index, ref); if(ref[0] == null) { break; } } loadedPk = ref[0]; } // load the fk ref[0] = null; for(int i = 0; i < relatedKeyFields.length; ++i) { index = relatedKeyFields[i].loadPrimaryKeyResults(rs, index, ref); if(ref[0] == null) { break; } } Object loadedFk = ref[0]; if(loadedFk != null) { // add this value to the list for loadedPk List results = (List)resultsMap.get(loadedPk); results.add(loadedFk); // if the related cmr field is single valued we can pre-load // the reverse relationship if(relatedCMRField.isSingleValued()) { relatedReadAheadCache.addPreloadData( loadedFk, relatedCMRField, Collections.singletonList(loadedPk)); } // read the preload fields if(preloadMask != null) { JDBCFieldBridge[] relatedFields = cmrField.getRelatedJDBCEntity().getTableFields(); for(int i = 0; i < relatedFields.length; ++i) { if(preloadMask[i]) { JDBCFieldBridge field = relatedFields[i]; ref[0] = null; // read the value and store it in the readahead cache index = field.loadArgumentResults(rs, index, ref); relatedReadAheadCache.addPreloadData(loadedFk, field, ref[0]); } } } } } // set all of the preloaded values JDBCReadAheadMetaData readAhead = relatedCMRField.getReadAhead(); for(Iterator iter = resultsMap.keySet().iterator(); iter.hasNext();) { Object key = iter.next(); // get the results for this key List results = (List)resultsMap.get(key); // store the results list for readahead on-load relatedReadAheadCache.addFinderResults(results, readAhead); // store the preloaded relationship (unless this is the realts we // are actually after) if(!key.equals(pk)) { readAheadCache.addPreloadData(key, cmrField, results); } } // success, return the results return (List)resultsMap.get(pk); } catch(EJBException e) { throw e; } catch(Exception e) { throw new EJBException("Load relation failed", e); } finally { JDBCUtil.safeClose(rs); JDBCUtil.safeClose(ps); JDBCUtil.safeClose(con); } } private String getSQL(JDBCCMRFieldBridge cmrField, boolean[] preloadMask, int keyCount) throws DeploymentException { JDBCCMPFieldBridge[] myKeyFields = getMyKeyFields(cmrField); JDBCCMPFieldBridge[] relatedKeyFields = getRelatedKeyFields(cmrField); String relationTable = getQualifiedRelationTable(cmrField); JDBCEntityBridge relatedEntity = cmrField.getRelatedJDBCEntity(); String relatedTable = relatedEntity.getQualifiedTableName(); // do we need to join the relation table and the related table boolean join = ((preloadMask != null) || cmrField.allFkFieldsMappedToPkFields()) && (relatedKeyFields != relatedEntity.getPrimaryKeyFields()); // aliases for the tables, only required if we are joining the tables String relationTableAlias; String relatedTableAlias; if(join) { relationTableAlias = getRelationTable(cmrField); relatedTableAlias = ( relatedTable.equals(relationTable) ? getRelationTable(cmrField) + '_' + cmrField.getFieldName() : relatedEntity.getTableName() ); } else { relationTableAlias = ""; relatedTableAlias = ""; } JDBCFunctionMappingMetaData selectTemplate = getSelectTemplate(cmrField); return selectTemplate == null ? getPlainSQL( keyCount, myKeyFields, relationTableAlias, relatedKeyFields, preloadMask, cmrField, relatedTableAlias, relationTable, join, relatedTable) : getSQLByTemplate( keyCount, myKeyFields, relationTableAlias, relatedKeyFields, preloadMask, cmrField, relatedTableAlias, relationTable, join, relatedTable, selectTemplate); } private JDBCCMPFieldBridge[] getMyKeyFields(JDBCCMRFieldBridge cmrField) { if(cmrField.getRelationMetaData().isTableMappingStyle()) { // relation table return (JDBCCMPFieldBridge[]) cmrField.getTableKeyFields(); } else if(cmrField.getRelatedCMRField().hasForeignKey()) { // related has foreign key return (JDBCCMPFieldBridge[]) cmrField.getRelatedCMRField().getForeignKeyFields(); } else { // i have foreign key return (JDBCCMPFieldBridge[])entity.getPrimaryKeyFields(); } } private static JDBCCMPFieldBridge[] getRelatedKeyFields(JDBCCMRFieldBridge cmrField) { if(cmrField.getRelationMetaData().isTableMappingStyle()) { // relation table return (JDBCCMPFieldBridge[]) cmrField.getRelatedCMRField().getTableKeyFields(); } else if(cmrField.getRelatedCMRField().hasForeignKey()) { // related has foreign key return (JDBCCMPFieldBridge[])cmrField.getRelatedJDBCEntity().getPrimaryKeyFields(); } else { // i have foreign key return (JDBCCMPFieldBridge[]) cmrField.getForeignKeyFields(); } } private static boolean[] getPreloadMask(JDBCCMRFieldBridge cmrField) { boolean[] preloadMask = null; if(cmrField.getReadAhead().isOnFind()) { JDBCEntityBridge relatedEntity = cmrField.getRelatedJDBCEntity(); String eagerLoadGroup = cmrField.getReadAhead().getEagerLoadGroup(); preloadMask = relatedEntity.getLoadGroupMask(eagerLoadGroup); } return preloadMask; } private String getQualifiedRelationTable(JDBCCMRFieldBridge cmrField) { if(cmrField.getRelationMetaData().isTableMappingStyle()) { // relation table return cmrField.getQualifiedTableName(); } else if(cmrField.getRelatedCMRField().hasForeignKey()) { // related has foreign key return cmrField.getRelatedJDBCEntity().getQualifiedTableName(); } else { // i have foreign key return entity.getQualifiedTableName(); } } private String getRelationTable(JDBCCMRFieldBridge cmrField) { if(cmrField.getRelationMetaData().isTableMappingStyle()) { // relation table return cmrField.getTableName(); } else if(cmrField.getRelatedCMRField().hasForeignKey()) { // related has foreign key return cmrField.getRelatedJDBCEntity().getTableName(); } else { // i have foreign key return entity.getTableName(); } } private JDBCFunctionMappingMetaData getSelectTemplate(JDBCCMRFieldBridge cmrField) throws DeploymentException { JDBCFunctionMappingMetaData selectTemplate = null; if(cmrField.getRelationMetaData().isTableMappingStyle()) { // relation table if(cmrField.getRelationMetaData().hasRowLocking()) { selectTemplate = cmrField.getRelationMetaData().getTypeMapping().getRowLockingTemplate(); if(selectTemplate == null) { throw new IllegalStateException( "row-locking is not allowed for this type of datastore"); } } } else if(cmrField.getRelatedCMRField().hasForeignKey()) { // related has foreign key if(cmrField.getRelatedJDBCEntity().getMetaData().hasRowLocking()) { selectTemplate = cmrField.getRelatedJDBCEntity().getMetaData().getTypeMapping().getRowLockingTemplate(); if(selectTemplate == null) { throw new IllegalStateException( "row-locking is not allowed for this type of datastore"); } } } else { // i have foreign key if(entity.getMetaData().hasRowLocking()) { selectTemplate = entity.getMetaData().getTypeMapping().getRowLockingTemplate(); if(selectTemplate == null) { throw new IllegalStateException( "row-locking is not allowed for this type of datastore"); } } } return selectTemplate; } private static String getPlainSQL(int keyCount, JDBCCMPFieldBridge[] myKeyFields, String relationTableAlias, JDBCCMPFieldBridge[] relatedKeyFields, boolean[] preloadMask, JDBCCMRFieldBridge cmrField, String relatedTableAlias, String relationTable, boolean join, String relatedTable) { // // column names clause // StringBuffer sql = new StringBuffer(400); sql.append(SQLUtil.SELECT); if(keyCount > 1) { SQLUtil.getColumnNamesClause(myKeyFields, relationTableAlias, sql) .append(SQLUtil.COMMA); } SQLUtil.getColumnNamesClause(relatedKeyFields, relationTableAlias, sql); if(preloadMask != null) { SQLUtil.appendColumnNamesClause( cmrField.getRelatedJDBCEntity().getTableFields(), preloadMask, relatedTableAlias, sql); } // // from clause // sql.append(SQLUtil.FROM).append(relationTable); if(join) { sql.append(' ') .append(relationTableAlias) .append(SQLUtil.COMMA) .append(relatedTable) .append(' ') .append(relatedTableAlias); } // // where clause // sql.append(SQLUtil.WHERE); // add the join if(join) { // join the tables sql.append('('); SQLUtil.getJoinClause( relatedKeyFields, relationTableAlias, cmrField.getRelatedJDBCEntity().getPrimaryKeyFields(), relatedTableAlias, sql) .append(')') .append(SQLUtil.AND) .append('('); } // add the keys String pkWhere = SQLUtil.getWhereClause(myKeyFields, relationTableAlias, new StringBuffer(50)).toString(); for(int i = 0; i < keyCount; i++) { if(i > 0) sql.append(SQLUtil.OR); sql.append('(').append(pkWhere).append(')'); } if(join) sql.append(')'); return sql.toString(); } private static String getSQLByTemplate(int keyCount, JDBCCMPFieldBridge[] myKeyFields, String relationTableAlias, JDBCCMPFieldBridge[] relatedKeyFields, boolean[] preloadMask, JDBCCMRFieldBridge cmrField, String relatedTableAlias, String relationTable, boolean join, String relatedTable, JDBCFunctionMappingMetaData selectTemplate) { // // column names clause // StringBuffer columnNamesClause = new StringBuffer(100); if(keyCount > 1) { SQLUtil.getColumnNamesClause(myKeyFields, relationTableAlias, columnNamesClause) .append(SQLUtil.COMMA); } SQLUtil.getColumnNamesClause(relatedKeyFields, relationTableAlias, columnNamesClause); if(preloadMask != null) { SQLUtil.appendColumnNamesClause( cmrField.getRelatedJDBCEntity().getTableFields(), preloadMask, relatedTableAlias, columnNamesClause); } // // from clause // StringBuffer fromClause = new StringBuffer(100); fromClause.append(relationTable); if(join) { fromClause.append(' ') .append(relationTableAlias) .append(SQLUtil.COMMA) .append(relatedTable) .append(' ') .append(relatedTableAlias); } // // where clause // StringBuffer whereClause = new StringBuffer(150); // add the join if(join) { // join the tables whereClause.append('('); SQLUtil.getJoinClause( relatedKeyFields, relationTableAlias, cmrField.getRelatedJDBCEntity().getPrimaryKeyFields(), relatedTableAlias, whereClause) .append(')') .append(SQLUtil.AND) .append('('); } // add the keys String pkWhere = SQLUtil.getWhereClause(myKeyFields, relationTableAlias, new StringBuffer(50)).toString(); for(int i = 0; i < keyCount; i++) { if(i > 0) { whereClause.append(SQLUtil.OR); } whereClause.append('(').append(pkWhere).append(')'); } if(join) { whereClause.append(')'); } // // assemble pieces into final statement // String[] args = new String[]{ columnNamesClause.toString(), fromClause.toString(), whereClause.toString(), null // order by }; return selectTemplate.getFunctionSql(args, new StringBuffer(500)).toString(); } }