/* * Copyright 2004-2015 the Seasar Foundation and the Others. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, * either express or implied. See the License for the specific language * governing permissions and limitations under the License. */ package org.seasar.extension.jdbc.query; import java.lang.reflect.Field; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.Map.Entry; import org.seasar.extension.jdbc.AutoSelect; import org.seasar.extension.jdbc.ColumnMeta; import org.seasar.extension.jdbc.ConditionType; import org.seasar.extension.jdbc.DbmsDialect; import org.seasar.extension.jdbc.EntityMapper; import org.seasar.extension.jdbc.EntityMeta; import org.seasar.extension.jdbc.FromClause; import org.seasar.extension.jdbc.IterationCallback; import org.seasar.extension.jdbc.JoinColumnMeta; import org.seasar.extension.jdbc.JoinMeta; import org.seasar.extension.jdbc.JoinType; import org.seasar.extension.jdbc.OrderByClause; import org.seasar.extension.jdbc.OrderByItem; import org.seasar.extension.jdbc.PropertyMapper; import org.seasar.extension.jdbc.PropertyMeta; import org.seasar.extension.jdbc.ResultSetHandler; import org.seasar.extension.jdbc.SelectClause; import org.seasar.extension.jdbc.SelectForUpdateType; import org.seasar.extension.jdbc.ValueType; import org.seasar.extension.jdbc.Where; import org.seasar.extension.jdbc.WhereClause; import org.seasar.extension.jdbc.exception.BaseJoinNotFoundRuntimeException; import org.seasar.extension.jdbc.exception.EntityColumnNotFoundRuntimeException; import org.seasar.extension.jdbc.exception.IllegalIdPropertySizeRuntimeException; import org.seasar.extension.jdbc.exception.JoinDuplicatedRuntimeException; import org.seasar.extension.jdbc.exception.PropertyNotFoundRuntimeException; import org.seasar.extension.jdbc.exception.VersionPropertyNotExistsRuntimeException; import org.seasar.extension.jdbc.handler.BeanAutoResultSetHandler; import org.seasar.extension.jdbc.handler.BeanIterationAutoResultSetHandler; import org.seasar.extension.jdbc.handler.BeanListAutoResultSetHandler; import org.seasar.extension.jdbc.handler.ObjectResultSetHandler; import org.seasar.extension.jdbc.manager.JdbcManagerImplementor; import org.seasar.extension.jdbc.mapper.AbstractEntityMapper; import org.seasar.extension.jdbc.mapper.AbstractRelationshipEntityMapper; import org.seasar.extension.jdbc.mapper.EntityMapperImpl; import org.seasar.extension.jdbc.mapper.ManyToOneEntityMapperImpl; import org.seasar.extension.jdbc.mapper.OneToManyEntityMapperImpl; import org.seasar.extension.jdbc.mapper.OneToOneEntityMapperImpl; import org.seasar.extension.jdbc.mapper.PropertyMapperImpl; import org.seasar.extension.jdbc.util.QueryTokenizer; import org.seasar.extension.jdbc.where.ComplexWhere; import org.seasar.extension.jdbc.where.SimpleWhere; import org.seasar.framework.exception.EmptyRuntimeException; import org.seasar.framework.message.MessageFormatter; import org.seasar.framework.util.StringUtil; import org.seasar.framework.util.tiger.CollectionsUtil; import org.seasar.framework.util.tiger.Pair; /** * {@link AutoSelect}の実装クラスです。 * * @author higa * @param <T> * エンティティの型です。 * */ public class AutoSelectImpl<T> extends AbstractSelect<T, AutoSelect<T>> implements AutoSelect<T> { /** * 結合メタデータのリストです。 */ protected List<JoinMeta> joinMetaList = new ArrayList<JoinMeta>(); /** * テーブルのインデックスです。 */ protected int tableIndex; /** * テーブル別名のマップです。 */ protected Map<String, String> tableAliasMap = new HashMap<String, String>(); /** * エンティティメタデータのマップです。 */ protected Map<String, EntityMeta> entityMetaMap = new HashMap<String, EntityMeta>(); /** * エンティティ名です。 */ protected String entityName; /** * select句へ追加するプロパティ */ protected final Set<String> includesProperties = CollectionsUtil .newHashSet(); /** * select句から除外するプロパティ */ protected final Set<String> excludesProperties = CollectionsUtil .newHashSet(); /** * select句です。 */ protected SelectClause selectClause = new SelectClause(); /** * from句です。 */ protected FromClause fromClause = new FromClause(); /** * where句です。 */ protected WhereClause whereClause = new WhereClause(); /** * order by句です。 */ protected OrderByClause orderByClause = new OrderByClause(); /** * ソート順です。 */ protected String orderBy = ""; /** * SELECT ~ FOR UPDATEのSQL文字列です。 */ protected String forUpdate = ""; /** * SELECT ~ FOR UPDATEのタイプです。 */ protected SelectForUpdateType forUpdateType; /** * SELECT ~ FOR UPDATEでロック対象となるエンティティからプロパティへのマップです。 */ protected Map<String, String> forUpdateTargets = CollectionsUtil .newLinkedHashMap(); /** * SELECT ~ FOR UPDATEでの待機時間 (秒単位) です。 */ protected int forUpdateWaitSeconds = 0; /** * EAGERフェッチするプロパティです。 */ protected Set<String> eagerProperties = new LinkedHashSet<String>(); /** * ヒントです。 */ protected String hint; /** * 値タイプのリストです。 */ protected List<ValueType> valueTypeList = new ArrayList<ValueType>(50); /** * selectリストのインデックスです。 */ protected int selectListIndex; /** * エンティティマッパーのマップです。 */ protected Map<String, AbstractEntityMapper> entityMapperMap = new HashMap<String, AbstractEntityMapper>(); /** * where句のパラメータです。 */ protected List<Object> whereParams = new ArrayList<Object>(); /** * Mapによるwhere句の条件指定です。 */ protected Map<String, ? extends Object> conditions; /** * クライテリアです。 */ protected String criteria; /** * クライテリア内のパラメータの配列です。 */ protected Object[] criteriaParams = new Object[] {}; /** * クライテリア内のパラメータに対応するプロパティ名の配列です。 */ protected String[] criteriaPropertyNames = new String[] {}; /** * IDプロパティのメタデータのリストです。 */ protected List<PropertyMeta> idPropertyMetaList; /** * IDプロパティの値の配列です。 */ protected Object[] idProperties; /** * バージョンプロパティのメタデータです。 */ protected PropertyMeta versionPropertyMeta; /** * バージョンプロパティの値です。 */ protected Object versionProperty; /** * {@link AutoSelectImpl}を作成します。 * * @param jdbcManager * 内部的なJDBCマネージャ * @param baseClass * ベースクラス */ public AutoSelectImpl(JdbcManagerImplementor jdbcManager, Class<T> baseClass) { super(jdbcManager, baseClass); } public AutoSelect<T> includes(final CharSequence... propertyNames) { includesProperties.addAll(Arrays.asList(toStringArray(propertyNames))); return this; } public AutoSelect<T> excludes(final CharSequence... propertyNames) { excludesProperties.addAll(Arrays.asList(toStringArray(propertyNames))); return this; } public AutoSelect<T> innerJoin(CharSequence name) { return join(name, JoinType.INNER); } public AutoSelect<T> innerJoin(CharSequence name, String condition, Object... params) { return join(name, JoinType.INNER, condition, params); } public AutoSelect<T> innerJoin(CharSequence name, Where condition) { return join(name, JoinType.INNER, condition); } public AutoSelect<T> innerJoin(CharSequence name, Where... conditions) { return join(name, JoinType.INNER, conditions); } public AutoSelect<T> innerJoin(CharSequence name, boolean fetch) { return join(name, JoinType.INNER, fetch); } public AutoSelect<T> innerJoin(CharSequence name, boolean fetch, String condition, Object... params) { return join(name, JoinType.INNER, fetch, condition, params); } public AutoSelect<T> innerJoin(CharSequence name, boolean fetch, Where condition) { return join(name, JoinType.INNER, fetch, condition); } public AutoSelect<T> innerJoin(CharSequence name, boolean fetch, Where... conditions) { return join(name, JoinType.INNER, fetch, conditions); } public AutoSelect<T> leftOuterJoin(CharSequence name) { return join(name, JoinType.LEFT_OUTER); } public AutoSelect<T> leftOuterJoin(CharSequence name, String condition, Object... params) { return join(name, JoinType.LEFT_OUTER, condition, params); } public AutoSelect<T> leftOuterJoin(CharSequence name, Where condition) { return join(name, JoinType.LEFT_OUTER, condition); } public AutoSelect<T> leftOuterJoin(CharSequence name, Where... conditions) { return join(name, JoinType.LEFT_OUTER, conditions); } public AutoSelect<T> leftOuterJoin(CharSequence name, boolean fetch) { return join(name, JoinType.LEFT_OUTER, fetch); } public AutoSelect<T> leftOuterJoin(CharSequence name, boolean fetch, String condition, Object... params) { return join(name, JoinType.LEFT_OUTER, fetch, condition, params); } public AutoSelect<T> leftOuterJoin(CharSequence name, boolean fetch, Where condition) { return join(name, JoinType.LEFT_OUTER, fetch, condition); } public AutoSelect<T> leftOuterJoin(CharSequence name, boolean fetch, Where... conditions) { return join(name, JoinType.LEFT_OUTER, fetch, conditions); } public AutoSelect<T> join(CharSequence name, JoinType joinType) { return join(name, joinType, true); } public AutoSelect<T> join(CharSequence name, JoinType joinType, String condition, Object... params) { return join(name, joinType, true, condition, params); } public AutoSelect<T> join(CharSequence name, JoinType joinType, Where condition) { return join(name, joinType, true, condition); } public AutoSelect<T> join(CharSequence name, JoinType joinType, Where... conditions) { return join(name, joinType, true, conditions); } public AutoSelect<T> join(CharSequence name, JoinType joinType, boolean fetch) { joinMetaList.add(new JoinMeta(name.toString(), joinType, fetch)); return this; } public AutoSelect<T> join(CharSequence name, JoinType joinType, boolean fetch, String condition, Object... params) { joinMetaList.add(new JoinMeta(name.toString(), joinType, fetch, condition, params)); return this; } public AutoSelect<T> join(CharSequence name, JoinType joinType, boolean fetch, Where condition) { joinMetaList.add(new JoinMeta(name.toString(), joinType, fetch, condition.getCriteria(), condition.getParams(), condition .getPropertyNames())); return this; } public AutoSelect<T> join(CharSequence name, JoinType joinType, boolean fetch, Where... conditions) { if (conditions == null) { throw new NullPointerException("conditions"); } if (conditions.length > 0) { final ComplexWhere where = new ComplexWhere(); for (final Where w : conditions) { where.and(w); } String criteria = where.getCriteria().trim(); if (StringUtil.isEmpty(criteria)) { return this; } joinMetaList.add(new JoinMeta(name.toString(), joinType, fetch, criteria, where.getParams(), where.getPropertyNames())); } return this; } /** * 結合メタデータの数を返します。 * * @return 結合メタデータの数 */ protected int getJoinMetaSize() { return joinMetaList.size(); } /** * 結合メタデータを返します。 * * @param index * 位置 * @return 結合メタデータ */ protected JoinMeta getJoinMeta(int index) { return joinMetaList.get(index); } @Override protected void prepare(String methodName) { prepareCallerClassAndMethodName(methodName); prepareTarget(); prepareJoins(); prepareIdVersion(); prepareWhere(); prepareConditions(); prepareCriteria(); prepareOrderBy(); prepareForUpdate(); prepareParams(); prepareSql(); } /** * 対象エンティティの準備をします。 */ protected void prepareTarget() { String tableAlias = prepareTableAlias(null); EntityMeta entityMeta = prepareEntityMeta(baseClass, null); entityName = entityMeta.getName(); List<PropertyMapper> propertyMapperList = new ArrayList<PropertyMapper>( 50); List<Integer> idIndexList = new ArrayList<Integer>(); prepareEntity(entityMeta, null, tableAlias, propertyMapperList, idIndexList); PropertyMapper[] propertyMappers = toPropertyMapperArray(propertyMapperList); int[] idIndices = toIdIndexArray(idIndexList); entityMapperMap.put(null, new EntityMapperImpl(baseClass, propertyMappers, idIndices)); final String lockHint = getLockHint(null); if (StringUtil.isEmpty(lockHint)) { fromClause.addSql(entityMeta.getTableMeta().getFullName(), tableAlias); } else { fromClause.addSql(entityMeta.getTableMeta().getFullName(), tableAlias, lockHint); } } /** * エンティティの準備をします。 * * @param em * エンティティメタデータ * @param joinMeta * 結合メタデータ * @param tableAlias * テーブル別名 * @param propertyMapperList * プロパティマッパーのリスト * @param idIndexList * 識別子のインデックスのリスト */ protected void prepareEntity(EntityMeta em, JoinMeta joinMeta, String tableAlias, List<PropertyMapper> propertyMapperList, List<Integer> idIndexList) { if (count) { final String selectList = jdbcManager.getDialect() .getCountSqlSelectList( idPropertyMetaList); selectClause.addSql(selectList); valueTypeList.add(jdbcManager.getDialect().getValueType(Long.class, false, null)); } else { for (int i = 0; i < em.getPropertyMetaSize(); i++) { PropertyMeta pm = em.getPropertyMeta(i); if (pm.isTransient() || pm.isRelationship()) { continue; } if (!isTargetProperty(pm, joinMeta)) { continue; } selectClause.addSql(tableAlias, pm.getColumnMeta().getName()); valueTypeList.add(jdbcManager.getDialect().getValueType(pm)); propertyMapperList.add(new PropertyMapperImpl(pm.getField(), selectListIndex)); if (pm.isId()) { idIndexList.add(new Integer(selectListIndex)); } ++selectListIndex; } } } /** * select句に追加するプロパティなら{@literal true}を返します。 * * @param propertyMeta * プロパティメタデータ * @param joinMeta * 結合メタデータ * @return select句に追加するプロパティなら{@literal true} */ protected boolean isTargetProperty(final PropertyMeta propertyMeta, final JoinMeta joinMeta) { if (propertyMeta.isId()) { return true; } final boolean lazy = isLazy(propertyMeta, joinMeta); if (includesProperties.isEmpty() && excludesProperties.isEmpty()) { return !lazy; } String propertyName = propertyMeta.getName(); if (joinMeta != null) { propertyName = joinMeta.getName() + "." + propertyName; } boolean relationship = false; int index = propertyName.length(); while (index != -1) { propertyName = propertyName.substring(0, index); if (includesProperties.contains(propertyName)) { return !relationship || !lazy; } if (excludesProperties.contains(propertyName)) { return false; } relationship = true; index = propertyName.lastIndexOf("."); } return includesProperties.isEmpty() && !lazy; } /** * プロパティのフェッチタイプがLAZYなら{@literal true}を返します。 * * @param propertyMeta * プロパティメタデータ * @param joinMeta * 結合メタデータ * @return プロパティのフェッチタイプがLAZYなら{@literal true} */ protected boolean isLazy(final PropertyMeta propertyMeta, final JoinMeta joinMeta) { if (!propertyMeta.isLazy()) { return false; } final String propertyName = propertyMeta.getName(); if (joinMeta == null) { return !eagerProperties.contains(propertyName); } final String qualifiedName = joinMeta.getName() + '.' + propertyName; return !eagerProperties.contains(qualifiedName); } /** * テーブル別名を返します。 * * @param join * 結合名 * @return テーブル別名 */ protected String getTableAlias(String join) { return tableAliasMap.get(join); } /** * テーブル別名を作成します。 * * @return テーブル別名 */ protected String createTableAlias() { return "T" + ++tableIndex + "_"; } /** * テーブル別名を準備します。 * * @param join * 結合名 * @return テーブル別名 */ protected String prepareTableAlias(String join) { String tableAlias = createTableAlias(); tableAliasMap.put(join, tableAlias); return tableAlias; } /** * エンティティメタデータを返します。 * * @param join * 結合名 * @return エンティティメタデータ */ protected EntityMeta getEntityMeta(String join) { return entityMetaMap.get(join); } /** * エンティティメタデータを準備します。 * * @param entityClass * エンティティクラス * @param join * 結合名 * @return エンティティメタデータ */ protected EntityMeta prepareEntityMeta(Class<?> entityClass, String join) { EntityMeta entityMeta = null; try { entityMeta = jdbcManager.getEntityMetaFactory().getEntityMeta( entityClass); } catch (RuntimeException e) { logger.log("ESSR0709", new Object[] { callerClass.getName(), callerMethodName }); logger.log("ESSR0711", new Object[] { baseClass.getName() }); throw e; } entityMetaMap.put(join, entityMeta); return entityMeta; } /** * 値タイプの配列を返します。 * * @return 値タイプの配列 */ protected ValueType[] getValueTypes() { return valueTypeList.toArray(new ValueType[valueTypeList.size()]); } /** * プロパティマッパーの配列に変換します。 * * @param propertyMapperList * プロパティマッパーのリスト * @return プロパティマッパーの配列 */ protected PropertyMapperImpl[] toPropertyMapperArray( List<PropertyMapper> propertyMapperList) { return propertyMapperList .toArray(new PropertyMapperImpl[propertyMapperList.size()]); } /** * 識別子のインデックスのリストをintの配列に変換します。 * * @param idIndexList * @return intの配列 */ protected int[] toIdIndexArray(List<Integer> idIndexList) { int[] idIndices = new int[idIndexList.size()]; for (int i = 0; i < idIndices.length; i++) { idIndices[i] = idIndexList.get(i).intValue(); } return idIndices; } /** * エンティティマッパーを返します。 * * @param join * 結合名 * @return エンティティマッパー */ protected AbstractEntityMapper getEntityMapper(String join) { return entityMapperMap.get(join); } /** * 結合の準備をします。 * * @see #prepareJoin(JoinMeta) */ protected void prepareJoins() { for (JoinMeta joinMeta : joinMetaList) { prepareJoin(joinMeta); } } /** * 結合の準備をします。 * * @param joinMeta * 結合メタデータ */ protected void prepareJoin(JoinMeta joinMeta) { String tableAlias = prepareTableAlias(joinMeta.getName()); String[] names = splitBaseAndProperty(joinMeta.getName()); EntityMeta baseEntityMeta = getBaseEntityMeta(joinMeta.getName(), names[0]); AbstractEntityMapper baseEntityMapper = getBaseEntityMapper(joinMeta .getName(), names[0]); PropertyMeta propertyMeta = getPropertyMeta(baseEntityMeta, joinMeta .getName(), names[1]); Class<?> relationshipClass = propertyMeta.getRelationshipClass(); EntityMeta inverseEntityMeta = getInverseEntityMeta(relationshipClass, joinMeta.getName()); PropertyMeta inversePropertyMeta = getInversePropertyMeta( inverseEntityMeta, propertyMeta); String baseTableAlias = getTableAlias(names[0]); String fkTableAlias = baseTableAlias; String pkTableAlias = tableAlias; List<JoinColumnMeta> joinColumnMetaList = propertyMeta .getJoinColumnMetaList(); if (propertyMeta.getMappedBy() != null) { fkTableAlias = tableAlias; pkTableAlias = baseTableAlias; joinColumnMetaList = inversePropertyMeta.getJoinColumnMetaList(); } if (joinMeta.isFetch()) { List<PropertyMapper> propertyMapperList = new ArrayList<PropertyMapper>( 50); List<Integer> idIndexList = new ArrayList<Integer>(); if (!count) { prepareEntity(inverseEntityMeta, joinMeta, tableAlias, propertyMapperList, idIndexList); } PropertyMapper[] propertyMappers = toPropertyMapperArray(propertyMapperList); int[] idIndices = toIdIndexArray(idIndexList); AbstractRelationshipEntityMapper remapper = createRelationshipEntityMapper( relationshipClass, propertyMappers, idIndices, propertyMeta, inversePropertyMeta); entityMapperMap.put(joinMeta.getName(), remapper); baseEntityMapper.addRelationshipEntityMapper(remapper); } final String lockHint = getLockHint(joinMeta.getName()); jdbcManager.getDialect().setupJoin(fromClause, whereClause, joinMeta.getJoinType(), inverseEntityMeta.getTableMeta().getFullName(), tableAlias, fkTableAlias, pkTableAlias, joinColumnMetaList, lockHint, convertCriteria(joinMeta.getCondition())); if (!StringUtil.isEmpty(joinMeta.getCondition())) { final Object[] params = joinMeta.getConditionParams(); final String[] propertyNames = joinMeta.getConditionPropertyNames(); if (propertyNames == null) { for (Object param : params) { addParam(param); } } else { for (int i = 0; i < params.length; ++i) { prepareParams(propertyNames[i], params[i]); } } } } /** * <p> * 関連名をベースとプロパティに分離します。 * </p> * <p> * <code>aaa.bbb.ccc</code>ならベースが<code>aaa.bbb</code>、プロパティが<code>ccc</code> * になります。 * </p> * * @param name * 関連名 * @return ベースとプロパティの配列 */ protected String[] splitBaseAndProperty(String name) { String[] ret = new String[2]; int index = name.lastIndexOf('.'); if (index < 0) { ret[1] = name; } else { ret[0] = name.substring(0, index); ret[1] = name.substring(index + 1); } return ret; } /** * ベースのエンティティメタデータを返します。 * * @param join * 結合名 * @param base * ベースの結合名 * @return ベースのエンティティメタデータ * @throws BaseJoinNotFoundRuntimeException * ベースの結合が見つからない場合。 */ protected EntityMeta getBaseEntityMeta(String join, String base) throws BaseJoinNotFoundRuntimeException { EntityMeta baseEntityMeta = getEntityMeta(base); if (baseEntityMeta == null) { logger.log("ESSR0709", new Object[] { callerClass.getName(), callerMethodName }); throw new BaseJoinNotFoundRuntimeException(entityName, join, base); } return baseEntityMeta; } /** * ベースのエンティティメタマッパーを返します。 * * @param join * 結合名 * @param baseJoin * ベースの結合名 * @return ベースのエンティティマッパー * @throws BaseJoinNotFoundRuntimeException * ベースの結合が見つからない場合。 */ protected AbstractEntityMapper getBaseEntityMapper(String join, String baseJoin) throws BaseJoinNotFoundRuntimeException { AbstractEntityMapper baseEntityMapper = getEntityMapper(baseJoin); if (baseEntityMapper == null) { logger.log("ESSR0709", new Object[] { callerClass.getName(), callerMethodName }); throw new BaseJoinNotFoundRuntimeException(entityName, join, baseJoin); } return baseEntityMapper; } /** * プロパティメタデータを返します。 * * @param baseEntityMeta * ベースのエンティティメタデータ * @param fullPropertyName * 全体のプロパティ名 * @param propertyName * プロパティ名 * @return プロパティメタデータ * @throws RuntimeException * 実行時例外が発生した場合。 */ protected PropertyMeta getPropertyMeta(EntityMeta baseEntityMeta, String fullPropertyName, String propertyName) throws RuntimeException { PropertyMeta pm = null; try { pm = baseEntityMeta.getPropertyMeta(propertyName); } catch (RuntimeException e) { logger.log("ESSR0709", new Object[] { callerClass.getName(), callerMethodName }); logger.log("ESSR0708", new Object[] { entityName, fullPropertyName }); throw e; } return pm; } /** * 関連の逆側のエンティティメタデータを返します。 * * @param relationshipClass * 関連クラス * @param join * 結合名 * @return 関連の逆側のエンティティメタデータ * @throws RuntimeException * 実行時例外が発生した場合。 * @throws JoinDuplicatedRuntimeException * 結合が重複している場合。 * */ protected EntityMeta getInverseEntityMeta(Class<?> relationshipClass, String join) throws RuntimeException, JoinDuplicatedRuntimeException { EntityMeta inverseEntityMeta = null; try { inverseEntityMeta = jdbcManager.getEntityMetaFactory() .getEntityMeta(relationshipClass); } catch (RuntimeException e) { logger.log("ESSR0709", new Object[] { callerClass.getName(), callerMethodName }); logger.log("ESSR0710", new Object[] { entityName, join }); throw e; } if (entityMetaMap.put(join, inverseEntityMeta) != null) { logger.log("ESSR0709", new Object[] { callerClass.getName(), callerMethodName }); throw new JoinDuplicatedRuntimeException(entityName, join); } return inverseEntityMeta; } /** * 逆側のプロパティメタデータを返します。 * * @param inverseEntityMeta * 逆側のエンティティメタデータ * @param relationshipPropertyMeta * 関連のプロパティメタデータ * @return 逆側のプロパティメタデータ * */ protected PropertyMeta getInversePropertyMeta(EntityMeta inverseEntityMeta, PropertyMeta relationshipPropertyMeta) { if (relationshipPropertyMeta.getMappedBy() != null) { return inverseEntityMeta.getPropertyMeta(relationshipPropertyMeta .getMappedBy()); } return inverseEntityMeta.getMappedByPropertyMeta( relationshipPropertyMeta.getName(), baseClass); } /** * 関連エンティティマッパーを作成します。 * * @param relationshipClass * 関連クラス * @param propertyMappers * プロパティマッパーの配列 * @param idIndices * 識別子のインデックスの配列 * @param propertyMeta * 関連のプロパティメタデータ * @param inversePropertyMeta * 逆側の関連のプロパティメタデータ * @return 関連エンティティマッパー */ protected AbstractRelationshipEntityMapper createRelationshipEntityMapper( Class<?> relationshipClass, PropertyMapper[] propertyMappers, int[] idIndices, PropertyMeta propertyMeta, PropertyMeta inversePropertyMeta) { Field inverseField = inversePropertyMeta != null ? inversePropertyMeta .getField() : null; switch (propertyMeta.getRelationshipType()) { case ONE_TO_ONE: return new OneToOneEntityMapperImpl(relationshipClass, propertyMappers, idIndices, propertyMeta.getField(), inverseField); case ONE_TO_MANY: return new OneToManyEntityMapperImpl(relationshipClass, propertyMappers, idIndices, propertyMeta.getField(), inverseField); case MANY_TO_ONE: return new ManyToOneEntityMapperImpl(relationshipClass, propertyMappers, idIndices, propertyMeta.getField(), inverseField); } throw new IllegalStateException(propertyMeta.getRelationshipType() .toString()); } /** * SQLに変換します。 * * @return SQL */ protected String toSql() { String hintComment; if (!StringUtil.isEmpty(hint)) { hintComment = jdbcManager.getDialect().getHintComment( convertEntityNameToTableAlias(hint)); } else { hintComment = ""; } StringBuilder sb = new StringBuilder(7 + hintComment.length() + selectClause.getLength() + fromClause.getLength() + whereClause.getLength() + orderByClause.getLength()); return sb.append("select ").append(hintComment).append( selectClause.toSql()).append(fromClause.toSql()).append( whereClause.toSql()).append(orderByClause.toSql()).toString(); } public AutoSelect<T> where(String criteria, Object... params) { if (criteria == null) { throw new NullPointerException("criteria"); } this.criteria = criteria; if (params == null) { throw new NullPointerException("params"); } for (Object o : params) { whereParams.add(o); } return this; } public AutoSelect<T> where(Where where) { if (where == null) { throw new NullPointerException("where"); } String criteria = where.getCriteria(); if (StringUtil.isEmpty(criteria)) { return this; } this.criteria = criteria; this.criteriaParams = where.getParams(); this.criteriaPropertyNames = where.getPropertyNames(); return this; } public AutoSelect<T> where(Where... wheres) { if (wheres == null) { throw new NullPointerException("whereList"); } if (wheres.length > 0) { final ComplexWhere where = new ComplexWhere(); for (final Where w : wheres) { where.and(w); } String criteria = where.getCriteria().trim(); if (StringUtil.isEmpty(criteria)) { return this; } this.criteria = criteria; this.criteriaParams = where.getParams(); this.criteriaPropertyNames = where.getPropertyNames(); } return this; } public AutoSelect<T> where(Map<String, ? extends Object> conditions) { if (conditions == null) { throw new NullPointerException("conditions"); } this.conditions = conditions; return this; } public AutoSelect<T> id(final Object... idProperties) { if (idProperties == null) { throw new NullPointerException("idProperties"); } final EntityMeta entityMeta = jdbcManager.getEntityMetaFactory() .getEntityMeta(baseClass); idPropertyMetaList = entityMeta.getIdPropertyMetaList(); if (idPropertyMetaList.size() != idProperties.length) { throw new IllegalIdPropertySizeRuntimeException(entityMeta .getName(), idPropertyMetaList.size(), idProperties.length); } this.idProperties = idProperties; return this; } public AutoSelect<T> version(final Object versionProperty) { if (versionProperty == null) { throw new NullPointerException("versionProperty"); } final EntityMeta entityMeta = jdbcManager.getEntityMetaFactory() .getEntityMeta(baseClass); if (!entityMeta.hasVersionPropertyMeta()) { throw new VersionPropertyNotExistsRuntimeException(entityMeta .getName()); } versionPropertyMeta = entityMeta.getVersionPropertyMeta(); this.versionProperty = versionProperty; return this; } /** * where句のパラメータを準備します。 */ protected void prepareWhere() { for (final Object param : whereParams) { addParam(param); } } /** * where句の条件を準備します。 */ protected void prepareConditions() { if (conditions == null || conditions.size() == 0) { return; } final WhereClause whereTerm = new WhereClause(); for (Map.Entry<String, ? extends Object> e : conditions.entrySet()) { prepareCondition(whereTerm, e.getKey(), e.getValue()); } final String whereTermString = whereTerm.toSql(); if (StringUtil.isEmpty(whereTermString)) { return; } whereClause.addAndSql("("); whereClause.addSql(whereTermString.substring(WhereClause.WHERE_KEYWORD .length())); whereClause.addSql(")"); } /** * 条件を準備します。 * * @param whereTerm * WHERE句の項 * @param name * プロパティ名 * @param value * プロパティの値 * @param valueList * 値のリスト * @param valueClassList * 値のクラスのリスト */ protected void prepareCondition(WhereClause whereTerm, String name, Object value) { ConditionType conditionType = ConditionType.getConditionType(name); String pname = conditionType.removeSuffix(name); String[] names = splitBaseAndProperty(pname); String tableAlias = getTableAlias(names[0]); if (tableAlias == null) { logger.log("ESSR0709", new Object[] { callerClass.getName(), callerMethodName }); logger.log("ESSR0716", new Object[] { name }); throw new BaseJoinNotFoundRuntimeException(entityName, pname, names[0]); } EntityMeta baseEntityMeta = getBaseEntityMeta(pname, names[0]); PropertyMeta propertyMeta = getPropertyMeta(baseEntityMeta, pname, names[1]); ColumnMeta columnMeta = propertyMeta.getColumnMeta(); if (columnMeta == null) { logger.log("ESSR0709", new Object[] { callerClass.getName(), callerMethodName }); logger.log("ESSR0716", new Object[] { name }); throw new EntityColumnNotFoundRuntimeException(entityName, propertyMeta.getName()); } String columnName = columnMeta.getName(); List<Object> valueList = CollectionsUtil.newArrayList(); int size = conditionType.addCondition(tableAlias, columnName, value, whereTerm, valueList); for (int i = 0; i < size; i++) { addParam(valueList.get(i), propertyMeta.getPropertyClass(), jdbcManager.getDialect().getValueType(propertyMeta)); } } /** * クライテリアの準備をします。 */ protected void prepareCriteria() { if (criteria == null) { return; } whereClause.addAndSql("("); whereClause.addSql(convertCriteria(criteria)); whereClause.addSql(")"); } /** * IDプロパティ及びバージョンを準備します。 */ protected void prepareIdVersion() { if (idProperties == null) { if (versionProperty != null) { logger.log("ESSR0709", new Object[] { callerClass.getName(), callerMethodName }); throw new UnsupportedOperationException(MessageFormatter .getMessage("ESSR0758", null)); } return; } final SimpleWhere where = new SimpleWhere(); for (int i = 0; i < idProperties.length; ++i) { where.eq(idPropertyMetaList.get(i).getName(), idProperties[i]); } if (versionProperty != null) { where.eq(versionPropertyMeta.getName(), versionProperty); } whereClause.addSql(convertCriteria(where.getCriteria())); for (int i = 0; i < idProperties.length; ++i) { prepareParams(idPropertyMetaList.get(i).getName(), idProperties[i]); } if (versionProperty != null) { prepareParams(versionPropertyMeta.getName(), versionProperty); } } /** * パラメータを準備します。 */ protected void prepareParams() { for (int i = 0; i < criteriaParams.length; i++) { final String name = criteriaPropertyNames[i]; final Object value = criteriaParams[i]; prepareParams(name, value); } } /** * パラメータを準備します。 * * @param name * パラメータ名 * @param value * パラメータ値 */ protected void prepareParams(final String name, final Object value) { final String[] names = splitBaseAndProperty(name); final EntityMeta entityMeta = getEntityMeta(names[0]); if (entityMeta == null) { logger.log("ESSR0709", new Object[] { callerClass.getName(), callerMethodName }); logger.log("ESSR0716", new Object[] { name }); throw new BaseJoinNotFoundRuntimeException(entityName, name, names[0]); } if (!entityMeta.hasPropertyMeta(names[1])) { logger.log("ESSR0709", new Object[] { callerClass.getName(), callerMethodName }); throw new PropertyNotFoundRuntimeException(entityName, name); } final PropertyMeta pm = entityMeta.getPropertyMeta(names[1]); final ValueType valueType = jdbcManager.getDialect().getValueType(pm); addParam(value, value.getClass(), valueType); } /** * SQLを準備します。 */ protected void prepareSql() { executedSql = convertLimitSql(toSql()) + forUpdate; } @Override protected ResultSetHandler createResultListResultSetHandler() { return new BeanListAutoResultSetHandler(getValueTypes(), getEntityMapper(), executedSql, limit); } @Override protected ResultSetHandler createSingleResultResultSetHandler() { if (count) { return new ObjectResultSetHandler(valueTypeList.get(0), executedSql); } return new BeanAutoResultSetHandler(getValueTypes(), getEntityMapper(), executedSql); } @Override protected ResultSetHandler createIterateResultSetHandler( final IterationCallback<T, ?> callback) { return new BeanIterationAutoResultSetHandler(getValueTypes(), getEntityMapper(), executedSql, limit, callback); } /** * エンティティマッパーを返します。 * * @return エンティティマッパー */ protected EntityMapper getEntityMapper() { return (EntityMapper) getEntityMapper(null); } public AutoSelect<T> orderBy(String orderBy) { if (orderBy == null) { throw new NullPointerException("orderBy"); } this.orderBy = orderBy; return this; } public AutoSelect<T> orderBy(final OrderByItem... orderByItems) { if (orderByItems == null) { throw new NullPointerException("orderByItems"); } if (orderByItems.length > 0) { final StringBuilder buf = new StringBuilder( 20 * orderByItems.length); for (final OrderByItem item : orderByItems) { buf.append(item.getCriteria()).append(", "); } buf.setLength(buf.length() - 2); orderBy = new String(buf); } return this; } /** * order by句の準備をします。 */ protected void prepareOrderBy() { if (StringUtil.isEmpty(orderBy)) { return; } orderByClause.addSql(convertCriteria(orderBy, true)); } /** * プロパティ名で記述されたクライテリアをカラム名に変換します。 * * @param str * クライテリア * @return カラム名で記述されたクライテリア */ protected String convertCriteria(String str) { return convertCriteria(str, false); } /** * プロパティ名で記述されたクライテリアをカラム名に変換します。 * * @param str * クライテリア * @param convertAlias * カラム名をエイリアスに置換する場合は<code>true</code> * @return カラム名で記述されたクライテリア */ protected String convertCriteria(String str, boolean convertAlias) { if (StringUtil.isEmpty(str)) { return str; } StringBuilder sb = new StringBuilder(20 + str.length()); QueryTokenizer tokenizer = new QueryTokenizer(str); for (int type = tokenizer.nextToken(); type != QueryTokenizer.TT_EOF; type = tokenizer .nextToken()) { String token = tokenizer.getToken(); if (type == QueryTokenizer.TT_WORD) { String[] names = splitBaseAndProperty(token); String tableAlias = getTableAlias(names[0]); EntityMeta entityMeta = getEntityMeta(names[0]); if (entityMeta == null || !entityMeta.hasPropertyMeta(names[1])) { sb.append(token); } else { PropertyMeta pm = entityMeta.getPropertyMeta(names[1]); String itemName = tableAlias + "." + pm.getColumnMeta().getName(); if (convertAlias) { String alias = selectClause.getColumnAlias(itemName); if (!StringUtil.isEmpty(alias)) { itemName = alias; } } sb.append(itemName); } } else { sb.append(token); } } return sb.toString(); } /** * 文字列中のエンティティ名をテーブルの別名に変換します。 * * @param str * 入力文字列 * @return 入力文字列中のエンティティ名をテーブルの別名に変換した文字列 */ protected String convertEntityNameToTableAlias(final String str) { final StringBuilder buf = new StringBuilder(20 + str.length()); final QueryTokenizer tokenizer = new QueryTokenizer(str); for (int type = tokenizer.nextToken(); type != QueryTokenizer.TT_EOF; type = tokenizer .nextToken()) { final String token = tokenizer.getToken(); if (type == QueryTokenizer.TT_WORD) { final String tableAlias = getTableAlias(token .equals(entityName) ? null : token); if (StringUtil.isEmpty(tableAlias)) { buf.append(token); } else { buf.append(tableAlias); } } else { buf.append(token); } } return new String(buf); } public AutoSelect<T> forUpdate() { final DbmsDialect dialect = getJdbcManager().getDialect(); if (!dialect.supportsForUpdate(SelectForUpdateType.NORMAL, false)) { final EntityMeta entityMeta = getJdbcManager() .getEntityMetaFactory().getEntityMeta(baseClass); throw new UnsupportedOperationException(MessageFormatter .getMessage("ESSR0746", new Object[] { entityMeta.getName(), dialect.getName() })); } forUpdateType = SelectForUpdateType.NORMAL; return this; } public AutoSelect<T> forUpdate(final CharSequence... propertyNames) { if (propertyNames == null) { throw new NullPointerException("properties"); } if (propertyNames.length == 0) { logger.log("ESSR0709", new Object[] { callerClass.getName(), callerMethodName }); throw new EmptyRuntimeException("properties"); } final DbmsDialect dialect = getJdbcManager().getDialect(); if (!dialect.supportsForUpdate(SelectForUpdateType.NORMAL, true)) { final EntityMeta entityMeta = getJdbcManager() .getEntityMetaFactory().getEntityMeta(baseClass); throw new UnsupportedOperationException(MessageFormatter .getMessage("ESSR0747", new Object[] { entityMeta.getName(), dialect.getName() })); } forUpdateType = SelectForUpdateType.NORMAL; setupForUpdateTargets(toStringArray(propertyNames)); return this; } public AutoSelect<T> forUpdateNowait() { final DbmsDialect dialect = getJdbcManager().getDialect(); if (!dialect.supportsForUpdate(SelectForUpdateType.NOWAIT, false)) { final EntityMeta entityMeta = getJdbcManager() .getEntityMetaFactory().getEntityMeta(baseClass); throw new UnsupportedOperationException(MessageFormatter .getMessage("ESSR0748", new Object[] { entityMeta.getName(), dialect.getName() })); } forUpdateType = SelectForUpdateType.NOWAIT; return this; } public AutoSelect<T> forUpdateNowait(final CharSequence... propertyNames) { if (propertyNames == null) { throw new NullPointerException("properties"); } if (propertyNames.length == 0) { throw new EmptyRuntimeException("properties"); } final DbmsDialect dialect = getJdbcManager().getDialect(); if (!dialect.supportsForUpdate(SelectForUpdateType.NOWAIT, true)) { final EntityMeta entityMeta = getJdbcManager() .getEntityMetaFactory().getEntityMeta(baseClass); throw new UnsupportedOperationException(MessageFormatter .getMessage("ESSR0749", new Object[] { entityMeta.getName(), dialect.getName() })); } forUpdateType = SelectForUpdateType.NOWAIT; setupForUpdateTargets(toStringArray(propertyNames)); return this; } public AutoSelect<T> forUpdateWait(final int seconds) { final DbmsDialect dialect = getJdbcManager().getDialect(); if (!dialect.supportsForUpdate(SelectForUpdateType.WAIT, false)) { final EntityMeta entityMeta = getJdbcManager() .getEntityMetaFactory().getEntityMeta(baseClass); throw new UnsupportedOperationException(MessageFormatter .getMessage("ESSR0750", new Object[] { entityMeta.getName(), dialect.getName() })); } forUpdateType = SelectForUpdateType.WAIT; forUpdateWaitSeconds = seconds; return this; } public AutoSelect<T> forUpdateWait(final int seconds, final CharSequence... propertyNames) { if (propertyNames == null) { throw new NullPointerException("properties"); } if (propertyNames.length == 0) { throw new EmptyRuntimeException("properties"); } final DbmsDialect dialect = getJdbcManager().getDialect(); if (!dialect.supportsForUpdate(SelectForUpdateType.WAIT, true)) { final EntityMeta entityMeta = getJdbcManager() .getEntityMetaFactory().getEntityMeta(baseClass); throw new UnsupportedOperationException(MessageFormatter .getMessage("ESSR0751", new Object[] { entityMeta.getName(), dialect.getName() })); } forUpdateType = SelectForUpdateType.WAIT; forUpdateWaitSeconds = seconds; setupForUpdateTargets(toStringArray(propertyNames)); return this; } public AutoSelect<T> eager(CharSequence... propertyNames) { eagerProperties.addAll(Arrays.asList(toStringArray(propertyNames))); return this; } public AutoSelect<T> hint(final String hint) { this.hint = hint; return this; } /** * FOR UPDATE句を準備します。 */ @SuppressWarnings("unchecked") protected void prepareForUpdate() { if (forUpdateType == null) { forUpdate = ""; return; } if (limit > 0 || offset > 0) { throw new UnsupportedOperationException(MessageFormatter .getMessage("ESSR0754", null)); } final DbmsDialect dialect = getJdbcManager().getDialect(); for (JoinMeta joinMeta : joinMetaList) { switch (joinMeta.getJoinType()) { case INNER: if (!dialect.supportsInnerJoinForUpdate()) { logger.log("ESSR0709", new Object[] { callerClass.getName(), callerMethodName }); final EntityMeta entityMeta = getJdbcManager() .getEntityMetaFactory().getEntityMeta(baseClass); throw new UnsupportedOperationException(MessageFormatter .getMessage("ESSR0763", new Object[] { entityMeta.getName(), dialect.getName() })); } break; case LEFT_OUTER: if (!dialect.supportsOuterJoinForUpdate()) { logger.log("ESSR0709", new Object[] { callerClass.getName(), callerMethodName }); final EntityMeta entityMeta = getJdbcManager() .getEntityMetaFactory().getEntityMeta(baseClass); throw new UnsupportedOperationException(MessageFormatter .getMessage("ESSR0752", new Object[] { entityMeta.getName(), dialect.getName() })); } break; } } final int length = forUpdateTargets.size(); final Pair<String, String>[] aliases = new Pair[length]; int i = 0; for (final Entry<String, String> entry : forUpdateTargets.entrySet()) { aliases[i++] = toAliasPair(entry.getKey(), entry.getValue()); } forUpdate = dialect.getForUpdateString(forUpdateType, forUpdateWaitSeconds, aliases); } /** * ロックヒントを返します。 * * @param baseName * ベース名 * @return ロックヒント */ protected String getLockHint(final String baseName) { if (forUpdateType == null) { return ""; } if (!forUpdateTargets.isEmpty() && !forUpdateTargets.containsKey(baseName)) { return ""; } return jdbcManager.getDialect().getLockHintString(forUpdateType, forUpdateWaitSeconds); } /** * SELECT ~ FOR UPDATEの対象となるプロパティを準備します * * @param propertyNames * SELECT ~ FOR UPDATEの対象となるプロパティ名の並び */ protected void setupForUpdateTargets(final String[] propertyNames) { for (final String propertyName : propertyNames) { final String[] names = splitBaseAndProperty(propertyName); forUpdateTargets.put(names[0], names[1]); } } /** * SELECT ~ FOR UPDATEの対象となるテーブルエイリアスとカラムエイリアスのペアを返します。 * * @param baseName * ベース名 * @param propertyName * プロパティ名 * @return SELECT ~ FOR UPDATEの対象となるテーブルエイリアスとカラムエイリアスのペア */ @SuppressWarnings("unchecked") protected Pair<String, String> toAliasPair(final String baseName, final String propertyName) { final String tableAlias = getTableAlias(baseName); final EntityMeta entityMeta = entityMetaMap.get(baseName); if (entityMeta == null) { logger.log("ESSR0709", new Object[] { callerClass.getName(), callerMethodName }); logger.log("ESSR0716", new Object[] { baseName }); throw new BaseJoinNotFoundRuntimeException(entityName, baseName + "." + propertyName, baseName); } final PropertyMeta propertyMeta = entityMeta .getPropertyMeta(propertyName); final ColumnMeta columnMeta = propertyMeta.getColumnMeta(); if (columnMeta == null) { logger.log("ESSR0709", new Object[] { callerClass.getName(), callerMethodName }); throw new PropertyNotFoundRuntimeException(entityName, baseName == null ? propertyName : baseName + "." + propertyName); } final String columnAlias = columnMeta.getName(); return Pair.pair(tableAlias, columnAlias); } }