/*
* JEF - Copyright 2009-2010 Jiyi (mr.jiyi@gmail.com)
*
* 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 jef.database;
import java.io.Serializable;
import java.io.StringReader;
import java.lang.reflect.Type;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import javax.persistence.NonUniqueResultException;
import javax.persistence.OptimisticLockException;
import javax.persistence.PersistenceException;
import jef.common.log.LogUtil;
import jef.common.wrapper.IntRange;
import jef.common.wrapper.Page;
import jef.database.Condition.Operator;
import jef.database.Transaction.TransactionFlag;
import jef.database.cache.Cache;
import jef.database.cache.CacheImpl;
import jef.database.dialect.DatabaseDialect;
import jef.database.dialect.type.ColumnMapping;
import jef.database.dialect.type.VersionSupportColumn;
import jef.database.innerpool.IConnection;
import jef.database.innerpool.IUserManagedPool;
import jef.database.innerpool.MetadataService;
import jef.database.innerpool.PartitionSupport;
import jef.database.jdbc.result.IResultSet;
import jef.database.jdbc.result.ResultSetContainer;
import jef.database.jdbc.result.ResultSetWrapper;
import jef.database.jdbc.result.ResultSets;
import jef.database.jsqlparser.parser.ParseException;
import jef.database.jsqlparser.parser.StSqlParser;
import jef.database.meta.AbstractMetadata;
import jef.database.meta.AbstractRefField;
import jef.database.meta.EntityType;
import jef.database.meta.ITableMetadata;
import jef.database.meta.MetaHolder;
import jef.database.meta.Reference;
import jef.database.query.AllTableColumns;
import jef.database.query.ConditionQuery;
import jef.database.query.EntityMappingProvider;
import jef.database.query.Join;
import jef.database.query.JoinElement;
import jef.database.query.PKQuery;
import jef.database.query.Query;
import jef.database.query.Selects;
import jef.database.query.SqlContext;
import jef.database.query.SqlExpression;
import jef.database.query.TypedQuery;
import jef.database.routing.PartitionResult;
import jef.database.support.DbOperatorListener;
import jef.database.support.MultipleDatabaseOperateException;
import jef.database.support.RDBMS;
import jef.database.wrapper.ResultIterator;
import jef.database.wrapper.clause.BindSql;
import jef.database.wrapper.clause.CountClause;
import jef.database.wrapper.clause.InsertSqlClause;
import jef.database.wrapper.clause.QueryClause;
import jef.database.wrapper.clause.SqlBuilder;
import jef.database.wrapper.clause.UpdateClause;
import jef.database.wrapper.populator.AbstractResultSetTransformer;
import jef.database.wrapper.populator.ResultPopulatorImpl;
import jef.database.wrapper.populator.Transformer;
import jef.script.javascript.Var;
import jef.tools.ArrayUtils;
import jef.tools.Assert;
import jef.tools.JefConfiguration;
import jef.tools.PageLimit;
import jef.tools.StringUtils;
import org.easyframe.enterprise.spring.TransactionMode;
/**
* 描述一个事务(会话)的数据库操作句柄,提供了各种操作数据库的方法供用户使用。
* <p>
* 大部分数据库操作方法都被封装在这个类上。这个类也是EF-ORM中用户使用最多的一个类。 该类有两类实现
* <ul>
* <li>Transaction 事务状态下的数据库封装,可以回滚和提交,设置SavePoint。</li>
* <li>DbClient 非事务状态下的数据库连接,每次操作后自动提交不能回滚。但可以执行建表、删表等DDL语句。</li>
* </ul>
* 这个类是线程安全的。
*
* @author Jiyi
* @see DbClient
* @see Transaction
* @see #getSqlTemplate(String)
*/
public abstract class Session {
// 这六个值在初始化的时候赋值
protected SqlProcessor preProcessor;
protected InsertProcessor insertp;
protected UpdateProcessor updatep;
protected DeleteProcessor deletep;
protected SelectProcessor selectp;
/**
* 获取数据库方言<br>
* 当有多数据源的时候,这个方法总是返回默认数据源的方言。
*
* @return 方言
* @deprecated 为更好支持异构多数据库,尽量使用{@link #getProfile(String)}
*/
public DatabaseDialect getProfile() {
return getProfile(null);
}
/**
* 获取指定数据库的方言,支持从不同数据源中获取<br/>
*
* @param key
* 数据源名称,如果单数据源的场合,可以传入null
* @return 指定数据源的方言
*/
public abstract DatabaseDialect getProfile(String key);
/*
* 内部使用
*/
abstract IUserManagedPool getPool();
/*
* 内部使用 得到数据库连接
*/
abstract IConnection getConnection() throws SQLException;
/*
* 内部使用 释放(当前线程)连接
*/
abstract void releaseConnection(IConnection conn);
/*
* 内部使用 得到数据库名
*/
abstract protected String getDbName(String dbKey);
/*
* 内部使用 得到缓存
*/
abstract protected Cache getCache();
/*
* 得到数据库操作监听器(观测者模式的回调对象)
*/
abstract protected DbOperatorListener getListener();
/*
* 当前数据库操作所在的事务,用于记录日志以跟踪SQL查询所在事务
*/
abstract protected String getTransactionId(String dbKey);//
/*
* 返回目前已知的所有可连接的数据源名称
*
* @return 多个数据源的名称
*/
abstract protected Collection<String> getAllDatasourceNames();
/*
* 获取当前数据库的事务管理模式
*/
protected abstract TransactionMode getTxType();
/*
* 当前操作是否位于一个JPA事务中。
*
* @return true is current is in a JPA transaction.
*/
abstract boolean isJpaTx();
/*
* 当前是否处理多数据库路由场景中
*/
abstract boolean isRoutingDataSource();
/**
* 关闭数据库事务。<br>
* <ul>
* <li>在DbClient上调用此方法,无任何影响。</li>
* <li>在Transaction上调用此方法,将会关闭事务,未被提交的修改将会回滚。</li>
* </ul>
*/
public abstract void close();
/**
* 清理一级缓存
*
* @param entity
* 要清理的数据或查询
*/
public final void evict(IQueryableEntity entity) {
getCache().evict(entity);
}
/**
* 清空全部的一级缓存
*/
public final void evictAll() {
getCache().evictAll();
}
/**
* 判断Session是否有效
*
* @return true if the session is open.
*/
public abstract boolean isOpen();
/**
* 创建命名查询
*
* <h3>什么是命名查询</h3>
* 事先将E-SQL编写在配置文件或者数据库中,运行时加载并解析,使用时按名称进行调用。这类SQL查询被称为NamedQuery。对应JPA规范当中的
* “命名查询”。
*
* <h3>使用示例</h3>
*
* <pre>
* <code>NativeQuery<ResultWrapper> query = db.createNamedQuery("unionQuery-1", ResultWrapper.class);
*List<ResultWrapper> result = query.getResultList();
*
*配置在named-queries.xml中的SQL语句
*<query name="unionQuery-1">
*<![CDATA[
*select * from(
*(select upper(t1.person_name) AS name, T1.gender, '1' AS GRADE, T2.NAME AS SCHOOLNAME
* from T_PERSON T1
* inner join SCHOOL T2 ON T1.CURRENT_SCHOOL_ID=T2.ID)
* union
*(select t.NAME,t.GENDER,t.GRADE,'Unknown' AS SCHOOLNAME from STUDENT t)) a
*]]>
*</query></code>
*
* <pre>
* 即使用本方法返回的NativeQuery对象上,可以执行和该SQL语句相关的各种操作。
*
* @param name
* 数据库中或者文件中配置的命名查询的名称
* @param resultWrapper
* 想要的查询结果包装类型
* @return 查询对象(NativeQuery)
* @see NativeQuery
*/
abstract public <T> NativeQuery<T> createNamedQuery(String name, Class<T> resultWrapper);
/**
* {@linkplain #createNamedQuery(String, Class) 什么是命名查询}
*
* @param name
* 数据库中或者文件中配置的命名查询的名称
* @param resultMeta
* 想要的查询结果包装类型
* @return 查询对象(NativeQuery)
* @see NativeQuery
*/
abstract public <T> NativeQuery<T> createNamedQuery(String name, ITableMetadata resultMeta);
/**
* 创建命名查询,不指定其返回类型,一般用于executeUpdate()的场合<br>
* {@linkplain #createNamedQuery(String, Class) 什么是命名查询}
*
* @param name
* 命名查询的名称
* @return 查询对象(NativeQuery)
* @see NativeQuery
*/
public final <T> NativeQuery<T> createNamedQuery(String name) {
return createNamedQuery(name, (Class<T>) null);
}
/**
* 返回SQL操作句柄,可以在该对象上使用SQL语句和JPQL语句操作数据库<br/>
*
* <tt>特点:支持不同的数据源<tt>
*
* @param dbKey
* 数据源名称,如果单数据源的场合,可以传入null
* @return SQL语句操作句柄
* @see SqlTemplate
*/
public final SqlTemplate getSqlTemplate(String dbKey) {
return selectTarget(dbKey);
}
protected abstract OperateTarget selectTarget(String dbKey);
/**
* 创建SQL查询(支持绑定变量)
*
* @param sqlString
* SQL语句
* @return 查询对象(NativeQuery)
* @see NativeQuery
*/
public NativeQuery<?> createNativeQuery(String sqlString) {
return selectTarget(null).createNativeQuery(sqlString, (Class<?>) null);
}
/**
* 创建SQL查询(支持绑定变量)
*
* @param sqlString
* SQL语句
* @param resultClass
* 返回结果类型
* @return 查询对象(NativeQuery)
* @see NativeQuery
*/
public <T> NativeQuery<T> createNativeQuery(String sqlString, Class<T> resultClass) {
return selectTarget(null).createNativeQuery(sqlString, resultClass);
}
/**
* 创建SQL查询(支持绑定变量)
*
* @param sqlString
* SQL语句
* @param resultMeta
* 返回结果类型(元模型)
* @return 查询对象(NativeQuery)
* @see NativeQuery
*/
public <T> NativeQuery<T> createNativeQuery(String sqlString, ITableMetadata resultMeta) {
return selectTarget(null).createNativeQuery(sqlString, resultMeta);
}
/**
* 创建一个对存储过程、函数的调用对象,允许带返回对象
*
* @param procedureName
* 存储过程名称
* @param paramClass
* 参数的类型,用法如下:
* <ul>
* <li>凡是入参,直接传入类型,如String.class, Long.class</li>
* <li>出参,单个的写作OutParam.typeOf(type),例如OutParam.typeOf(Integer.
* class)</li>
* <li>出参,以游标形式返回多个的写作OutParam.listOf(type),例如OutParam.listOf(
* Entity .class)</li>
* </ul>
* @return 调用对象NativeCall
* @throws SQLException
* @see NativeCall
*/
public NativeCall createNativeCall(String procedureName, Type... paramClass) throws SQLException {
return selectTarget(null).createNativeCall(procedureName, paramClass);
}
/**
* 创建匿名过程(匿名块)调用对象
*
* @param callString
* SQL语句
* @param paramClass
* 参数的类型,用法如下
* <ul>
* <li>凡是入参,直接传入类型,如String.class, Long.class</li>
* <li>出参,单个的写作OutParam.typeOf(type),例如OutParam.typeOf(Integer.
* class)</li>
* <li>出参,以游标形式返回多个的写作OutParam.listOf(type),例如OutParam.listOf(
* Entity .class)</li>
* </ul>
* @return 调用对象NativeCall
* @throws SQLException
* @see NativeCall
*/
public NativeCall createAnonymousNativeCall(String callString, Type... paramClass) throws SQLException {
return selectTarget(null).createAnonymousNativeCall(callString, paramClass);
}
/**
* 创建JPQL的NativeQuery查询
*
* @param jpql
* JPQL语句
* @return 查询对象NativeQuery
* @see NativeQuery
*/
public NativeQuery<?> createQuery(String jpql) {
try {
return selectTarget(null).createQuery(jpql, null);
} catch (SQLException e) {
throw new PersistenceException(e);
}
}
/**
* 创建JPQL的NativeQuery查询
*
* @param jpql
* JPQL语句
* @param resultClass
* 返回数据类型
* @return 查询对象NativeQuery
* @see NativeQuery
*/
public <T> NativeQuery<T> createQuery(String jpql, Class<T> resultClass) {
try {
return selectTarget(null).createQuery(jpql, resultClass);
} catch (SQLException e) {
throw new PersistenceException(e);
}
}
/**
* 更新数据(带级联)<br>
* 如果和其他表具有关联的关系,那么插入时会自动维护其他表中的数据,这些操作包括了Delete操作(删除子表的部分数据)
*
* @param obj
* 被更新的对象
* @return 更新的记录数 (仅主表,级联修改的记录行数未算在内。)
* @throws SQLException
* 如果数据库操作错误,抛出。
* @see {@link #update(IQueryableEntity)}
*/
public int updateCascade(Object entity) throws SQLException {
if (entity == null)
return 0;
IQueryableEntity obj;
if (entity instanceof IQueryableEntity) {
obj = (IQueryableEntity) entity;
} else {
ITableMetadata meta = MetaHolder.getMeta(entity);
obj = meta.transfer(entity, true);
}
return updateCascade0(obj, 0);
}
/**
* 删除数据(带级联) <br>
* 如果和其他表具有关联的关系,那么插入时会自动维护其他表中的数据,这些操作包括了Delete操作
*
* @param obj
* 删除请求的Entity对象
* @return 影响的记录数 (仅主表,级联修改的记录行数未算在内。)
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public int deleteCascade(Object entity) throws SQLException {
if (entity == null)
return 0;
IQueryableEntity obj;
if (entity instanceof IQueryableEntity) {
obj = (IQueryableEntity) entity;
return deleteCascade0(obj, 0);
} else {
ITableMetadata meta = MetaHolder.getMeta(entity);
obj = meta.transfer(entity, true);
return delete(obj.getQuery());
}
}
/**
* 合并记录——记录如果已经存在,则比较并更新;如果不存在则新增。(无级联操作)
*
* @param entity
* 要合并的记录数据
* @return 如果插入返回对象本身,如果是更新则返回旧记录的值
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public <T extends IQueryableEntity> T merge(T entity) throws SQLException {
T old = null;
@SuppressWarnings("unchecked")
Query<T> q = entity.getQuery();
q.setCascade(false);
ITableMetadata meta = q.getMeta();
if (meta.getPKFields().isEmpty()) {
q.setMaxResult(2);
List<T> list = select(q);
if (list.size() == 1) {
old = list.get(0);
DbUtils.compareToUpdateMap(entity, old);
if (old.needUpdate())
update(old);
entity.clearQuery();
return old;
}
} else if (DbUtils.getPrimaryKeyValue(entity) != null) {
old = load(entity, true);
entity.clearQuery();
if (old != null) {
DbUtils.compareToUpdateMap(entity, old);
if (old.needUpdate())
update(old);
return old;
}
}
// 如果旧数据不存在
insert(entity);
return entity;
}
/**
* 合并记录——记录如果已经存在,则比较并更新;如果不存在则新增。(带级联操作)
*
* @param entity
* 要合并的记录数据
* @return 如果插入返回对象本身,如果是更新则返回旧记录的值
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public <T extends IQueryableEntity> T mergeCascade(T entity) throws SQLException {
T old = null;
ITableMetadata meta = MetaHolder.getMeta(entity);
// 无主键匹配法
if (meta.getPKFields().isEmpty()) {
throw new UnsupportedOperationException("Do not support merge operate on table without primary key.");
} else if (DbUtils.getPrimaryKeyValue(entity) != null) {
old = load(entity, true);
if (old != null) {
CascadeUtil.compareToNewUpdateMap(entity, old);// 之所以是将对比结果放到新对象中,是为了能将新对象中级联关系也保存到数据库中。
updateCascade(entity);
return old;
}
}
// 如果旧数据不存在
insertCascade(entity);
return entity;
}
/**
* 插入数据(带级联)<br>
* 如果和其他表具有1VS1、1VSN的关系,那么插入时会自动维护其他表中的数据。这些操作包括了Insert或者update.
*
* @param obj
* 插入的对象
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public void insertCascade(Object obj) throws SQLException {
insertCascade0(obj, ORMConfig.getInstance().isDynamicInsert(), 0);
}
/**
* 插入数据(带级联)<br>
* 如果和其他表具有1VS1或1VSN的关系,那么插入时会自动维护其他表中的数据,这些操作包括了Insert或者update.
*
* @param obj
* 插入的对象
* @param dynamic
* dynamic模式:某些字段在数据库中设置了defauelt value。
* 如果在实体中为null,那么会将null值插入数据库,造成数据库的缺省值无效。 为了使用dynamic模式后,
* 只有手工设置为null的属性,插入数据库时才是null。如果没有设置过值,在插入数据库时将使用数据库的默认值。
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public void insertCascade(Object obj, boolean dynamic) throws SQLException {
insertCascade0(obj, dynamic, 0);
}
/**
* 插入对象 <br>
* 不处理级联关系。如果要支持级联插入,请使用{@link #insertCascade(IQueryableEntity)}
*
* @param obj
* 插入的对象。
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public void insert(Object obj) throws SQLException {
insert(obj, null, ORMConfig.getInstance().isDynamicInsert());
}
/**
* 插入对象。<br>
* 如果使用dynamic模式将会忽略掉没有set过的属性值
*
* @param obj
* 插入的对象。
* @param dynamic
* dynamic模式:某些字段在数据库中设置了defauelt value。
* 如果在实体中为null,那么会将null值插入数据库,造成数据库的缺省值无效。 为了使用dynamic模式后,
* 只有手工设置为null的属性,插入数据库时才是null。如果没有设置过值,在插入数据库时将使用数据库的默认值。
*/
public void insert(IQueryableEntity obj, boolean dynamic) throws SQLException {
insert(obj, null, dynamic);
}
/**
* 插入对象(自定义插入的表名)
*
* @param obj
* 要插入的对象
* @param myTableName
* 自定义表名称,一旦自定义了表名,将直接使用此表;不再计算表名
* @throws SQLException
* 如果数据库操作错误,抛出。
*
*/
public void insert(IQueryableEntity obj, String myTableName) throws SQLException {
insert(obj, myTableName, ORMConfig.getInstance().isDynamicInsert());
}
/**
* 插入对象(自定义插入的表名)
*
* @param obj
* 要插入的对象
* @param myTableName
* 自定义表名称,一旦自定义了表名,将直接使用此表;不再计算表名(支持Schema重定向)
* @param dynamic
* dynamic模式:某些字段在数据库中设置了defauelt
* value,此时如果在实体中为null,那么会将null值插入数据库,造成数据库的缺省值无效。
* 为了使用dynamic模式后,
* 只有手工设置为null的属性,插入数据库时才是null。如果没有设置过值,在插入数据库时将使用数据库的默认值。
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public void insert(Object entity, String myTableName, boolean dynamic) throws SQLException {
if (entity == null)
return;
ITableMetadata meta = MetaHolder.getMeta(entity);
if (meta.getExtendsTable() != null) {
if (myTableName != null) {
throw new UnsupportedOperationException();// 暂不支持扩展表时定制表名
}
insertCascade0(entity, dynamic, 5);
return;
}
IQueryableEntity obj;
if (entity instanceof IQueryableEntity) {
obj = (IQueryableEntity) entity;
} else {
obj = meta.transfer(entity, false);
}
insert0(obj, myTableName, dynamic);
}
/**
* 按主键删除<strong>单条</strong>对象
*
* @param clz
* 实体类型
* @param keys
* 主键的值。<br>
* (注意,可变参数不是用于传入多行记录的值,而是用于传入单条记录的复合主键, 要批量删除多条请用
* {@linkplain #batchDelete}方法)
* @return 删除记录条数
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public int delete(Class<?> entityClass, Serializable... keys) throws SQLException {
ITableMetadata meta = MetaHolder.getMeta(entityClass);
return delete(meta, keys);
}
/**
* 按主键删除<strong>单条</strong>对象
*
* @param entityClass
* 实体的元模型
* @param keys
* 主键的值。<br>
* (注意,可变参数不是用于传入多行记录的值,而是用于传入单条记录的复合主键, 要批量删除多条请用
* {@linkplain #batchDelete}方法)
* @return 删除记录条数
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
@SuppressWarnings("rawtypes")
public int delete(ITableMetadata meta, Serializable... keys) throws SQLException {
return delete(new PKQuery(meta, keys));
}
/**
* 按指定字段的值删除对象。<br>
* 如果要按该字段批量删除对象,请使用 {@link #batchDeleteByField(Field, List) }方法。
*
*
* @param field
* 作为删除条件的字段
* @param value
* 删除条件值
* @return 删除的行数
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public <T> int deleteByField(Field field, Object value) throws SQLException {
ITableMetadata meta = DbUtils.getTableMeta(field);
Query<?> query = meta.newInstance().getQuery();
query.addCondition(field, Operator.EQUALS, value);
return this.delete(query);
}
/**
* 删除对象
*
* @param obj
* 删除请求
* @return 影响的记录数
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public int delete(Object entity) throws SQLException {
if (entity == null)
return 0;
IQueryableEntity obj;
if (entity instanceof IQueryableEntity) {
obj = (IQueryableEntity) entity;
} else {
ITableMetadata meta = MetaHolder.getMeta(entity);
obj = meta.transfer(entity, true);
}
return delete(obj.getQuery());
}
/**
* 根据一个Query条件删除数据
*
* @param query
* 删除请求
* @return 影响的记录数
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public int delete(Query<?> query) throws SQLException {
ITableMetadata meta = query.getMeta();
if (meta.getExtendsTable() != null) {
return deleteCascade0(query.getInstance(), 5);
}
return delete0(query);
}
/**
* 更新对象,无级联操作
*
* @param obj
* 被更新的对象
* @return 影响的记录行数
* @throws SQLException
* @see {@link #updateCascade(IQueryableEntity)}
*/
public int update(Object obj) throws SQLException {
int n = update(obj, null);
return n;
}
/**
* 更新对象,无级联操作,可以指定操作的表名
*
* @param obj
* 被更新的对象
* @param myTableName
* 要操作的表名,支持Schema重定向
* @return 影响的记录行数
* @throws SQLException
* 如果数据库操作错误,抛出。
* @see #update(IQueryableEntity)
*/
public int update(Object entity, String myTableName) throws SQLException {
if (entity == null)
return 0;
ITableMetadata meta = MetaHolder.getMeta(entity);
IQueryableEntity obj;
if (entity instanceof IQueryableEntity) {
obj = (IQueryableEntity) entity;
} else {
obj = meta.transfer(entity, true);
}
if (meta.getExtendsTable() != null) {
if (myTableName != null) {
throw new UnsupportedOperationException();// 暂不支持扩展表时定制表名
}
return updateCascade0(obj, 5);
} else {
return update0(obj, myTableName);
}
}
/**
* <h3>最基本的查询方法</h3> 根据指定的条件查询数据库
*
* <h3>支持级联操作</h3> 根据在实体中的定义,查询时会自动填充关联到其他表的字段<br>
* 在OneToOne和ManyToOne当中,会使用左连接一次查询出来(默认情况下)<br>
* 在OneToMany和ManyToMany中,会使用延迟加载,当访问字段属性时去数据库查询。<br>
*
*
* <h3>性能的平衡</h3>
* ORM框架会尽可能减少查询操作次数以确保性能。但是如果您返回一个很大的结果集,并且每个结果都需要关联到其他表再做一次查询的话,<br>
* 可能会给性能带来一定的影响。如果您确定不需要这些填充关联字段的,可以使用{@link Query#setCascade(boolean)},例如:
*
* <pre>
* <code>
* Person p=new Person();
* p.setId(1);
* p.getQuery().setCascade(false); //关闭级联查询
* List<Person> result = db.select(p);
* </code>
* </pre>
*
* <h3>和其他方法关系</h3> 这两种写法是完全等效的
*
* <pre>
* <tt>
* List<Person> result = db.select(p);
* List<Person> result = db.select(p.getQuery());
* </tt>
* </pre>
* <p>
* 当需要指定结果范围(分页)时 {@link #select(IQueryableEntity, IntRange)}
*
*
*
* @param <T>
* 泛型: 被查询的数据类型
* @param obj
* 被查询的对象(可携带{@link Query}以表示复杂的查询)
* @return 查询结果列表。不会返回null.
* @throws SQLException
* 如果数据库操作错误,抛出。
* @see Query
*/
public <T extends IQueryableEntity> List<T> select(T obj) throws SQLException {
return select(obj, null);
}
/**
* <h3>基本的查询方法</h3> 根据指定的条件(Query格式)查询结果<br>
* 可能的查询操作包括多表连接查询和多次查询。(一对一和多对一查询使用多表连接,一对多和多对多使用多次查询。<br>
* JEF会尽可能减少查询操作次数以确保性能,但是要注意,如果您返回一个很大的结果集,并且每个结果都需要关联到其他表再做一次查询的话,<br>
* 可能会给性能带来一定的影响。如果您确定不需要这些填充关联字段的,请尽量使用selectSingel方法。<br>
*
* @param queryObj
* 查询对象
* @return 查询结果,不会返回null
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public <T> List<T> select(TypedQuery<T> queryObj) throws SQLException {
return select(queryObj, null);
}
/**
* 根据指定的条件查询结果,带分页返回
*
* @param obj
* 查询对象,
* @param range
* 范围,含头含尾的区间,比如new IntRange(1,10)表示从第1条到第10条。
* @return 查询结果
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
@SuppressWarnings("unchecked")
public <T extends IQueryableEntity> List<T> select(T obj, IntRange range) throws SQLException {
Query<T> query = (Query<T>) obj.getQuery();
QueryOption option = QueryOption.createFrom(query);
return typedSelect(query, PageLimit.parse(range), option);
}
/**
* 根据拼装好的Query进行查询
*
* @param cq
* 查询条件
* @param metadata
* 拼装结果类型
* @param range
* 分页范围,如果无须分页,查询语句已经包含了范围,此处可传null
* @return 查询结果
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
@SuppressWarnings("unchecked")
public <T> List<T> select(ConditionQuery queryObj, IntRange range) throws SQLException {
QueryOption option;
if (queryObj instanceof JoinElement) {
option = QueryOption.createFrom((JoinElement) queryObj);
if (queryObj instanceof Query<?>) {
Query<?> simpleQuery = (Query<?>) queryObj;
JoinElement element = DbUtils.toReferenceJoinQuery(simpleQuery, null);
return this.innerSelect(element, PageLimit.parse(range), simpleQuery.getFilterCondition(), option);
} else {
return this.innerSelect(queryObj, PageLimit.parse(range), null, option);
}
} else {
option = QueryOption.DEFAULT;
return this.innerSelect(queryObj, PageLimit.parse(range), null, option);
}
}
/**
* 根据拼装好的Query进行查询。并将结果转换为期望的对象。
*
* @param <T>
*
* @param queryObj
* 查询条件
* @param resultClz
* 结果转换类型
* @param range
* 分页范围,如果无须分页,查询语句已经包含了范围,此处可传null
*
* @return 查询结果
*
* @throws SQLException
* 如果数据库操作错误,抛出。
*
* @see {@link #select(ConditionQuery, IntRange)}
*
* after calling
* {@code queryObj.getResultTransformer().setResultType(resultClass)}
* then use {@link #select(ConditionQuery, IntRange)} instead.
*/
@SuppressWarnings("unchecked")
public <T> List<T> selectAs(ConditionQuery queryObj, Class<T> resultClz, IntRange range) throws SQLException {
queryObj.getResultTransformer().setResultType(resultClz);
QueryOption option;
if (queryObj instanceof JoinElement) {
option = QueryOption.createFrom((JoinElement) queryObj);
} else {
option = QueryOption.DEFAULT;
}
return this.innerSelect(queryObj, PageLimit.parse(range), null, option);
}
/**
* 查询并用指定的结果返回。并将结果转换为期望的对象。
*
* @param obj
* 查询
* @param resultType
* 每条记录将被转换为指定的类型
* @return 查询结果
* @throws SQLException
* 如果数据库操作错误,抛出。
* @see {@link ConditionQuery#getResultTransformer()} and
* {@link Transformer#setResultType(Class)}
*/
public <T> List<T> selectAs(ConditionQuery obj, Class<T> resultType) throws SQLException {
return selectAs(obj, resultType, null);
}
/**
* 遍历器模式查找,一般用于超大结果集的返回。 <h3>作用</h3> 当结果集超大时,如果用List<T>返回,内存占用很大甚至会溢出。<br>
* JDBC设计时考虑到这个问题,因此其返回的ResultSet对象只是查询结果视图的一段,用户向后滚动结果集时,数据库才将需要的数据传到客户端。
* 如果客户端不缓存整个结果集,那么前面已经滚动过的结果数据就被释放。
* <p>
* 这种处理方式实际上是一种流式处理模型,iteratedSelect就是这种模型的封装。<br>
* iteratedSelect并不会将查询出的所有数据放置到一个List对象中(这常常导致内存溢出)。而是返回一个Iterator对象,
* 用户不停的调用next方法向后滚动, 同时释放掉之前处理过的结果对象。这就避免了超大结果返回时内存溢出的问题。
*
*
* <h3>注意事项</h3> 由于 ResultIterator
* 对象中有尚未关闭的ResultSet对象,因此必须确保使用完后关闭ResultIteratpr.如下示例
*
* <pre>
* <tt>ResultIterator<TestEntity> iter = db.iteratedSelect(QB.create(TestEntity.class), null);
* try{
* for(; iter.hasNext();) {
* iter.next();
* //do something.
* }
* }finally{
* //必须在finally块中关闭。否则一旦业务逻辑抛出异常,则ResultIterator未释放造成游标泄露.
* iter.close();
* }</tt>
* </pre>
*
* 如果ResultSet不释放,相当于数据库上打开了一个不关闭的游标,而数据库的游标数是很有限的,耗尽后将不能执行任何数据库操作。<br>
*
* @param queryObj
* 查询条件,可以是一个普通的Query,也可以是一个UnionQuery
* @param range
* 限制结果返回的条数,即分页信息。(传入null表示不限制)
* @param strategies
* 结果拼装参数
* @return 遍历器,可以用于遍历查询结果。
* @throws SQLException
* 如果数据库操作错误,抛出。
* @see ResultIterator
*/
public <T extends IQueryableEntity> ResultIterator<T> iteratedSelect(TypedQuery<T> queryObj, IntRange range) throws SQLException {
QueryOption option;
ConditionQuery query;
if (queryObj instanceof JoinElement) {
option = QueryOption.createFrom((JoinElement) queryObj);
query = (JoinElement) queryObj;
if (queryObj instanceof Query<?>) {
Query<?> q = (Query<?>) queryObj;
query = DbUtils.toReferenceJoinQuery(q, null);
}
} else {
option = QueryOption.DEFAULT;
query = queryObj;
}
return innerIteratedSelect(query, PageLimit.parse(range), option);
}
/**
* 遍历器模式查找,一般用于超大结果集的返回。
* {@linkplain #iteratedSelect(ConditionQuery, IntRange) 什么是结果遍历器}
* 注意ResultIterator对象需要释放。如果不释放,相当于数据库上打开了一个不关闭的游标,而数据库的游标数是很有限的,
* 耗尽后将不能执行任何数据库操作。
*
* @param queryObj
* 查询条件,可以是一个普通Query,或者UnionQuery,或者Join.
* @param resultClz
* 返回结果类型
* @param range
* 限制结果返回的条数,即分页信息。(传入null表示不限制)
* @param strategies
* 结果拼装参数
* @return 遍历器,可以用于遍历查询结果。
* @throws SQLException
* 如果数据库操作错误,抛出。
* @since 1.1
* @see ResultIterator
* @deprecated use {@link #iteratedSelect(ConditionQuery, IntRange)}
* instead.
*/
public <T> ResultIterator<T> iteratedSelect(ConditionQuery queryObj, Class<T> resultClz, IntRange range) throws SQLException {
queryObj.getResultTransformer().setResultType(resultClz);
return iteratedSelect(queryObj, range);
}
/**
* 遍历器模式查找,一般用于超大结果集的返回。 <h3>作用</h3> 当结果集超大时,如果用List<T>返回,内存占用很大甚至会溢出。<br>
* JDBC设计时考虑到这个问题,因此其返回的ResultSet对象只是查询结果视图的一段,用户向后滚动结果集时,数据库才将需要的数据传到客户端。
* 如果客户端不缓存整个结果集,那么前面已经滚动过的结果数据就被释放。
* <p>
* 这种处理方式实际上是一种流式处理模型,iteratedSelect就是这种模型的封装。<br>
* iteratedSelect并不会将查询出的所有数据放置到一个List对象中(这常常导致内存溢出)。而是返回一个Iterator对象,
* 用户不停的调用next方法向后滚动, 同时释放掉之前处理过的结果对象。这就避免了超大结果返回时内存溢出的问题。
*
*
* <h3>注意事项</h3> 由于 ResultIterator
* 对象中有尚未关闭的ResultSet对象,因此必须确保使用完后关闭ResultIteratpr.如下示例
*
* <pre>
* <tt>ResultIterator<TestEntity> iter = db.iteratedSelect(QB.create(TestEntity.class), null);
* try{
* for(; iter.hasNext();) {
* iter.next();
* //do something.
* }
* }finally{
* //必须在finally块中关闭。否则一旦业务逻辑抛出异常,则ResultIterator未释放造成游标泄露.
* iter.close();
* }</tt>
* </pre>
*
* 如果ResultSet不释放,相当于数据库上打开了一个不关闭的游标,而数据库的游标数是很有限的,耗尽后将不能执行任何数据库操作。<br>
*
*
* @param queryObj
* 查询条件,可以是一个普通Query,或者UnionQuery,或者Join.
* @param range
* 限制结果返回的条数,即分页信息。(传入null表示不限制)
* @param strategies
* 结果拼装参数
* @return 遍历器,可以用于遍历查询结果。
* @throws SQLException
* 如果数据库操作错误,抛出。
* @since 1.2
* @see ResultIterator
*/
public <T> ResultIterator<T> iteratedSelect(ConditionQuery queryObj, IntRange range) throws SQLException {
QueryOption option;
if (queryObj instanceof JoinElement) {
option = QueryOption.createFrom((JoinElement) queryObj);
} else {
option = QueryOption.DEFAULT;
}
return innerIteratedSelect(queryObj, PageLimit.parse(range), option);
}
/**
* 使用指定的查询对象查询,返回结果遍历器。
* {@linkplain #iteratedSelect(ConditionQuery, IntRange) 什么是结果遍历器}
*
* @param obj
* 查询请求
* @param range
* 查询对象范围
* @return 结果遍历器(ResultIterator)
* @throws SQLException
* 如果数据库操作错误,抛出。
* @see {@link ResultIterator}
*/
public <T extends IQueryableEntity> ResultIterator<T> iteratedSelect(T obj, IntRange range) throws SQLException {
@SuppressWarnings("unchecked")
Query<T> query = obj.getQuery();
QueryOption option = QueryOption.createFrom(query);
// 预处理
Transformer t = query.getResultTransformer();
if (!t.isLoadVsOne() || query.getMeta().getRefFieldsByName().isEmpty()) {
return innerIteratedSelect(query, PageLimit.parse(range), option);
}
// 拼装出带连接的查询请求
JoinElement q = DbUtils.toReferenceJoinQuery(query, null);
return innerIteratedSelect(q, PageLimit.parse(range), option);
}
/**
* 返回一个可以更新操作的结果数据{@link RecordHolder}<br>
* 用户可以在这个RecordHolder上直接更新数据库中的数据,包括插入记录和删除记录<br>
*
* <h3>实现原理</h3> RecordHolder对象,是JDBC ResultSet的封装<br>
* 实质对用JDBC中ResultSet的updateRow,deleteRow,insertRow等方法,<br>
* 该操作模型需要持有ResultSet对象,因此注意使用完毕后要close()方法关闭结果集。 <h3>注意事项</h3>
* RecordHolder对象需要手动关闭。如果不关闭将造成数据库游标泄露。 <h3>使用示例</h3>
*
*
* @param obj
* 查询对象
* @return 查询结果被放在RecordHolder对象中,用户可以直接在查询结果上修改数据。最后调用
* {@link RecordHolder#commit}方法提交到数据库。
* @throws SQLException
* 如果数据库操作错误,抛出。
* @see RecordHolder
*/
public <T extends IQueryableEntity> RecordHolder<T> loadForUpdate(T obj) throws SQLException {
Assert.notNull(obj);
@SuppressWarnings("unchecked")
RecordsHolder<T> r = selectForUpdate(obj.getQuery(), null);
if (r.size() == 0) {
r.close();// must close it!!
return null;
}
return r.get(0);
}
/**
* 返回一个可以更新操作的结果数据集合 实质对用JDBC中ResultSet的updateRow,deleteRow,insertRow等方法, <br>
* 该操作模型需要持有ResultSet对象,因此注意使用完毕后要close()方法关闭结果集<br>
*
* RecordsHolder可以对选择出来结果集进行更新、删除、新增三种操作,操作完成后调用commit方法<br>
*
* @param obj
* 查询请求
* @return RecordsHolder对象,这是一个可供操作的数据库结果集句柄。注意使用完后一定要关闭。
* @throws SQLException
* 如果数据库操作错误,抛出。
* @see RecordsHolder
*/
@SuppressWarnings("unchecked")
public <T extends IQueryableEntity> RecordsHolder<T> selectForUpdate(T query) throws SQLException {
Assert.notNull(query);
return selectForUpdate(query.getQuery(), null);
}
/**
* 返回一个可以更新操作的结果数据集合——RecordsHolder,可以在这个RecordsHolder上直接针对单表添加记录、删除记录、修改记录。
* RecordsHolder是JDBC ResultSet的封装。目的是使用ResultSet上的updateRow,deleteRow,
* insertRow等方法直接在JDBC数据集上对数据库进行写操作。
*
* 类似于PLSQL Developer中的select for update操作。select for update会锁定查询出来的记录。
*
*
* @param query
* 查询请求
* @param range
* 限定结果范围
* @return RecordsHolder对象,这是一个可供操作的数据库结果集句柄。注意使用完后一定要关闭。
* @throws SQLException
* 如果数据库操作错误,抛出。
* @see RecordsHolder
*/
public <T extends IQueryableEntity> RecordsHolder<T> selectForUpdate(Query<T> query, IntRange range) throws SQLException {
Assert.notNull(query);
QueryOption option = QueryOption.createFrom(query);
option.holdResult = true;
// 对于Oracle来说,如果用select * 会造成结果集强制变为只读,因此必须显式指定列名称。
if (DbUtils.getMappingProvider(query) == null) {
Selects select = QB.selectFrom(query);
AllTableColumns at = select.allColumns(query).noVirtualColumn();
at.setAliasType(AllTableColumns.AliasMode.RAWNAME);
}
@SuppressWarnings("unchecked")
List<T> objs = innerSelect(query, PageLimit.parse(range), query.getFilterCondition(), option);
RecordsHolder<T> result = new RecordsHolder<T>(query.getMeta());
ResultSetContainer rawrs = option.getRs();
try {
if (rawrs.size() > 1) {
throw new UnsupportedOperationException("select from update operate can only support one table.");
}
IResultSet rset = option.getRs().toProperResultSet(null);
result.init((ResultSetWrapper) rset, objs, rset.getProfile());
return result;
} catch (RuntimeException t) {
rawrs.close();// 如果出现异常必须关闭,防止泄漏
throw t;
} catch (Error t) {
rawrs.close();// 如果出现异常必须关闭,防止泄漏
throw t;
}
}
/**
* 查出单个对象。如果结果不唯一抛出NonUniqueResultException。
*
* @param obj
* 查询条件
* @return 使用传入的对象进行查询,结果返回记录的第一条。如未查到返回null。。
* @throws SQLException
* 如果数据库操作错误,抛出。
* @throws NonUniqueResultException
* 结果不唯一
*/
public <T> T load(T obj) throws SQLException {
return load(obj, true);
}
/**
* 查出单个对象
*
* @param obj
* 查询条件
* @param unique
* true要求查询结果必须唯一。false允许结果不唯一,但仅取第一条。
* @return 使用传入的对象进行查询,结果返回记录的第一条。如未查到返回null
* @throws SQLException
* 如果数据库操作错误,抛出。
* @throws NonUniqueResultException
* 当unique为true且查询结果不唯一时。
*/
@SuppressWarnings("unchecked")
public <T> T load(T obj, boolean unique) throws SQLException {
Assert.notNull(obj);
if (obj instanceof IQueryableEntity) {
return (T) innerLoad0((IQueryableEntity) obj, unique);
} else {
PojoWrapper vw = innerLoad0(MetaHolder.getMeta(obj.getClass()).transfer(obj, true), unique);
return vw == null ? null : (T) vw.get();
}
}
private <T extends IQueryableEntity> T innerLoad0(T obj, boolean unique) throws SQLException {
@SuppressWarnings("unchecked")
Query<T> q = obj.getQuery();
QueryOption option = QueryOption.createMax1Option(q);
List<T> l = typedSelect(q, null, option);
if (l.isEmpty()) {
return null;
} else if (l.size() > 1 && unique) {
throw new NonUniqueResultException("Result is not unique." + q);
}
return l.get(0);
}
/**
* 根据样例查找
*
* @param entity
* 查询条件
* @param property
* 作为查询条件的字段名。当不指定properties时,首先检查entity当中是否设置了主键,如果有主键按主键删除,
* 否则按所有非空字段作为匹配条件。
* @return 查询结果
*/
@SuppressWarnings("unchecked")
public <T> List<T> selectByExample(T entity, String... propertyName) throws SQLException {
if (entity instanceof IQueryableEntity) {
return select(DbUtils.populateExampleConditions((IQueryableEntity) entity, propertyName), null);
} else {
ITableMetadata meta = MetaHolder.getMeta(entity.getClass());
Query<PojoWrapper> q;
if (propertyName.length == 0) {
q = (Query<PojoWrapper>) meta.transfer(entity, true).getQuery();
} else {
q = DbUtils.populateExampleConditions(meta.transfer(entity, false), propertyName);
}
return PojoWrapper.unwrapList(select(q));
}
}
/**
* 根据字段查询
*
* @param meta
* 表元数据
* @param propertyName
* 字段名
* @param value
* 字段值
* @return 查询结果
* @throws SQLException
*/
@SuppressWarnings("unchecked")
public List<?> selectByField(ITableMetadata meta, String propertyName, Object value) throws SQLException {
if (meta == null || propertyName == null)
return null;
ColumnMapping field = meta.findField(propertyName);
if (field == null) {
throw new IllegalArgumentException("There's no property named " + propertyName + " in type of " + meta.getName());
}
Query<?> q = QB.create(meta);
q.addCondition(field.field(), Operator.EQUALS, value);
if (meta.getType() == EntityType.POJO) {
return PojoWrapper.unwrapList(select((Query<PojoWrapper>) q));
} else {
return select(q);
}
}
@SuppressWarnings("unchecked")
public <T> List<T> selectByField(Class<T> meta, String propertyName, Object value) throws SQLException {
return (List<T>) selectByField(MetaHolder.getMeta(meta), propertyName, value);
}
/**
* 按指定的字段的值加载记录<br>
* 如果要根据该字段的值批量加载记录,可使用 {@link #batchLoadByField(Field, List) }方法。
*
* @param field
* 作为查询条件的字段
* @param values
* 要查询的值
* @return 符合条件的记录
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
@SuppressWarnings("unchecked")
public <T> List<T> selectByField(jef.database.Field field, Object value) throws SQLException {
ITableMetadata meta = DbUtils.getTableMeta(field);
@SuppressWarnings("rawtypes")
Query query = QB.create(meta);
query.addCondition(field, Operator.EQUALS, value);
return typedSelect(query, null, QueryOption.DEFAULT);
}
/**
* 按指定的字段的值加载记录<br>
* 如果查询结果不唯一,抛出NonUniqueResultException异常
*
* @param field
* 作为查询条件的字段
* @param value
* 要查询的值
* @return 符合条件的记录
* @throws SQLException
* 如果数据库操作错误,抛出。
* @throws NonUniqueResultException
* 结果不唯一
*/
public <T> T loadByField(jef.database.Field field, Object value) throws SQLException {
return loadByField(field, value, true);
}
/**
* 按指定的字段的值加载记录<br>
* 如果指定的字段不是主键,也只返回第一条数据。
*
* @param field
* 作为查询条件的字段
* @param value
* 要查询的值
* @param unique
* 是否要求结果唯一,为true时如结果不唯一将抛出NonUniqueResultException异常
*
* @return 符合条件的记录
* @throws SQLException
* 如果数据库操作错误,抛出。
* @throws NonUniqueResultException
* 结果不唯一
*/
@SuppressWarnings("unchecked")
public <T> T loadByField(jef.database.Field field, Object value, boolean unique) throws SQLException {
ITableMetadata meta = DbUtils.getTableMeta(field);
Query<?> query = meta.newInstance().getQuery();
query.addCondition(field, Operator.EQUALS, value);
List<?> list = typedSelect(query, null, QueryOption.DEFAULT_MAX1);
if (list.isEmpty()) {
return null;
} else if (list.size() > 1 && unique) {
throw new NonUniqueResultException();
}
if (meta.getType() == EntityType.POJO) {
return (T) ((PojoWrapper) list.get(0)).get();
} else {
return (T) list.get(0);
}
}
/**
* 按指定的字段查询记录
*
* @param type
* 实体类型
* @param field
* 字段名
* @param value
* 查询值
* @return 查询结果
* @throws SQLException
*/
public <T> T loadByField(Class<T> type, String field, Object value) throws SQLException {
ITableMetadata meta = MetaHolder.getMeta(type);
return loadByField(meta, field, value, true);
}
/**
* 按指定的字段查询记录
*
* @param meta
* 实体类型
* @param field
* 字段名
* @param value
* 查询值
* @param unique
* 是否唯一
* @return 查询结果
* @throws SQLException
*/
@SuppressWarnings("unchecked")
public <T> T loadByField(ITableMetadata meta, String field, Object value, boolean unique) throws SQLException {
if (meta == null || field == null) {
return null;
}
ColumnMapping def = meta.findField(field);
if (def == null) {
throw new IllegalArgumentException("There's no field [" + field + "] in " + meta.getName());
}
return (T) loadByField(def.field(), value, unique);
}
/**
* 按主键获取一条记录。注意这里的可变参数是为了支持复合主键,并不是加载多条记录。<br>
* 如需加载多条记录,请用 {@link #batchLoad(Class, List) } 方法
*
* @param meta
* 元数据
* @param keys
* 主键的值。
* @return 查询结果
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public <T> T load(ITableMetadata meta, Serializable... keys) throws SQLException {
if (meta.getType() == EntityType.POJO) {
PKQuery<PojoWrapper> query = new PKQuery<PojoWrapper>(meta, keys);
List<PojoWrapper> result = innerSelect(query, null, null, QueryOption.DEFAULT_MAX1);
if (result.isEmpty())
return null;
return (T) result.get(0).get();
} else {
PKQuery query = new PKQuery(meta, keys);
List<T> result = innerSelect(query, null, null, QueryOption.DEFAULT_MAX1);
if (result.isEmpty())
return null;
return result.get(0);
}
}
/**
* 按主键获取一条记录。注意这里的可变参数是为了支持复合主键,并不是加载多条记录。
*
* @param clz
* 类型
* @param keys
* 主键的值。
* @return 查询结果
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public <T> T load(Class<T> entityClass, Serializable... keys) throws SQLException {
if (keys.length == 0 || keys[0] == null) {
throw new IllegalArgumentException("Please input a valid value as primary key.");
}
AbstractMetadata meta = MetaHolder.getMetaOrTemplate(entityClass);
return load(meta, keys);
}
/**
* 按主键加载多条记录。适用与拥有大量主键值,需要在数据库中查询与之对应的记录时。<br>
* 查询会使用IN条件来减少操作数据库的次数。如果要查询的条件超过了500个,会自动分多次进行查询。
* <p>
* <strong>注意:在多库操作下,这一方法不支持对每条记录单独分组并计算路由。</strong>
* <strong>注意:此方法不支持复合主键</strong>
*
* @param clz
* 实体类
* @param pkValues
* 主键的值(多值)
* @return 查询结果
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public final <T> List<T> batchLoad(Class<T> clz, List<? extends Serializable> pkValues) throws SQLException {
ITableMetadata meta = MetaHolder.getMeta(clz);
return batchLoad(meta, pkValues);
}
/**
* 按主键加载多条记录。适用与拥有大量主键值,需要在数据库中查询与之对应的记录时。<br>
* 查询会使用IN条件来减少操作数据库的次数。如果要查询的条件超过了500个,会自动分多次进行查询。
* <p>
* <strong>注意:在多库操作下,这一方法不支持对每条记录单独分组并计算路由。</strong>
* <strong>注意:此方法不支持复合主键</strong>
*
* @param meta
* 实体元数据
* @param pkValues
* 主键的值(多值)
* @return 查询结果
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
@SuppressWarnings("unchecked")
public final <T> List<T> batchLoad(ITableMetadata meta, List<? extends Serializable> pkValues) throws SQLException {
int MAX_IN_CONDITIONS = ORMConfig.getInstance().getMaxInConditions();
if (pkValues.size() < MAX_IN_CONDITIONS) {
return batchLoadByPK0(meta, pkValues);
}
List<T> result = new ArrayList<T>(MAX_IN_CONDITIONS);
int offset = 0;
while (pkValues.size() - offset > MAX_IN_CONDITIONS) {
List<T> r = batchLoadByPK0(meta, pkValues.subList(offset, offset + MAX_IN_CONDITIONS));
result.addAll(r);
offset += MAX_IN_CONDITIONS;
}
if (pkValues.size() > offset) {
result.addAll(batchLoadByPK0(meta, pkValues.subList(offset, pkValues.size())));
}
return result;
}
/**
* 按指定的字段加载多条记录.适用与拥有大量键值,需要在数据库中查询与之对应的记录时。<br>
* 查询会使用IN条件来减少操作数据库的次数。如果要查询的条件超过了500个,会自动分多次进行查询。 <strong>注意:</strong>
* <ol>
* <li>在多库操作下,这一方法不支持对每条记录单独分组并计算路由。</li>
* <li>不支持复合主键。</li>
* </ol>
*
* @param field
* 字段
* @param values
* 条件值
* @return 查询结果
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public final <T extends IQueryableEntity> List<T> batchLoadByField(jef.database.Field field, List<?> values) throws SQLException {
int MAX_IN_CONDITIONS = ORMConfig.getInstance().getMaxInConditions();
if (values.size() < MAX_IN_CONDITIONS)
return batchLoadByField0(field, values);
List<T> result = new ArrayList<T>(800);
int offset = 0;
while (values.size() - offset > MAX_IN_CONDITIONS) {
List<T> r = batchLoadByField0(field, values.subList(offset, offset + MAX_IN_CONDITIONS));
result.addAll(r);
offset += MAX_IN_CONDITIONS;
}
if (values.size() > offset) {
List<T> r = batchLoadByField0(field, values.subList(offset, values.size()));
result.addAll(r);
}
return result;
}
/**
* 执行数据库查询。并将结果转换为期望的对象。查询结果不唯一抛出NonUniqueResultException
*
* @param queryObj
* 查询
* @param resultClz
* 单条记录返回结果类型
* @return 查询结果将只返回第一条。如果查询结果数量为0,那么将返回null
*
* @throws SQLException
* 如果数据库操作错误,抛出。
* @throws NonUniqueResultException
* 结果不唯一,如不想接收到异常,请用
* {@link #loadAs(ConditionQuery, Class, boolean)}
*
*/
public <T> T loadAs(ConditionQuery queryObj, Class<T> resultClz) throws SQLException {
return loadAs(queryObj, resultClz, true);
}
/**
* 执行数据库查询。并将结果转换为期望的对象。
*
* @param queryObj
* 查询
* @param resultClz
* 单条记录返回结果类型
* @param unique
* 要求查询结果唯一。为true时如果查询结果不唯一抛出NonUniqueResultException
* @return 查询结果将只返回第一条。如果查询结果数量为0,那么将返回null
*
* @throws SQLException
* 如果数据库操作错误,抛出。
* @throws NonUniqueResultException
* 结果不唯一
*
*/
public <T> T loadAs(ConditionQuery queryObj, Class<T> resultClz, boolean unique) throws SQLException {
queryObj.getResultTransformer().setResultType(resultClz);
return load(queryObj, unique);
}
/**
* 查询并指定返回结果。要求查询结果必须唯一。
*
* @param queryObj
* 查询
* @return 查询结果将只返回第一条。如果查询结果数量为0,那么将返回null。
*
* @throws SQLException
* 如果数据库操作错误,抛出。
* @throws NonUniqueResultException
* 结果不唯一
*/
public <T> T load(ConditionQuery queryObj) throws SQLException {
return load(queryObj, true);
}
/**
* 查询并指定返回结果。
*
* @param queryObj
* 查询
* @param unique
* 要求查询结果是否唯一。为true时,查询结果不唯一将抛出异常。为false时,查询结果不唯一仅取第一条。
*
* @return 查询结果将只返回第一条。如果查询结果数量为0,那么将返回null。
*
* @throws SQLException
* 如果数据库操作错误,抛出。
* @throws NonUniqueResultException
* 结果不唯一
*/
public <T> T load(ConditionQuery queryObj, boolean unique) throws SQLException {
Assert.notNull(queryObj);
QueryOption option;
Map<Reference, List<Condition>> filters = null;
if (queryObj instanceof JoinElement) {
if (queryObj instanceof Query<?>) {
Query<?> qq = (Query<?>) queryObj;
Transformer t = queryObj.getResultTransformer();
if (t.isLoadVsOne() && !qq.getMeta().getRefFieldsByName().isEmpty()) {
queryObj = DbUtils.toReferenceJoinQuery(qq, null);
}
filters = qq.getFilterCondition();
}
}
option = queryObj.getMaxResult() > 0 ? QueryOption.createFrom(queryObj) : QueryOption.DEFAULT_MAX1;
@SuppressWarnings("unchecked")
List<T> l = innerSelect(queryObj, null, filters, option);
if (l.isEmpty()) {
return null;
} else if (l.size() > 1 && unique) {
throw new NonUniqueResultException("Result is not unique." + queryObj);
}
T result = l.get(0);
if (ORMConfig.getInstance().isDebugMode()) {
LogUtil.show("Result:" + result);
}
return result;
}
/**
* 查询某个对象表中的所有数据
*
* @param cls
* 实体类型
* @return 该类型所对应的表中的所有数据
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public <T extends IQueryableEntity> List<T> selectAll(Class<T> cls) throws SQLException {
return selectAll(MetaHolder.getMeta(cls));
}
/**
* 查询某个模型的表中的所有数据
*
* @param meta
* 表的元模型
* @return 该类型所对应的表中的所有数据
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
@SuppressWarnings("unchecked")
public <T extends IQueryableEntity> List<T> selectAll(ITableMetadata meta) throws SQLException {
return (List<T>) select(QB.create(meta));
}
/**
* 允许所有条件的分页查询,查询结果将转换为指定的类型。
* <p>
* 也可以用以下的方法指定查询结果要转换的类型
* {@link #jef.database.Session.pageSelect(ConditionQuery, int)}<br>
* and <br>
* <tt> query.getResultTransformer().setResultType(type) to assign return type.</tt>
*
* @param query
* 查询请求
* @param resultWrapper
* 查询返回结果类型
* @param pageSize
* 每页记录条数
* @return PagingIterator对象,可实现结果范围限定和前后翻页的对象。
* @throws SQLException
* 如果数据库操作错误,抛出。
*
* @see PagingIterator
*/
public <T> PagingIterator<T> pageSelect(ConditionQuery query, Class<T> resultWrapper, int pageSize) throws SQLException {
query.getResultTransformer().setResultType(resultWrapper);
return new PagingIteratorObjImpl<T>(query, pageSize, this);// 因为query包含了路由信息,所以可以允许直接传入session
}
/**
* 按自定义的条件实现分页查询
*
* @param query
* 查询请求
* @param pageSize
* 每页记录条数
* @return PagingIterator对象,可实现结果范围限定和前后翻页的对象。
* @throws SQLException
* 如果数据库操作错误,抛出。
* @see PagingIterator
*/
public <T> PagingIterator<T> pageSelect(ConditionQuery query, int pageSize) throws SQLException {
return new PagingIteratorObjImpl<T>(query, pageSize, this);// 因为query包含了路由信息,所以可以允许直接传入session
}
/**
* 将传入的SQL语句创建为NativeQuery,然后再以此进行分页查询
*
* @param sql
* E-SQL语句
* @param resultClass
* 返回结果类型
* @param pageSize
* 每页记录条数
* @return PagingIterator对象,可实现结果范围限定和前后翻页的对象。
* @throws SQLException
* 如果数据库操作错误,抛出。
* @see #createNativeQuery(String, Class)
* @see NativeQuery
* @see PagingIterator
*/
public <T> PagingIterator<T> pageSelect(String sql, Class<T> resultClass, int pageSize) throws SQLException {
NativeQuery<T> q = this.createNativeQuery(sql, resultClass);
PagingIterator<T> result = new PagingIteratorNativeQImpl<T>(q, pageSize);
return result;
}
/**
* 将传入的SQL语句创建为NativeQuery,然后再以此进行分页查询
*
* @param sql
* E-SQL语句
* @param meta
* 返回结果类型
* @param pageSize
* 每页数据条数
* @return PagingIterator对象,可实现结果范围限定和前后翻页的对象。
* @throws SQLException
* 如果数据库操作错误,抛出。
* @see #createNativeQuery(String, ITableMetadata)
* @see NativeQuery
* @see PagingIterator
*/
public <T> PagingIterator<T> pageSelect(String sql, ITableMetadata meta, int pageSize) throws SQLException {
NativeQuery<T> q = this.createNativeQuery(sql, meta);
PagingIterator<T> result = new PagingIteratorNativeQImpl<T>(q, pageSize);
return result;
}
/**
* 根据NativeQuery进行分页查询,SQL中不必写分页逻辑。JEF会自动编写count语句查询总数,并且限定结果
*
* @param sql
* 查询Query对象
* @param pageSize
* 每页数据条数
* @return PagingIterator对象,可实现结果范围限定和前后翻页的对象。
* @throws SQLException
* 如果数据库操作错误,抛出。
* @see PagingIterator
*/
public final <T> PagingIterator<T> pageSelect(NativeQuery<T> sql, int pageSize) throws SQLException {
PagingIterator<T> result = new PagingIteratorNativeQImpl<T>(sql, pageSize);
return result;
}
/**
* 根据查询对象和表名实现分页查询
*
* @param obj
* 查询请求
* @param pageSize
* 每页数据条数
* @return PagingIterator对象,可实现结果范围限定和前后翻页的对象。
* @throws SQLException
* 如果数据库操作错误,抛出。
* @see PagingIterator
*/
public final <T extends IQueryableEntity> PagingIterator<T> pageSelect(T obj, int pageSize) throws SQLException {
PagingIterator<T> result = new PagingIteratorObjImpl<T>(obj, pageSize, this);
return result;
}
/**
*
* @param entity
* @param start
* @param limit
* @return
* @throws SQLException
*/
@SuppressWarnings("unchecked")
public <T> Page<T> selectPage(T entity, int start, int limit) throws SQLException {
if (entity instanceof IQueryableEntity) {
return (Page<T>) pageSelect((IQueryableEntity) entity, limit).setOffset(start).getPageData();
} else {
ITableMetadata meta = MetaHolder.getMeta(entity.getClass());
PojoWrapper vw = meta.transfer(entity, true);
Page<PojoWrapper> page = pageSelect(vw, limit).setOffset(start).getPageData();
return PojoWrapper.unwrapPage(page);
}
}
/*
* 查询实现,解析查询对象,将单表对象解析为Join查询
*/
@SuppressWarnings("unchecked")
protected final <T extends IQueryableEntity> List<T> typedSelect(Query<T> queryObj, PageLimit range, QueryOption option) throws SQLException {
// 预处理
Transformer t = queryObj.getResultTransformer();
if (!t.isLoadVsOne() || queryObj.getMeta().getRefFieldsByName().isEmpty()) {
return innerSelect(queryObj, range, null, option);
}
// 拼装出带连接的查询请求
JoinElement q = DbUtils.toReferenceJoinQuery(queryObj, null);
return innerSelect(q, range, queryObj.getFilterCondition(), option);
}
@SuppressWarnings("unchecked")
final <T> ResultIterator<T> innerIteratedSelect(ConditionQuery queryObj, PageLimit range, QueryOption option) throws SQLException {
if (range != null && range.getLimit() <= 0) {
return new ResultIterator.Impl<T>(new ArrayList<T>().iterator(), null);
}
long start = System.currentTimeMillis();// 开始时间
QueryClause sql = selectp.toQuerySql(queryObj, range, true);
if (sql.isEmpty())
return new ResultIterator.Impl<T>(new ArrayList<T>().iterator(), null);
ResultIterator<T> result;
ResultSetContainer rs = new ResultSetContainer(false);
long parse = System.currentTimeMillis();
selectp.processSelect(sql, this, queryObj, rs, option, 1);
long dbselect = System.currentTimeMillis();
LogUtil.show(StringUtils.concat("Result: Iterator", "\t Time cost([ParseSQL]:", String.valueOf(parse - start), "ms, [DbAccess]:", String.valueOf(dbselect - parse), "ms) |", getTransactionId(null)));
EntityMappingProvider mapping = DbUtils.getMappingProvider(queryObj);
Transformer transformer = queryObj.getResultTransformer();
IResultSet irs = rs.toProperResultSet(null, transformer.getStrategy());
result = new ResultIterator.Impl<T>(iterateResultSet(irs, mapping, transformer), irs);
return result;
}
@SuppressWarnings({ "unchecked", "rawtypes" })
final List innerSelect(ConditionQuery queryObj, PageLimit range, Map<Reference, List<Condition>> filters, QueryOption option) throws SQLException {
boolean debugMode = ORMConfig.getInstance().isDebugMode();
if (range != null && range.getLimit() <= 0) {
if (debugMode)
LogUtil.show("Query has limit to no range. return empty list. " + range);
return Collections.EMPTY_LIST;
}
long start = System.currentTimeMillis();// 开始时间
// 生成 SQL
QueryClause sql = selectp.toQuerySql(queryObj, range, true);
if (sql.isEmpty())
return Collections.EMPTY_LIST;
Transformer transformer = queryObj.getResultTransformer();
boolean noCache = !queryObj.isCacheable() || queryObj.isSelectCustomized() || range != null;
// 缓存命中
List resultList = noCache ? null : getCache().load(sql.getCacheKey());
if (resultList == null) {
ResultSetContainer rs = new ResultSetContainer(option.cacheResultset && !option.holdResult);// 只有当非读写模式并且开启结果缓存才缓存结果集
long parse = System.currentTimeMillis();
selectp.processSelect(sql, this, queryObj, rs, option, option.holdResult ? 2 : 0);
long dbselect = System.currentTimeMillis(); // 查询完成时间
try {
EntityMappingProvider mapping = DbUtils.getMappingProvider(queryObj);
resultList = populateResultSet(rs.toProperResultSet(filters, transformer.getStrategy()), mapping, transformer);
if (debugMode) {
LogUtil.show(StringUtils.concat("Result Count:", String.valueOf(resultList.size()), "\t Time cost([ParseSQL]:", String.valueOf(parse - start), "ms, [DbAccess]:", String.valueOf(dbselect - parse), "ms, [Populate]:",
String.valueOf(System.currentTimeMillis() - dbselect), "ms) max:", option.toString(), " |", getTransactionId(null)));
}
} finally {
if (option.holdResult) {
option.setRs(rs);
} else {
rs.close();
}
}
// Jiyi modified 2014-11-4 如果查询结果为空,不缓存
// 目的是减少CacheKey的计算,这部分计算有一定开销,权衡之下,缓存空结果意义不大。
if (!noCache && !option.holdResult && !resultList.isEmpty()) {
getCache().onLoad(sql.getCacheKey(), resultList, transformer.getResultClazz());
}
}
// 凡是对多查询都通过分次查询来解决,填充1vsN字段
if (transformer.isLoadVsMany() && transformer.isQueryableEntity()) {
Map<Reference, List<AbstractRefField>> map;
if (queryObj instanceof Query<?>) {
// 说明由于既无自动关联,也无手动关联,此时所有字段都需要作为延迟加载字段处理
map = DbUtils.getLazyLoadRef(transformer.getResultMeta(), option.skipReference == null ? Collections.EMPTY_LIST : option.skipReference);
} else if (queryObj instanceof Join) {
// 说明可能是手动关联,也可能是自动关联,还可能是自由关联。
Join jj = (Join) queryObj;
Query<?> root = jj.elements().get(0);// RootObject
map = DbUtils.getLazyLoadRef(transformer.getResultMeta(), root.isCascadeViaOuterJoin() ? null : jj.getIncludedCascadeOuterJoin());
} else {
// 常规处理
map = DbUtils.getLazyLoadRef(transformer.getResultMeta(), null);
}
for (Map.Entry<Reference, List<AbstractRefField>> entry : map.entrySet()) {
// 如果是缓存命中的情况,依然要重新更新缓存中的级联对象
CascadeUtil.fillOneVsManyReference(resultList, entry, filters == null ? Collections.EMPTY_MAP : filters, this);
}
}
return resultList;
}
/**
* 执行原生SQL语句。 {@linkplain #selectBySql(String, Class, Object...) 什么是原生SQL}
*
* @param sql
* SQL语句
* @param params
* 参数绑定变量
* @return 影响的记录条数
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public final int executeSql(String sql, Object... params) throws SQLException {
return selectTarget(null).executeSql(sql, params);
}
/**
* 用SQL查询出结果集 <br>
* <b>不支持schema重定向,不支持Sql本地化改写</b>,因此尽量用{@link #createNativeQuery(String)}或者
* {@link #createNativeQuery(String, Class)}
*
* @param sql
* SQL语句
* @param maxReturn
* 限制结果最多返回若干条记录设置为-1表示不限制
* @return 缓存的结果集,所有结果将被缓存在内存中,不会持续占用连接,也不会接收数据库中的数据变化
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public final ResultSet getResultSet(String sql, int maxRows, Object... params) throws SQLException {
return selectTarget(null).innerSelectBySql(sql, AbstractResultSetTransformer.cacheResultSet(maxRows, 0), Arrays.asList(params), null);
}
/**
* 原生SQL查询 <br/>
*
* <h3>原生SQL</h3>
* 原生SQL和NativeQuery不同。凡是NativeQuery系列的方法都是对SQL进行解析和改写处理的,而原生SQ不作任何解析和改写,
* 直接用于数据库操作。
* <p>
*
* 原生SQL中,绑定变量占位符和E-SQL不同,用一个问号表示——
*
* <pre>
* <tt>select * from t_person where id=? and name like ?</tt>
* </pre>
*
* 原生SQL适用于不希望进行SQL解析和改写场合,一般情况下用在SQL解析器解析不了的SQL语句上,用作规避手段。<br>
* 建议,在需要保证应用的可移植性的场合下,尽可能使用{@link #createNativeQuery(String, Class)}代替。
*
* @param sql
* SQL语句
* @param resultClz
* 要返回的数据类型
* @param params
* 绑定变量参数
* @return 查询结果
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public final <T> List<T> selectBySql(String sql, Class<T> resultClz, Object... params) throws SQLException {
return selectBySql(sql, new Transformer(resultClz), null, params);
}
/**
* 原生SQL查询 <br/>
* {@linkplain #selectBySql(String, Class, Object...) 什么是原生SQL}
*
* @param sql
* SQL语句
* @param transformer
* 返回的数据类型转换器
* @param range
* 限定结果集范围
* @param params
* 绑定变量的参数
* @return 查询结果
* @throws SQLException
* 如果数据库操作错误,抛出。
* @see #createNativeQuery(String)
* @see #createNativeQuery(String,Class)
*/
public final <T> List<T> selectBySql(String sql, Transformer transformer, IntRange range, Object... params) throws SQLException {
return selectTarget(null).selectBySql(sql, transformer, range, params);
}
/**
* 原生SQL查询,返回单条记录的结果。 {@linkplain #selectBySql(String, Class, Object...)
* 什么是原生SQL}
*
* @param sql
* SQL语句
* @param returnType
* 返回结果类型
* @param params
* 绑定变量参数
* @return 查询结果对象
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public final <T> T loadBySql(String sql, Class<T> returnType, Object... params) throws SQLException {
return selectTarget(null).loadBySql(sql, returnType, params);
}
/**
* 查询符合条件的记录条数
*
* @param obj
* 查询请求
* @return 记录条数
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public final int count(ConditionQuery obj) throws SQLException {
long total = countLong(obj);
if (total > Integer.MAX_VALUE) {
throw new IllegalArgumentException("Result count too big, please use method 'countLong()'");
}
return (int) total;
}
/**
* 查询符合条件的记录条数
*
* @param obj
* 查询请求
* @return 记录条数
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public final long countLong(ConditionQuery obj) throws SQLException {
long start = System.currentTimeMillis(); // 开始时间
long parse = 0; // 解析时间
boolean debugMode = ORMConfig.getInstance().isDebugMode();
if (obj instanceof Query<?>) {
// 预处理
Transformer t = obj.getResultTransformer();
if (t.isLoadVsOne()) {
obj = DbUtils.toReferenceJoinQuery((Query<?>) obj, null);
}
}
Assert.notNull(obj);
CountClause sqls = selectp.toCountSql(obj);
parse = System.currentTimeMillis();
long total = selectp.processCount(this, sqls);
if (debugMode) {
long dbAccess = System.currentTimeMillis() - parse; // 数据库查询时间
parse = parse - start; // 解析SQL时间
LogUtil.show(StringUtils.concat("Total Count:", String.valueOf(total), "\t Time cost([ParseSQL]:", String.valueOf(parse), "ms, [DbAccess]:", String.valueOf(dbAccess), "ms) |", getTransactionId(null)));
}
return total;
}
/**
* 查记录条数
*
* @param obj
* 查询请求
* @param tableName
* 如果表名较为特殊的情况下,允许手工传入,一般情况下传入null。
* @return 记录条数
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public final int count(IQueryableEntity obj) throws SQLException {
return count(obj.getQuery());
}
/**
* 批量删除数据。每个传入参数都是一个实体对象。可以表示多组参数。 因此这一批量删除可以按相同的SQL语句执行多组参数。并不仅仅用于删除多条记录。
*
* @param entities
* 要删除的实体对象
* @return 实际删除记录行数
* @throws SQLException
*/
public final <T> int executeBatchDeletion(List<T> entities) throws SQLException {
return executeBatchDeletion(entities, null);
}
/**
* 批量删除数据。每个传入参数都是一个实体对象。可以表示多组参数。 因此这一批量删除可以按相同的SQL语句执行多组参数。并不仅仅用于删除多条记录。
*
* @param entities
* 要删除的实体对象。
* @param group
* 是否对传入的对象按所属表重新分组。<br>
* 在启用分库分表后,用户如果不确定传入的多个对象在路由计算后属于同一张表,则需打开此开关。<br>
* 开关开启后会对每个对象进行路由计算并重新分组操作(这一操作将损耗一定的性能)。
* @return 实际删除记录行数
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
@SuppressWarnings("unchecked")
public final <T> int executeBatchDeletion(List<T> entities, Boolean group) throws SQLException {
if (entities == null || entities.isEmpty())
return 0;
T t = entities.get(0);
if (t instanceof IQueryableEntity) {
return executeBatchDeletion0((List<IQueryableEntity>) entities, group);
} else {
List<PojoWrapper> list = PojoWrapper.wrap(entities, false);
return executeBatchDeletion0(list, group);
}
}
private final <T extends IQueryableEntity> int executeBatchDeletion0(List<T> entities, Boolean group) throws SQLException {
Batch<T> batch = this.startBatchDelete(entities.get(0), null);
if (group != null) {
batch.setGroupForPartitionTable(group);
}
return batch.execute(entities);
}
/**
* 按主键删除多条记录。适用与拥有大量主键值,需要在数据库中查询与之对应的记录时。<br>
* 会使用IN条件来减少操作数据库的次数。如果要删除的条件超过了500个,会自动分多次进行删除。
*
* <p>
* <strong>注意1:在多库操作下,这一方法不支持对每条记录单独分组并计算路由。</strong><br>
* 需要路由的场景下请使用 {@link #batchDelete(List, boolean)}方法
* <p>
* <strong>注意2:不支持复合主键</strong><br>
* 需要复合主键的场景下请使用 {@link #batchDelete(List, boolean)}方法
*
* @param clz
* 要删除的记录类型
* @param keys
* 主键列表。复合主键不支持。如需批量删除复合主键的类请用{@link #batchDelete(List, boolean)}
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public final <T> int batchDelete(Class<?> clz, List<? extends Serializable> keys) throws SQLException {
ITableMetadata meta = MetaHolder.getMeta(clz);
return batchDelete(meta, keys);
}
/**
* 按主键删除多条记录。适用与拥有大量主键值,需要在数据库中查询与之对应的记录时。<br>
* 会使用IN条件来减少操作数据库的次数。如果要删除的条件超过了500个,会自动分多次进行删除。
*
* <p>
* <strong>注意:在多库操作下,这一方法不支持对每条记录单独分组并计算路由。</strong>
*
* @param meta
* 要删除的数据类
* @param pkValues
* 主键值列表。复合主键不支持。如需批量删除复合主键的类请用{@link #batchDelete(List)}
* @return 实际删除数量
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public final int batchDelete(ITableMetadata meta, List<? extends Serializable> pkValues) throws SQLException {
if (pkValues.isEmpty())
return 0;
if (meta.getPKFields().size() != 1) {
throw new SQLException("Only supports [1] column as primary key, but " + meta.getSimpleName() + " has " + meta.getPKFields().size() + " columns.");
}
int MAX_IN_CONDITIONS = ORMConfig.getInstance().getMaxInConditions();
if (pkValues.size() < MAX_IN_CONDITIONS) {
return batchDeleteByPK0(meta, pkValues);
}
int total = 0;
int offset = 0;
while (pkValues.size() - offset > MAX_IN_CONDITIONS) {
total += batchDeleteByPK0(meta, pkValues.subList(offset, offset + MAX_IN_CONDITIONS));
offset += MAX_IN_CONDITIONS;
}
if (pkValues.size() > offset) {
total += batchDeleteByPK0(meta, pkValues.subList(offset, pkValues.size()));
}
return total;
}
/**
* 批量删除数据(按主键)<br>
* <ol>
* <li>本方法需要传入要删除的实体对象。(对象中只要主键设置了值即可,其他字段无需设值)。</li>
* <li>本方法可以批量删除支持复合主键的实体。</li>
* </ol>
*
* @param entities
* 要删除的实体对象
*
* @return 实际删除数量
* @throws SQLException
* 如果没有主键或者数据库操作错误,抛出SQLException。
*/
public final <T> int batchDelete(List<T> entities) throws SQLException {
return batchDelete(entities, false);
}
/**
* 批量删除数据(按主键)<br>
* <ol>
* <li>本方法需要传入要删除的实体对象。(对象中只要主键设置了值即可,其他字段无需设值)。</li>
* <li>本方法可以批量删除支持复合主键的实体。</li>
* <li>本方法可以支持数据路由(第二个参数传入true的场景下)</li>
* </ol>
*
* @param entities
* 要删除的实体对象
* @param group
* 在分库分表情况下,是否对每条记录进行路由计算并重新分组。<br>
* 在启用分库分表后,用户如果不确定传入的多个对象在路由计算后属于同一张表,则需打开此开关。<br>
* 开关开启后会对每个对象进行路由计算并重新分组操作(这一操作将损耗一定的性能)。
*
* @return 实际删除数量
* @throws SQLException
* 如果没有主键或者数据库操作错误,抛出SQLException。
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public final <T> int batchDelete(List<T> entities, boolean group) throws SQLException {
if (entities.isEmpty())
return 0;
ITableMetadata meta = MetaHolder.getMeta(entities.get(0));
if (meta.getPKFields().isEmpty()) {
throw new SQLException("The type " + meta.getTableName(false) + " has no primary key, can not execute batch remove by primarykey");
}
List<? extends IQueryableEntity> data;
if (meta.getType() == EntityType.POJO) {
data = PojoWrapper.wrap(entities, false);
} else {
data = (List<? extends IQueryableEntity>) entities;
}
long start = System.nanoTime();
PKQuery<?> query = new PKQuery(meta, DbUtils.getPKValueSafe((IQueryableEntity) data.get(0)), meta.newInstance());
BindSql wherePart = preProcessor.toWhereClause(query, new SqlContext(null, query), null, getProfile(null),true);
Batch.Delete batch = new Batch.Delete(this, meta, wherePart);
batch.parseTime = System.nanoTime() - start;
batch.pkMpode = true;
batch.setGroupForPartitionTable(group);
return batch.execute(data);
}
/**
* 按某个字段值进行批量删除。
*
* @param field
* 要作为删除条件的字段。
* @param values
* 需要删除的值
* @return 实际删除行数。
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public final <T extends IQueryableEntity> int batchDeleteByField(Field field, List<? extends Serializable> values) throws SQLException {
int MAX_IN_CONDITIONS = ORMConfig.getInstance().getMaxInConditions();
if (values.size() < MAX_IN_CONDITIONS)
return batchDeleteByField0(field, values);
int total = 0;
int offset = 0;
while (values.size() - offset > MAX_IN_CONDITIONS) {
total += batchDeleteByField0(field, values.subList(offset, offset + MAX_IN_CONDITIONS));
offset += MAX_IN_CONDITIONS;
}
if (values.size() > offset) {
total += batchDeleteByField0(field, values.subList(offset, values.size()));
}
return total;
}
/**
* 获得一个Bach对象,这个batch对象上可以执行批量删除操作。
*
* @param template
* 批操作的模板。传入的对象必须是一个构成delete的完整请求(含查询条件(默认为主键))。
* 后续的所有批量操作都按此模板執行操作。
* @param tableName
* 强制指定表名,也就是说template当中的表名无效。(传入的表名支持Schema重定向)
* @return Batch对象,可执行批操作
* @throws SQLException
* 如果数据库操作错误,抛出。
* @see Batch
*/
public final <T extends IQueryableEntity> Batch<T> startBatchDelete(T template, String tableName) throws SQLException {
// 位于批当中的绑定变量
long start = System.nanoTime();
BindSql wherePart = preProcessor.toWhereClause(template.getQuery(), new SqlContext(null, template.getQuery()), null, getProfile(null),true);
ITableMetadata meta = MetaHolder.getMeta(template);
Batch.Delete<T> batch = new Batch.Delete<T>(this, meta, wherePart);
batch.forceTableName = MetaHolder.toSchemaAdjustedName(tableName);
batch.parseTime = System.nanoTime() - start;
return batch;
}
/**
* 执行批量插入操作。
*
* @param entities
* 要插入的对象
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public final <T> void batchInsert(List<T> entities) throws SQLException {
batchInsert(entities, null, null);
}
/**
* 执行批量插入操作。
*
* @param entities
* 要插入的对象
* @param group
* 是否对传入的对象按所属表重新分组。<br>
* 在启用分库分表后,用户如果不确定传入的多个对象在路由计算后属于同一张表,则需打开此开关。<br>
* 开关开启后会对每个对象进行路由计算并重新分组操作(这一操作将损耗一定的性能)。
* @throws SQLException
*/
public final <T> void batchInsert(List<T> entities, Boolean group) throws SQLException {
batchInsert(entities, group, null);
}
/**
* 执行批量插入操作。
*
* @param entities
* 要插入的对象
* @param group
* 是否对传入的对象按所属表重新分组
* @param dynamic
* 是否Dynamic模式插入,Dynamic模式下会跳过未设值的字段。<br>
* 某些字段在数据库中设置了defauelt value。
* 如果在实体中为null,那么会将null值插入数据库,造成数据库的缺省值无效。 为了使用dynamic模式后,
* 只有手工设置为null的属性,插入数据库时才是null。如果没有设置过值,在插入数据库时将使用数据库的默认值。
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
@SuppressWarnings("unchecked")
public final <T> void batchInsert(List<T> entities, Boolean doGroup, Boolean dynamic) throws SQLException {
if (entities == null || entities.isEmpty())
return;
T t = entities.get(0);
if (t instanceof IQueryableEntity) {
batchInsert0((List<IQueryableEntity>) entities, doGroup, dynamic);
} else {
List<PojoWrapper> list = PojoWrapper.wrap(entities, false);
batchInsert0(list, doGroup, dynamic);
}
}
private final <T extends IQueryableEntity> void batchInsert0(List<T> entities, Boolean group, Boolean dynamic) throws SQLException {
boolean flag = dynamic == null ? ORMConfig.getInstance().isDynamicInsert() : dynamic.booleanValue();
Batch<T> batch = startBatchInsert(entities.get(0), null, flag, false);
if (group != null)
batch.setGroupForPartitionTable(group);
batch.execute(entities);
}
/**
* 极限模式下的批量插入操作<br>
* extreme模式:extreme是为了性能而优化的特殊模式,该模式下数据库自增主键将不会被回写到对象中。
* 此外在一些特殊的数据库上会使用特定的语法来加速。<br>
* 比如Oracle上,会使用 / *+ APPEND * /等特殊的SQL语法来提高性能。
*
* @param entities
* 要插入的对象
*
* @param group
* 在分库分表情况下,是否对每条记录进行路由计算并重新分组。<br>
* 在启用分库分表后,用户如果不确定传入的多个对象在路由计算后属于同一张表,则需打开此开关。<br>
* 开关开启后会对每个对象进行路由计算并重新分组操作(这一操作将损耗一定的性能)。
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public final <T> void extremeInsert(List<T> entities, Boolean group) throws SQLException {
if (entities.isEmpty())
return;
T t = entities.get(0);
Batch batch;
if (t instanceof IQueryableEntity) {
batch = startBatchInsert((IQueryableEntity) t, null, false, true);
if (group != null)
batch.setGroupForPartitionTable(group);
batch.execute(entities);
} else {
List<PojoWrapper> list = PojoWrapper.wrap(entities, false);
batch = startBatchInsert(list.get(0), null, false, true);
if (group != null)
batch.setGroupForPartitionTable(group);
batch.execute(list);
}
}
/**
* 极限模式下的批量更新操作 extreme模式:extreme是为了性能而优化的特殊模式,该模式下数据库自增主键将不会被回写到对象中。
* 此外在一些特殊的数据库上会使用特定的语法来加速。<br>
* 比如Oracle上,会使用 / *+ APPEND * /等特殊的SQL语法来提高性能。
*
* @param entities
* 要插入的对象
* @param group
* 在分库分表情况下,是否对每条记录进行路由计算并重新分组。<br>
* 在启用分库分表后,用户如果不确定传入的多个对象在路由计算后属于同一张表,则需打开此开关。<br>
* 开关开启后会对每个对象进行路由计算并重新分组操作(这一操作将损耗一定的性能)。
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public final <T extends IQueryableEntity> void extremeUpdate(List<T> entities, Boolean group) throws SQLException {
if (entities.isEmpty())
return;
Batch<T> batch = startBatchUpdate(entities.get(0), null, false, true);
if (group != null)
batch.setGroupForPartitionTable(group);
batch.execute(entities);
}
/**
* 获得一个Bach对象,这个batch对象上可以执行批量插入操作。<br>
* 一个Batch对象就是一个已经编译好的SQL语句。用户可以传入一批参数批量执行。<br>
* batch对象可以反复使用,每次执行一批的参数。
*
* @param template
* 批操作的模板。传入的对象是可以插入的。后续的所有批量操作都按此模板執行操作。 如果开启了
* {@link JefConfiguration.Item#DB_DYNAMIC_INSERT
* DB_DYNAMIC_INSERT}
* 功能,那么模板中插入数据库的字段就是后续任务中插入数据库的字段。后续数据库中其他字段即使赋值了也不会入库。
*
* @param dynamic
* 是否跳过未赋值的字段。 强制指定表名,也就是说template当中的表名无效。(传入的表名支持Schema重定向)
* @return Batch操作句柄
* @throws SQLException
* 如果数据库操作错误,抛出。
* @see Batch
*/
public final <T extends IQueryableEntity> Batch<T> startBatchInsert(T template, boolean dynamic) throws SQLException {
return startBatchInsert(template, null, dynamic, false);
}
/**
* 创建一个Batch对象。<br>
* 一个Batch对象就是一个已经编译好的SQL语句。用户可以传入一批参数批量执行。<br>
* batch对象可以反复使用,每次执行一批的参数。
*
* @param template
* 批操作的模板。传入的对象是可以插入的。后续的所有批量操作都按此模板執行操作。 如果开启了
* {@link JefConfiguration.Item#DB_DYNAMIC_INSERT
* DB_DYNAMIC_INSERT}
* 功能,那么模板中插入数据库的字段就是后续任务中插入数据库的字段。后续数据库中其他字段即使赋值了也不会入库。
* @param tableName
* 强制指定表名,也就是说template当中的表名无效。(传入的表名支持Schema重定向) *
* @param dynamic
* dynamic模式:某些字段在数据库中设置了defauelt value。
* 如果在实体中为null,那么会将null值插入数据库,造成数据库的缺省值无效。 为了使用dynamic模式后,
* 只有手工设置为null的属性,插入数据库时才是null。如果没有设置过值,在插入数据库时将使用数据库的默认值。
* @param extreme
* extreme模式:extreme是为了性能而优化的特殊模式,该模式下数据库自增主键将不会被回写到对象中。
* 此外在一些特殊的数据库上会使用特定的语法来加速。<br>
* 比如Oracle上,会使用 / *+ APPEND * /等特殊的SQL语法来提高性能。
*
* @return Batch操作句柄
* @throws SQLException
* 如果数据库操作错误,抛出。
* @see Batch
*/
public final <T extends IQueryableEntity> Batch<T> startBatchInsert(T template, String tableName, boolean dynamic, boolean extreme) throws SQLException {
long start = System.nanoTime();
ITableMetadata meta = MetaHolder.getMeta(template);
Batch.Insert<T> b = new Batch.Insert<T>(this, meta);
InsertSqlClause insertPart = insertp.toInsertSqlBatch((IQueryableEntity) template, tableName, dynamic, extreme, null);
b.setInsertPart(insertPart);
b.setForceTableName(tableName);
b.extreme = extreme;
b.parseTime = System.nanoTime() - start;
return b;
}
/**
* 获得一个Bach对象,这个batch对象上可以执行批量更新操作。
*
* @param template
* 批操作的模板。传入的对象必须是一个构成update的完整请求(包含update的字段和查询条件(默认为主键))。
* 后续的所有批量操作都按此模板執行操作。 <strong>注意这个模板并未加入到批任务当中</strong>
* @param dynamic
* dynamic模式:如果开启,则只更新被修改过的字段。如果关闭则更新除了主键以外的所有字段。<br>
* 该参数可传入null。当传入null时,按照全局设置的db.dynamic.update参数确定是否使用动态更新。
* 此外,当传入对象的无任何字段被修改过时,也会更新除了主键以外的所有字段。
* @return Batch对象,可执行批操作
* @throws SQLException
* @see Batch
*/
public final <T extends IQueryableEntity> Batch<T> startBatchUpdate(T template, boolean dynamic) throws SQLException {
return startBatchUpdate(template, null, dynamic, false);
}
/**
* 获得一个Bach对象,这个batch对象上可以执行批量更新操作。
*
* @param template
* 批操作的模板。传入的对象必须是一个构成update的完整请求(包含update的字段和查询条件(默认为主键))。
* 后续的所有批量操作都按此模板執行操作。 <strong>注意这个模板并未加入到批任务当中</strong>
* @param tableName
* 强制指定表名,也就是说template当中的表名无效。(传入的表名支持Schema重定向)
* @param dynamic
* dynamic模式:如果开启,则只更新被修改过的字段。如果关闭则更新除了主键以外的所有字段。<br>
* 该参数可传入null。当传入null时,按照全局设置的db.dynamic.update参数确定是否使用动态更新。
* 此外,当传入对象的无任何字段被修改过时,也会更新除了主键以外的所有字段。
* @param extreme
* extreme模式:extreme是为了性能而优化的特殊模式,该模式下数据库自增主键将不会被回写到对象中。
* 此外在一些特殊的数据库上会使用特定的语法来加速。<br>
* 比如Oracle上,会使用 / *+ APPEND * /等特殊的SQL语法来提高性能。
* @return Batch对象,可执行批操作
* @throws SQLException
* 如果数据库操作错误,抛出。
* @see Batch
*/
public final <T extends IQueryableEntity> Batch<T> startBatchUpdate(T template, String tableName, boolean dynamic, boolean extreme) throws SQLException {
if (dynamic && !template.needUpdate()) {
throw new IllegalArgumentException("The input object is not a valid update query Template, since its update value map is empty, change to ");
}
long start = System.nanoTime();
UpdateClause updatePart = updatep.toUpdateClauseBatch((IQueryableEntity) template, null, dynamic);
// 位于批当中的绑定变量
UpdateContext context = new UpdateContext(template.getQuery().getMeta().getVersionColumn());
BindSql wherePart = preProcessor.toWhereClause(template.getQuery(), new SqlContext(null, template.getQuery()), context, getProfile(null),true);
ITableMetadata meta = MetaHolder.getMeta(template);
Batch.Update<T> batch = new Batch.Update<T>(this, meta);
batch.forceTableName = MetaHolder.toSchemaAdjustedName(tableName);
batch.setUpdatePart(updatePart);
batch.setWherePart(wherePart);
batch.extreme = extreme;
batch.parseTime = System.nanoTime() - start;
return batch;
}
/**
* 用传入的Query作为条件,将数据库中的记录更为为对象中的值
*
* @param query
* @param entities
* @throws SQLException
*/
public final <T extends IQueryableEntity> void batchUpdateBy(Query<T> query, List<T> entities) throws SQLException {
}
/**
* 批量更新
*
* @param entities
* 要更新的操作请求。<br>
* 批量更新时第一个对象会作为整批的模板。该对象中的Query部分作为where条件。
* 该对象本身被修改过的值作为set部分。后续的对象仅作为操作参数使用。
* @return 实际修改的记录行数
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public final <T> void batchUpdate(List<T> entities) throws SQLException {
batchUpdate(entities, null, null);
}
/**
* 批量更新
*
* @param entities
* 要更新的操作请求。<br>
* 批量更新时第一个对象会作为整批的模板。该对象中的Query部分作为where条件。
* 该对象本身被修改过的值作为set部分。后续的对象仅作为操作参数使用。
* @param group
* 在分库分表情况下,是否对每条记录进行路由计算并重新分组。<br>
* 在启用分库分表后,用户如果不确定传入的多个对象在路由计算后属于同一张表,则需打开此开关。<br>
* 开关开启后会对每个对象进行路由计算并重新分组操作(这一操作将损耗一定的性能)。
* @return 实际修改的记录行数
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
public final <T> int batchUpdate(List<T> entities, Boolean group) throws SQLException {
return batchUpdate(entities, group, null);
}
/**
* 批量更新
*
* @param entities
* 要更新的操作请求。<br>
* 批量更新时第一个对象会作为整批的模板。该对象中的Query部分作为where条件。
* 该对象本身被修改过的值作为set部分。后续的对象仅作为操作参数使用。
* @param group
* 在分库分表情况下,是否对每条记录进行路由计算并重新分组。<br>
* 在启用分库分表后,用户如果不确定传入的多个对象在路由计算后属于同一张表,则需打开此开关。<br>
* 开关开启后会对每个对象进行路由计算并重新分组操作(这一操作将损耗一定的性能)。
* @param dynamic
* dynamic模式:如果开启,则只更新被修改过的字段。如果关闭则更新除了主键以外的所有字段。<br>
* 该参数可传入null。当传入null时,按照全局设置的db.dynamic.update参数确定是否使用动态更新。
* 此外,当传入对象的无任何字段被修改过时,也会更新除了主键以外的所有字段。
* @return 实际修改的记录行数
* @throws SQLException
* 如果数据库操作错误,抛出。
*/
@SuppressWarnings("unchecked")
public final <T> int batchUpdate(List<T> entities, Boolean group, Boolean dynamic) throws SQLException {
if (entities == null || entities.isEmpty())
return 0;
T t = entities.get(0);
if (t instanceof IQueryableEntity) {
return batchUpdate0((List<IQueryableEntity>) entities, group, null);
} else {
List<PojoWrapper> list = PojoWrapper.wrap(entities, false);
return batchUpdate0(list, group, null);
}
}
private final <T extends IQueryableEntity> int batchUpdate0(List<T> entities, Boolean group, Boolean dynamic) throws SQLException {
if (entities.isEmpty())
return 0;
T template = null;
boolean dyna;
if (dynamic == null) {// 如果未指定时
dyna = ORMConfig.getInstance().isDynamicUpdate();
} else {
dyna = dynamic.booleanValue();
}
for (int i = 0; i < 3; i++) {
template = entities.get(i);
if (!dyna || template.needUpdate()) {
break;
}
}
if (!template.needUpdate()) {
dyna = false;
}
Batch<T> batch = this.startBatchUpdate(template, null, dyna, false);
if (group != null) {
batch.setGroupForPartitionTable(group);
}
return batch.execute(entities);
}
/**
* 返回数据库函数表达式
*
* @param func
* 函数,在{@link DbFunction}中枚举
* @param params
* 函数的参数
* @return 符合数据库方言的函数表达式
*/
public final SqlExpression func(DbFunction func, Object... params) {
return selectTarget(null).func(func, params);
}
/**
* 在数据库中查询得到表达式的值 <h3>Example.</h3>
*
* <pre>
* <code>
* //在任何数据库上获得当前时间
* Date d=session.getExpressionValue(session.func(Func.current_timestamp), Date.class);
* //获取Sequence的下一个值
* long next=session.getExpressionValue(new SqlExpression("seq_name.nextval"),Long.class);
* </code>
* </pre>
*
* 如果要指定操作的数据源,可以先获得SqlTemplate对象,然后调用其同名方法
*
* @param expression
* SQL表达式
* @param clz
* 返回值类型
* @return 查询结果
* @throws SQLException
* 当数据库异常时抛出
* @see SqlTemplate#getExpressionValue(String, Class, Object...)
*/
public final <T> T getExpressionValue(String expression, Class<T> clz) throws SQLException {
return selectTarget(null).getExpressionValue(expression.toString(), clz);
}
/**
* 在数据库中查询得到函数表达式的值 <h3>Example.</h3>
*
* @param func
* 函数种类
* @param clz
* 返回结果类型
* @param params
* 函数入参
* @return 该函数的执行结果
* @throws SQLException
* 当数据库异常时抛出
* @see SqlTemplate#getExpressionValue(DbFunction, Class, Object...)
*/
public final <T> T getExpressionValue(DbFunction func, Class<T> clz, Object... params) throws SQLException {
return selectTarget(null).getExpressionValue(func, clz, params);
}
/**
* 分库分表计算(数据路由)
* <p>
* 根据查询/插入/更新/删除的请求来计算其影响到的表
*
* @param entity
* 要分表的对象或查询
* @return PartitionResult数组,数组的每个元素(PartitionResult)表示一个独立的数据库, 其名称可以用
* getDatabase()获得,对于每一个数据库,可能会有多张表,用getTables()获得
* 如果你能确定本次操作只会操作一张表,可以用getAsOneTable()获得表名.
* @see PartitionResult
*/
public final PartitionResult[] getPartitionResults(IQueryableEntity entity) {
return DbUtils.toTableNames(entity, null, entity.getQuery(), getPartitionSupport());
}
/**
* 得到DbClient对象,该对象上能够执行更多的DDL指令。
*
*
* @return DbClient对象
* @see DbClient
* @see Session
*/
public abstract DbClient getNoTransactionSession();
/**
* <h3>枚举对象,描述查询结果拼装到对象的策略</h3>
* <ul>
* <li>{@link #PLAIN_MODE}<br>
* 强制使用PLAIN_MODE.(即使是dataobject子类,也使用Plain模式拼装)</li>
* <li>{@link #SKIP_COLUMN_ANNOTATION}<br>
* 拼装时,忽略Column名,直接使用类的Field名作为列名/li>
* <li>{@link #NO_RESORT}<br>
* 禁用内存重新排序功能</li>
* </ul>
*/
public enum PopulateStrategy {
/**
* 忽略@Column注释。<br/>
*
* 一个名为 createTime 的字段,其注解为@Column(name="CREATE_TIME")。<br>
* 正常情况下,标记为的字段会对应查询结果中的"CREATE_TIME"列。<br>
* 使用SKIP_COLUMN_ANNOTATION参数后,对应到查询结果中的createtime列。
*
*/
SKIP_COLUMN_ANNOTATION,
/**
* <b>关于PLAIN_MODE的用法</b>
* 正常情况下,检测到查询结果是DataObject的子类时,就会采用嵌套法(NESTED)拼装结果,比如
* 这种情况下将结果集作为立体结构,将不同表选出的字段作为不同的实体的属性。如:<br>
* <li>Person.name <-> 'T1__name'</li> <li>Person.school.id <-> 'T2__id'
* </li>
* <p>
* 嵌套法拼装时,会忽略没有绑定到数据库表的字段的拼装(即非数据元模型中定义的字段)。
* 如果我们要将结果集中的列直接按名称对应到实体上,那么就需要使用PLAIN_MODE.
*/
PLAIN_MODE,
/**
* 当返回结果是数组时,将查出的每个列作为一个元素,用数组的形式返回
*/
COLUMN_TO_ARRAY
}
@SuppressWarnings({ "unchecked", "rawtypes" })
Iterator iterateResultSet(IResultSet rs, EntityMappingProvider mapping, Transformer transformers) throws SQLException {
Class returnClz = transformers.getResultClazz();
if (ArrayUtils.contains(MetadataService.SIMPLE_CLASSES, returnClz)) {
return ResultPopulatorImpl.instance.iteratorSimple(rs, returnClz);
}
if (Object[].class == returnClz) {
return ResultPopulatorImpl.instance.iteratorMultipie(rs, mapping, transformers);
}
if (returnClz == Var.class || returnClz == Map.class) {
return ResultPopulatorImpl.instance.iteratorMap(rs, transformers);
}
if (transformers.isVarObject()) {
return ResultPopulatorImpl.instance.iteratorNormal(this, rs, mapping, transformers);
}
boolean plain = ArrayUtils.contains(transformers.getStrategy(), PopulateStrategy.PLAIN_MODE) || (mapping == null && !IQueryableEntity.class.isAssignableFrom(returnClz));
if (plain) {
return ResultPopulatorImpl.instance.iteratorPlain(rs, transformers);
}
return ResultPopulatorImpl.instance.iteratorNormal(this, rs, mapping, transformers);
}
@SuppressWarnings("unchecked")
<T> List<T> populateResultSet(IResultSet rsw, EntityMappingProvider mapping, Transformer transformers) throws SQLException {
Class<T> returnClz = (Class<T>) transformers.getResultClazz();
if (returnClz == null) {// 未指定时。如果结果只有1列直接返回;如果有多列,Map返回。
if (rsw.getColumns().length() > 1) {
returnClz = (Class<T>) Var.class;
} else {
return (List<T>) ResultSets.toObjectList(rsw, 1, Integer.MAX_VALUE);
}
}
// 基础类型返回
if (ArrayUtils.fastContains(MetadataService.SIMPLE_CLASSES, returnClz)) {
return ResultPopulatorImpl.instance.toSimpleObjects(rsw, returnClz);
}
// 数组返回——模式1:每张表映射成一个元素 模式2:每列映射成一个元素 模式3:自定义Mapper
if (returnClz.isArray()) {
return (List<T>) ResultPopulatorImpl.instance.toDataObjectMap(rsw, mapping, transformers);
}
// Map返回。
if (returnClz == Var.class || returnClz == Map.class) {
return (List<T>) ResultPopulatorImpl.instance.toVar(rsw, transformers);
}
// 动态表返回
if (transformers.isVarObject()) {
return (List<T>) ResultPopulatorImpl.instance.toJavaObject(this, rsw, mapping, transformers);
}
boolean plain = transformers.hasStrategy(PopulateStrategy.PLAIN_MODE) || (mapping == null && !IQueryableEntity.class.isAssignableFrom(returnClz));
if (plain) {
return (List<T>) ResultPopulatorImpl.instance.toPlainJavaObject(rsw, transformers);
}
return (List<T>) ResultPopulatorImpl.instance.toJavaObject(this, rsw, mapping, transformers);
}
// 包装当前AbsDbClient,包装为缺省的操作对象即无dbkey.
protected final OperateTarget wrapTarget(String dbName, int mustTx) throws SQLException {
if (mustTx > 0 && this instanceof DbClient) {// 如果不是在事务中,那么就用一个内嵌事务将其包裹住,作用是在resultSet的生命周期内,该连接不会被归还。并且也预防了基于线程的连接模型中,该连接被本线程的其他SQL操作再次取用然后释放回池
Transaction tx = new TransactionImpl((DbClient) this, TransactionFlag.ResultHolder, mustTx == 1);
OperateTarget target = new OperateTarget(tx, dbName);
if (target.getProfile().getName() == RDBMS.sqlite) {
tx.setReadonly(false);// The new Driver (3.8.11) do not support
// setReadOnly() while connection is
// created.
}
return target;
} else {
return new OperateTarget(this, dbName);
}
}
@SuppressWarnings("unchecked")
private <T extends IQueryableEntity> List<T> batchLoadByField0(Field field, List<?> values) throws SQLException {
ITableMetadata meta = DbUtils.getTableMeta(field);
Query<?> q = meta.newInstance().getQuery();
q.addCondition(field, Operator.IN, values);
return innerSelect(q, null, null, QueryOption.DEFAULT);
}
private int batchDeleteByField0(Field field, List<? extends Serializable> values) throws SQLException {
ITableMetadata meta = DbUtils.getTableMeta(field);
Query<?> q = meta.newInstance().getQuery();
q.addCondition(field, Operator.IN, values);
return this.delete(q);
}
private int batchDeleteByPK0(ITableMetadata meta, List<? extends Serializable> pkValues) throws SQLException {
if (meta.getType() == EntityType.POJO) {
Query<?> q = meta.newInstance().getQuery();
q.addCondition(meta.getPKFields().get(0).field(), Operator.IN, pkValues);
return this.delete(q);
} else {
Query<?> q = meta.newInstance().getQuery();
q.addCondition(meta.getPKFields().get(0).field(), Operator.IN, pkValues);
return this.delete(q);
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private List batchLoadByPK0(ITableMetadata meta, List<? extends Serializable> pkValues) throws SQLException {
if (meta.getPKFields().size() != 1) {
return batchLoadEachTimes(meta, pkValues);
}
if (meta.getType() == EntityType.POJO) {
Query<?> q = meta.newInstance().getQuery();
q.addCondition(meta.getPKFields().get(0).field(), Operator.IN, pkValues);
return PojoWrapper.unwrapList(innerSelect(q, null, null, QueryOption.DEFAULT));
} else {
Query<?> q = meta.newInstance().getQuery();
q.addCondition(meta.getPKFields().get(0).field(), Operator.IN, pkValues);
return innerSelect(q, null, null, QueryOption.DEFAULT);
}
}
@SuppressWarnings({ "rawtypes", "unchecked" })
private List batchLoadEachTimes(ITableMetadata meta, List<? extends Serializable> pkValues) throws SQLException {
List list = new ArrayList(pkValues.size());
for (Serializable id : pkValues) {
list.add(load(meta, (Serializable[]) id));
}
return list;
}
// 计算手工执行的各种SQL语句下缓存刷新问题
@SuppressWarnings({ "rawtypes", "unchecked" })
protected void checkCacheUpdate(String sql, List list) {
if (getCache().isDummy())
return;
jef.database.jsqlparser.visitor.Statement st = null;
StSqlParser parser = new StSqlParser(new StringReader(sql));
try {
st = parser.Statement();
} catch (ParseException e) {
// 解析错误就不管
}
if (st instanceof jef.database.jsqlparser.statement.insert.Insert) {
getCache().process((jef.database.jsqlparser.statement.insert.Insert) st, list);
} else if (st instanceof jef.database.jsqlparser.statement.update.Update) {
getCache().process((jef.database.jsqlparser.statement.update.Update) st, list);
} else if (st instanceof jef.database.jsqlparser.statement.delete.Delete) {
getCache().process((jef.database.jsqlparser.statement.delete.Delete) st, list);
} else if (st instanceof jef.database.jsqlparser.statement.truncate.Truncate) {
getCache().process((jef.database.jsqlparser.statement.truncate.Truncate) st, list);
}
}
protected int delete0(Query<?> query) throws SQLException {
long start = System.currentTimeMillis();
IQueryableEntity obj = query.getInstance();
String myTableName = (String) query.getAttribute(Query.CUSTOM_TABLE_NAME);
myTableName = MetaHolder.toSchemaAdjustedName(StringUtils.trimToNull(myTableName));
PartitionResult[] sites = DbUtils.toTableNames(obj, myTableName, query, getPartitionSupport());
if (sites != null && sites.length > 0) {
DatabaseDialect profile = this.getProfile(sites[0].getDatabase());
getListener().beforeDelete(obj, this);
BindSql where = deletep.toWhereClause(query, new SqlContext(null, query), profile);
int count = deletep.processDelete(this, obj, where, sites, start);
if (count > 0) {
getCache().onDelete(myTableName == null ? query.getMeta().getTableName(false) : myTableName, where.getSql(), CacheImpl.toParamList(where.getBind()));
}
getListener().afterDelete(obj, count, this);
return count;
} else {
return 0;
}
}
static class UpdateContext {
VersionSupportColumn versionColumn;
Object bean;
private Boolean isPkQuery;
public UpdateContext(VersionSupportColumn versionColumn) {
this.versionColumn = versionColumn;
}
public boolean checkIsPKCondition() {
return versionColumn != null && isPkQuery == null;
}
public void setIsPkQuery(boolean flag) {
this.isPkQuery = flag;
}
public boolean needVersionCondition() {
return versionColumn != null && isPkQuery != null && isPkQuery.booleanValue();
}
public void appendVersionCondition(SqlBuilder builder, SqlContext context, SqlProcessor processor, IQueryableEntity instance, DatabaseDialect profile, boolean batch) {
Object value = versionColumn.getFieldAccessor().get(instance);
if (value != null) {
Condition cond = QB.eq(versionColumn.field(), value);
builder.startSection(" and ");
//FIXME 此处有误,在BatchUpdate中,应当是产生一个始终从对象取值的Variable,而不是生成一个常量条件。
//由Condition转换而成的SQL绑定语句中,都是常量这是不对的。
cond.toPrepareSqlClause(builder,versionColumn.getMeta(), context, processor, instance, profile ,batch);
builder.endSection();
}
}
}
protected int update0(IQueryableEntity obj, String myTableName) throws SQLException {
myTableName = MetaHolder.toSchemaAdjustedName(myTableName);
Query<?> query = obj.getQuery();
long parseCost = System.currentTimeMillis();
PartitionResult[] sites = DbUtils.toTableNames(obj, myTableName, obj.getQuery(), getPartitionSupport());
if (sites.length == 0) {
return 0;
}
DatabaseDialect profile = getProfile(sites[0].getDatabase());
UpdateContext context = new UpdateContext(query.getMeta().getVersionColumn());
BindSql whereClause = updatep.toWhereClause(query, new SqlContext(null, query), context, profile);
if (ORMConfig.getInstance().isSafeMerge() && !obj.needUpdate()) {// 重新检查一遍
return 0;
}
UpdateClause updateClause = updatep.toUpdateClause(obj, sites, ORMConfig.getInstance().isDynamicUpdate());
parseCost = System.currentTimeMillis() - parseCost;
if(updateClause.isEmpty()){
return 0;
}
getListener().beforeUpdate(obj, this);
int count = updatep.processUpdate(this, obj, updateClause, whereClause, sites, parseCost);
if (count > 0) {
String tableName = myTableName == null ? query.getMeta().getTableName(false) : myTableName;
getCache().onUpdate(tableName, whereClause.getSql(), CacheImpl.toParamList(whereClause.getBind()));
} else if (context.needVersionCondition()) {// 基于版本的乐观锁并发检测,记录没有成功更新
throw new OptimisticLockException("The row in database has been modified by others after the entity was loaded.", null, obj);
}
getListener().afterUpdate(obj, count, this);
return count;
}
protected void insert0(IQueryableEntity obj, String myTableName, boolean dynamic) throws SQLException {
getListener().beforeInseret(obj, this);
myTableName = MetaHolder.toSchemaAdjustedName(myTableName);
long start = System.currentTimeMillis();
PartitionResult pr = null;
try {
pr = DbUtils.toTableName(obj, myTableName, obj.hasQuery() ? obj.getQuery() : null, getPartitionSupport());
} catch (MultipleDatabaseOperateException e) {
// 先路由方式失败。但是还是可以继续向后走。
// 有一种情况下,后续操作可能成功。如果以Sequence作为分库分表主键,此时由于自增值尚未就绪,分库分表失败。
// 待SQL语句解析完成后,分库分表就能成功。
}
InsertSqlClause sqls = insertp.toInsertSql(obj, myTableName, dynamic, pr);
if (sqls.getCallback() != null) {
sqls.getCallback().callBefore(Arrays.asList(obj));
}
// 回调完成,此时自增主键可能已经获得,因此有机会再执行一次分库分表
if (pr == null) {
pr = DbUtils.toTableName(obj, myTableName, obj.hasQuery() ? obj.getQuery() : null, getPartitionSupport());
sqls.setTableNames(pr);
}
long parse = System.currentTimeMillis();
insertp.processInsert(selectTarget(sqls.getTable().getDatabase()), obj, sqls, start, parse);
obj.clearUpdate();
getCache().onInsert(obj, myTableName);
getListener().afterInsert(obj, this);
}
/**
* @param minPriority
* 一般性级联操作的优先级是0,KV表扩展时的级联操作是5
*/
private int updateCascade0(IQueryableEntity obj, int minPriority) throws SQLException {
if ((this instanceof Transaction) || getTxType() != TransactionMode.JPA) {
return CascadeUtil.updateWithRefInTransaction(obj, this, minPriority);
} else if (this instanceof DbClient) {
Transaction trans = new TransactionImpl((DbClient) this, TransactionFlag.Cascade, false);
try {
int i = CascadeUtil.updateWithRefInTransaction(obj, trans, minPriority);
trans.commit(true);
return i;
} catch (SQLException e) {
trans.rollback(true);
throw e;
} catch (RuntimeException e) {
trans.rollback(true);
throw e;
}
} else {
throw new IllegalArgumentException("unknown DbClient");
}
}
private int deleteCascade0(IQueryableEntity obj, int minPriority) throws SQLException {
if ((this instanceof Transaction) || getTxType() != TransactionMode.JPA) {
return CascadeUtil.deleteWithRefInTransaction(obj, this, 0);
} else if (this instanceof DbClient) {
Transaction trans = new TransactionImpl((DbClient) this, TransactionFlag.Cascade, false);
try {
int i = CascadeUtil.deleteWithRefInTransaction(obj, trans, 0);
trans.commit(true);
return i;
} catch (SQLException e) {
trans.rollback(true);
throw e;
} catch (RuntimeException e) {
trans.rollback(true);
throw e;
}
} else {
throw new IllegalArgumentException("unknown DbClient");
}
}
private void insertCascade0(Object entity, boolean dynamic, int minPriority) throws SQLException {
if (entity == null)
return;
IQueryableEntity obj;
if (entity instanceof IQueryableEntity) {
obj = (IQueryableEntity) entity;
} else {
ITableMetadata meta = MetaHolder.getMeta(entity.getClass());
obj = meta.transfer(entity, false);
}
if ((this instanceof Transaction) || getTxType() != TransactionMode.JPA) {
CascadeUtil.insertWithRefInTransaction(Arrays.asList(obj), this, dynamic, minPriority);
} else if (this instanceof DbClient) {
Transaction trans = new TransactionImpl((DbClient) this, TransactionFlag.Cascade, false);
try {
CascadeUtil.insertWithRefInTransaction(Arrays.asList(obj), trans, dynamic, minPriority);
trans.commit(true);
} catch (SQLException e) {
trans.rollback(true);
throw e;
} catch (RuntimeException e) {
trans.rollback(true);
throw e;
}
} else {
throw new IllegalArgumentException("unknown DbClient");
}
}
abstract PartitionSupport getPartitionSupport();
}