/* * 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.List; import javax.ejb.EJBException; import javax.ejb.NoSuchEntityException; import org.jboss.ejb.EntityEnterpriseContext; 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.JDBCEntityBridge; import org.jboss.ejb.plugins.cmp.jdbc.metadata.JDBCFunctionMappingMetaData; import org.jboss.logging.Logger; import org.jboss.deployment.DeploymentException; /** * JDBCLoadEntityCommand loads the data for an instance from the table. * This command implements specified eager loading. For CMP 2.x, the * entity can be configured to only load some of the fields, which is * helpful for entitys with lots of data. * * @author <a href="mailto:dain@daingroup.com">Dain Sundstrom</a> * @author <a href="mailto:on@ibis.odessa.ua">Oleg Nitz</a> * @author <a href="mailto:rickard.oberg@telkel.com">Rickard Oberg</a> * @author <a href="mailto:marc.fleury@telkel.com">Marc Fleury</a> * @author <a href="mailto:shevlandj@kpi.com.au">Joe Shevland</a> * @author <a href="mailto:justin@j-m-f.demon.co.uk">Justin Forder</a> * @author <a href="mailto:dirk@jboss.de">Dirk Zimmermann</a> * @author <a href="mailto:danch@nvisia.com">danch (Dan Christopherson)</a> * @author <a href="mailto:alex@jboss.org">Alexey Loubyansky</a> * @version $Revision: 81030 $ */ public final class JDBCLoadEntityCommand { private final JDBCStoreManager manager; private final JDBCEntityBridge entity; private final Logger log; private final JDBCFunctionMappingMetaData rowLockingTemplate; public JDBCLoadEntityCommand(JDBCStoreManager manager) throws DeploymentException { this.manager = manager; entity = (JDBCEntityBridge) manager.getEntityBridge(); boolean rowLocking = entity.getMetaData().hasRowLocking(); rowLockingTemplate = rowLocking ? entity.getMetaData().getTypeMapping().getRowLockingTemplate() : null; // Create the Log log = Logger.getLogger( this.getClass().getName() + "." + manager.getMetaData().getName()); } /** * Loads entity. * If failIfNotFound is true and entity wasn't found then NoSuchEntityException is thrown. * Otherwise, if entity wasn't found, returns false. * If entity was loaded successfully return true. * @param ctx - entity context; * @param failIfNotFound - whether to fail if entity wasn't found; * @return true if entity was loaded, false - otherwise. */ public boolean execute(EntityEnterpriseContext ctx, boolean failIfNotFound) { return execute(null, ctx, failIfNotFound); } /** * Loads entity or required field. If entity not found throws NoSuchEntityException. * @param requiredField - required field or null; * @param ctx - the corresponding context; */ public void execute(JDBCCMPFieldBridge requiredField, EntityEnterpriseContext ctx) { execute(requiredField, ctx, true); } /** * Loads entity or required field. * If failIfNotFound is set to true, then NoSuchEntityException is thrown if the * entity wasn't found. * If failIfNotFound is false then if the entity wasn't found returns false, * if the entity was loaded successfully, returns true. * @param requiredField - required field; * @param ctx - entity context; * @param failIfNotFound - whether to fail if entity wasn't loaded. * @return true if entity was loaded, false - otherwise. */ private boolean execute(JDBCCMPFieldBridge requiredField, EntityEnterpriseContext ctx, boolean failIfNotFound) { // load the instance primary key fields into the context Object id = ctx.getId(); // TODO: when exactly do I need to do the following? entity.injectPrimaryKeyIntoInstance(ctx, id); // get the read ahead cache ReadAheadCache readAheadCache = manager.getReadAheadCache(); // load any preloaded fields into the context if(readAheadCache.load(ctx)) { if(requiredField == null || (requiredField != null && requiredField.isLoaded(ctx))) { return true; } } // get the finder results associated with this context, if it exists ReadAheadCache.EntityReadAheadInfo info = readAheadCache.getEntityReadAheadInfo(id); // determine the fields to load JDBCEntityBridge.FieldIterator loadIter = entity.getLoadIterator(requiredField, info.getReadAhead(), ctx); if(!loadIter.hasNext()) return true; // get the keys to load List loadKeys = info.getLoadKeys(); // generate the sql String sql = (rowLockingTemplate != null ? getRawLockingSQL(loadIter, loadKeys.size()) : getSQL(loadIter, loadKeys.size())); Connection con = null; PreparedStatement ps = null; ResultSet rs = null; try { // create the statement if (log.isDebugEnabled()) { log.debug("Executing SQL: " + sql); } // get the connection con = entity.getDataSource().getConnection(); ps = con.prepareStatement(sql); // Set the fetch size of the statement if (entity.getFetchSize() > 0) { ps.setFetchSize(entity.getFetchSize()); } // set the parameters int paramIndex = 1; for (int i = 0; i < loadKeys.size(); i++) { paramIndex = entity.setPrimaryKeyParameters(ps, paramIndex, loadKeys.get(i)); } // execute statement rs = ps.executeQuery(); // load results boolean mainEntityLoaded = false; 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 load ref[0] = null; // if we are loading more then one entity, load the pk from the row Object pk = null; if (loadKeys.size() > 1) { // load the pk index = entity.loadPrimaryKeyResults(rs, index, ref); pk = ref[0]; } // is this the main entity or a preload entity if (loadKeys.size() == 1 || pk.equals(id)) { // main entity; load the values into the context loadIter.reset(); while(loadIter.hasNext()) { JDBCCMPFieldBridge field = loadIter.next(); index = field.loadInstanceResults(rs, index, ctx); field.setClean(ctx); } mainEntityLoaded = true; } else { // preload entity; load the values into the read ahead cahce loadIter.reset(); while(loadIter.hasNext()) { JDBCCMPFieldBridge field = loadIter.next(); // ref must be reset to null before load ref[0] = null; // load the result of the field index = field.loadArgumentResults(rs, index, ref); // cache the field value readAheadCache.addPreloadData(pk, field, ref[0]); } } } // clear LOAD_REQUIRED flag loadIter.removeAll(); // did we load the main results if (!mainEntityLoaded) { if (failIfNotFound) throw new NoSuchEntityException("Entity not found: primaryKey=" + ctx.getId()); else return false; } else return true; } catch (EJBException e) { throw e; } catch (Exception e) { throw new EJBException("Load failed", e); } finally { JDBCUtil.safeClose(rs); JDBCUtil.safeClose(ps); JDBCUtil.safeClose(con); } } private String getSQL(JDBCEntityBridge.FieldIterator loadIter, int keyCount) { StringBuffer sql = new StringBuffer(250); sql.append(SQLUtil.SELECT); // if we are loading more then one entity we need to add the primry // key to the load fields to match up the results with the correct entity. JDBCFieldBridge[] primaryKeyFields = entity.getPrimaryKeyFields(); if (keyCount > 1) { SQLUtil.getColumnNamesClause(primaryKeyFields, sql); sql.append(SQLUtil.COMMA); } SQLUtil.getColumnNamesClause(loadIter, sql); sql.append(SQLUtil.FROM) .append(entity.getQualifiedTableName()) .append(SQLUtil.WHERE); // // where clause String pkWhere = SQLUtil.getWhereClause(primaryKeyFields, new StringBuffer(50)).toString(); sql.append('(').append(pkWhere).append(')'); for (int i = 1; i < keyCount; i++) { sql.append(SQLUtil.OR).append('(').append(pkWhere).append(')'); } return sql.toString(); } private String getRawLockingSQL(JDBCEntityBridge.FieldIterator loadIter, int keyCount) { // // column names clause StringBuffer columnNamesClause = new StringBuffer(250); // if we are loading more then one entity we need to add the primry // key to the load fields to match up the results with the correct // entity. if (keyCount > 1) { SQLUtil.getColumnNamesClause(entity.getPrimaryKeyFields(), columnNamesClause); columnNamesClause.append(SQLUtil.COMMA); } SQLUtil.getColumnNamesClause(loadIter, columnNamesClause); // // table name clause String tableName = entity.getQualifiedTableName(); // // where clause String whereClause = SQLUtil. getWhereClause(entity.getPrimaryKeyFields(), new StringBuffer(50)).toString(); if (keyCount > 0) { StringBuffer sb = new StringBuffer((whereClause.length() + 6) * keyCount + 4); for (int i = 0; i < keyCount; i++) { if (i > 0) sb.append(SQLUtil.OR); sb.append('(').append(whereClause).append(')'); } whereClause = sb.toString(); } String[] args = new String[]{ columnNamesClause.toString(), tableName, whereClause, null // order by }; return rowLockingTemplate.getFunctionSql(args, new StringBuffer(300)).toString(); } }