All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.avaje.ebeaninternal.server.query.CQuery Maven / Gradle / Ivy

The newest version!
/**
 * Copyright (C) 2006  Robin Bygrave
 * 
 * This file is part of Ebean.
 * 
 * Ebean 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.
 *  
 * Ebean 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 Ebean; if not, write to the Free Software Foundation, Inc.,
 * 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA  
 */
package com.avaje.ebeaninternal.server.query;

import java.lang.ref.WeakReference;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.persistence.PersistenceException;

import com.avaje.ebean.QueryIterator;
import com.avaje.ebean.QueryListener;
import com.avaje.ebean.bean.BeanCollection;
import com.avaje.ebean.bean.BeanCollectionAdd;
import com.avaje.ebean.bean.EntityBean;
import com.avaje.ebean.bean.EntityBeanIntercept;
import com.avaje.ebean.bean.NodeUsageCollector;
import com.avaje.ebean.bean.NodeUsageListener;
import com.avaje.ebean.bean.ObjectGraphNode;
import com.avaje.ebean.bean.PersistenceContext;
import com.avaje.ebeaninternal.api.LoadContext;
import com.avaje.ebeaninternal.api.SpiExpressionList;
import com.avaje.ebeaninternal.api.SpiQuery;
import com.avaje.ebeaninternal.api.SpiQuery.Mode;
import com.avaje.ebeaninternal.api.SpiTransaction;
import com.avaje.ebeaninternal.server.autofetch.AutoFetchManager;
import com.avaje.ebeaninternal.server.core.Message;
import com.avaje.ebeaninternal.server.core.OrmQueryRequest;
import com.avaje.ebeaninternal.server.core.SpiOrmQueryRequest;
import com.avaje.ebeaninternal.server.deploy.BeanCollectionHelp;
import com.avaje.ebeaninternal.server.deploy.BeanCollectionHelpFactory;
import com.avaje.ebeaninternal.server.deploy.BeanDescriptor;
import com.avaje.ebeaninternal.server.deploy.BeanPropertyAssocMany;
import com.avaje.ebeaninternal.server.deploy.DbReadContext;
import com.avaje.ebeaninternal.server.el.ElPropertyValue;
import com.avaje.ebeaninternal.server.lib.util.StringHelper;
import com.avaje.ebeaninternal.server.querydefn.OrmQueryProperties;
import com.avaje.ebeaninternal.server.transaction.DefaultPersistenceContext;
import com.avaje.ebeaninternal.server.type.DataBind;
import com.avaje.ebeaninternal.server.type.DataReader;

/**
 * An object that represents a SqlSelect statement.
 * 

* The SqlSelect is based on a tree (Object Graph). The tree is traversed to see * what parts are included in the tree according to the value of * find.getInclude(); *

*

* The tree structure is flattened into a SqlSelectChain. The SqlSelectChain is * the key object used in reading the flat resultSet back into Objects. *

*/ public class CQuery implements DbReadContext, CancelableQuery { private static final Logger logger = Logger.getLogger(CQuery.class.getName()); private static final int GLOBAL_ROW_LIMIT = 1000000; /** * The resultSet rows read. */ private int rowCount; /** * The number of master EntityBeans loaded. */ private int loadedBeanCount; /** * Flag set when no more rows are in the resultSet. */ private boolean noMoreRows; /** * Id of loaded 'master' bean. */ private Object loadedBeanId; /** * Flag set when 'master' bean changed. */ boolean loadedBeanChanged; /** * The 'master' bean just loaded. */ private Object loadedBean; /** * Holds the previous loaded bean. */ private Object prevLoadedBean; /** * The detail bean just loaded. */ private Object loadedManyBean; /** * The previous 'detail' collection remembered so that for manyToMany we can * turn on the modify listening. */ private Object prevDetailCollection; /** * The current 'detail' collection being populated. */ private Object currentDetailCollection; /** * The 'master' collection being populated. */ private final BeanCollection collection; /** * The help for the 'master' collection. */ private final BeanCollectionHelp help; /** * The overall find request wrapper object. */ private final OrmQueryRequest request; private final BeanDescriptor desc; private final SpiQuery query; private final QueryListener queryListener; private Map currentPathMap; private String currentPrefix; /** * Flag set true when reading 'master' and 'detail' beans. */ private final boolean manyIncluded; /** * Where clause predicates. */ private final CQueryPredicates predicates; /** * Object handling the SELECT generation and reading. */ private final SqlTree sqlTree; private final boolean rawSql; /** * The final sql that is generated. */ private final String sql; /** * Where clause to show in logs when using an existing query plan. */ private final String logWhereSql; /** * Set to true if the row number column is included in the sql. */ private final boolean rowNumberIncluded; /** * Tree that knows how to build the master and detail beans from the * resultSet. */ private final SqlTreeNode rootNode; /** * For master detail query. */ private final BeanPropertyAssocMany manyProperty; /** * The many property Expression language object. */ private final ElPropertyValue manyPropertyEl; private final int backgroundFetchAfter; private final int maxRowsLimit; /** * Flag set when backgroundFetchAfter limit is hit. */ private boolean hasHitBackgroundFetchAfter; private final PersistenceContext persistenceContext; private DataReader dataReader; /** * The statement used to create the resultSet. */ private PreparedStatement pstmt; private boolean cancelled; private String bindLog; private final CQueryPlan queryPlan; private long startNano; private final Mode queryMode; private final boolean autoFetchProfiling; private final ObjectGraphNode autoFetchParentNode; private final AutoFetchManager autoFetchManager; private final WeakReference autoFetchManagerRef; private int executionTimeMicros; private final Boolean readOnly; private final SpiExpressionList filterMany; /** * Create the Sql select based on the request. */ @SuppressWarnings("unchecked") public CQuery(OrmQueryRequest request, CQueryPredicates predicates, CQueryPlan queryPlan) { this.request = request; this.queryPlan = queryPlan; this.query = request.getQuery(); this.queryMode = query.getMode(); this.readOnly = request.isReadOnly(); this.autoFetchManager = query.getAutoFetchManager(); this.autoFetchProfiling = autoFetchManager != null; this.autoFetchParentNode = autoFetchProfiling ? query.getParentNode() : null; this.autoFetchManagerRef = autoFetchProfiling ? new WeakReference(autoFetchManager) : null; // set the generated sql back to the query // so its available to the user... query.setGeneratedSql(queryPlan.getSql()); this.sqlTree = queryPlan.getSqlTree(); this.rootNode = sqlTree.getRootNode(); this.manyProperty = sqlTree.getManyProperty(); this.manyPropertyEl = sqlTree.getManyPropertyEl(); this.manyIncluded = sqlTree.isManyIncluded(); if (manyIncluded) { // get filter to put on the collection for reuse with refresh String manyPropertyName = sqlTree.getManyPropertyName(); OrmQueryProperties chunk = query.getDetail().getChunk(manyPropertyName, false); this.filterMany = chunk.getFilterMany(); } else { this.filterMany = null; } this.sql = queryPlan.getSql(); this.rawSql = queryPlan.isRawSql(); this.rowNumberIncluded = queryPlan.isRowNumberIncluded(); this.logWhereSql = queryPlan.getLogWhereSql(); this.desc = request.getBeanDescriptor(); this.predicates = predicates; this.queryListener = query.getListener(); if (queryListener == null) { // normal, use the one from the transaction this.persistenceContext = request.getPersistenceContext(); } else { // 'Row Level Transaction Context'... // local transaction context that will be reset // after each 'master' bean is sent to the listener this.persistenceContext = new DefaultPersistenceContext(); } this.maxRowsLimit = query.getMaxRows() > 0 ? query.getMaxRows() : GLOBAL_ROW_LIMIT; this.backgroundFetchAfter = query.getBackgroundFetchAfter() > 0 ? query.getBackgroundFetchAfter() : Integer.MAX_VALUE; this.help = createHelp(request); this.collection = (BeanCollection)(help != null ? help.createEmpty(false) : null); } private BeanCollectionHelp createHelp(OrmQueryRequest request) { if (request.isFindById()) { return null; } else { SpiQuery.Type manyType = request.getQuery().getType(); if (manyType == null){ // subQuery compiled for InQueryExpression return null; } return BeanCollectionHelpFactory.create(request); } } public Boolean isReadOnly() { return readOnly; } public void propagateState(Object e) { if (Boolean.TRUE.equals(readOnly)){ if (e instanceof EntityBean){ ((EntityBean)e)._ebean_getIntercept().setReadOnly(true); } } } public DataReader getDataReader() { return dataReader; } public Mode getQueryMode() { return queryMode; } /** * Return true if we want to return vanilla (not enhanced) objects. */ public boolean isVanillaMode() { return request.isVanillaMode(); } public CQueryPredicates getPredicates() { return predicates; } public LoadContext getGraphContext() { return request.getGraphContext(); } public SpiOrmQueryRequest getQueryRequest() { return request; } public void cancel() { synchronized (this) { this.cancelled = true; if (pstmt != null){ try { pstmt.cancel(); } catch (SQLException e){ String msg = "Error cancelling query"; throw new PersistenceException(msg, e); } } } } public boolean prepareBindExecuteQuery() throws SQLException { synchronized (this) { if (cancelled || query.isCancelled()){ // cancelled before we started cancelled = true; return false; } startNano = System.nanoTime(); // prepare SpiTransaction t = request.getTransaction(); Connection conn = t.getInternalConnection(); pstmt = conn.prepareStatement(sql); if (query.getTimeout() > 0){ pstmt.setQueryTimeout(query.getTimeout()); } if (query.getBufferFetchSizeHint() > 0){ pstmt.setFetchSize(query.getBufferFetchSizeHint()); } DataBind dataBind = new DataBind(pstmt); // bind keys for encrypted properties queryPlan.bindEncryptedProperties(dataBind); bindLog = predicates.bind(dataBind); // executeQuery ResultSet rset = pstmt.executeQuery(); dataReader = queryPlan.createDataReader(rset); return true; } } /** * Close the resources. *

* The jdbc resultSet and statement need to be closed. Its important that * this method is called. *

*/ public void close() { try { if (dataReader != null) { dataReader.close(); dataReader = null; } } catch (SQLException e) { logger.log(Level.SEVERE, null, e); } try { if (pstmt != null) { pstmt.close(); pstmt = null; } } catch (SQLException e) { logger.log(Level.SEVERE, null, e); } } // /** // * Return the reference options used to define cache use. // */ // public ReferenceOptions getReferenceOptionsFor(BeanPropertyAssocOne beanProp) { // // String beanPropName = beanProp.getName(); // if (currentPrefix != null){ // beanPropName = currentPrefix+"."+beanPropName; // } // //ReferenceOptions opt = referenceOptionsMap.get(beanPropName); // if (opt == null){ // OrmQueryProperties chunk = queryDetail.getChunk(beanPropName, false); // if (chunk != null) { // // get the options from the query // opt = chunk.getReferenceOptions(); // } // if (opt == null){ // // get the default options defined for the target bean type // opt = beanProp.getTargetDescriptor().getReferenceOptions(); // } // referenceOptionsMap.put(beanPropName, opt); // } // // return opt; // } /** * Return the persistence context. */ public PersistenceContext getPersistenceContext(){ return persistenceContext; } public void setLoadedBean(Object bean, Object id) { if (id != null && id.equals(loadedBeanId)) { // master/detail loading with master bean // unchanged. NB Using id to avoid any issue // with equals not being implemented } else { if (manyIncluded) { if (rowCount > 1) { loadedBeanChanged = true; } this.prevLoadedBean = loadedBean; this.loadedBeanId = id; } this.loadedBean = bean; } } public void setLoadedManyBean(Object manyValue) { this.loadedManyBean = manyValue; } /** * Return the last read bean. */ @SuppressWarnings("unchecked") public T getLoadedBean() { if (manyIncluded) { if (prevDetailCollection instanceof BeanCollection) { ((BeanCollection)prevDetailCollection).setModifyListening(manyProperty.getModifyListenMode()); } else if (currentDetailCollection instanceof BeanCollection) { ((BeanCollection)currentDetailCollection).setModifyListening(manyProperty.getModifyListenMode()); } } if (prevLoadedBean != null) { return (T)prevLoadedBean; } else { return (T)loadedBean; } } private boolean hasMoreRows() throws SQLException { synchronized (this) { if (cancelled){ return false; } return dataReader.next(); } } /** * Read a row from the result set returning a bean. *

* If the query includes a many then the first object in the returned array * is the one/master and the second the many/detail. *

*/ private boolean readRow() throws SQLException { synchronized (this) { if (cancelled){ return false; } if (!dataReader.next()){ return false; } rowCount++; dataReader.resetColumnPosition(); if (rowNumberIncluded) { // row_number() column used for limit features dataReader.incrementPos(1); } rootNode.load(this, null); return true; } } public int getQueryExecutionTimeMicros(){ return executionTimeMicros; } public boolean readBean() throws SQLException { boolean result = readBeanInternal(true); updateExecutionStatistics(); return result; } private boolean readBeanInternal(boolean inForeground) throws SQLException { if (loadedBeanCount >= maxRowsLimit) { collection.setHasMoreRows(hasMoreRows()); return false; } if (inForeground && loadedBeanCount >= backgroundFetchAfter) { hasHitBackgroundFetchAfter = true; collection.setFinishedFetch(false); return false; } if (!manyIncluded) { // simple query... no details... return readRow(); } if (noMoreRows) { return false; } if (rowCount == 0) { if (!readRow()) { // no rows at all... return false; } else { createNewDetailCollection(); } } if (readIntoCurrentDetailCollection()) { createNewDetailCollection(); // return prevLoadedBean return true; } else { // return loadedBean prevDetailCollection = null; prevLoadedBean = null; noMoreRows = true; return true; } } private boolean readIntoCurrentDetailCollection() throws SQLException { while (readRow()) { if (loadedBeanChanged) { loadedBeanChanged = false; return true; } else { addToCurrentDetailCollection(); } } return false; } private BeanCollectionAdd currentDetailAdd; private void createNewDetailCollection() { prevDetailCollection = currentDetailCollection; if (queryMode.equals(Mode.LAZYLOAD_MANY)){ // just populate the current collection currentDetailCollection = manyPropertyEl.elGetValue(loadedBean); } else { // create a new collection to populate and assign to the bean currentDetailCollection = manyProperty.createEmpty(request.isVanillaMode()); manyPropertyEl.elSetValue(loadedBean, currentDetailCollection, false, false); } if (filterMany != null && !request.isVanillaMode()){ // remember the for use with a refresh ((BeanCollection)currentDetailCollection).setFilterMany(filterMany); } // the manyKey is always null for this case, just using default mapKey on the property currentDetailAdd = manyProperty.getBeanCollectionAdd(currentDetailCollection, null); addToCurrentDetailCollection(); } private void addToCurrentDetailCollection() { if (loadedManyBean != null) { currentDetailAdd.addBean(loadedManyBean); } } public BeanCollection continueFetchingInBackground() throws SQLException { readTheRows(false); collection.setFinishedFetch(true); return collection; } public BeanCollection readCollection() throws SQLException { readTheRows(true); updateExecutionStatistics(); return collection; } protected void updateExecutionStatistics() { try { long exeNano = System.nanoTime() - startNano; executionTimeMicros = (int)exeNano/1000; if (autoFetchProfiling){ autoFetchManager.collectQueryInfo(autoFetchParentNode, loadedBeanCount, executionTimeMicros); } queryPlan.executionTime(loadedBeanCount, executionTimeMicros); } catch (Exception e){ logger.log(Level.SEVERE, null, e); } } public QueryIterator readIterate(int bufferSize, OrmQueryRequest request) { if (bufferSize > 0){ return new CQueryIteratorWithBuffer(this, request, bufferSize); } else { return new CQueryIteratorSimple(this, request); } } private void readTheRows(boolean inForeground) throws SQLException { while (hasNextBean(inForeground)) { if (queryListener != null) { queryListener.process(getLoadedBean()); } else { // add to the list/set/map help.add(collection, getLoadedBean()); } } } protected boolean hasNextBean(boolean inForeground) throws SQLException { if (!readBeanInternal(inForeground)) { return false; } else { loadedBeanCount++; return true; } } public String getLoadedRowDetail() { if (!manyIncluded) { return String.valueOf(rowCount); } else { return loadedBeanCount + ":" + rowCount; } } public void register(String path, EntityBeanIntercept ebi){ path = getPath(path); request.getGraphContext().register(path, ebi); } public void register(String path, BeanCollection bc){ path = getPath(path); request.getGraphContext().register(path, bc); } public boolean useBackgroundToContinueFetch() { return hasHitBackgroundFetchAfter; } /** * Return the query name. */ public String getName() { return query.getName(); } /** * Return true if this is a raw sql query as opposed to Ebean generated sql. */ public boolean isRawSql() { return rawSql; } /** * Return the where predicate for display in the transaction log. */ public String getLogWhereSql() { return logWhereSql; } /** * Return the property that is associated with the many. There can only be * one per SqlSelect. This can be null. */ public BeanPropertyAssocMany getManyProperty() { return manyProperty; } /** * Get the summary of the sql. */ public String getSummary() { return sqlTree.getSummary(); } /** * Return the SqlSelectChain. This is the flattened structure that * represents this query. */ public SqlTree getSqlTree() { return sqlTree; } public String getBindLog() { return bindLog; } public SpiTransaction getTransaction() { return request.getTransaction(); } public String getBeanType() { return desc.getFullName(); } /** * Return the short bean name. */ public String getBeanName() { return desc.getName(); } /** * Return the generated sql. */ public String getGeneratedSql() { return sql; } /** * Create a PersistenceException including interesting information like the bindLog and sql used. */ public PersistenceException createPersistenceException(SQLException e) { return createPersistenceException(e, getTransaction(), bindLog, sql); } /** * Create a PersistenceException including interesting information like the bindLog and sql used. */ public static PersistenceException createPersistenceException(SQLException e, SpiTransaction t, String bindLog, String sql) { if (t.isLogSummary()) { // log the error to the transaction log String errMsg = StringHelper.replaceStringMulti(e.getMessage(), new String[] { "\r", "\n" }, "\\n "); String msg = "ERROR executing query: bindLog[" + bindLog + "] error[" + errMsg + "]"; t.logInternal(msg); } // ensure 'rollback' is logged if queryOnly transaction t.getConnection(); // build a decent error message for the exception String m = Message.msg("fetch.sqlerror", e.getMessage(), bindLog, sql); return new PersistenceException(m, e); } /** * Should we create profileNodes for beans created in this query. *

* This is true for all queries except lazy load bean queries. *

*/ public boolean isAutoFetchProfiling() { // need query.isProfiling() because we just take the data // from the lazy loaded or refreshed beans and put it into the already // existing beans which are already collecting usage information return autoFetchProfiling && query.isUsageProfiling(); } private String getPath(String propertyName) { if (currentPrefix == null){ return propertyName; } else if (propertyName == null) { return currentPrefix; } String path = currentPathMap.get(propertyName); if (path != null){ return path; } else { return currentPrefix+"."+propertyName; } } public void profileBean(EntityBeanIntercept ebi, String prefix) { ObjectGraphNode node = request.getGraphContext().getObjectGraphNode(prefix); ebi.setNodeUsageCollector(new NodeUsageCollector(node, autoFetchManagerRef)); } public void setCurrentPrefix(String currentPrefix, Map currentPathMap) { this.currentPrefix = currentPrefix; this.currentPathMap = currentPathMap; } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy