/*
* 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.sql.SQLException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import javax.persistence.FlushModeType;
import javax.persistence.LockModeType;
import javax.persistence.NonUniqueResultException;
import javax.persistence.Parameter;
import javax.persistence.PersistenceException;
import javax.persistence.TemporalType;
import jef.common.wrapper.IntRange;
import jef.database.NamedQueryConfig.ParameterMetadata;
import jef.database.OperateTarget.TransformerAdapter;
import jef.database.OperateTarget.TransformerIteratrAdapter;
import jef.database.Session.PopulateStrategy;
import jef.database.dialect.type.ResultSetAccessor;
import jef.database.jdbc.GenerateKeyReturnOper;
import jef.database.jdbc.result.IResultSet;
import jef.database.jsqlparser.expression.JpqlDataType;
import jef.database.jsqlparser.statement.select.Select;
import jef.database.jsqlparser.visitor.Statement;
import jef.database.query.ParameterProvider;
import jef.database.query.QueryHints;
import jef.database.query.SqlExpression;
import jef.database.routing.sql.ExecuteablePlan;
import jef.database.routing.sql.QueryablePlan;
import jef.database.routing.sql.SimpleExecutionPlan;
import jef.database.routing.sql.SqlAnalyzer;
import jef.database.routing.sql.SqlAndParameter;
import jef.database.wrapper.ResultIterator;
import jef.database.wrapper.populator.AbstractResultSetTransformer;
import jef.database.wrapper.populator.ColumnDescription;
import jef.database.wrapper.populator.Mapper;
import jef.database.wrapper.populator.ResultSetExtractor;
import jef.database.wrapper.populator.Transformer;
import jef.tools.Assert;
import jef.tools.DateUtils;
import jef.tools.PageLimit;
import jef.tools.StringUtils;
import jef.tools.reflect.BeanWrapper;
/**
* JEF的NativeQuery实现(对应JPA的TypedQuery)。 <h2>概览</h2>
* 让用户能根据临时拼凑的或者预先写好的SQL语句进行数据库查询,查询结果将被转换为用户需要的类型。<br>
* NativeQuery支持哪些功能?
* <ul>
* <li>支持绑定变量,允许在SQL中用占位符来描述变量。</li>
* <li>一个NativeQuery可携带不同的绑定变量参数值,反复使用</li>
* <li>可以指定{@code fetch-size} , {@code max-result} 等参数,进行性能调优</li>
* <li>可以自定义查询结果到返回对象之间的映射关系,根据自定义映射转换结果</li>
* <li>支持{@code E-SQL},即对传统SQL进行解析和改写以支持一些高级功能,参见下文《什么是E-SQL》节</li>
* </ul>
* <h2>什么是E-SQL</h2>
* EF-ORM会对用户输入的SQL进行解析,改写,从而使得SQL语句的使用更加方便,EF-ORM将不同数据库DBMS下的SQL语句写法进行了兼容处理。
* 并且提供给上层统一的SQL写法,为此我们将其称为 E-SQL (Enhanced SQL). E-SQL可以让用户使用以下特性:
* <ul>
* <li>Schema重定向</li>
* <li>数据库方言——语法格式整理</li>
* <li>数据库方言——函数转换</li>
* <li>增强的绑定变量占位符表示功能</li>
* <li>绑定变量占位符中可以指定变量数据类型</li>
* <li>动态SQL语句——表达式忽略</li>
* </ul>
*
* <h3>示例</h3> 面我们逐一举例这些特性 <h4>Schema重定向</h4>
* 在Oracle,PG等数据库下,我们可以跨Schema操作。Oracle数据库会为每个用户启用独立Schema,
* 例如USERA用户下和USERB用户下都有一张名为TT的表。 我们可以在一个SQL语句中访问两个用户下的表,
*
* <pre>
* <tt>select * from usera.tt union all select * from userb.tt </tt>
* </pre>
*
* 但是这样就带来一个问题,在某些场合,实际部署的数据库用户是未定的,在编程时开发人员无法确定今后系统将会以什么用户部署。因此EF-
* ORM设计了Schema重定向功能。<br>
* 在开发时,用户根据设计中的虚拟用户名编写代码,而在实际部署时,可以配置文件jef.properties指定虚拟schema对应到部署中的实际schema上
* 。<br>
* 例如,上面的SQL语句,如果在jef.properties中配置
*
* <pre>
* <tt>schema.mapping=USERA:ZHANG, USERB:WANG</tt>
* </pre>
*
* 那么SQL语句在实际执行时,就变为
*
* <pre>
* <tt>select * from zhang.tt union all select * from wang.tt //实际被执行的SQL</tt>
* </pre>
*
* 用schema重定向功能,可以解决开发和部署的 schema耦合问题,为测试、部署等带来更大的灵活性。
*
* <h4>数据库方言——语法格式整理</h4> 根据不同的数据库语法,EF-ORM会在执行SQL语句前根据本地方言对SQL进行修改,以适应当前数据库的需要。<br>
* <strong>例1:</strong>
*
* <pre>
* <tt>select t.id||t.name as u from t</tt>
* </pre>
*
* 在本例中{@code ||}表示字符串相连,这在大部分数据库上执行都没有问题,但是如果在MySQL上执行就不行了,MySQL中{@code ||}
* 表示或关系,不表示字符串相加。 因此,EF-ORM在MySQL上执行上述E-SQL语句时,实际在数据库上执行的语句变为<br>
*
* <pre>
* <tt> select concat(t.id, t.name) as u from t</tt>
* </pre>
*
* <br>
* 这保证了SQL语句按大多数人的习惯在MYSQL上正常使用。
* <p>
* <strong>例2:</strong>
*
* <pre>
* <tt>select count(*) total from t</tt>
* </pre>
*
* 这句SQL语句在Oracle上是能正常运行的,但是在postgresql上就不行了。因为postgresql要求每个列的别名前都有as关键字。
* 对于这种情况EF-ORM会自动为这样的SQL语句加上缺少的as关键字,从而保证SQL语句在Postgres上也能正常执行。
*
* <pre>
* <tt>select count(*) as total from t</tt>
* </pre>
* <p>
* 这些功能提高了SQL语句的兼容性,能对用户屏蔽数据库方言的差异,避免操作者因为使用了SQL而遇到数据库难以迁移的情况。
* <p>
* 注意:并不是所有情况都能实现自动改写SQL,比如有些Oracle的使用者喜欢用+号来表示外连接,写成
* {@code select t1.*,t2.* from t1,t2 where t1.id=t2.id(+) } 这样,但在其他数据库上不支持。
* 目前EF-ORM还<strong>不支持</strong>将这种SQL语句改写为其他数据库支持的语法(今后可能会支持)。
* 因此如果要编写能跨数据库的SQL语句,还是要使用‘OUTER JOIN’这样标准的SQL语法。
* <p>
*
* <h4>数据库方言——函数转换</h4>
* EF-ORM能够自动识别SQL语句中的函数,并将其转换为在当前数据库上能够使用的函数。<br>
* <strong>例1:</strong>
*
* <pre>
* <tt>select replace(person_name,'张','王') person_name,decode(nvl(gender,'M'),'M','男','女') gender from t_person</tt>
* </pre>
*
* 这个语句如果在postgresql上执行,就会发现问题,因为postgres不支持nvl和decode函数。 但实际上,框架会将这句SQL修改为
*
* <pre>
* <tt>select replace(person_name, '张', '王') AS person_name,
* CASE
* WHEN coalesce(gender, 'M') = 'M'
* THEN '男'
* ELSE '女'
* END AS gender
* from t_person</tt>
* </pre>
*
* 从而在Postgresql上实现相同的功能。
* <h4>绑定变量改进</h4>
* E-SQL中表示参数变量有两种方式 :
* <ul>
* <li>:param-name (:id :name,用名称表示参数)</li>
* <li>?param-index (如 ?1 ?2,用序号表示参数)。</li>
* 上述绑定变量占位符是和JPA规范完全一致的。<br>
*
* E-SQL中,绑定变量可以声明其参数类型,也可以不声明。比如<br>
*
* <pre>
* <tt>select count(*) from Person_table where id in (:ids<int>)</tt>
* </pre>
*
* 也可以写作
*
* <pre>
* <tt>select count(*) from Person_table where id in (:ids)</tt>
* </pre>
*
* 类型成名不区分大小写。如果不声明类型,那么传入的参数如果为List<String>,
* 那么数据库是否能正常执行这个SQL语句取决于JDBC驱动能否支持。(因为数据库里的id字段是number类型而传入了string)。
* <p>
* <br>
* {@linkplain jef.database.jsqlparser.expression.JpqlDataType 各种支持的参数类型和作用}<br>
*
*
* <h4>动态SQL语句——表达式忽略</h4>
* EF-ORM可以根据未传入的参数,动态的省略某些SQL片段。这个特性往往用于某些参数不传场合下的动态条件,避免写大量的SQL。
* 有点类似于IBatis的动态SQL功能。 我们先来看一个例子
*
* <pre>
* <code>//SQL语句中写了四个查询条件
* String sql="select * from t_person where id=:id " +
* "and person_name like :person_name<$string$> " +
* "and currentSchoolId=:schoolId " +
* "and gender=:gender";
* NativeQuery<Person> query=db.createNativeQuery(sql,Person.class);
* {
* System.out.println("== 按ID查询 ==");
* query.setParameter("id", 1);
* Person p=query.getSingleResult(); //只传入ID时,其他三个条件消失
* System.out.println(p.getId());
* System.out.println(p);
* }
* {
* System.out.println("== 由于参数'ID'并未清除,所以变为 ID + NAME查询 ==");
* query.setParameter("person_name", "张"); //传入ID和NAME时,其他两个条件消失
* System.out.println(query.getResultList());
* }
* {
* System.out.println("== 参数清除后,只传入NAME,按NAME查询 ==");
* query.clearParameters();
* query.setParameter("person_name", "张"); //只传入NAME时,其他三个条件消失
* System.out.println(query.getResultList());
* }
* {
* System.out.println("== 按NAME+GENDER查询 ==");
* query.setParameter("gender", "F"); //传入GENDER和NAME时,其他两个条件消失
* System.out.println(query.getResultList());
* }
* {
* query.clearParameters(); //一个条件都不传入时,整个where子句全部消失
* System.out.println(query.getResultList());
* }</code>
* </pre>
*
* 上面列举了五种场合,每种场合都没有完整的传递四个WHERE条件。
* 这种常见需求一般发生在按条件查询中,比较典型的一个例子是用户Web界面上的搜索工具栏,当用户输入条件时
* ,按条件搜索。当用户未输入条件时,该字段不作为搜索条件
* 。使用动态SQL功能后,一个固定的SQL语句就能满足整个视图的所有查询场景,极大的简化了视图查询的业务操作。
*
* <h3>注意</h3> 不支持多线程。每次使用前需要createNamedQuery或者createNativeQuery.
*
*
*
* @author Administrator
* @param <X>
* 返回结果的参数类型
* @see jef.database.jsqlparser.expression.JpqlDataType
*/
@SuppressWarnings({ "unchecked", "hiding" })
public class NativeQuery<X> implements javax.persistence.TypedQuery<X>, ParameterProvider {
private NamedQueryConfig config; // 查询本体、本身线程安全
private OperateTarget db;
private LockModeType lock = null;
private FlushModeType flushType = null;
private PageLimit range; // 额外的范围要求
// 实例数据
private Map<Object, Object> nameParams = new HashMap<Object, Object>();// 按名参数
private Transformer resultTransformer; // 返回类型
private final Map<String, Object> hint = new HashMap<String, Object>();
private int fetchSize = ORMConfig.getInstance().getGlobalFetchSize();
/**
* 是否支持数据路由
*/
private boolean routing;
/**
* 是否启用SQL语句路由功能
*
* @return SQL语句路由
*/
public boolean isRouting() {
return routing;
}
/**
* 设置是否启用SQL语句路由功能
*
* @param routing
* SQL语句路由
*/
public NativeQuery<X> setRouting(boolean routing) {
this.routing = routing;
return this;
}
/**
* 设置启用SQL语句路由功能
*
* @return 当前NativeQuery本身
*/
public NativeQuery<X> withRouting() {
this.routing = true;
return this;
}
/**
* 从SQL语句加上返回类型 构造
*
* @param db 目标数据库
*
* @param sql SQL语句
* @param t 查询转换器
* @param isJpql
*/
NativeQuery(OperateTarget db, String sql, Transformer t,boolean isJpql) {
if (StringUtils.isEmpty(sql)) {
throw new IllegalArgumentException("Please don't input an empty SQL.");
}
this.db = db;
this.resultTransformer = t;
this.config = new NamedQueryConfig("", sql, isJpql, 0);
resultTransformer.addStrategy(PopulateStrategy.PLAIN_MODE);
}
/*
* 构造,从NamedQueryConfig构造出来
*/
NativeQuery(OperateTarget db, NamedQueryConfig config, Transformer t) {
this.db = db;
this.resultTransformer = t;
this.config = config;
resultTransformer.addStrategy(PopulateStrategy.PLAIN_MODE);
}
/**
* 获取结果数量<br />
*
* <h3>注意</h3> 该方法不是将语句执行后获得全部返回结果,而是尝试将sql语句重写为count语句,然后查询出结果。<br/>
* 因此,count返回的数值不受结果集限制的影响。<br />
* 如果通过{@link #setMaxResults(int)}或者{@link #setFirstResult(int)}或者
* {@link #setRange(IntRange)}设置结果集限制,将不会对COUNT结果产生影响。
*
* @return count结果
* @see #setFirstResult(int)
* @see #setMaxResults(int)
* @see #setRange(IntRange)
*/
public long getResultCount() {
try {
SqlAndParameter paramHolder = config.getCountSqlAndParams(db, this);
QueryablePlan plan = null;
if (routing) {
plan = SqlAnalyzer.getSelectExecutionPlan((Select) paramHolder.statement, paramHolder.getParamsMap(), paramHolder.params, db);
} else {
plan = new SimpleExecutionPlan(paramHolder.statement, paramHolder.params, null, db);
}
long maxSize = paramHolder.getLimitSpan(); // 查询分页条件,count结果不可能大于分页的最大结果
paramHolder.setNewLimit(null);
if (plan.mustGetAllResultsToCount()) {
// 路由,并且必须遍历结果集场合
// 很麻烦的场景——由于分库后启用了group聚合,造成必须先将结果集在内存中混合后才能得到正确的count数……
long total = doQuery(AbstractResultSetTransformer.countResultSet(fetchSize), true); // 全量查询,去除排序。排序是不必要的
return (maxSize > 0 && maxSize < total) ? maxSize : total;
} else {
return plan.getCount(paramHolder, (int) maxSize, fetchSize);
}
} catch (SQLException e) {
throw new PersistenceException(e.getMessage() + " " + e.getSQLState(), e);
}
}
/**
* 返回fetchSize
*
* @return 每次游标获取的缓存大小
*/
public int getFetchSize() {
if (fetchSize > 0)
return fetchSize;
return config.getFetchSize();
}
/**
* 设置fetchSize
*
* @param size
* 设置每次获取的缓冲大小
*/
public void setFetchSize(int size) {
this.fetchSize = size;
}
/**
* 以迭代器模式返回查询结果
*
* @return 结果迭代器
* @see ResultIterator
*/
public ResultIterator<X> getResultIterator() {
TransformerIteratrAdapter<X> r = new TransformerIteratrAdapter<X>(resultTransformer, this.db);
r.setFetchSize(fetchSize);
try {
return doQuery(r, false);
} catch (SQLException e) {
throw new PersistenceException(e.getMessage() + " " + e.getSQLState(), e);
}
}
/**
* 执行查询语句,返回结果
*
* @return 返回List类型的结果
*/
public List<X> getResultList() {
try {
TransformerAdapter<X> rst = new TransformerAdapter<X>(resultTransformer, db);
rst.setFetchSize(fetchSize);
return doQuery(rst, false);
} catch (SQLException e) {
throw new PersistenceException(e.getMessage() + " " + e.getSQLState(), e);
}
}
/**
* 真正的执行查询
*
* @param extractor
* 结果集转换器,查询参数
* @param forCount
* 如果是count场合,那么返回true。count场合可以去除排序并执行一些SQL变化以优化查询速度。
* @return 返回结果
* @throws SQLException
* 数据库异常
*/
private <T> T doQuery(ResultSetExtractor<T> extractor, boolean forCount) throws SQLException {
SqlAndParameter sqlContext = config.getSqlAndParams(db, this);
QueryablePlan plan = null;
if (routing) {
plan = SqlAnalyzer.getSelectExecutionPlan((Select) sqlContext.statement, sqlContext.getParamsMap(), sqlContext.params, db);
} else {
plan = new SimpleExecutionPlan(sqlContext.statement, sqlContext.params, null, db);
}
return plan.doQuery(sqlContext, extractor, forCount, range);
}
/**
* 设置查询结果的条数限制,即分页 包含了{@link #setMaxResults(int)}和
* {@link #setFirstResult(int)}的功能<br>
* 这一设置会影响 {@link #getResultList()} {@link #getResultIterator()} 的结果。 <br>
* 这一设置不影响 {@link #getResultCount()}的结果
*
* @param range
* 结果集范围。一个含头含尾的区间。
* @return this
* @see IntRange
* @see #getResultList()
* @see #getResultIterator()
* @see #getResultCount()
* @deprecated use {@link #setRange(long, int)}
*/
public void setRange(IntRange range) {
this.range = PageLimit.parse(range);
}
/**
* 设置查询结果的条数限制,即分页 包含了{@link #setMaxResults(int)}和
* {@link #setFirstResult(int)}的功能<br>
* 这一设置会影响 {@link #getResultList()} {@link #getResultIterator()} 的结果。 <br>
* 这一设置不影响 {@link #getResultCount()}的结果
*
* @param range
* 结果集范围。一个含头含尾的区间。
* @return this
* @see range
* @see #getResultList()
* @see #getResultIterator()
* @see #getResultCount()
*/
public void setRange(PageLimit range) {
this.range = range;
}
/**
* 设置查询结果的条数限制,即分页 包含了{@link #setMaxResults(int)}和
* {@link #setFirstResult(int)}的功能<br>
* 这一设置会影响 {@link #getResultList()} {@link #getResultIterator()} 的结果。 <br>
* 这一设置不影响 {@link #getResultCount()}的结果
*
* @param start
* 结果集范围。相当于SQL中的offset(从0开始)
* @param limit
* 限定结果条数。相当于SQL中的limit。
*/
public void setRange(long start, int limit) {
this.range = new PageLimit(start, limit);
}
/**
* 当确认返回结果只有一条时,使用此方法得到结果。 如果查询条数>1,不会抛出异常,而是返回第一条结果。
*
* @return 如果查询结果条数是0,返回null
*/
public X getSingleResult() {
TransformerAdapter<X> rst = new TransformerAdapter<X>(resultTransformer, db);
rst.setMaxRows(2);
try {
List<X> list = doQuery(rst, false);
if (list.isEmpty())
return null;
return list.get(0);
} catch (SQLException e) {
throw new PersistenceException(e.getMessage() + " [SQL:" + e.getSQLState() + "]", e);
}
}
/**
* 当确认返回结果只有一条时,使用此方法得到结果。 如果查询条数>1,会抛出异常
*
* @return 查询结果
* @throws NonUniqueResultException
* 如果查询结果超过1条,抛出
*/
public X getSingleOnlyResult() throws NonUniqueResultException {
try {
List<X> list = doQuery(new TransformerAdapter<X>(resultTransformer, db).setMaxRows(2), false);
if (list.isEmpty())
return null;
if (list.size() > 1) {
throw new NonUniqueResultException("Too many results found.");
}
return list.get(0);
} catch (SQLException e) {
throw new PersistenceException(e.getMessage() + " [SQL:" + e.getSQLState() + "]", e);
}
}
/**
* 对于各种DDL、insert、update、delete等语句,不需要返回结果的,调用此方法来执行
*
* @return 返回影响到的记录条数(针对update\delete)语句
*/
public int executeUpdate() {
try {
SqlAndParameter parse = config.getSqlAndParams(db, this);
Statement sql = parse.statement;
ExecuteablePlan plan = null;
if (routing) {
plan = SqlAnalyzer.getExecutionPlan(sql, parse.getParamsMap(), parse.params, db);
} else {
plan = new SimpleExecutionPlan(sql, parse.params, null, db);
}
return plan.processUpdate(GenerateKeyReturnOper.NONE).getAffectedRows();
} catch (SQLException e) {
throw new PersistenceException(e.getMessage() + " " + e.getSQLState(), e);
}
}
/**
* 限制返回的最大结果数<br>
* 这一设置会影响 {@link #getResultList()} {@link #getResultIterator()} 的结果。 <br>
* 这一设置不影响 {@link #getResultCount()}的结果
*
* @param maxResult
* 最多返回的结果条数
* @return this
* @see #getResultList()
* @see #getResultIterator()
* @see #getResultCount()
*/
public NativeQuery<X> setMaxResults(int maxResult) {
if (range == null) {
range = new PageLimit(0, maxResult);
} else {
range.setLimit(maxResult);
}
return this;
}
/**
* 获取当前的结果拼装策略
*
* @return populate strategy
* @see PopulateStrategy
*/
public PopulateStrategy[] getStrategies() {
return resultTransformer.getStrategy();
}
/**
* 设置结果拼装策略,可以同时使用多个选项策略
*
* @see {@link jef.database.Session.PopulateStrategy}
* @param strategies
* 拼装策略
*/
public void setStrategies(PopulateStrategy... strategies) {
resultTransformer.setStrategy(strategies);
}
/**
* 获取当前设置的最大结果设置
*
* @return 查询最大返回结果集
*/
public int getMaxResults() {
if (range != null)
return range.getLimit();
return 0;
}
/**
* 设置结果的开始偏移(即分页时要跳过的记录数)。从0开始。<br>
* 这一设置会影响 {@link #getResultList()} {@link #getResultIterator()} 的结果。 <br>
* 这一设置不影响 {@link #getResultCount()}的结果
*
* @param startPosition
* offset of the result.
* @return this (The nativeQuery)
* @see #getResultIterator()
* @see #getResultList()
* @see #getResultCount()
*/
public NativeQuery<X> setFirstResult(int startPosition) {
if (range == null) {
range = new PageLimit(startPosition, 5000000 - startPosition);
} else {
range.setStart(startPosition);
;
}
return this;
}
/**
* JPA Method<br/>
* 得到目前的开始偏移(即分页时要跳过的记录数)。从0开始
*
* @return offset值
*
*/
public int getFirstResult() {
if (range == null)
return 0;
return range.getStartAsInt();
}
/**
* JPA规范方法 {@inheritDoc}
*/
public NativeQuery<X> setHint(String hintName, Object value) {
hint.put(hintName, value);
if (QueryHints.START_LIMIT.equals(hintName)) {
int[] startLimit = StringUtils.toIntArray(String.valueOf(value), ',');
setRange(startLimit[0], startLimit[1]);
} else if (QueryHints.FETCH_SIZE.equals(hintName)) {
this.setFetchSize(StringUtils.toInt(String.valueOf(value), 0));
}
return this;
}
/**
* JPA规范方法 {@inheritDoc}
*/
public Map<String, Object> getHints() {
return Collections.unmodifiableMap(hint);
}
/**
* 目前不支持的JPA方法 抛出异常
*
* @deprecated throws UnsupportedOperationException
*/
public <X> X unwrap(Class<X> cls) {
throw new UnsupportedOperationException();
}
/**
* JPA规范方法 {@inheritDoc}
*/
public <T> NativeQuery<X> setParameter(Parameter<T> param, T value) {
if (param.getPosition() != null) {
setParameter(param.getPosition(), value);
} else if (StringUtils.isNotEmpty(param.getName())) {
setParameter(param.getName(), value);
}
return this;
}
/**
* JPA规范方法 {@inheritDoc}
*/
public NativeQuery<X> setParameter(Parameter<Calendar> param, Calendar value, TemporalType temporalType) {
setParameter(param, value);
return this;
}
/**
* JPA规范方法 {@inheritDoc}
*/
public NativeQuery<X> setParameter(Parameter<Date> param, Date value, TemporalType temporalType) {
setParameter(param, value);
return this;
}
/**
* JPA规范方法 {@inheritDoc}
*/
public NativeQuery<X> setParameter(String name, Calendar value, TemporalType temporalType) {
return setParameter(name, value);
}
/**
* JPA规范方法 {@inheritDoc}
*/
public NativeQuery<X> setParameter(String name, Date value, TemporalType temporalType) {
return setParameter(name, value);
}
/**
* JPA规范方法 {@inheritDoc}
* <p>
* 设置查询的绑定变量参数
*
* @param name
* 参数名
* @param value
* 参数值
* @throws NoSuchElementException
* 该参数未在查询语句中定义
*/
public NativeQuery<X> setParameter(String name, Object value) throws NoSuchElementException {
if (StringUtils.isNotEmpty(name)) {
ParameterMetadata p = config.getParams(db).get(name);
if (p == null) {
throw new NoSuchElementException("the parameter [" + name + "] doesn't exist in the query:" + config.getName());
}
value = processValue(p, value);
this.nameParams.put(name, value);
}
return this;
}
/**
* 设置查询的绑定变量参数
*
* @param position
* 位置(序号)
* @param value
* 参数值
* @return 当前NativeQuery本身
* @throws NoSuchElementException
* 该参数未在查询语句中定义
*/
public NativeQuery<X> setParameter(int position, Object value) {
ParameterMetadata p = config.getParams(db).get(Integer.valueOf(position));
if (p == null) {
throw new NoSuchElementException("the parameter [" + position + "] doesn't exist in the named query:" + config.getName());
}
value = processValue(p, value);
nameParams.put(Integer.valueOf(position), value);
return this;
}
/**
* 设置参数的值,传入的参数类型为String,
*
* @param name
* 参数名
* @param value
* 参数值
* @return 当前NativeQuery本身
* @throws NoSuchElementException
* 该参数未在查询语句中定义
*/
public NativeQuery<X> setParameterByString(String name, String value) {
if (StringUtils.isNotEmpty(name)) {
ParameterMetadata p = config.getParams(db).get(name);
if (p == null) {
throw new NoSuchElementException("the parameter [" + name + "] doesn't exist in the named query." + config.getName());
}
Object v = value;
if (p.getDataType() != null) {
v = toProperType(p.getDataType(), value, p);
}
this.nameParams.put(name, v);
}
return this;
}
/**
* 设置参数的值,传入的参数类型为String[]
*
* @param name
* 参数名
* @param value
* 参数值
* @return 当前NativeQuery本身
* @throws NoSuchElementException
* 该参数未在查询语句中定义
*/
public NativeQuery<X> setParameterByString(String name, String[] value) {
if (StringUtils.isNotEmpty(name)) {
ParameterMetadata p = config.getParams(db).get(name);
if (p == null) {
throw new NoSuchElementException("the parameter [" + name + "] doesn't exist in the named query.");
}
Object v = value;
if (p.getDataType() != null) {
v = toProperType(p.getDataType(), value);
}
this.nameParams.put(name, v);
}
return this;
}
/**
* 设置参数,如果被设置的值为空字符串或null,则不设置
*
* @param name
* 参数名
* @param value
* 参数值
* @return 当前NativeQuery本身
*/
public NativeQuery<X> setNotEmptyParameter(String name, String value) {
if (!StringUtils.isEmpty(value)) {
setParameterByString(name, value);
}
return this;
}
/**
* 设置参数,如果被设置的对象为null,则不设置
*
* @param name
* 参数名
* @param value
* 参数值
* @return 当前NativeQuery本身
*/
public NativeQuery<X> setNotNullParameter(String name, Object value) {
if (value != null) {
setParameter(name, value);
}
return this;
}
/**
* 设置参数的值,传入的参数类型为String,
*
* @param name
* 参数名
* @param value
* 参数值
* @return 当前NativeQuery本身
* @throws NoSuchElementException
* 该参数未在查询语句中定义
*/
public NativeQuery<X> setParameterByString(int position, String value) {
ParameterMetadata p = config.getParams(db).get(position);
if (p == null) {
throw new NoSuchElementException("the parameter [" + position + "] doesn't exist in the named query.");
}
Object v = value;
if (p.getDataType() != null) {
v = toProperType(p.getDataType(), value, p);
}
nameParams.put(Integer.valueOf(position), v);
return this;
}
/**
* 设置参数的值,传入的参数类型为String[]
*
* @param name
* 参数名
* @param value
* 参数值
* @return this
* @throws NoSuchElementException
* 该参数未在查询语句中定义
*/
public NativeQuery<X> setParameterByString(int position, String[] value) {
ParameterMetadata p = config.getParams(db).get(position);
if (p == null) {
throw new NoSuchElementException("the parameter [" + position + "] doesn't exist in the named query.");
}
Object v = value;
if (p.getDataType() != null) {
v = toProperType(p.getDataType(), value);
}
nameParams.put(Integer.valueOf(position), v);
return this;
}
/**
* 设置参数的值
*/
public NativeQuery<X> setParameter(int position, Calendar value, TemporalType temporalType) {
return setParameter(position, fixTemporal(value.getTime(), temporalType));
}
/*
* 将参数按照命名查询中的类型提示转换为合适的类型
*/
private Object toProperType(JpqlDataType type, String[] value) {
// 如果是动态SQL片段类型,则将数组转换成1个String值。
if (JpqlDataType.SQL.equals(type)) {
return new SqlExpression(StringUtils.join(value));
}
Object[] result = new Object[value.length];
for (int i = 0; i < value.length; i++) {
result[i] = toProperType(type, value[i], null);
}
return result;
}
/**
* 以Map形式设置参数的值
*
* @param params
* 所有参数的Map
* @return this
*/
public NativeQuery<X> setParameterMap(Map<String, Object> params) {
if (params == null)
return this;
for (String key : params.keySet()) {
setParameter(key, params.get(key));
}
return this;
}
/**
* JPA规范方法 {@inheritDoc}
*/
public NativeQuery<X> setParameter(int position, Date value, TemporalType temporalType) {
return setParameter(position, value);
}
/**
* JPA规范方法 {@inheritDoc}
*/
public Set<Parameter<?>> getParameters() {
Set<Parameter<?>> result = new HashSet<Parameter<?>>();
for (ParameterMetadata jp : config.getParams(this.db).values()) {
result.add(jp.param);
}
return result;
}
/**
* JPA规范方法 {@inheritDoc}
*
* @throws NoSuchElementException
* 该参数未在查询语句中定义
*/
public Parameter<?> getParameter(String name) {
ParameterMetadata param = config.getParams(db).get(name);
if (param == null) {
throw new NoSuchElementException(name);
}
return param.param;
}
/**
* JPA规范方法 {@inheritDoc}
*
* @throws NoSuchElementException
* 该参数未在查询语句中定义
*/
public <X> Parameter<X> getParameter(String name, Class<X> type) {
ParameterMetadata param = config.getParams(db).get(name);
if (param == null || param.param.getParameterType() != type) {
throw new NoSuchElementException(name);
}
return param.param;
}
/**
* JPA规范方法,获得指定的参数 {@inheritDoc}
*
* @throws NoSuchElementException
* 该参数未在查询语句中定义
*/
public Parameter<?> getParameter(int position) {
ParameterMetadata param = config.getParams(db).get(position);
if (param == null) {
throw new NoSuchElementException(String.valueOf(position));
}
return param.param;
}
/**
* JPA规范方法,获得指定的参数 {@inheritDoc}
*
* @throws NoSuchElementException
* 该参数未在查询语句中定义
*/
public <X> Parameter<X> getParameter(int position, Class<X> type) {
ParameterMetadata param = config.getParams(db).get(position);
if (param == null || param.param.getParameterType() != type) {
throw new NoSuchElementException(String.valueOf(position));
}
return param.param;
}
/**
* JPA规范方法,目前相关特性未实现,总是返回false {@inheritDoc}
*/
public boolean isBound(Parameter<?> param) {
return false;
}
/**
* JPA规范方法,得到参数的值 {@inheritDoc}
*/
public Object getParameterValue(String name) {
return nameParams.get(name);
}
/**
* JPA规范方法,得到参数的值 {@inheritDoc}
*/
public Object getParameterValue(int position) {
return nameParams.get(position);
}
/**
* JPA规范方法,得到参数的值 {@inheritDoc}
*/
public <T> T getParameterValue(Parameter<T> param) {
if (param.getPosition() != null && param.getPosition() > -1) {
return (T) getParameterValue(param.getPosition());
} else {
return (T) getParameterValue(param.getName());
}
}
/**
* JPA规范方法。 {@inheritDoc} 设置FlushType 目前JEF未实现相关特性,该方法可以调用,但对数据库操作无实际影响
*
*/
public javax.persistence.TypedQuery<X> setFlushMode(FlushModeType flushMode) {
this.flushType = flushMode;
return this;
}
/**
* JPA规范方法。 {@inheritDoc} 返回FlushMode 目前JEF未实现相关特性,该方法可以调用,但对数据库操作无实际影响
*/
public FlushModeType getFlushMode() {
return flushType;
}
/**
* JPA规范方法。 {@inheritDoc} 设置lockMode 目前JEF未实现相关特性,该方法可以调用,但对数据库操作无实际影响
*/
public javax.persistence.TypedQuery<X> setLockMode(LockModeType lockMode) {
this.lock = lockMode;
return this;
}
/**
* JPA规范方法。 {@inheritDoc} 返回LockMode 目前JEF未实现相关特性,该方法可以调用,但对数据库操作无实际影响
*/
public LockModeType getLockMode() {
return lock;
}
/**
* 设置是否为Native查询,
*
* @param isNative
* SQL即为Native,JPQL则不是
*/
public void setIsNative(boolean isNative) {
this.config.setType(isNative ? NamedQueryConfig.TYPE_SQL : NamedQueryConfig.TYPE_JPQL);
}
/**
* JPA规范方法。对于以名称为key的参数,获取其参数值 {@inheritDoc}
*
* @param 参数名
* @return 参数值
*/
public Object getNamedParam(String name) {
if (this.nameParams == null)
return null;
return nameParams.get(name);
}
/**
* JPA规范方法。对于以序号排列的参数,获取其第index个参数的值 {@inheritDoc}
*
* @param index
* 参数序号
* @return 参数值
*/
public Object getIndexedParam(int index) {
if (this.nameParams == null)
return null;
return nameParams.get(index);
}
/**
* 对于命名查询,获取其tag
*
* @return Tag
*/
public String getTag() {
return config.getTag();
}
/**
* 得到查询所在的数据库操作对象
*
* @return 数据库操作对象
*/
public OperateTarget getDb() {
return db;
}
/**
* JPA规范方法。查询指定的参数是否已经设置过值
*
* @param key
* 检查某个位置是否已经设置过参数
*
*/
public boolean containsParam(Object key) {
return nameParams.containsKey(key);
}
/**
* 设置一个字段的列值转换器。 当查询返回对象是Var/VarObject/Map等不确定类型的容器时,字段的值将使用JDBC驱动默认返回的对象类型。<br>
* 这种情况下,可能不能准确预期返回的数据类型。(比如数值在某些数据库上是Integer,某些数据库上是BigDecimal,
* Boolean类型的返回结果就更为不确定了。)<br>
* 使用这个接口可以明确指定特定列返回的数据类型。 <h3>举例</h3> <code><pre>
* NativeQuery<Var> query=db.createNativeQuery("select 1 as bool_column from dual",Var.class);
*
* //指定列bool_column以Boolean格式读取。如果不指定,那么该列值将被转换为Integer.
* query.setColumnAccessor("bool_column", ColumnMappings.BOOLEAN);
* Boolean flag=query.getSingleResult().get("bool_column");
* </pre></code>
*
*
* @param name
* 列名
* @param accessor
* 结果转换器
* @deprecated 使用难度大,后续考虑用别的形式封装
*/
public void setColumnAccessor(String name, ResultSetAccessor accessor) {
resultTransformer.ignoreColumn(name);
resultTransformer.addMapper(new DefaultMapperAccessorAdapter(name, accessor));
}
/**
* 获得结果集的转换配置
*
* @return
*/
public Transformer getResultTransformer() {
return resultTransformer;
}
/**
* 清除之前设置过的所有参数。 <br>
* 此方法当一个NativeQuery被重复使用时十分有用。
*/
public void clearParameters() {
nameParams.clear();
hint.clear();
}
/**
* 清除指定的参数
*
* @param name
* 参数名
*/
public void clearParameter(String name) {
nameParams.remove(name);
}
/**
* 清除指定的参数
*
* @param index
* 序号
*/
public void clearParameter(int index) {
nameParams.remove(index);
}
/**
* 得到所有的参数名称
*
* @return 所有参数的名称
*/
public List<String> getParameterNames() {
List<String> result = new ArrayList<String>();
for (Object o : config.getParams(db).keySet()) {
result.add(String.valueOf(o));
}
return result;
}
@Override
public String toString() {
return config.toString();
}
/*
* 转换String为合适的参数类型
*
* @param type
*
* @param value
*
* @return
*/
private Object toProperType(JpqlDataType type, String value, ParameterMetadata p) {
switch (type) {
case DATE:
return DateUtils.toSqlDate(DateUtils.autoParse(value));
case BOOLEAN:
return StringUtils.toBoolean(value, null);
case DOUBLE:
return StringUtils.toDouble(value, 0.0);
case FLOAT:
return StringUtils.toFloat(value, 0.0f);
case INT:
return StringUtils.toInt(value, 0);
case LONG:
return StringUtils.toLong(value, 0L);
case SHORT:
return (short) StringUtils.toInt(value, 0);
case TIMESTAMP:
return DateUtils.toSqlTimeStamp(DateUtils.autoParse(value));
case SQL:
return new SqlExpression(value);
case $STRING:
value = ensureIsEscape(p, value);
return "%".concat(value);
case STRING$:
value = ensureIsEscape(p, value);
return value.concat("%");
case $STRING$:
value = ensureIsEscape(p, value);
StringBuilder sb = new StringBuilder(value.length() + 2);
return sb.append('%').append(value).append('%').toString();
default:
return value;
}
}
private static final String[] ESCAPE_SEARCH = new String[] { "%", "_" };
private static final String[] ESCAPE_REPLACE = new String[] { "/%", "/_" };
private String ensureIsEscape(ParameterMetadata p, String value) {
if (p == null)
return value;
if (p.escape) {
return StringUtils.replaceEach(value, ESCAPE_SEARCH, ESCAPE_REPLACE);
}
return value;
}
private Object processValue(ParameterMetadata p, Object value) {
JpqlDataType type = p.getDataType();
if (value instanceof String) {
if (type != null) {
value = toProperType(type, (String) value, p);
}
} else if ((value instanceof java.util.Date)) {
Class<?> clz = value.getClass();
if (clz == java.sql.Time.class || clz == java.sql.Timestamp.class || clz == java.sql.Time.class) {
// do nothing
} else if (type == JpqlDataType.TIMESTAMP) {
value = new java.sql.Timestamp(((java.util.Date) value).getTime());
} else {
value = new java.sql.Date(((java.util.Date) value).getTime());
}
}
// 如果是动态SQL片段类型且参数值是数组类型,则将数组转换成1个String值。
else if (JpqlDataType.SQL.equals(type) && value instanceof Object[]) {
value = new SqlExpression(StringUtils.join((Object[]) value));
} else if (value != null) {
if (type == JpqlDataType.STRING) {
value = String.valueOf(value);
}
}
return value;
}
private Object fixTemporal(Date time, TemporalType temporalType) {
if (time == null)
return time;
switch (temporalType) {
case DATE:
if (time instanceof java.sql.Date)
return time;
return new java.sql.Date(time.getTime());
case TIME:
if (time instanceof java.sql.Time)
return time;
return new java.sql.Time(time.getTime());
case TIMESTAMP:
if (time instanceof java.sql.Timestamp)
return time;
return new java.sql.Timestamp(time.getTime());
default:
return time;
}
}
private static final class DefaultMapperAccessorAdapter extends Mapper<Object> {
private int n;
private String name;
private ResultSetAccessor accessor;
public DefaultMapperAccessorAdapter(String name, ResultSetAccessor accessor) {
this.accessor = accessor;
this.name = name;
}
public void process(BeanWrapper wrapper, IResultSet rs) throws SQLException {
wrapper.setPropertyValue(name, accessor.jdbcGet(rs, n));
}
protected void transform(Object wrapped, IResultSet rs) {
}
public void prepare(Map<String, ColumnDescription> nameIndex) {
ColumnDescription columnDesc = nameIndex.get(name.toUpperCase());
Assert.notNull(columnDesc);
this.n = columnDesc.getN();
}
}
/**
* 使用当前的Query配置创建一个新的NativeQuery对象
*
* @param target
* @return
*/
public NativeQuery<X> rebind(Session session, String dbKey) {
OperateTarget target = this.db;
if (session != null) {
target = session.selectTarget(dbKey == null ? this.db.getDbkey() : dbKey);
} else if (dbKey != null) {
target = this.db.getTarget(dbKey);
}
NativeQuery<X> result = new NativeQuery<X>(target, this.config, this.resultTransformer);
result.fetchSize = this.fetchSize;
result.flushType = this.flushType;
result.lock = this.lock;
result.routing = this.routing;
return result;
}
/**
* 通过更换ResultTransformer中的结果返回类型,将当前NativeQuery<X>转为NativeQuery<T> <br>
* 注意这一操作不会创建新的NativeQuery对象,而是将当前对象的类型进行了转换。
*
* @param type
* @return this
*/
public <T> NativeQuery<T> asTypeOf(Class<T> type) {
this.resultTransformer.setResultType(type);
return (NativeQuery<T>) this;
}
}