Browse Source

add page of sql support

Looly 5 years ago
parent
commit
36588f7e07
23 changed files with 699 additions and 439 deletions
  1. 2 1
      CHANGELOG.md
  2. 9 9
      hutool-core/src/main/java/cn/hutool/core/net/URLEncoder.java
  3. 60 8
      hutool-db/src/main/java/cn/hutool/db/AbstractDb.java
  4. 1 1
      hutool-db/src/main/java/cn/hutool/db/DaoTemplate.java
  5. 298 0
      hutool-db/src/main/java/cn/hutool/db/DialectRunner.java
  6. 37 18
      hutool-db/src/main/java/cn/hutool/db/Page.java
  7. 3 3
      hutool-db/src/main/java/cn/hutool/db/Session.java
  8. 66 296
      hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java
  9. 21 14
      hutool-db/src/main/java/cn/hutool/db/StatementUtil.java
  10. 51 28
      hutool-db/src/main/java/cn/hutool/db/dialect/Dialect.java
  11. 22 30
      hutool-db/src/main/java/cn/hutool/db/dialect/impl/AnsiSqlDialect.java
  12. 2 2
      hutool-db/src/main/java/cn/hutool/db/dialect/impl/H2Dialect.java
  13. 2 2
      hutool-db/src/main/java/cn/hutool/db/dialect/impl/MysqlDialect.java
  14. 4 3
      hutool-db/src/main/java/cn/hutool/db/dialect/impl/OracleDialect.java
  15. 2 2
      hutool-db/src/main/java/cn/hutool/db/dialect/impl/PostgresqlDialect.java
  16. 2 2
      hutool-db/src/main/java/cn/hutool/db/dialect/impl/SqlServer2012Dialect.java
  17. 2 2
      hutool-db/src/main/java/cn/hutool/db/dialect/impl/Sqlite3Dialect.java
  18. 1 1
      hutool-db/src/main/java/cn/hutool/db/handler/NumberHandler.java
  19. 15 4
      hutool-db/src/main/java/cn/hutool/db/sql/Query.java
  20. 54 4
      hutool-db/src/main/java/cn/hutool/db/sql/SqlBuilder.java
  21. 16 0
      hutool-db/src/main/java/cn/hutool/db/sql/SqlExecutor.java
  22. 10 9
      hutool-db/src/main/java/cn/hutool/db/sql/SqlFormatter.java
  23. 19 0
      hutool-db/src/test/java/cn/hutool/db/DbTest.java

+ 2 - 1
CHANGELOG.md

@@ -3,12 +3,13 @@
 
 -------------------------------------------------------------------------------------------------------------
 
-# 5.5.3 (2020-12-05)
+# 5.5.3 (2020-12-06)
 
 ### 新特性
 * 【core   】     IdcardUtil增加行政区划83(issue#1277@Github)
 * 【core   】     multipart中int改为long,解决大文件上传越界问题(issue#I27WZ3@Gitee)
 * 【core   】     ListUtil.page增加检查(pr#224@Gitee)
+* 【db     】     Db增加使用sql的page方法(issue#247@Gitee)
 
 ### Bug修复
 * 【cache  】     修复Cache中get重复misCount计数问题(issue#1281@Github)

+ 9 - 9
hutool-core/src/main/java/cn/hutool/core/net/URLEncoder.java

@@ -26,7 +26,7 @@ public class URLEncoder implements Serializable {
 
 	// --------------------------------------------------------------------------------------------- Static method start
 	/**
-	 * 默认{@link URLEncoder}<br>
+	 * 默认URLEncoder<br>
 	 * 默认的编码器针对URI路径编码,定义如下:
 	 *
 	 * <pre>
@@ -38,7 +38,7 @@ public class URLEncoder implements Serializable {
 	public static final URLEncoder DEFAULT = createDefault();
 
 	/**
-	 * 用于查询语句的{@link URLEncoder}<br>
+	 * 用于查询语句的URLEncoder<br>
 	 * 编码器针对URI路径编码,定义如下:
 	 *
 	 * <pre>
@@ -53,7 +53,7 @@ public class URLEncoder implements Serializable {
 	public static final URLEncoder QUERY = createQuery();
 
 	/**
-	 * 全编码的{@link URLEncoder}<br>
+	 * 全编码的URLEncoder<br>
 	 * <pre>
 	 * 	 0x2A, 0x2D, 0x2E, 0x30 to 0x39, 0x41 to 0x5A, 0x5F, 0x61 to 0x7A as-is
 	 * 	 '*', '-', '.', '0' to '9', 'A' to 'Z', '_', 'a' to 'z' 不编码
@@ -63,7 +63,7 @@ public class URLEncoder implements Serializable {
 	public static final URLEncoder ALL = createAll();
 
 	/**
-	 * 创建默认{@link URLEncoder}<br>
+	 * 创建默认URLEncoder<br>
 	 * 默认的编码器针对URI路径编码,定义如下:
 	 *
 	 * <pre>
@@ -72,7 +72,7 @@ public class URLEncoder implements Serializable {
 	 * sub-delims = "!" / "$" / "&amp;" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
 	 * </pre>
 	 *
-	 * @return {@link URLEncoder}
+	 * @return URLEncoder
 	 */
 	public static URLEncoder createDefault() {
 		final URLEncoder encoder = new URLEncoder();
@@ -102,7 +102,7 @@ public class URLEncoder implements Serializable {
 	}
 
 	/**
-	 * 创建用于查询语句的{@link URLEncoder}<br>
+	 * 创建用于查询语句的URLEncoder<br>
 	 * 编码器针对URI路径编码,定义如下:
 	 *
 	 * <pre>
@@ -114,7 +114,7 @@ public class URLEncoder implements Serializable {
 	 * <p>
 	 * 详细见:https://www.w3.org/TR/html5/forms.html#application/x-www-form-urlencoded-encoding-algorithm
 	 *
-	 * @return {@link URLEncoder}
+	 * @return URLEncoder
 	 */
 	public static URLEncoder createQuery() {
 		final URLEncoder encoder = new URLEncoder();
@@ -133,7 +133,7 @@ public class URLEncoder implements Serializable {
 	}
 
 	/**
-	 * 创建{@link URLEncoder}<br>
+	 * 创建URLEncoder<br>
 	 * 编码器针对URI路径编码,定义如下:
 	 *
 	 * <pre>
@@ -144,7 +144,7 @@ public class URLEncoder implements Serializable {
 	 * <p>
 	 * 详细见:https://www.w3.org/TR/html5/forms.html#application/x-www-form-urlencoded-encoding-algorithm
 	 *
-	 * @return {@link URLEncoder}
+	 * @return URLEncoder
 	 */
 	public static URLEncoder createAll() {
 		final URLEncoder encoder = new URLEncoder();

+ 60 - 8
hutool-db/src/main/java/cn/hutool/db/AbstractDb.java

@@ -12,6 +12,7 @@ import cn.hutool.db.sql.Condition;
 import cn.hutool.db.sql.Condition.LikeType;
 import cn.hutool.db.sql.LogicalOperator;
 import cn.hutool.db.sql.Query;
+import cn.hutool.db.sql.SqlBuilder;
 import cn.hutool.db.sql.SqlExecutor;
 import cn.hutool.db.sql.SqlUtil;
 import cn.hutool.db.sql.Wrapper;
@@ -660,7 +661,7 @@ public abstract class AbstractDb implements Serializable {
 	 * @return 复合条件的结果数
 	 * @throws SQLException SQL执行异常
 	 */
-	public int count(Entity where) throws SQLException {
+	public long count(Entity where) throws SQLException {
 		Connection conn = null;
 		try {
 			conn = this.getConnection();
@@ -671,6 +672,23 @@ public abstract class AbstractDb implements Serializable {
 	}
 
 	/**
+	 * 结果的条目数
+	 *
+	 * @param selectSql 查询SQL语句
+	 * @return 复合条件的结果数
+	 * @throws SQLException SQL执行异常
+	 */
+	public long count(CharSequence selectSql) throws SQLException {
+		Connection conn = null;
+		try {
+			conn = this.getConnection();
+			return runner.count(conn, selectSql);
+		} finally {
+			this.closeConnection(conn);
+		}
+	}
+
+	/**
 	 * 分页查询<br>
 	 * 查询条件为多个key value对表示,默认key = value,如果使用其它条件可以使用:where.put("key", " &gt; 1"),value也可以传Condition对象,key被忽略
 	 *
@@ -779,20 +797,39 @@ public abstract class AbstractDb implements Serializable {
 
 	/**
 	 * 分页查询<br>
-	 * 查询条件为多个key value对表示,默认key = value,如果使用其它条件可以使用:where.put("key", " &gt; 1"),value也可以传Condition对象,key被忽略
 	 *
-	 * @param fields     返回的字段列表,null则返回所有字段
-	 * @param where      条件实体类(包含表名)
-	 * @param page       分页对象
-	 * @param numPerPage 每页条目数
+	 * @param <T>  结果对象类型
+	 * @param sql  SQL构建器,可以使用{@link SqlBuilder#of(CharSequence)} 包装普通SQL
+	 * @param page 分页对象
+	 * @param rsh  结果集处理对象
+	 * @return 结果对象
+	 * @throws SQLException SQL执行异常
+	 * @since 5.5.3
+	 */
+	public <T> T page(CharSequence sql, Page page, RsHandler<T> rsh) throws SQLException {
+		Connection conn = null;
+		try {
+			conn = this.getConnection();
+			return runner.page(conn, SqlBuilder.of(sql), page, rsh);
+		} finally {
+			this.closeConnection(conn);
+		}
+	}
+
+	/**
+	 * 分页查询
+	 *
+	 * @param sql  SQL语句字符串
+	 * @param page 分页对象
 	 * @return 结果对象
 	 * @throws SQLException SQL执行异常
+	 * @since 5.5.3
 	 */
-	public PageResult<Entity> page(Collection<String> fields, Entity where, int page, int numPerPage) throws SQLException {
+	public PageResult<Entity> page(CharSequence sql, Page page) throws SQLException {
 		Connection conn = null;
 		try {
 			conn = this.getConnection();
-			return runner.page(conn, fields, where, page, numPerPage);
+			return runner.page(conn, SqlBuilder.of(sql), page);
 		} finally {
 			this.closeConnection(conn);
 		}
@@ -802,6 +839,21 @@ public abstract class AbstractDb implements Serializable {
 	 * 分页查询<br>
 	 * 查询条件为多个key value对表示,默认key = value,如果使用其它条件可以使用:where.put("key", " &gt; 1"),value也可以传Condition对象,key被忽略
 	 *
+	 * @param fields     返回的字段列表,null则返回所有字段
+	 * @param where      条件实体类(包含表名)
+	 * @param pageNumber 页码
+	 * @param pageSize   每页结果数
+	 * @return 结果对象
+	 * @throws SQLException SQL执行异常
+	 */
+	public PageResult<Entity> page(Collection<String> fields, Entity where, int pageNumber, int pageSize) throws SQLException {
+		return page(fields, where, new Page(pageNumber, pageSize));
+	}
+
+	/**
+	 * 分页查询<br>
+	 * 查询条件为多个key value对表示,默认key = value,如果使用其它条件可以使用:where.put("key", " &gt; 1"),value也可以传Condition对象,key被忽略
+	 *
 	 * @param fields 返回的字段列表,null则返回所有字段
 	 * @param where  条件实体类(包含表名)
 	 * @param page   分页对象

+ 1 - 1
hutool-db/src/main/java/cn/hutool/db/DaoTemplate.java

@@ -326,7 +326,7 @@ public class DaoTemplate {
 	 * @return 数量
 	 * @throws SQLException SQL执行异常
 	 */
-	public int count(Entity where) throws SQLException{
+	public long count(Entity where) throws SQLException{
 		return db.count(fixEntity(where));
 	}
 	

+ 298 - 0
hutool-db/src/main/java/cn/hutool/db/DialectRunner.java

@@ -0,0 +1,298 @@
+package cn.hutool.db;
+
+import cn.hutool.core.collection.CollUtil;
+import cn.hutool.core.lang.Assert;
+import cn.hutool.core.util.ArrayUtil;
+import cn.hutool.core.util.StrUtil;
+import cn.hutool.db.dialect.Dialect;
+import cn.hutool.db.dialect.DialectFactory;
+import cn.hutool.db.handler.NumberHandler;
+import cn.hutool.db.handler.RsHandler;
+import cn.hutool.db.sql.Query;
+import cn.hutool.db.sql.SqlBuilder;
+import cn.hutool.db.sql.SqlExecutor;
+import cn.hutool.db.sql.SqlUtil;
+import cn.hutool.db.sql.Wrapper;
+
+import java.io.Serializable;
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.SQLException;
+
+public class DialectRunner implements Serializable {
+	private static final long serialVersionUID = 1L;
+
+	private Dialect dialect;
+	/**
+	 * 是否大小写不敏感(默认大小写不敏感)
+	 */
+	protected boolean caseInsensitive = GlobalDbConfig.caseInsensitive;
+
+	/**
+	 * 构造
+	 *
+	 * @param dialect 方言
+	 */
+	public DialectRunner(Dialect dialect) {
+		this.dialect = dialect;
+	}
+
+	/**
+	 * 构造
+	 *
+	 * @param driverClassName 驱动类名,用于识别方言
+	 */
+	public DialectRunner(String driverClassName) {
+		this(DialectFactory.newDialect(driverClassName));
+	}
+
+	//---------------------------------------------------------------------------- CRUD start
+	/**
+	 * 批量插入数据<br>
+	 * 批量插入必须严格保持Entity的结构一致,不一致会导致插入数据出现不可预知的结果<br>
+	 * 此方法不会关闭Connection
+	 *
+	 * @param conn    数据库连接
+	 * @param records 记录列表,记录KV必须严格一致
+	 * @return 插入行数
+	 * @throws SQLException SQL执行异常
+	 */
+	public int[] insert(Connection conn, Entity... records) throws SQLException {
+		checkConn(conn);
+		if (ArrayUtil.isEmpty(records)) {
+			return new int[]{0};
+		}
+
+		PreparedStatement ps = null;
+		try {
+			if(1 == records.length){
+				//单条单独处理
+				ps = dialect.psForInsert(conn, records[0]);
+				return new int[]{ps.executeUpdate()};
+			}
+
+			// 批量
+			ps = dialect.psForInsertBatch(conn, records);
+			return ps.executeBatch();
+		} finally {
+			DbUtil.close(ps);
+		}
+	}
+
+	/**
+	 * 插入数据<br>
+	 * 此方法不会关闭Connection
+	 *
+	 * @param conn   数据库连接
+	 * @param record 记录
+	 * @return 主键列表
+	 * @throws SQLException SQL执行异常
+	 */
+	public <T> T insert(Connection conn, Entity record, RsHandler<T> generatedKeysHandler) throws SQLException {
+		checkConn(conn);
+		if (CollUtil.isEmpty(record)) {
+			throw new SQLException("Empty entity provided!");
+		}
+
+		PreparedStatement ps = null;
+		try {
+			ps = dialect.psForInsert(conn, record);
+			ps.executeUpdate();
+			if(null == generatedKeysHandler){
+				return null;
+			}
+			return StatementUtil.getGeneratedKeys(ps, generatedKeysHandler);
+		} finally {
+			DbUtil.close(ps);
+		}
+	}
+
+	/**
+	 * 删除数据<br>
+	 * 此方法不会关闭Connection
+	 *
+	 * @param conn  数据库连接
+	 * @param where 条件
+	 * @return 影响行数
+	 * @throws SQLException SQL执行异常
+	 */
+	public int del(Connection conn, Entity where) throws SQLException {
+		checkConn(conn);
+		if (CollUtil.isEmpty(where)) {
+			//不允许做全表删除
+			throw new SQLException("Empty entity provided!");
+		}
+
+		PreparedStatement ps = null;
+		try {
+			ps = dialect.psForDelete(conn, Query.of(where));
+			return ps.executeUpdate();
+		} finally {
+			DbUtil.close(ps);
+		}
+	}
+
+	/**
+	 * 更新数据<br>
+	 * 此方法不会关闭Connection
+	 *
+	 * @param conn   数据库连接
+	 * @param record 记录
+	 * @param where  条件
+	 * @return 影响行数
+	 * @throws SQLException SQL执行异常
+	 */
+	public int update(Connection conn, Entity record, Entity where) throws SQLException {
+		checkConn(conn);
+		if (CollUtil.isEmpty(record)) {
+			throw new SQLException("Empty entity provided!");
+		}
+		if (CollUtil.isEmpty(where)) {
+			//不允许做全表更新
+			throw new SQLException("Empty where provided!");
+		}
+
+		//表名可以从被更新记录的Entity中获得,也可以从Where中获得
+		String tableName = record.getTableName();
+		if (StrUtil.isBlank(tableName)) {
+			tableName = where.getTableName();
+			record.setTableName(tableName);
+		}
+
+		final Query query = new Query(SqlUtil.buildConditions(where), tableName);
+		PreparedStatement ps = null;
+		try {
+			ps = dialect.psForUpdate(conn, record, query);
+			return ps.executeUpdate();
+		} finally {
+			DbUtil.close(ps);
+		}
+	}
+
+	/**
+	 * 查询<br>
+	 * 此方法不会关闭Connection
+	 *
+	 * @param <T>   结果对象类型
+	 * @param conn  数据库连接对象
+	 * @param query {@link Query}
+	 * @param rsh   结果集处理对象
+	 * @return 结果对象
+	 * @throws SQLException SQL执行异常
+	 */
+	public <T> T find(Connection conn, Query query, RsHandler<T> rsh) throws SQLException {
+		checkConn(conn);
+		Assert.notNull(query, "[query] is null !");
+		return SqlExecutor.queryAndClosePs(dialect.psForFind(conn, query), rsh);
+	}
+
+	/**
+	 * 获取结果总数,生成类似于select count(1) from XXX wher XXX=? and YYY=?
+	 *
+	 * @param conn  数据库连接对象
+	 * @param where 查询条件
+	 * @return 复合条件的结果数
+	 * @throws SQLException SQL执行异常
+	 */
+	public long count(Connection conn, Entity where) throws SQLException {
+		checkConn(conn);
+		return SqlExecutor.queryAndClosePs(dialect.psForCount(conn, Query.of(where)), new NumberHandler()).longValue();
+	}
+
+	/**
+	 * 分页查询<br>
+	 * 此方法不会关闭Connection
+	 *
+	 * @param <T>    结果对象类型
+	 * @param conn   数据库连接对象
+	 * @param query 查询条件(包含表名)
+	 * @param rsh    结果集处理对象
+	 * @return 结果对象
+	 * @throws SQLException SQL执行异常
+	 */
+	public <T> T page(Connection conn, Query query, RsHandler<T> rsh) throws SQLException {
+		checkConn(conn);
+		if (null == query.getPage()) {
+			return this.find(conn, query, rsh);
+		}
+
+		return SqlExecutor.queryAndClosePs(dialect.psForPage(conn, query), rsh);
+	}
+
+	/**
+	 * 分页查询<br>
+	 * 此方法不会关闭Connection
+	 *
+	 * @param <T>        结果对象类型
+	 * @param conn       数据库连接对象
+	 * @param sqlBuilder SQL构建器,可以使用{@link SqlBuilder#of(CharSequence)} 包装普通SQL
+	 * @param page       分页对象
+	 * @param rsh        结果集处理对象
+	 * @return 结果对象
+	 * @throws SQLException SQL执行异常
+	 * @since 5.5.3
+	 */
+	public <T> T page(Connection conn, SqlBuilder sqlBuilder, Page page, RsHandler<T> rsh) throws SQLException {
+		checkConn(conn);
+		if (null == page) {
+			return SqlExecutor.query(conn, sqlBuilder, rsh);
+		}
+
+		return SqlExecutor.queryAndClosePs(dialect.psForPage(conn, sqlBuilder, page), rsh);
+	}
+	//---------------------------------------------------------------------------- CRUD end
+
+	//---------------------------------------------------------------------------- Getters and Setters start
+
+	/**
+	 * 设置是否在结果中忽略大小写<br>
+	 * 如果忽略,则在Entity中调用getXXX时,字段值忽略大小写,默认忽略
+	 *
+	 * @param caseInsensitive 否在结果中忽略大小写
+	 * @since 5.2.4
+	 */
+	public void setCaseInsensitive(boolean caseInsensitive) {
+		this.caseInsensitive = caseInsensitive;
+	}
+
+	/**
+	 * @return SQL方言
+	 */
+	public Dialect getDialect() {
+		return dialect;
+	}
+
+	/**
+	 * 设置SQL方言
+	 *
+	 * @param dialect 方言
+	 */
+	public void setDialect(Dialect dialect) {
+		this.dialect = dialect;
+	}
+
+	/**
+	 * 设置包装器,包装器用于对表名、字段名进行符号包装(例如双引号),防止关键字与这些表名或字段冲突
+	 *
+	 * @param wrapperChar 包装字符,字符会在SQL生成时位于表名和字段名两边,null时表示取消包装
+	 */
+	public void setWrapper(Character wrapperChar) {
+		setWrapper(new Wrapper(wrapperChar));
+	}
+
+	/**
+	 * 设置包装器,包装器用于对表名、字段名进行符号包装(例如双引号),防止关键字与这些表名或字段冲突
+	 *
+	 * @param wrapper 包装器,null表示取消包装
+	 */
+	public void setWrapper(Wrapper wrapper) {
+		this.dialect.setWrapper(wrapper);
+	}
+	//---------------------------------------------------------------------------- Getters and Setters end
+
+	//---------------------------------------------------------------------------- Private method start
+	private void checkConn(Connection conn) {
+		Assert.notNull(conn, "Connection object must be not null!");
+	}
+	//---------------------------------------------------------------------------- Private method start
+}

+ 37 - 18
hutool-db/src/main/java/cn/hutool/db/Page.java

@@ -9,26 +9,44 @@ import java.util.Arrays;
 
 /**
  * 分页对象
- * 
- * @author Looly
  *
+ * @author Looly
  */
 public class Page implements Serializable {
 	private static final long serialVersionUID = 97792549823353462L;
 
 	public static final int DEFAULT_PAGE_SIZE = 20;
 
-	/** 页码,0表示第一页 */
+	/**
+	 * 页码,0表示第一页
+	 */
 	private int pageNumber;
-	/** 每页结果数 */
+	/**
+	 * 每页结果数
+	 */
 	private int pageSize;
-	/** 排序 */
+	/**
+	 * 排序
+	 */
 	private Order[] orders;
 
+	/**
+	 * 创建Page对象
+	 *
+	 * @param pageNumber 页码,0表示第一页
+	 * @param pageSize   每页结果数
+	 * @return Page
+	 * @since 5.5.3
+	 */
+	public static Page of(int pageNumber, int pageSize) {
+		return new Page(pageNumber, pageSize);
+	}
+
 	// ---------------------------------------------------------- Constructor start
+
 	/**
 	 * 构造,默认第0页,每页{@value #DEFAULT_PAGE_SIZE} 条
-	 * 
+	 *
 	 * @since 4.5.16
 	 */
 	public Page() {
@@ -37,9 +55,9 @@ public class Page implements Serializable {
 
 	/**
 	 * 构造
-	 * 
+	 *
 	 * @param pageNumber 页码,0表示第一页
-	 * @param pageSize 每页结果数
+	 * @param pageSize   每页结果数
 	 */
 	public Page(int pageNumber, int pageSize) {
 		this.pageNumber = Math.max(pageNumber, 0);
@@ -48,18 +66,19 @@ public class Page implements Serializable {
 
 	/**
 	 * 构造
-	 * 
+	 *
 	 * @param pageNumber 页码,0表示第一页
-	 * @param pageSize 每页结果数
-	 * @param order 排序对象
+	 * @param pageSize   每页结果数
+	 * @param order      排序对象
 	 */
 	public Page(int pageNumber, int pageSize, Order order) {
 		this(pageNumber, pageSize);
-		this.orders = new Order[] { order };
+		this.orders = new Order[]{order};
 	}
 	// ---------------------------------------------------------- Constructor start
 
 	// ---------------------------------------------------------- Getters and Setters start
+
 	/**
 	 * @return 页码,0表示第一页
 	 */
@@ -69,7 +88,7 @@ public class Page implements Serializable {
 
 	/**
 	 * 设置页码,0表示第一页
-	 * 
+	 *
 	 * @param pageNumber 页码
 	 */
 	public void setPageNumber(int pageNumber) {
@@ -87,7 +106,7 @@ public class Page implements Serializable {
 
 	/**
 	 * 设置每页结果数
-	 * 
+	 *
 	 * @param pageSize 每页结果数
 	 * @deprecated 使用 {@link #setPageSize(int)} 代替
 	 */
@@ -105,7 +124,7 @@ public class Page implements Serializable {
 
 	/**
 	 * 设置每页结果数
-	 * 
+	 *
 	 * @param pageSize 每页结果数
 	 */
 	public void setPageSize(int pageSize) {
@@ -121,7 +140,7 @@ public class Page implements Serializable {
 
 	/**
 	 * 设置排序
-	 * 
+	 *
 	 * @param orders 排序
 	 */
 	public void setOrder(Order... orders) {
@@ -130,7 +149,7 @@ public class Page implements Serializable {
 
 	/**
 	 * 设置排序
-	 * 
+	 *
 	 * @param orders 排序
 	 */
 	public void addOrder(Order... orders) {
@@ -162,7 +181,7 @@ public class Page implements Serializable {
 	 * 页码:2,每页10 =》 [21, 30]
 	 * 。。。
 	 * </pre>
-	 * 
+	 *
 	 * @return 第一个数为开始位置,第二个数为结束位置
 	 */
 	public int[] getStartEnd() {

+ 3 - 3
hutool-db/src/main/java/cn/hutool/db/Session.java

@@ -31,7 +31,7 @@ public class Session extends AbstractDb implements Closeable {
 	/**
 	 * 创建默认数据源会话
 	 * 
-	 * @return {@link Session}
+	 * @return Session
 	 * @since 3.2.3
 	 */
 	public static Session create() {
@@ -42,7 +42,7 @@ public class Session extends AbstractDb implements Closeable {
 	 * 创建会话
 	 * 
 	 * @param group 分组
-	 * @return {@link Session}
+	 * @return Session
 	 * @since 4.0.11
 	 */
 	public static Session create(String group) {
@@ -53,7 +53,7 @@ public class Session extends AbstractDb implements Closeable {
 	 * 创建会话
 	 * 
 	 * @param ds 数据源
-	 * @return {@link Session}
+	 * @return Session
 	 */
 	public static Session create(DataSource ds) {
 		return new Session(ds);

+ 66 - 296
hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java

@@ -1,26 +1,21 @@
 package cn.hutool.db;
 
 import cn.hutool.core.collection.CollUtil;
-import cn.hutool.core.lang.Assert;
 import cn.hutool.core.map.MapUtil;
-import cn.hutool.core.util.ArrayUtil;
-import cn.hutool.core.util.StrUtil;
 import cn.hutool.db.dialect.Dialect;
 import cn.hutool.db.dialect.DialectFactory;
 import cn.hutool.db.handler.EntityListHandler;
+import cn.hutool.db.handler.HandleHelper;
 import cn.hutool.db.handler.NumberHandler;
 import cn.hutool.db.handler.PageResultHandler;
 import cn.hutool.db.handler.RsHandler;
 import cn.hutool.db.sql.Condition.LikeType;
 import cn.hutool.db.sql.Query;
-import cn.hutool.db.sql.SqlExecutor;
+import cn.hutool.db.sql.SqlBuilder;
 import cn.hutool.db.sql.SqlUtil;
-import cn.hutool.db.sql.Wrapper;
 
 import javax.sql.DataSource;
-import java.io.Serializable;
 import java.sql.Connection;
-import java.sql.PreparedStatement;
 import java.sql.SQLException;
 import java.util.Collection;
 import java.util.List;
@@ -32,15 +27,9 @@ import java.util.List;
  *
  * @author Luxiaolei
  */
-public class SqlConnRunner implements Serializable {
+public class SqlConnRunner extends DialectRunner {
 	private static final long serialVersionUID = 1L;
 
-	private Dialect dialect;
-	/**
-	 * 是否大小写不敏感(默认大小写不敏感)
-	 */
-	protected boolean caseInsensitive = GlobalDbConfig.caseInsensitive;
-
 	/**
 	 * 实例化一个新的SQL运行对象
 	 *
@@ -79,7 +68,7 @@ public class SqlConnRunner implements Serializable {
 	 * @param dialect 方言
 	 */
 	public SqlConnRunner(Dialect dialect) {
-		this.dialect = dialect;
+		super(dialect);
 	}
 
 	/**
@@ -88,36 +77,13 @@ public class SqlConnRunner implements Serializable {
 	 * @param driverClassName 驱动类名,,用于识别方言
 	 */
 	public SqlConnRunner(String driverClassName) {
-		this(DialectFactory.newDialect(driverClassName));
+		super(driverClassName);
 	}
 	//------------------------------------------------------- Constructor end
 
 	//---------------------------------------------------------------------------- CRUD start
 
 	/**
-	 * 插入数据<br>
-	 * 此方法不会关闭Connection
-	 *
-	 * @param conn   数据库连接
-	 * @param record 记录
-	 * @return 插入行数
-	 * @throws SQLException SQL执行异常
-	 */
-	public int insert(Connection conn, Entity record) throws SQLException {
-		checkConn(conn);
-		if (CollUtil.isEmpty(record)) {
-			throw new SQLException("Empty entity provided!");
-		}
-		PreparedStatement ps = null;
-		try {
-			ps = dialect.psForInsert(conn, record);
-			return ps.executeUpdate();
-		} finally {
-			DbUtil.close(ps);
-		}
-	}
-
-	/**
 	 * 插入或更新数据<br>
 	 * 此方法不会关闭Connection
 	 *
@@ -152,33 +118,16 @@ public class SqlConnRunner implements Serializable {
 	}
 
 	/**
-	 * 批量插入数据<br>
-	 * 批量插入必须严格保持Entity的结构一致,不一致会导致插入数据出现不可预知的结果<br>
+	 * 插入数据<br>
 	 * 此方法不会关闭Connection
 	 *
-	 * @param conn    数据库连接
-	 * @param records 记录列表,记录KV必须严格一致
+	 * @param conn   数据库连接
+	 * @param record 记录
 	 * @return 插入行数
 	 * @throws SQLException SQL执行异常
 	 */
-	public int[] insert(Connection conn, Entity... records) throws SQLException {
-		checkConn(conn);
-		if (ArrayUtil.isEmpty(records)) {
-			return new int[]{0};
-		}
-
-		//单条单独处理
-		if (1 == records.length) {
-			return new int[]{insert(conn, records[0])};
-		}
-
-		PreparedStatement ps = null;
-		try {
-			ps = dialect.psForInsertBatch(conn, records);
-			return ps.executeBatch();
-		} finally {
-			DbUtil.close(ps);
-		}
+	public int insert(Connection conn, Entity record) throws SQLException {
+		return insert(conn, new Entity[]{record})[0];
 	}
 
 	/**
@@ -191,19 +140,7 @@ public class SqlConnRunner implements Serializable {
 	 * @throws SQLException SQL执行异常
 	 */
 	public List<Object> insertForGeneratedKeys(Connection conn, Entity record) throws SQLException {
-		checkConn(conn);
-		if (CollUtil.isEmpty(record)) {
-			throw new SQLException("Empty entity provided!");
-		}
-
-		PreparedStatement ps = null;
-		try {
-			ps = dialect.psForInsert(conn, record);
-			ps.executeUpdate();
-			return StatementUtil.getGeneratedKeys(ps);
-		} finally {
-			DbUtil.close(ps);
-		}
+		return insert(conn, record, HandleHelper::handleRowToList);
 	}
 
 	/**
@@ -216,106 +153,17 @@ public class SqlConnRunner implements Serializable {
 	 * @throws SQLException SQL执行异常
 	 */
 	public Long insertForGeneratedKey(Connection conn, Entity record) throws SQLException {
-		checkConn(conn);
-		if (CollUtil.isEmpty(record)) {
-			throw new SQLException("Empty entity provided!");
-		}
-
-		PreparedStatement ps = null;
-		try {
-			ps = dialect.psForInsert(conn, record);
-			ps.executeUpdate();
-			return StatementUtil.getGeneratedKeyOfLong(ps);
-		} finally {
-			DbUtil.close(ps);
-		}
-	}
-
-	/**
-	 * 删除数据<br>
-	 * 此方法不会关闭Connection
-	 *
-	 * @param conn  数据库连接
-	 * @param where 条件
-	 * @return 影响行数
-	 * @throws SQLException SQL执行异常
-	 */
-	public int del(Connection conn, Entity where) throws SQLException {
-		checkConn(conn);
-		if (CollUtil.isEmpty(where)) {
-			//不允许做全表删除
-			throw new SQLException("Empty entity provided!");
-		}
-
-		final Query query = new Query(SqlUtil.buildConditions(where), where.getTableName());
-		PreparedStatement ps = null;
-		try {
-			ps = dialect.psForDelete(conn, query);
-			return ps.executeUpdate();
-		} finally {
-			DbUtil.close(ps);
-		}
-	}
-
-	/**
-	 * 更新数据<br>
-	 * 此方法不会关闭Connection
-	 *
-	 * @param conn   数据库连接
-	 * @param record 记录
-	 * @param where  条件
-	 * @return 影响行数
-	 * @throws SQLException SQL执行异常
-	 */
-	public int update(Connection conn, Entity record, Entity where) throws SQLException {
-		checkConn(conn);
-		if (CollUtil.isEmpty(record)) {
-			throw new SQLException("Empty entity provided!");
-		}
-		if (CollUtil.isEmpty(where)) {
-			//不允许做全表更新
-			throw new SQLException("Empty where provided!");
-		}
-
-		//表名可以从被更新记录的Entity中获得,也可以从Where中获得
-		String tableName = record.getTableName();
-		if (StrUtil.isBlank(tableName)) {
-			tableName = where.getTableName();
-			record.setTableName(tableName);
-		}
-
-		final Query query = new Query(SqlUtil.buildConditions(where), tableName);
-		PreparedStatement ps = null;
-		try {
-			ps = dialect.psForUpdate(conn, record, query);
-			return ps.executeUpdate();
-		} finally {
-			DbUtil.close(ps);
-		}
-	}
-
-	/**
-	 * 查询<br>
-	 * 此方法不会关闭Connection
-	 *
-	 * @param <T>   结果对象类型
-	 * @param conn  数据库连接对象
-	 * @param query {@link Query}
-	 * @param rsh   结果集处理对象
-	 * @return 结果对象
-	 * @throws SQLException SQL执行异常
-	 */
-	public <T> T find(Connection conn, Query query, RsHandler<T> rsh) throws SQLException {
-		checkConn(conn);
-		Assert.notNull(query, "[query] is null !");
-
-		PreparedStatement ps = null;
-		try {
-			ps = dialect.psForFind(conn, query);
-			return SqlExecutor.query(ps, rsh);
-		} finally {
-			DbUtil.close(ps);
-		}
+		return insert(conn, record, (rs)->{
+			Long generatedKey = null;
+			if (rs != null && rs.next()) {
+				try {
+					generatedKey = rs.getLong(1);
+				} catch (SQLException e) {
+					// 自增主键不为数字或者为Oracle的rowid,跳过
+				}
+			}
+			return generatedKey;
+		});
 	}
 
 	/**
@@ -331,9 +179,7 @@ public class SqlConnRunner implements Serializable {
 	 * @throws SQLException SQL执行异常
 	 */
 	public <T> T find(Connection conn, Collection<String> fields, Entity where, RsHandler<T> rsh) throws SQLException {
-		final Query query = new Query(SqlUtil.buildConditions(where), where.getTableName());
-		query.setFields(fields);
-		return find(conn, query, rsh);
+		return find(conn, Query.of(where).setFields(fields), rsh);
 	}
 
 	/**
@@ -433,24 +279,16 @@ public class SqlConnRunner implements Serializable {
 	}
 
 	/**
-	 * 结果的条目数
+	 * 获取查询结果总数,生成类似于 SELECT count(1) from (sql)
 	 *
-	 * @param conn  数据库连接对象
-	 * @param where 查询条件
-	 * @return 复合条件的结果数
-	 * @throws SQLException SQL执行异常
+	 * @param conn 数据库连接对象
+	 * @param selectSql 查询语句
+	 * @return 结果数
+	 * @throws SQLException SQL异常
 	 */
-	public int count(Connection conn, Entity where) throws SQLException {
-		checkConn(conn);
-
-		final Query query = new Query(SqlUtil.buildConditions(where), where.getTableName());
-		PreparedStatement ps = null;
-		try {
-			ps = dialect.psForCount(conn, query);
-			return SqlExecutor.query(ps, new NumberHandler()).intValue();
-		} finally {
-			DbUtil.close(ps);
-		}
+	public long count(Connection conn, CharSequence selectSql) throws SQLException {
+		SqlBuilder sqlBuilder = SqlBuilder.of(selectSql).insertPreFragment("SELECT count(1) from(").append(")");
+		return page(conn, sqlBuilder, null, new NumberHandler()).intValue();
 	}
 
 	/**
@@ -468,32 +306,25 @@ public class SqlConnRunner implements Serializable {
 	 * @throws SQLException SQL执行异常
 	 */
 	public <T> T page(Connection conn, Collection<String> fields, Entity where, int pageNumber, int numPerPage, RsHandler<T> rsh) throws SQLException {
-		return page(conn, fields, where, new Page(pageNumber, numPerPage), rsh);
+		return page(conn, Query.of(where).setFields(fields).setPage(new Page(pageNumber, numPerPage)), rsh);
 	}
 
 	/**
 	 * 分页查询<br>
 	 * 此方法不会关闭Connection
 	 *
-	 * @param <T>    结果对象类型
-	 * @param conn   数据库连接对象
-	 * @param fields 返回的字段列表,null则返回所有字段
-	 * @param where  条件实体类(包含表名)
-	 * @param page   分页对象
-	 * @param rsh    结果集处理对象
+	 * @param conn       数据库连接对象
+	 * @param sqlBuilder SQL构建器,可以使用{@link SqlBuilder#of(CharSequence)} 包装普通SQL
+	 * @param page       分页对象
 	 * @return 结果对象
 	 * @throws SQLException SQL执行异常
+	 * @since 5.5.3
 	 */
-	public <T> T page(Connection conn, Collection<String> fields, Entity where, Page page, RsHandler<T> rsh) throws SQLException {
-		checkConn(conn);
-		if (null == page) {
-			return this.find(conn, fields, where, rsh);
-		}
-
-		final Query query = new Query(SqlUtil.buildConditions(where), where.getTableName());
-		query.setFields(fields);
-		query.setPage(page);
-		return SqlExecutor.queryAndClosePs(dialect.psForPage(conn, query), rsh);
+	public PageResult<Entity> page(Connection conn, SqlBuilder sqlBuilder, Page page) throws SQLException {
+		final PageResultHandler pageResultHandler = new PageResultHandler(
+				new PageResult<>(page.getPageNumber(), page.getPageSize(), (int)count(conn, sqlBuilder.build())),
+				this.caseInsensitive);
+		return page(conn, sqlBuilder, page, pageResultHandler);
 	}
 
 	/**
@@ -509,38 +340,7 @@ public class SqlConnRunner implements Serializable {
 	 * @throws SQLException SQL执行异常
 	 */
 	public PageResult<Entity> page(Connection conn, Collection<String> fields, Entity where, int page, int numPerPage) throws SQLException {
-		checkConn(conn);
-
-		final int count = count(conn, where);
-		final PageResultHandler pageResultHandler = new PageResultHandler(new PageResult<>(page, numPerPage, count), this.caseInsensitive);
-		return this.page(conn, fields, where, page, numPerPage, pageResultHandler);
-	}
-
-	/**
-	 * 分页查询<br>
-	 * 此方法不会关闭Connection
-	 *
-	 * @param conn   数据库连接对象
-	 * @param fields 返回的字段列表,null则返回所有字段
-	 * @param where  条件实体类(包含表名)
-	 * @param page   分页对象
-	 * @return 结果对象
-	 * @throws SQLException SQL执行异常
-	 */
-	public PageResult<Entity> page(Connection conn, Collection<String> fields, Entity where, Page page) throws SQLException {
-		checkConn(conn);
-
-		//查询全部
-		if (null == page) {
-			List<Entity> entityList = this.find(conn, fields, where, new EntityListHandler(GlobalDbConfig.caseInsensitive));
-			final PageResult<Entity> pageResult = new PageResult<>(0, entityList.size(), entityList.size());
-			pageResult.addAll(entityList);
-			return pageResult;
-		}
-
-		final int count = count(conn, where);
-		PageResultHandler pageResultHandler = new PageResultHandler(new PageResult<>(page.getPageNumber(), page.getPageSize(), count), this.caseInsensitive);
-		return this.page(conn, fields, where, page, pageResultHandler);
+		return page(conn, fields, where, new Page(page, numPerPage));
 	}
 
 	/**
@@ -556,68 +356,38 @@ public class SqlConnRunner implements Serializable {
 	public PageResult<Entity> page(Connection conn, Entity where, Page page) throws SQLException {
 		return this.page(conn, null, where, page);
 	}
-	//---------------------------------------------------------------------------- CRUD end
-
-	//---------------------------------------------------------------------------- Getters and Setters end
-
-	/**
-	 * 设置是否在结果中忽略大小写<br>
-	 * 如果忽略,则在Entity中调用getXXX时,字段值忽略大小写,默认忽略
-	 *
-	 * @param caseInsensitive 否在结果中忽略大小写
-	 * @since 5.2.4
-	 */
-	public void setCaseInsensitive(boolean caseInsensitive) {
-		this.caseInsensitive = caseInsensitive;
-	}
-
-	/**
-	 * @return SQL方言
-	 */
-	public Dialect getDialect() {
-		return dialect;
-	}
-
-	/**
-	 * 设置SQL方言
-	 *
-	 * @param dialect 方言
-	 * @return this
-	 */
-	public SqlConnRunner setDialect(Dialect dialect) {
-		this.dialect = dialect;
-		return this;
-	}
 
 	/**
-	 * 设置包装器,包装器用于对表名、字段名进行符号包装(例如双引号),防止关键字与这些表名或字段冲突
+	 * 分页查询<br>
+	 * 此方法不会关闭Connection
 	 *
-	 * @param wrapperChar 包装字符,字符会在SQL生成时位于表名和字段名两边,null时表示取消包装
-	 * @return this
-	 * @since 4.0.0
+	 * @param conn   数据库连接对象
+	 * @param fields 返回的字段列表,null则返回所有字段
+	 * @param where  条件实体类(包含表名)
+	 * @param page   分页对象
+	 * @return 结果对象
+	 * @throws SQLException SQL执行异常
 	 */
-	public SqlConnRunner setWrapper(Character wrapperChar) {
-		return setWrapper(new Wrapper(wrapperChar));
+	public PageResult<Entity> page(Connection conn, Collection<String> fields, Entity where, Page page) throws SQLException {
+		final PageResultHandler pageResultHandler = new PageResultHandler(
+				new PageResult<>(page.getPageNumber(), page.getPageSize(), (int)count(conn, where)),
+				this.caseInsensitive);
+		return page(conn, fields, where, page, pageResultHandler);
 	}
 
 	/**
-	 * 设置包装器,包装器用于对表名、字段名进行符号包装(例如双引号),防止关键字与这些表名或字段冲突
+	 * 分页查询<br>
+	 * 此方法不会关闭Connection
 	 *
-	 * @param wrapper 包装器,null表示取消包装
-	 * @return this
-	 * @since 4.0.0
+	 * @param conn   数据库连接对象
+	 * @param fields 返回的字段列表,null则返回所有字段
+	 * @param where  条件实体类(包含表名)
+	 * @param page   分页对象
+	 * @return 结果对象
+	 * @throws SQLException SQL执行异常
 	 */
-	public SqlConnRunner setWrapper(Wrapper wrapper) {
-		this.dialect.setWrapper(wrapper);
-		return this;
+	public <T> T page(Connection conn, Collection<String> fields, Entity where, Page page, RsHandler<T> handler) throws SQLException {
+		return this.page(conn, Query.of(where).setFields(fields).setPage(page), handler);
 	}
-	//---------------------------------------------------------------------------- Getters and Setters end
-
-	//---------------------------------------------------------------------------- Private method start
-	private void checkConn(Connection conn) {
-		if (null == conn) {
-			throw new NullPointerException("Connection object is null!");
-		}
-	}
-	//---------------------------------------------------------------------------- Private method start
+	//---------------------------------------------------------------------------- CRUD end
 }

+ 21 - 14
hutool-db/src/main/java/cn/hutool/db/StatementUtil.java

@@ -6,6 +6,8 @@ import cn.hutool.core.convert.Convert;
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.StrUtil;
+import cn.hutool.db.handler.HandleHelper;
+import cn.hutool.db.handler.RsHandler;
 import cn.hutool.db.sql.NamedSql;
 import cn.hutool.db.sql.SqlBuilder;
 import cn.hutool.db.sql.SqlLog;
@@ -21,7 +23,6 @@ import java.sql.ResultSet;
 import java.sql.SQLException;
 import java.sql.Statement;
 import java.sql.Types;
-import java.util.ArrayList;
 import java.util.Collection;
 import java.util.HashMap;
 import java.util.List;
@@ -230,14 +231,14 @@ public class StatementUtil {
 
 	/**
 	 * 获得自增键的值<br>
-	 * 此方法对于Oracle无效
+	 * 此方法对于Oracle无效(返回null)
 	 *
 	 * @param ps PreparedStatement
-	 * @return 自增键的值
+	 * @return 自增键的值,不存在返回null
 	 * @throws SQLException SQL执行异常
 	 */
 	public static Long getGeneratedKeyOfLong(Statement ps) throws SQLException {
-		try (final ResultSet rs = ps.getGeneratedKeys()) {
+		return getGeneratedKeys(ps, (rs)->{
 			Long generatedKey = null;
 			if (rs != null && rs.next()) {
 				try {
@@ -247,7 +248,7 @@ public class StatementUtil {
 				}
 			}
 			return generatedKey;
-		}
+		});
 	}
 
 	/**
@@ -258,15 +259,21 @@ public class StatementUtil {
 	 * @throws SQLException SQL执行异常
 	 */
 	public static List<Object> getGeneratedKeys(Statement ps) throws SQLException {
-		final List<Object> keys = new ArrayList<>();
-		try (final ResultSet rs = ps.getGeneratedKeys()) {
-			if (null != rs) {
-				int i = 1;
-				while (rs.next()) {
-					keys.add(rs.getObject(i++));
-				}
-			}
-			return keys;
+		return getGeneratedKeys(ps, HandleHelper::handleRowToList);
+	}
+
+	/**
+	 * 获取主键,并使用{@link RsHandler} 处理后返回
+	 * @param statement {@link Statement}
+	 * @param rsHandler 主键结果集处理器
+	 * @param <T> 自定义主键类型
+	 * @return 主键
+	 * @throws SQLException SQL执行异常
+	 * @since 5.5.3
+	 */
+	public static <T> T getGeneratedKeys(Statement statement, RsHandler<T> rsHandler) throws SQLException {
+		try (final ResultSet rs = statement.getGeneratedKeys()) {
+			return rsHandler.handle(rs);
 		}
 	}
 

+ 51 - 28
hutool-db/src/main/java/cn/hutool/db/dialect/Dialect.java

@@ -1,23 +1,26 @@
 package cn.hutool.db.dialect;
 
+import cn.hutool.core.collection.ListUtil;
+import cn.hutool.db.Entity;
+import cn.hutool.db.Page;
+import cn.hutool.db.sql.Order;
+import cn.hutool.db.sql.Query;
+import cn.hutool.db.sql.SqlBuilder;
+import cn.hutool.db.sql.Wrapper;
+
 import java.io.Serializable;
 import java.sql.Connection;
 import java.sql.PreparedStatement;
 import java.sql.SQLException;
 
-import cn.hutool.db.Entity;
-import cn.hutool.db.sql.Query;
-import cn.hutool.db.sql.Wrapper;
-
 /**
  * SQL方言,不同的数据库由于在某些SQL上有所区别,故为每种数据库配置不同的方言。<br>
  * 由于不同数据库间SQL语句的差异,导致无法统一拼接SQL,<br>
  * Dialect接口旨在根据不同的数据库,使用不同的方言实现类,来拼接对应的SQL,并将SQL和参数放入PreparedStatement中
- * 
- * @author loolly
  *
+ * @author loolly
  */
-public interface Dialect extends Serializable{
+public interface Dialect extends Serializable {
 
 	/**
 	 * @return 包装器
@@ -26,36 +29,37 @@ public interface Dialect extends Serializable{
 
 	/**
 	 * 设置包装器
-	 * 
+	 *
 	 * @param wrapper 包装器
 	 */
 	void setWrapper(Wrapper wrapper);
 
 	// -------------------------------------------- Execute
+
 	/**
 	 * 构建用于插入的PreparedStatement
-	 * 
-	 * @param conn 数据库连接对象
+	 *
+	 * @param conn   数据库连接对象
 	 * @param entity 数据实体类(包含表名)
 	 * @return PreparedStatement
 	 * @throws SQLException SQL执行异常
 	 */
 	PreparedStatement psForInsert(Connection conn, Entity entity) throws SQLException;
-	
+
 	/**
 	 * 构建用于批量插入的PreparedStatement
-	 * 
-	 * @param conn 数据库连接对象
+	 *
+	 * @param conn     数据库连接对象
 	 * @param entities 数据实体,实体的结构必须全部一致,否则插入结果将不可预知
 	 * @return PreparedStatement
 	 * @throws SQLException SQL执行异常
 	 */
 	PreparedStatement psForInsertBatch(Connection conn, Entity... entities) throws SQLException;
-	
+
 	/**
 	 * 构建用于删除的PreparedStatement
-	 * 
-	 * @param conn 数据库连接对象
+	 *
+	 * @param conn  数据库连接对象
 	 * @param query 查找条件(包含表名)
 	 * @return PreparedStatement
 	 * @throws SQLException SQL执行异常
@@ -64,20 +68,21 @@ public interface Dialect extends Serializable{
 
 	/**
 	 * 构建用于更新的PreparedStatement
-	 * 
-	 * @param conn 数据库连接对象
+	 *
+	 * @param conn   数据库连接对象
 	 * @param entity 数据实体类(包含表名)
-	 * @param query 查找条件(包含表名)
+	 * @param query  查找条件(包含表名)
 	 * @return PreparedStatement
 	 * @throws SQLException SQL执行异常
 	 */
 	PreparedStatement psForUpdate(Connection conn, Entity entity, Query query) throws SQLException;
 
 	// -------------------------------------------- Query
+
 	/**
 	 * 构建用于获取多条记录的PreparedStatement
-	 * 
-	 * @param conn 数据库连接对象
+	 *
+	 * @param conn  数据库连接对象
 	 * @param query 查询条件(包含表名)
 	 * @return PreparedStatement
 	 * @throws SQLException SQL执行异常
@@ -86,8 +91,8 @@ public interface Dialect extends Serializable{
 
 	/**
 	 * 构建用于分页查询的PreparedStatement
-	 * 
-	 * @param conn 数据库连接对象
+	 *
+	 * @param conn  数据库连接对象
 	 * @param query 查询条件(包含表名)
 	 * @return PreparedStatement
 	 * @throws SQLException SQL执行异常
@@ -95,19 +100,37 @@ public interface Dialect extends Serializable{
 	PreparedStatement psForPage(Connection conn, Query query) throws SQLException;
 
 	/**
+	 * 构建用于分页查询的PreparedStatement<br>
+	 * 可以在此方法中使用{@link SqlBuilder#orderBy(Order...)}方法加入排序信息,
+	 * 排序信息通过{@link Page#getOrders()}获取
+	 *
+	 * @param conn       数据库连接对象
+	 * @param sqlBuilder SQL构建器,可以使用{@link SqlBuilder#of(CharSequence)} 包装普通SQL
+	 * @param page       分页对象
+	 * @return PreparedStatement
+	 * @throws SQLException SQL执行异常
+	 * @since 5.5.3
+	 */
+	PreparedStatement psForPage(Connection conn, SqlBuilder sqlBuilder, Page page) throws SQLException;
+
+	/**
 	 * 构建用于查询行数的PreparedStatement
-	 * 
-	 * @param conn 数据库连接对象
+	 *
+	 * @param conn  数据库连接对象
 	 * @param query 查询条件(包含表名)
 	 * @return PreparedStatement
 	 * @throws SQLException SQL执行异常
 	 */
-	PreparedStatement psForCount(Connection conn, Query query) throws SQLException;
+	default PreparedStatement psForCount(Connection conn, Query query) throws SQLException{
+		query.setFields(ListUtil.toList("count(1)"));
+		return psForFind(conn, query);
+	}
 
 	/**
 	 * 方言名
-	 * 
+	 *
 	 * @return 方言名
+	 * @since 5.5.3
 	 */
-	DialectName dialectName();
+	String dialectName();
 }

+ 22 - 30
hutool-db/src/main/java/cn/hutool/db/dialect/impl/AnsiSqlDialect.java

@@ -1,6 +1,5 @@
 package cn.hutool.db.dialect.impl;
 
-import cn.hutool.core.collection.ListUtil;
 import cn.hutool.core.lang.Assert;
 import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.core.util.StrUtil;
@@ -59,7 +58,7 @@ public class AnsiSqlDialect implements Dialect {
 
 	@Override
 	public PreparedStatement psForDelete(Connection conn, Query query) throws SQLException {
-		Assert.notNull(query, "query must not be null !");
+		Assert.notNull(query, "query must be not null !");
 
 		final Condition[] where = query.getWhere();
 		if (ArrayUtil.isEmpty(where)) {
@@ -73,7 +72,7 @@ public class AnsiSqlDialect implements Dialect {
 
 	@Override
 	public PreparedStatement psForUpdate(Connection conn, Entity entity, Query query) throws SQLException {
-		Assert.notNull(query, "query must not be null !");
+		Assert.notNull(query, "query must be not null !");
 
 		final Condition[] where = query.getWhere();
 		if (ArrayUtil.isEmpty(where)) {
@@ -88,32 +87,27 @@ public class AnsiSqlDialect implements Dialect {
 
 	@Override
 	public PreparedStatement psForFind(Connection conn, Query query) throws SQLException {
-		Assert.notNull(query, "query must not be null !");
-
-		final SqlBuilder find = SqlBuilder.create(wrapper).query(query);
-
-		return StatementUtil.prepareStatement(conn, find);
+		return psForPage(conn, query);
 	}
 
 	@Override
 	public PreparedStatement psForPage(Connection conn, Query query) throws SQLException {
-		// 验证
-		if (query == null || StrUtil.hasBlank(query.getTableNames())) {
-			throw new DbRuntimeException("Table name must not be null !");
+		Assert.notNull(query, "query must be not null !");
+		if (StrUtil.hasBlank(query.getTableNames())) {
+			throw new DbRuntimeException("Table name must be not empty !");
 		}
 
-		final Page page = query.getPage();
-		if (null == page) {
-			// 无分页信息默认使用find
-			return this.psForFind(conn, query);
-		}
-
-		SqlBuilder find = SqlBuilder.create(wrapper).query(query).orderBy(page.getOrders());
+		final SqlBuilder find = SqlBuilder.create(wrapper).query(query);
+		return psForPage(conn, find, query.getPage());
+	}
 
+	@Override
+	public PreparedStatement psForPage(Connection conn, SqlBuilder sqlBuilder, Page page) throws SQLException {
 		// 根据不同数据库在查询SQL语句基础上包装其分页的语句
-		find = wrapPageSql(find, page);
-
-		return StatementUtil.prepareStatement(conn, find);
+		if(null != page){
+			sqlBuilder = wrapPageSql(sqlBuilder.orderBy(page.getOrders()), page);
+		}
+		return StatementUtil.prepareStatement(conn, sqlBuilder);
 	}
 
 	/**
@@ -127,18 +121,16 @@ public class AnsiSqlDialect implements Dialect {
 	 */
 	protected SqlBuilder wrapPageSql(SqlBuilder find, Page page) {
 		// limit A offset B 表示:A就是你需要多少行,B就是查询的起点位置。
-		return find.append(" limit ").append(page.getPageSize()).append(" offset ").append(page.getStartPosition());
-	}
-
-	@Override
-	public PreparedStatement psForCount(Connection conn, Query query) throws SQLException {
-		query.setFields(ListUtil.toList("count(1)"));
-		return psForFind(conn, query);
+		return find
+				.append(" limit ")
+				.append(page.getPageSize())
+				.append(" offset ")
+				.append(page.getStartPosition());
 	}
 
 	@Override
-	public DialectName dialectName() {
-		return DialectName.ANSI;
+	public String dialectName() {
+		return DialectName.ANSI.name();
 	}
 
 	// ---------------------------------------------------------------------------- Protected method start

+ 2 - 2
hutool-db/src/main/java/cn/hutool/db/dialect/impl/H2Dialect.java

@@ -17,8 +17,8 @@ public class H2Dialect extends AnsiSqlDialect {
 	}
 
 	@Override
-	public DialectName dialectName() {
-		return DialectName.H2;
+	public String dialectName() {
+		return DialectName.H2.name();
 	}
 
 	@Override

+ 2 - 2
hutool-db/src/main/java/cn/hutool/db/dialect/impl/MysqlDialect.java

@@ -23,7 +23,7 @@ public class MysqlDialect extends AnsiSqlDialect{
 	}
 	
 	@Override
-	public DialectName dialectName() {
-		return DialectName.MYSQL;
+	public String dialectName() {
+		return DialectName.MYSQL.toString();
 	}
 }

+ 4 - 3
hutool-db/src/main/java/cn/hutool/db/dialect/impl/OracleDialect.java

@@ -13,7 +13,8 @@ public class OracleDialect extends AnsiSqlDialect{
 	private static final long serialVersionUID = 6122761762247483015L;
 
 	public OracleDialect() {
-//		wrapper = new Wrapper('"');	//Oracle所有字段名用双引号包围,防止字段名或表名与系统关键字冲突
+		//Oracle所有字段名用双引号包围,防止字段名或表名与系统关键字冲突
+		//wrapper = new Wrapper('"');
 	}
 	
 	@Override
@@ -27,7 +28,7 @@ public class OracleDialect extends AnsiSqlDialect{
 	}
 	
 	@Override
-	public DialectName dialectName() {
-		return DialectName.ORACLE;
+	public String dialectName() {
+		return DialectName.ORACLE.name();
 	}
 }

+ 2 - 2
hutool-db/src/main/java/cn/hutool/db/dialect/impl/PostgresqlDialect.java

@@ -17,7 +17,7 @@ public class PostgresqlDialect extends AnsiSqlDialect{
 	}
 
 	@Override
-	public DialectName dialectName() {
-		return DialectName.POSTGREESQL;
+	public String dialectName() {
+		return DialectName.POSTGREESQL.name();
 	}
 }

+ 2 - 2
hutool-db/src/main/java/cn/hutool/db/dialect/impl/SqlServer2012Dialect.java

@@ -34,7 +34,7 @@ public class SqlServer2012Dialect extends AnsiSqlDialect {
 	}
 
 	@Override
-	public DialectName dialectName() {
-		return DialectName.SQLSERVER2012;
+	public String dialectName() {
+		return DialectName.SQLSERVER2012.name();
 	}
 }

+ 2 - 2
hutool-db/src/main/java/cn/hutool/db/dialect/impl/Sqlite3Dialect.java

@@ -16,7 +16,7 @@ public class Sqlite3Dialect extends AnsiSqlDialect{
 	}
 	
 	@Override
-	public DialectName dialectName() {
-		return DialectName.SQLITE3;
+	public String dialectName() {
+		return DialectName.SQLITE3.name();
 	}
 }

+ 1 - 1
hutool-db/src/main/java/cn/hutool/db/handler/NumberHandler.java

@@ -21,6 +21,6 @@ public class NumberHandler implements RsHandler<Number>{
 
 	@Override
 	public Number handle(ResultSet rs) throws SQLException {
-		return rs.next() ? rs.getBigDecimal(1) : null;
+		return (null != rs && rs.next()) ? rs.getBigDecimal(1) : null;
 	}
 }

+ 15 - 4
hutool-db/src/main/java/cn/hutool/db/sql/Query.java

@@ -1,12 +1,13 @@
 package cn.hutool.db.sql;
 
-import java.util.Collection;
-
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.util.ArrayUtil;
 import cn.hutool.db.DbRuntimeException;
+import cn.hutool.db.Entity;
 import cn.hutool.db.Page;
 
+import java.util.Collection;
+
 /**
  * 查询对象,用于传递查询所需的字段值<br>
  * 查询对象根据表名(可以多个),多个条件 {@link Condition} 构建查询对象完成查询。<br>
@@ -26,6 +27,16 @@ public class Query {
 	/** 分页对象 */
 	Page page;
 
+	/**
+	 * 从{@link Entity}构建Query
+	 * @param where 条件查询{@link Entity},包含条件Map和表名
+	 * @return Query
+	 * @since 5.5.3
+	 */
+	public static Query of(Entity where){
+		return new Query(SqlUtil.buildConditions(where), where.getTableName());
+	}
+
 	// --------------------------------------------------------------- Constructor start
 	/**
 	 * 构造
@@ -147,9 +158,9 @@ public class Query {
 	}
 
 	/**
-	 * 获得分页对象
+	 * 获得分页对象,无分页返回{@code null}
 	 * 
-	 * @return 分页对象
+	 * @return 分页对象 or {@code null}
 	 */
 	public Page getPage() {
 		return page;

+ 54 - 4
hutool-db/src/main/java/cn/hutool/db/sql/SqlBuilder.java

@@ -3,7 +3,6 @@ package cn.hutool.db.sql;
 import cn.hutool.core.builder.Builder;
 import cn.hutool.core.collection.CollectionUtil;
 import cn.hutool.core.util.ArrayUtil;
-import cn.hutool.core.util.ObjectUtil;
 import cn.hutool.core.util.StrUtil;
 import cn.hutool.db.DbRuntimeException;
 import cn.hutool.db.Entity;
@@ -12,6 +11,7 @@ import cn.hutool.db.dialect.DialectName;
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.List;
 import java.util.Map.Entry;
 
@@ -46,6 +46,16 @@ public class SqlBuilder implements Builder<String>{
 		return new SqlBuilder(wrapper);
 	}
 
+	/**
+	 * 从已有的SQL中构建一个SqlBuilder
+	 * @param sql SQL语句
+	 * @return SqlBuilder
+	 * @since 5.5.3
+	 */
+	public static SqlBuilder of(CharSequence sql){
+		return create().append(sql);
+	}
+
 	// --------------------------------------------------------------- Static methods end
 
 	// --------------------------------------------------------------- Enums start
@@ -99,12 +109,25 @@ public class SqlBuilder implements Builder<String>{
 	/**
 	 * 插入<br>
 	 * 插入会忽略空的字段名及其对应值,但是对于有字段名对应值为{@code null}的情况不忽略
-	 * 
+	 *
 	 * @param entity 实体
 	 * @param dialectName 方言名,用于对特殊数据库特殊处理
 	 * @return 自己
 	 */
 	public SqlBuilder insert(Entity entity, DialectName dialectName) {
+		return insert(entity, dialectName.name());
+	}
+
+	/**
+	 * 插入<br>
+	 * 插入会忽略空的字段名及其对应值,但是对于有字段名对应值为{@code null}的情况不忽略
+	 * 
+	 * @param entity 实体
+	 * @param dialectName 方言名,用于对特殊数据库特殊处理
+	 * @return 自己
+	 * @since 5.5.3
+	 */
+	public SqlBuilder insert(Entity entity, String dialectName) {
 		// 验证
 		validateEntity(entity);
 
@@ -114,7 +137,7 @@ public class SqlBuilder implements Builder<String>{
 			entity.setTableName(wrapper.wrap(entity.getTableName()));
 		}
 
-		final boolean isOracle = ObjectUtil.equal(dialectName, DialectName.ORACLE);// 对Oracle的特殊处理
+		final boolean isOracle = StrUtil.equalsAnyIgnoreCase(dialectName, DialectName.ORACLE.name());// 对Oracle的特殊处理
 		final StringBuilder fieldsPart = new StringBuilder();
 		final StringBuilder placeHolder = new StringBuilder();
 
@@ -519,7 +542,14 @@ public class SqlBuilder implements Builder<String>{
 	}
 
 	/**
-	 * 追加SQL其它部分片段
+	 * 追加SQL其它部分片段,此方法只是简单的追加SQL字符串,空格需手动加入,例如:
+	 *
+	 * <pre>
+	 *     SqlBuilder builder = SqlBuilder.of("select *");
+	 *     builder.append(" from ").append("user");
+	 * </pre>
+	 *
+	 * 如果需要追加带占位符的片段,需调用{@link #addParams(Object...)} 方法加入对应参数值。
 	 * 
 	 * @param sqlFragment SQL其它部分片段
 	 * @return this
@@ -532,6 +562,26 @@ public class SqlBuilder implements Builder<String>{
 	}
 
 	/**
+	 * 手动增加参数,调用此方法前需确认SQL中有对应占位符,主要用于自定义SQL片段中有占位符的情况,例如:
+	 *
+	 * <pre>
+	 *     SqlBuilder builder = SqlBuilder.of("select * from user where id=?");
+	 *     builder.append(" and name=?")
+	 *     builder.addParams(1, "looly");
+	 * </pre>
+	 *
+	 * @param params 参数列表
+	 * @return this
+	 * @since 5.5.3
+	 */
+	public SqlBuilder addParams(Object... params){
+		if(ArrayUtil.isNotEmpty(params)){
+			Collections.addAll(this.paramValues, params);
+		}
+		return this;
+	}
+
+	/**
 	 * 构建查询SQL
 	 * 
 	 * @param query {@link Query}

+ 16 - 0
hutool-db/src/main/java/cn/hutool/db/sql/SqlExecutor.java

@@ -259,6 +259,22 @@ public class SqlExecutor {
 		}
 	}
 
+	/**
+	 * 执行查询语句<br>
+	 * 此方法不会关闭Connection
+	 *
+	 * @param <T> 处理结果类型
+	 * @param conn 数据库连接对象
+	 * @param sqlBuilder SQL构建器,包含参数
+	 * @param rsh 结果集处理对象
+	 * @return 结果对象
+	 * @throws SQLException SQL执行异常
+	 * @since 5.5.3
+	 */
+	public static <T> T query(Connection conn, SqlBuilder sqlBuilder, RsHandler<T> rsh) throws SQLException {
+		return query(conn, sqlBuilder.build(), rsh, sqlBuilder.getParamValueArray());
+	}
+
 	// -------------------------------------------------------------------------------------- Execute With PreparedStatement
 	/**
 	 * 用于执行 INSERT、UPDATE 或 DELETE 语句以及 SQL DDL(数据定义语言)语句,例如 CREATE TABLE 和 DROP TABLE。<br>

+ 10 - 9
hutool-db/src/main/java/cn/hutool/db/sql/SqlFormatter.java

@@ -4,6 +4,7 @@ import java.util.*;
 
 /**
  * SQL格式化器 from Hibernate
+ *
  * @author looly
  */
 public class SqlFormatter {
@@ -13,7 +14,7 @@ public class SqlFormatter {
 	private static final Set<String> QUANTIFIERS = new HashSet<>();
 	private static final Set<String> DML = new HashSet<>();
 	private static final Set<String> MISC = new HashSet<>();
-	
+
 	static {
 		BEGIN_CLAUSES.add("left");
 		BEGIN_CLAUSES.add("right");
@@ -21,7 +22,7 @@ public class SqlFormatter {
 		BEGIN_CLAUSES.add("outer");
 		BEGIN_CLAUSES.add("group");
 		BEGIN_CLAUSES.add("order");
-		
+
 		END_CLAUSES.add("where");
 		END_CLAUSES.add("set");
 		END_CLAUSES.add("having");
@@ -30,41 +31,41 @@ public class SqlFormatter {
 		END_CLAUSES.add("by");
 		END_CLAUSES.add("into");
 		END_CLAUSES.add("union");
-		
+
 		LOGICAL.add("and");
 		LOGICAL.add("or");
 		LOGICAL.add("when");
 		LOGICAL.add("else");
 		LOGICAL.add("end");
-		
+
 		QUANTIFIERS.add("in");
 		QUANTIFIERS.add("all");
 		QUANTIFIERS.add("exists");
 		QUANTIFIERS.add("some");
 		QUANTIFIERS.add("any");
-		
+
 		DML.add("insert");
 		DML.add("update");
 		DML.add("delete");
-		
+
 		MISC.add("select");
 		MISC.add("on");
 	}
-	
+
 	private static final String indentString = "    ";
 	private static final String initial = "\n    ";
 
 	public static String format(String source) {
 		return new FormatProcess(source).perform().trim();
 	}
-	
+
 	//------------------------------------------------------------------------------------------------
 
 	private static class FormatProcess {
 		boolean beginLine = true;
 		boolean afterBeginBeforeEnd = false;
 		boolean afterByOrSetOrFromOrSelect = false;
-//		boolean afterValues = false;
+		//		boolean afterValues = false;
 		boolean afterOn = false;
 		boolean afterBetween = false;
 		boolean afterInsert = false;

+ 19 - 0
hutool-db/src/test/java/cn/hutool/db/DbTest.java

@@ -38,6 +38,25 @@ public class DbTest {
 	}
 
 	@Test
+	public void pageTest2() throws SQLException {
+		String sql = "select * from user";
+		// 测试数据库中一共4条数据,第0页有3条,第1页有1条
+		List<Entity> page0 = Db.use().page(
+				sql, Page.of(0, 3));
+		Assert.assertEquals(3, page0.size());
+
+		List<Entity> page1 = Db.use().page(
+				sql, Page.of(1, 3));
+		Assert.assertEquals(1, page1.size());
+	}
+
+	@Test
+	public void countTest() throws SQLException {
+		final long count = Db.use().count("select * from user");
+		Assert.assertEquals(4, count);
+	}
+
+	@Test
 	public void findLikeTest() throws SQLException {
 		// 方式1
 		List<Entity> find = Db.use().find(Entity.create("user").set("name", "like 王%"));