diff --git a/CHANGELOG.md b/CHANGELOG.md index fdd19a094..0042e17dd 100644 --- a/CHANGELOG.md +++ b/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) diff --git a/hutool-all/src/main/java/cn/hutool/Hutool.java b/hutool-all/src/main/java/cn/hutool/Hutool.java index 891b5b499..c35dce5f7 100644 --- a/hutool-all/src/main/java/cn/hutool/Hutool.java +++ b/hutool-all/src/main/java/cn/hutool/Hutool.java @@ -45,6 +45,7 @@ public class Hutool { /** * 显示Hutool所有的工具类 * + * @return 工具类名集合 * @since 5.5.2 */ public static Set> getAllUtils() { diff --git a/hutool-core/src/main/java/cn/hutool/core/net/URLEncoder.java b/hutool-core/src/main/java/cn/hutool/core/net/URLEncoder.java index 2bfe00ee3..1da457b75 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/URLEncoder.java +++ b/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}
+ * 默认URLEncoder
* 默认的编码器针对URI路径编码,定义如下: * *
@@ -38,7 +38,7 @@ public class URLEncoder implements Serializable {
 	public static final URLEncoder DEFAULT = createDefault();
 
 	/**
-	 * 用于查询语句的{@link URLEncoder}
+ * 用于查询语句的URLEncoder
* 编码器针对URI路径编码,定义如下: * *
@@ -53,7 +53,7 @@ public class URLEncoder implements Serializable {
 	public static final URLEncoder QUERY = createQuery();
 
 	/**
-	 * 全编码的{@link URLEncoder}
+ * 全编码的URLEncoder
*
 	 * 	 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}
+ * 创建默认URLEncoder
* 默认的编码器针对URI路径编码,定义如下: * *
@@ -72,7 +72,7 @@ public class URLEncoder implements Serializable {
 	 * sub-delims = "!" / "$" / "&" / "'" / "(" / ")" / "*" / "+" / "," / ";" / "="
 	 * 
* - * @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}
+ * 创建用于查询语句的URLEncoder
* 编码器针对URI路径编码,定义如下: * *
@@ -114,7 +114,7 @@ public class URLEncoder implements Serializable {
 	 * 

* 详细见: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}
+ * 创建URLEncoder
* 编码器针对URI路径编码,定义如下: * *

@@ -144,7 +144,7 @@ public class URLEncoder implements Serializable {
 	 * 

* 详细见: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(); diff --git a/hutool-db/src/main/java/cn/hutool/db/AbstractDb.java b/hutool-db/src/main/java/cn/hutool/db/AbstractDb.java index 86a05ba3b..21eaad316 100644 --- a/hutool-db/src/main/java/cn/hutool/db/AbstractDb.java +++ b/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(); @@ -670,6 +671,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); + } + } + /** * 分页查询
* 查询条件为多个key value对表示,默认key = value,如果使用其它条件可以使用:where.put("key", " > 1"),value也可以传Condition对象,key被忽略 @@ -777,25 +795,59 @@ public abstract class AbstractDb implements Serializable { } } + /** + * 分页查询
+ * + * @param 结果对象类型 + * @param sql SQL构建器,可以使用{@link SqlBuilder#of(CharSequence)} 包装普通SQL + * @param page 分页对象 + * @param rsh 结果集处理对象 + * @return 结果对象 + * @throws SQLException SQL执行异常 + * @since 5.5.3 + */ + public T page(CharSequence sql, Page page, RsHandler 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 page(CharSequence sql, Page page) throws SQLException { + Connection conn = null; + try { + conn = this.getConnection(); + return runner.page(conn, SqlBuilder.of(sql), page); + } finally { + this.closeConnection(conn); + } + } + /** * 分页查询
* 查询条件为多个key value对表示,默认key = value,如果使用其它条件可以使用:where.put("key", " > 1"),value也可以传Condition对象,key被忽略 * * @param fields 返回的字段列表,null则返回所有字段 * @param where 条件实体类(包含表名) - * @param page 分页对象 - * @param numPerPage 每页条目数 + * @param pageNumber 页码 + * @param pageSize 每页结果数 * @return 结果对象 * @throws SQLException SQL执行异常 */ - public PageResult page(Collection fields, Entity where, int page, int numPerPage) throws SQLException { - Connection conn = null; - try { - conn = this.getConnection(); - return runner.page(conn, fields, where, page, numPerPage); - } finally { - this.closeConnection(conn); - } + public PageResult page(Collection fields, Entity where, int pageNumber, int pageSize) throws SQLException { + return page(fields, where, new Page(pageNumber, pageSize)); } /** diff --git a/hutool-db/src/main/java/cn/hutool/db/DaoTemplate.java b/hutool-db/src/main/java/cn/hutool/db/DaoTemplate.java index 2ba3e908b..ffe6dedd1 100644 --- a/hutool-db/src/main/java/cn/hutool/db/DaoTemplate.java +++ b/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)); } diff --git a/hutool-db/src/main/java/cn/hutool/db/DialectRunner.java b/hutool-db/src/main/java/cn/hutool/db/DialectRunner.java new file mode 100644 index 000000000..4bbe15a51 --- /dev/null +++ b/hutool-db/src/main/java/cn/hutool/db/DialectRunner.java @@ -0,0 +1,299 @@ +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 + /** + * 批量插入数据
+ * 批量插入必须严格保持Entity的结构一致,不一致会导致插入数据出现不可预知的结果
+ * 此方法不会关闭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); + } + } + + /** + * 插入数据
+ * 此方法不会关闭Connection + * + * @param conn 数据库连接 + * @param record 记录 + * @param generatedKeysHandler 自增主键处理器,用于定义返回自增主键的范围和类型 + * @return 主键列表 + * @throws SQLException SQL执行异常 + */ + public T insert(Connection conn, Entity record, RsHandler 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); + } + } + + /** + * 删除数据
+ * 此方法不会关闭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); + } + } + + /** + * 更新数据
+ * 此方法不会关闭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); + } + } + + /** + * 查询
+ * 此方法不会关闭Connection + * + * @param 结果对象类型 + * @param conn 数据库连接对象 + * @param query {@link Query} + * @param rsh 结果集处理对象 + * @return 结果对象 + * @throws SQLException SQL执行异常 + */ + public T find(Connection conn, Query query, RsHandler 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(); + } + + /** + * 分页查询
+ * 此方法不会关闭Connection + * + * @param 结果对象类型 + * @param conn 数据库连接对象 + * @param query 查询条件(包含表名) + * @param rsh 结果集处理对象 + * @return 结果对象 + * @throws SQLException SQL执行异常 + */ + public T page(Connection conn, Query query, RsHandler rsh) throws SQLException { + checkConn(conn); + if (null == query.getPage()) { + return this.find(conn, query, rsh); + } + + return SqlExecutor.queryAndClosePs(dialect.psForPage(conn, query), rsh); + } + + /** + * 分页查询
+ * 此方法不会关闭Connection + * + * @param 结果对象类型 + * @param conn 数据库连接对象 + * @param sqlBuilder SQL构建器,可以使用{@link SqlBuilder#of(CharSequence)} 包装普通SQL + * @param page 分页对象 + * @param rsh 结果集处理对象 + * @return 结果对象 + * @throws SQLException SQL执行异常 + * @since 5.5.3 + */ + public T page(Connection conn, SqlBuilder sqlBuilder, Page page, RsHandler 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 + + /** + * 设置是否在结果中忽略大小写
+ * 如果忽略,则在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 +} diff --git a/hutool-db/src/main/java/cn/hutool/db/Page.java b/hutool-db/src/main/java/cn/hutool/db/Page.java index d53b6bc99..8841ee4a3 100644 --- a/hutool-db/src/main/java/cn/hutool/db/Page.java +++ b/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] * 。。。 *

- * + * * @return 第一个数为开始位置,第二个数为结束位置 */ public int[] getStartEnd() { diff --git a/hutool-db/src/main/java/cn/hutool/db/Session.java b/hutool-db/src/main/java/cn/hutool/db/Session.java index 8b3a223e8..23cff582d 100644 --- a/hutool-db/src/main/java/cn/hutool/db/Session.java +++ b/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); diff --git a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java index ac428c8b7..067b36665 100644 --- a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java +++ b/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,35 +77,12 @@ public class SqlConnRunner implements Serializable { * @param driverClassName 驱动类名,,用于识别方言 */ public SqlConnRunner(String driverClassName) { - this(DialectFactory.newDialect(driverClassName)); + super(driverClassName); } //------------------------------------------------------- Constructor end //---------------------------------------------------------------------------- CRUD start - /** - * 插入数据
- * 此方法不会关闭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); - } - } - /** * 插入或更新数据
* 此方法不会关闭Connection @@ -152,33 +118,16 @@ public class SqlConnRunner implements Serializable { } /** - * 批量插入数据
- * 批量插入必须严格保持Entity的结构一致,不一致会导致插入数据出现不可预知的结果
+ * 插入数据
* 此方法不会关闭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 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); - } - } - - /** - * 删除数据
- * 此方法不会关闭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); - } - } - - /** - * 更新数据
- * 此方法不会关闭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); - } - } - - /** - * 查询
- * 此方法不会关闭Connection - * - * @param 结果对象类型 - * @param conn 数据库连接对象 - * @param query {@link Query} - * @param rsh 结果集处理对象 - * @return 结果对象 - * @throws SQLException SQL执行异常 - */ - public T find(Connection conn, Query query, RsHandler 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 find(Connection conn, Collection fields, Entity where, RsHandler 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 page(Connection conn, Collection fields, Entity where, int pageNumber, int numPerPage, RsHandler 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); } /** * 分页查询
* 此方法不会关闭Connection * - * @param 结果对象类型 - * @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 page(Connection conn, Collection fields, Entity where, Page page, RsHandler 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 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 page(Connection conn, Collection 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); - } - - /** - * 分页查询
- * 此方法不会关闭Connection - * - * @param conn 数据库连接对象 - * @param fields 返回的字段列表,null则返回所有字段 - * @param where 条件实体类(包含表名) - * @param page 分页对象 - * @return 结果对象 - * @throws SQLException SQL执行异常 - */ - public PageResult page(Connection conn, Collection fields, Entity where, Page page) throws SQLException { - checkConn(conn); - - //查询全部 - if (null == page) { - List entityList = this.find(conn, fields, where, new EntityListHandler(GlobalDbConfig.caseInsensitive)); - final PageResult 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,39 @@ public class SqlConnRunner implements Serializable { public PageResult page(Connection conn, Entity where, Page page) throws SQLException { return this.page(conn, null, where, page); } + + /** + * 分页查询
+ * 此方法不会关闭Connection + * + * @param conn 数据库连接对象 + * @param fields 返回的字段列表,null则返回所有字段 + * @param where 条件实体类(包含表名) + * @param page 分页对象 + * @return 结果对象 + * @throws SQLException SQL执行异常 + */ + public PageResult page(Connection conn, Collection 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); + } + + /** + * 分页查询
+ * 此方法不会关闭Connection + * + * @param conn 数据库连接对象 + * @param fields 返回的字段列表,null则返回所有字段 + * @param where 条件实体类(包含表名) + * @param page 分页对象 + * @param handler 结果集处理器 + * @return 结果对象 + * @throws SQLException SQL执行异常 + */ + public T page(Connection conn, Collection fields, Entity where, Page page, RsHandler handler) throws SQLException { + return this.page(conn, Query.of(where).setFields(fields).setPage(page), handler); + } //---------------------------------------------------------------------------- CRUD end - - //---------------------------------------------------------------------------- Getters and Setters end - - /** - * 设置是否在结果中忽略大小写
- * 如果忽略,则在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; - } - - /** - * 设置包装器,包装器用于对表名、字段名进行符号包装(例如双引号),防止关键字与这些表名或字段冲突 - * - * @param wrapperChar 包装字符,字符会在SQL生成时位于表名和字段名两边,null时表示取消包装 - * @return this - * @since 4.0.0 - */ - public SqlConnRunner setWrapper(Character wrapperChar) { - return setWrapper(new Wrapper(wrapperChar)); - } - - /** - * 设置包装器,包装器用于对表名、字段名进行符号包装(例如双引号),防止关键字与这些表名或字段冲突 - * - * @param wrapper 包装器,null表示取消包装 - * @return this - * @since 4.0.0 - */ - public SqlConnRunner setWrapper(Wrapper wrapper) { - this.dialect.setWrapper(wrapper); - return this; - } - //---------------------------------------------------------------------------- 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 } \ No newline at end of file diff --git a/hutool-db/src/main/java/cn/hutool/db/StatementUtil.java b/hutool-db/src/main/java/cn/hutool/db/StatementUtil.java index 413e5a5ce..67a07f089 100644 --- a/hutool-db/src/main/java/cn/hutool/db/StatementUtil.java +++ b/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 { /** * 获得自增键的值
- * 此方法对于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 getGeneratedKeys(Statement ps) throws SQLException { - final List 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 自定义主键类型 + * @return 主键 + * @throws SQLException SQL执行异常 + * @since 5.5.3 + */ + public static T getGeneratedKeys(Statement statement, RsHandler rsHandler) throws SQLException { + try (final ResultSet rs = statement.getGeneratedKeys()) { + return rsHandler.handle(rs); } } diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/Dialect.java b/hutool-db/src/main/java/cn/hutool/db/dialect/Dialect.java index 6bca51289..08677c1e8 100644 --- a/hutool-db/src/main/java/cn/hutool/db/dialect/Dialect.java +++ b/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上有所区别,故为每种数据库配置不同的方言。
* 由于不同数据库间SQL语句的差异,导致无法统一拼接SQL,
* 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,28 +91,46 @@ public interface Dialect extends Serializable{ /** * 构建用于分页查询的PreparedStatement - * - * @param conn 数据库连接对象 + * + * @param conn 数据库连接对象 * @param query 查询条件(包含表名) * @return PreparedStatement * @throws SQLException SQL执行异常 */ PreparedStatement psForPage(Connection conn, Query query) throws SQLException; + /** + * 构建用于分页查询的PreparedStatement
+ * 可以在此方法中使用{@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(); } diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/AnsiSqlDialect.java b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/AnsiSqlDialect.java index 03b58dda1..3793d41f0 100644 --- a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/AnsiSqlDialect.java +++ b/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()); + 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); - } - - @Override - public DialectName dialectName() { - return DialectName.ANSI; + public String dialectName() { + return DialectName.ANSI.name(); } // ---------------------------------------------------------------------------- Protected method start diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/H2Dialect.java b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/H2Dialect.java index 404814c45..96311bb0f 100644 --- a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/H2Dialect.java +++ b/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 diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/MysqlDialect.java b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/MysqlDialect.java index 528806ad8..d10e811fb 100644 --- a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/MysqlDialect.java +++ b/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(); } } diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/OracleDialect.java b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/OracleDialect.java index d89c66e88..925889611 100644 --- a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/OracleDialect.java +++ b/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(); } } diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/PostgresqlDialect.java b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/PostgresqlDialect.java index 404130fea..d7109e3c2 100644 --- a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/PostgresqlDialect.java +++ b/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(); } } diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/SqlServer2012Dialect.java b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/SqlServer2012Dialect.java index bc8e38588..5a4ab8a0d 100644 --- a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/SqlServer2012Dialect.java +++ b/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(); } } diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/Sqlite3Dialect.java b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/Sqlite3Dialect.java index 1531b9fee..bf209a00c 100644 --- a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/Sqlite3Dialect.java +++ b/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(); } } diff --git a/hutool-db/src/main/java/cn/hutool/db/handler/NumberHandler.java b/hutool-db/src/main/java/cn/hutool/db/handler/NumberHandler.java index eb5b78a91..e8842fd6c 100644 --- a/hutool-db/src/main/java/cn/hutool/db/handler/NumberHandler.java +++ b/hutool-db/src/main/java/cn/hutool/db/handler/NumberHandler.java @@ -21,6 +21,6 @@ public class NumberHandler implements RsHandler{ @Override public Number handle(ResultSet rs) throws SQLException { - return rs.next() ? rs.getBigDecimal(1) : null; + return (null != rs && rs.next()) ? rs.getBigDecimal(1) : null; } } diff --git a/hutool-db/src/main/java/cn/hutool/db/sql/Query.java b/hutool-db/src/main/java/cn/hutool/db/sql/Query.java index 12adbe889..0619e2097 100644 --- a/hutool-db/src/main/java/cn/hutool/db/sql/Query.java +++ b/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; + /** * 查询对象,用于传递查询所需的字段值
* 查询对象根据表名(可以多个),多个条件 {@link Condition} 构建查询对象完成查询。
@@ -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; diff --git a/hutool-db/src/main/java/cn/hutool/db/sql/SqlBuilder.java b/hutool-db/src/main/java/cn/hutool/db/sql/SqlBuilder.java index 893a6dae9..e10e1a7a8 100644 --- a/hutool-db/src/main/java/cn/hutool/db/sql/SqlBuilder.java +++ b/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{ 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{ /** * 插入
* 插入会忽略空的字段名及其对应值,但是对于有字段名对应值为{@code null}的情况不忽略 - * + * * @param entity 实体 * @param dialectName 方言名,用于对特殊数据库特殊处理 * @return 自己 */ public SqlBuilder insert(Entity entity, DialectName dialectName) { + return insert(entity, dialectName.name()); + } + + /** + * 插入
+ * 插入会忽略空的字段名及其对应值,但是对于有字段名对应值为{@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{ 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{ } /** - * 追加SQL其它部分片段 + * 追加SQL其它部分片段,此方法只是简单的追加SQL字符串,空格需手动加入,例如: + * + *
+	 *     SqlBuilder builder = SqlBuilder.of("select *");
+	 *     builder.append(" from ").append("user");
+	 * 
+ * + * 如果需要追加带占位符的片段,需调用{@link #addParams(Object...)} 方法加入对应参数值。 * * @param sqlFragment SQL其它部分片段 * @return this @@ -531,6 +561,26 @@ public class SqlBuilder implements Builder{ return this; } + /** + * 手动增加参数,调用此方法前需确认SQL中有对应占位符,主要用于自定义SQL片段中有占位符的情况,例如: + * + *
+	 *     SqlBuilder builder = SqlBuilder.of("select * from user where id=?");
+	 *     builder.append(" and name=?")
+	 *     builder.addParams(1, "looly");
+	 * 
+ * + * @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 * diff --git a/hutool-db/src/main/java/cn/hutool/db/sql/SqlExecutor.java b/hutool-db/src/main/java/cn/hutool/db/sql/SqlExecutor.java index 238c1b884..a64973625 100644 --- a/hutool-db/src/main/java/cn/hutool/db/sql/SqlExecutor.java +++ b/hutool-db/src/main/java/cn/hutool/db/sql/SqlExecutor.java @@ -259,6 +259,22 @@ public class SqlExecutor { } } + /** + * 执行查询语句
+ * 此方法不会关闭Connection + * + * @param 处理结果类型 + * @param conn 数据库连接对象 + * @param sqlBuilder SQL构建器,包含参数 + * @param rsh 结果集处理对象 + * @return 结果对象 + * @throws SQLException SQL执行异常 + * @since 5.5.3 + */ + public static T query(Connection conn, SqlBuilder sqlBuilder, RsHandler rsh) throws SQLException { + return query(conn, sqlBuilder.build(), rsh, sqlBuilder.getParamValueArray()); + } + // -------------------------------------------------------------------------------------- Execute With PreparedStatement /** * 用于执行 INSERT、UPDATE 或 DELETE 语句以及 SQL DDL(数据定义语言)语句,例如 CREATE TABLE 和 DROP TABLE。
diff --git a/hutool-db/src/main/java/cn/hutool/db/sql/SqlFormatter.java b/hutool-db/src/main/java/cn/hutool/db/sql/SqlFormatter.java index 0ba99bde8..b8e4e46e5 100644 --- a/hutool-db/src/main/java/cn/hutool/db/sql/SqlFormatter.java +++ b/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 QUANTIFIERS = new HashSet<>(); private static final Set DML = new HashSet<>(); private static final Set 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; diff --git a/hutool-db/src/test/java/cn/hutool/db/DbTest.java b/hutool-db/src/test/java/cn/hutool/db/DbTest.java index 816305b39..ca4ed605a 100644 --- a/hutool-db/src/test/java/cn/hutool/db/DbTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/DbTest.java @@ -37,6 +37,25 @@ public class DbTest { Assert.assertEquals(1, page1.size()); } + @Test + public void pageTest2() throws SQLException { + String sql = "select * from user"; + // 测试数据库中一共4条数据,第0页有3条,第1页有1条 + List page0 = Db.use().page( + sql, Page.of(0, 3)); + Assert.assertEquals(3, page0.size()); + + List 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