From 64d21372ec688dc009c0be5f102002a74f4ffa46 Mon Sep 17 00:00:00 2001 From: icefairy <860668820@qq.com> Date: Fri, 14 Jan 2022 12:34:10 +0800 Subject: [PATCH 01/28] =?UTF-8?q?upsert=20=E6=8E=A5=E5=8F=A3=E5=92=8Ch2?= =?UTF-8?q?=E6=96=B9=E8=A8=80=E7=9A=84upsert=E5=AE=8C=E6=88=90=E5=B9=B6?= =?UTF-8?q?=E6=B5=8B=E8=AF=95=E9=80=9A=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../main/java/cn/hutool/db/AbstractDb.java | 30 +++++++-- .../main/java/cn/hutool/db/DialectRunner.java | 31 ++++++++- .../main/java/cn/hutool/db/SqlConnRunner.java | 1 + .../java/cn/hutool/db/dialect/Dialect.java | 20 +++++- .../cn/hutool/db/dialect/impl/H2Dialect.java | 27 ++++++++ .../java/cn/hutool/db/sql/SqlBuilder.java | 67 +++++++++++++++++++ .../src/test/java/cn/hutool/db/H2Test.java | 16 +++-- 7 files changed, 179 insertions(+), 13 deletions(-) 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 c18399029..700a92027 100644 --- a/hutool-db/src/main/java/cn/hutool/db/AbstractDb.java +++ b/hutool-db/src/main/java/cn/hutool/db/AbstractDb.java @@ -200,9 +200,9 @@ public abstract class AbstractDb implements Serializable { * 执行自定义的{@link PreparedStatement},结果使用{@link RsHandler}处理
* 此方法主要用于自定义场景,如游标查询等 * - * @param 结果集需要处理的对象类型 + * @param 结果集需要处理的对象类型 * @param statementFunc 自定义{@link PreparedStatement}创建函数 - * @param rsh 结果集处理对象 + * @param rsh 结果集处理对象 * @return 结果对象 * @throws SQLException SQL执行异常 * @since 5.7.17 @@ -369,6 +369,26 @@ public abstract class AbstractDb implements Serializable { } } + /** + * 使用upsert语义插入或更新数据
+ * 根据给定的字段名查询数据,如果存在则更新这些数据,否则执行插入 + * 如果方言未实现本方法,内部会自动调用insertOrUpdate来实现功能,由于upsert和insert使用有区别,为了兼容性保留原有insertOrUpdate不做变动 + * @param record 记录 + * @param keys 需要检查唯一性的字段 + * @return 插入行数 + * @throws SQLException SQL执行异常 + * @since 5.7.21 + */ + public int upsert(Entity record, String... keys) throws SQLException { + Connection conn = null; + try { + conn = this.getConnection(); + return runner.upsert(conn, record, keys); + } finally { + this.closeConnection(conn); + } + } + /** * 批量插入数据
* 需要注意的是,批量插入每一条数据结构必须一致。批量插入数据时会获取第一条数据的字段结构,之后的数据会按照这个格式插入。
@@ -864,7 +884,7 @@ public abstract class AbstractDb implements Serializable { /** * 分页查询 * - * @param 处理结果类型,可以将ResultSet转换为给定类型 + * @param 处理结果类型,可以将ResultSet转换为给定类型 * @param sql SQL构建器 * @param page 分页对象 * @param rsh 结果集处理对象 @@ -884,8 +904,8 @@ public abstract class AbstractDb implements Serializable { /** * 分页查询 * - * @param sql SQL语句字符串 - * @param page 分页对象 + * @param sql SQL语句字符串 + * @param page 分页对象 * @param params 参数列表 * @return 结果对象 * @throws SQLException SQL执行异常 diff --git a/hutool-db/src/main/java/cn/hutool/db/DialectRunner.java b/hutool-db/src/main/java/cn/hutool/db/DialectRunner.java index 43d0574e3..b1003b987 100644 --- a/hutool-db/src/main/java/cn/hutool/db/DialectRunner.java +++ b/hutool-db/src/main/java/cn/hutool/db/DialectRunner.java @@ -86,6 +86,35 @@ public class DialectRunner implements Serializable { } } + /** + * 更新或插入数据
+ * 此方法不会关闭Connection + * 如果方言未实现此方法则内部自动使用insertOrUpdate来替代功能 + * + * @param conn 数据库连接 + * @param record 记录 + * @param keys 需要检查唯一性的字段 + * @return 插入行数 + * @throws SQLException SQL执行异常 + */ + public int upsert(Connection conn, Entity record, String... keys) throws SQLException { + PreparedStatement ps = getDialect().psForUpsert(conn, record, keys); + if (null != ps) { + try { + return ps.executeUpdate(); + } finally { + DbUtil.close(ps); + } + } else { + final Entity where = record.filter(keys); + if (MapUtil.isNotEmpty(where) && count(conn, where) > 0) { + return update(conn, record, where); + } else { + return insert(conn, record).length; + } + } + } + /** * 插入数据
* 此方法不会关闭Connection @@ -212,7 +241,7 @@ public class DialectRunner implements Serializable { * 获取查询结果总数,生成类似于 SELECT count(1) from (sql) hutool_alias_count_
* 此方法会重新构建{@link SqlBuilder},并去除末尾的order by子句 * - * @param conn 数据库连接对象 + * @param conn 数据库连接对象 * @param sqlBuilder 查询语句 * @return 复合条件的结果数 * @throws SQLException SQL执行异常 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 e21d8c5af..95290d6db 100644 --- a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java +++ b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java @@ -15,6 +15,7 @@ import cn.hutool.db.sql.SqlUtil; import javax.sql.DataSource; import java.sql.Connection; +import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Collection; import java.util.List; 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 7eacc8d14..16058bc4c 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 @@ -121,7 +121,7 @@ public interface Dialect extends Serializable { * @return PreparedStatement * @throws SQLException SQL执行异常 */ - default 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); } @@ -129,13 +129,13 @@ public interface Dialect extends Serializable { /** * 构建用于查询行数的PreparedStatement * - * @param conn 数据库连接对象 + * @param conn 数据库连接对象 * @param sqlBuilder 查询语句,应该包含分页等信息 * @return PreparedStatement * @throws SQLException SQL执行异常 * @since 5.7.2 */ - default PreparedStatement psForCount(Connection conn, SqlBuilder sqlBuilder) throws SQLException{ + default PreparedStatement psForCount(Connection conn, SqlBuilder sqlBuilder) throws SQLException { sqlBuilder = sqlBuilder .insertPreFragment("SELECT count(1) from(") // issue#I3IJ8X@Gitee,在子查询时需设置单独别名,此处为了防止和用户的表名冲突,使用自定义的较长别名 @@ -143,6 +143,20 @@ public interface Dialect extends Serializable { return psForPage(conn, sqlBuilder, null); } + /** + * 构建用于upsert的PreparedStatement + * + * @param conn 数据库连接对象 + * @param entity 数据实体类(包含表名) + * @param keys 查找字段 + * @return PreparedStatement + * @throws SQLException SQL执行异常 + */ + default PreparedStatement psForUpsert(Connection conn, Entity entity, String... keys) throws SQLException { + return null; + } + + /** * 方言名 * 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 96311bb0f..917980049 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 @@ -1,9 +1,21 @@ package cn.hutool.db.dialect.impl; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.db.Entity; import cn.hutool.db.Page; +import cn.hutool.db.StatementUtil; import cn.hutool.db.dialect.DialectName; +import cn.hutool.db.sql.Condition; +import cn.hutool.db.sql.Query; import cn.hutool.db.sql.SqlBuilder; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; +import java.util.Arrays; +import java.util.function.Function; + /** * H2数据库方言 * @@ -26,4 +38,19 @@ public class H2Dialect extends AnsiSqlDialect { // limit A , B 表示:A就是查询的起点位置,B就是你需要多少行。 return find.append(" limit ").append(page.getStartPosition()).append(" , ").append(page.getPageSize()); } + + /** + * 构建用于upsert的PreparedStatement + * + * @param conn 数据库连接对象 + * @param entity 数据实体类(包含表名) + * @param keys 查找字段 如果不提供keys将自动使用主键 + * @return PreparedStatement + * @throws SQLException SQL执行异常 + */ + @Override + public PreparedStatement psForUpsert(Connection conn, Entity entity, String... keys) throws SQLException { + final SqlBuilder upsert = SqlBuilder.create(wrapper).upsert(entity, this.dialectName(),keys); + return StatementUtil.prepareStatement(conn, upsert); + } } 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 95d15ed8a..cee913a2e 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 @@ -196,6 +196,73 @@ public class SqlBuilder implements Builder { return this; } + /** + * 插入
+ * 插入会忽略空的字段名及其对应值,但是对于有字段名对应值为{@code null}的情况不忽略 + * + * @param entity 实体 + * @param dialectName 方言名,用于对特殊数据库特殊处理 + * @param keys 根据何字段来确认唯一性,不传则用主键 + * @return 自己 + * @since 5.7.21 + */ + public SqlBuilder upsert(Entity entity, String dialectName, String... keys) { + // 验证 + validateEntity(entity); + + if (null != wrapper) { + // 包装表名 entity = wrapper.wrap(entity); + entity.setTableName(wrapper.wrap(entity.getTableName())); + } + + final boolean isOracle = DialectName.ORACLE.match(dialectName);// 对Oracle的特殊处理 + final StringBuilder fieldsPart = new StringBuilder(); + final StringBuilder placeHolder = new StringBuilder(); + + boolean isFirst = true; + String field; + Object value; + for (Entry entry : entity.entrySet()) { + field = entry.getKey(); + value = entry.getValue(); + if (StrUtil.isNotBlank(field) /* && null != value */) { + if (isFirst) { + isFirst = false; + } else { + // 非第一个参数,追加逗号 + fieldsPart.append(", "); + placeHolder.append(", "); + } + + this.fields.add(field); + fieldsPart.append((null != wrapper) ? wrapper.wrap(field) : field); + if (isOracle && value instanceof String && StrUtil.endWithIgnoreCase((String) value, ".nextval")) { + // Oracle的特殊自增键,通过字段名.nextval获得下一个值 + placeHolder.append(value); + } else { + placeHolder.append("?"); + this.paramValues.add(value); + } + } + } + + // issue#1656@Github Phoenix兼容 + if (DialectName.PHOENIX.match(dialectName)) { + sql.append("UPSERT INTO ").append(entity.getTableName()); + } else if (DialectName.H2.match(dialectName)) { + sql.append("MERGE INTO ").append(entity.getTableName()); + if (null != keys && keys.length > 0) { + sql.append(" KEY(").append(ArrayUtil.join(keys, ",")) + .append(") VALUES (") + .append(placeHolder) + .append(")"); + } + } else { + throw new RuntimeException(dialectName + " not support yet"); + } + return this; + } + /** * 删除 * diff --git a/hutool-db/src/test/java/cn/hutool/db/H2Test.java b/hutool-db/src/test/java/cn/hutool/db/H2Test.java index c61404527..85bd27627 100644 --- a/hutool-db/src/test/java/cn/hutool/db/H2Test.java +++ b/hutool-db/src/test/java/cn/hutool/db/H2Test.java @@ -1,5 +1,6 @@ package cn.hutool.db; +import com.alibaba.druid.support.json.JSONUtils; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; @@ -9,14 +10,14 @@ import java.util.List; /** * H2数据库单元测试 - * + * * @author looly * */ public class H2Test { - + private static final String DS_GROUP_NAME = "h2"; - + @BeforeClass public static void init() throws SQLException { Db db = Db.use(DS_GROUP_NAME); @@ -27,7 +28,7 @@ public class H2Test { db.insert(Entity.create("test").set("a", 3).set("b", 31)); db.insert(Entity.create("test").set("a", 4).set("b", 41)); } - + @Test public void queryTest() throws SQLException { List query = Db.use(DS_GROUP_NAME).query("select * from test"); @@ -39,4 +40,11 @@ public class H2Test { List query = Db.use(DS_GROUP_NAME).find(Entity.create("test")); Assert.assertEquals(4, query.size()); } + @Test + public void upsertTest() throws SQLException { + Db db=Db.use(DS_GROUP_NAME); + db.upsert(Entity.create("test").set("a",1).set("b",111),"a"); + Entity a1=db.get("test","a",1); + Assert.assertEquals(Long.valueOf(111),a1.getLong("b")); + } } From e492cf2a736f769d3937e2cf2f3999b285d690d4 Mon Sep 17 00:00:00 2001 From: icefairy <860668820@qq.com> Date: Fri, 14 Jan 2022 17:07:33 +0800 Subject: [PATCH 02/28] =?UTF-8?q?mysql=20+=20postgres=20upsert=E5=AE=8C?= =?UTF-8?q?=E6=88=90=E5=B9=B6=E6=B5=8B=E8=AF=95=E9=80=9A=E8=BF=87?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hutool/db/dialect/impl/MysqlDialect.java | 23 +++++++++++++++- .../db/dialect/impl/PostgresqlDialect.java | 25 ++++++++++++++++++ .../java/cn/hutool/db/sql/SqlBuilder.java | 16 +++++++++++- .../src/test/java/cn/hutool/db/MySQLTest.java | 26 ++++++++++++++++--- .../test/java/cn/hutool/db/PostgreTest.java | 16 ++++++++++-- 5 files changed, 98 insertions(+), 8 deletions(-) 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 d10e811fb..3ce7a199a 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 @@ -1,10 +1,16 @@ package cn.hutool.db.dialect.impl; +import cn.hutool.db.Entity; import cn.hutool.db.Page; +import cn.hutool.db.StatementUtil; import cn.hutool.db.dialect.DialectName; import cn.hutool.db.sql.SqlBuilder; import cn.hutool.db.sql.Wrapper; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + /** * MySQL方言 * @author loolly @@ -21,9 +27,24 @@ public class MysqlDialect extends AnsiSqlDialect{ protected SqlBuilder wrapPageSql(SqlBuilder find, Page page) { return find.append(" LIMIT ").append(page.getStartPosition()).append(", ").append(page.getPageSize()); } - + @Override public String dialectName() { return DialectName.MYSQL.toString(); } + + /** + * 构建用于upsert的PreparedStatement + * + * @param conn 数据库连接对象 + * @param entity 数据实体类(包含表名) + * @param keys 查找字段 + * @return PreparedStatement + * @throws SQLException SQL执行异常 + */ + @Override + public PreparedStatement psForUpsert(Connection conn, Entity entity, String... keys) throws SQLException { + final SqlBuilder upsert = SqlBuilder.create(wrapper).upsert(entity, this.dialectName(),keys); + return StatementUtil.prepareStatement(conn, upsert); + } } 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 d7109e3c2..82f5fe373 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 @@ -1,8 +1,15 @@ package cn.hutool.db.dialect.impl; +import cn.hutool.db.Entity; +import cn.hutool.db.StatementUtil; import cn.hutool.db.dialect.DialectName; +import cn.hutool.db.sql.SqlBuilder; import cn.hutool.db.sql.Wrapper; +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.SQLException; + /** * Postgree方言 @@ -20,4 +27,22 @@ public class PostgresqlDialect extends AnsiSqlDialect{ public String dialectName() { return DialectName.POSTGREESQL.name(); } + + /** + * 构建用于upsert的PreparedStatement + * + * @param conn 数据库连接对象 + * @param entity 数据实体类(包含表名) + * @param keys 查找字段 必须是有唯一索引的列且不能为空 + * @return PreparedStatement + * @throws SQLException SQL执行异常 + */ + @Override + public PreparedStatement psForUpsert(Connection conn, Entity entity, String... keys) throws SQLException { + if (null==keys || keys.length==0){ + throw new SQLException("keys不能为空"); + } + final SqlBuilder upsert = SqlBuilder.create(wrapper).upsert(entity, this.dialectName(),keys); + return StatementUtil.prepareStatement(conn, upsert); + } } 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 cee913a2e..ac666054b 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 @@ -202,7 +202,7 @@ public class SqlBuilder implements Builder { * * @param entity 实体 * @param dialectName 方言名,用于对特殊数据库特殊处理 - * @param keys 根据何字段来确认唯一性,不传则用主键 + * @param keys 根据何字段来确认唯一性,不传则用主键 * @return 自己 * @since 5.7.21 */ @@ -249,6 +249,12 @@ public class SqlBuilder implements Builder { // issue#1656@Github Phoenix兼容 if (DialectName.PHOENIX.match(dialectName)) { sql.append("UPSERT INTO ").append(entity.getTableName()); + } else if (DialectName.MYSQL.match(dialectName)) { + sql.append("INSERT INTO "); + sql.append(entity.getTableName()) + .append(" (").append(fieldsPart).append(") VALUES (") + .append(placeHolder).append(") on duplicate key update ") + .append(ArrayUtil.join(ArrayUtil.map(entity.keySet().toArray(), String.class, (k) -> k + "=values(" + k + ")"), ",")); } else if (DialectName.H2.match(dialectName)) { sql.append("MERGE INTO ").append(entity.getTableName()); if (null != keys && keys.length > 0) { @@ -257,6 +263,14 @@ public class SqlBuilder implements Builder { .append(placeHolder) .append(")"); } + } else if (DialectName.POSTGREESQL.match(dialectName)) { + sql.append("INSERT INTO "); + sql.append(entity.getTableName()) + .append(" (").append(fieldsPart).append(") VALUES (") + .append(placeHolder).append(") on conflict (") + .append(ArrayUtil.join(keys,",")) + .append(") do update set ") + .append(ArrayUtil.join(ArrayUtil.map(entity.keySet().toArray(), String.class, (k) -> k + "=excluded." + k ), ",")); } else { throw new RuntimeException(dialectName + " not support yet"); } diff --git a/hutool-db/src/test/java/cn/hutool/db/MySQLTest.java b/hutool-db/src/test/java/cn/hutool/db/MySQLTest.java index e3e72fba1..8ecebb74e 100644 --- a/hutool-db/src/test/java/cn/hutool/db/MySQLTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/MySQLTest.java @@ -1,6 +1,9 @@ package cn.hutool.db; import cn.hutool.core.lang.Console; +import cn.hutool.core.util.ArrayUtil; +import org.junit.Assert; +import org.junit.BeforeClass; import org.junit.Ignore; import org.junit.Test; @@ -9,11 +12,16 @@ import java.util.List; /** * MySQL操作单元测试 - * - * @author looly * + * @author looly */ public class MySQLTest { + @BeforeClass + @Ignore + public static void createTable() throws SQLException { + Db db = Db.use("mysql"); + db.executeBatch("drop table if exists testuser", "CREATE TABLE if not exists `testuser` ( `id` int(11) NOT NULL, `account` varchar(255) DEFAULT NULL, `pass` varchar(255) DEFAULT NULL, PRIMARY KEY (`id`) ) ENGINE=InnoDB DEFAULT CHARSET=utf8"); + } @Test @Ignore @@ -34,13 +42,13 @@ public class MySQLTest { * * @throws SQLException SQL异常 */ - @Test(expected=SQLException.class) + @Test(expected = SQLException.class) @Ignore public void txTest() throws SQLException { Db.use("mysql").tx(db -> { int update = db.update(Entity.create("user").set("text", "描述100"), Entity.create().set("id", 100)); db.update(Entity.create("user").set("text", "描述101"), Entity.create().set("id", 101)); - if(1 == update) { + if (1 == update) { // 手动指定异常,然后测试回滚触发 throw new RuntimeException("Error"); } @@ -64,4 +72,14 @@ public class MySQLTest { Console.log(all); } + @Test + @Ignore + public void upsertTest() throws SQLException { + Db db = Db.use("mysql"); + db.insert(Entity.create("testuser").set("id", 1).set("account", "ice").set("pass", "123456")); + db.upsert(Entity.create("testuser").set("id", 1).set("account", "icefairy").set("pass", "a123456")); + Entity user = db.get(Entity.create("testuser").set("id", 1)); + System.out.println("user======="+user.getStr("account")+"___"+user.getStr("pass")); + Assert.assertEquals(user.getStr("account"), new String("icefairy")); + } } diff --git a/hutool-db/src/test/java/cn/hutool/db/PostgreTest.java b/hutool-db/src/test/java/cn/hutool/db/PostgreTest.java index a19559a7e..250930efe 100644 --- a/hutool-db/src/test/java/cn/hutool/db/PostgreTest.java +++ b/hutool-db/src/test/java/cn/hutool/db/PostgreTest.java @@ -2,6 +2,7 @@ package cn.hutool.db; import java.sql.SQLException; +import org.junit.Assert; import org.junit.Ignore; import org.junit.Test; @@ -9,9 +10,8 @@ import cn.hutool.core.lang.Console; /** * PostgreSQL 单元测试 - * - * @author looly * + * @author looly */ public class PostgreTest { @@ -34,4 +34,16 @@ public class PostgreTest { Console.log(entity.get("id")); } } + + @Test + @Ignore + public void upsertTest() throws SQLException { + Db db = Db.use("postgre"); + db.executeBatch("drop table if exists ctest", + "create table if not exists \"ctest\" ( \"id\" serial4, \"t1\" varchar(255) COLLATE \"pg_catalog\".\"default\", \"t2\" varchar(255) COLLATE \"pg_catalog\".\"default\", \"t3\" varchar(255) COLLATE \"pg_catalog\".\"default\", CONSTRAINT \"ctest_pkey\" PRIMARY KEY (\"id\") ) "); + db.insert(Entity.create("ctest").set("id", 1).set("t1", "111").set("t2", "222").set("t3", "333")); + db.upsert(Entity.create("ctest").set("id", 1).set("t1", "new111").set("t2", "new222").set("t3", "bew333"),"id"); + Entity et=db.get(Entity.create("ctest").set("id", 1)); + Assert.assertEquals("new111",et.getStr("t1")); + } } From b63dbe7a9a74503fe27c955c0201e92b0942e538 Mon Sep 17 00:00:00 2001 From: Looly Date: Fri, 14 Jan 2022 19:41:55 +0800 Subject: [PATCH 03/28] add upsert --- CHANGELOG.md | 3 ++- .../main/java/cn/hutool/db/DialectRunner.java | 27 ++++++++++++++----- .../main/java/cn/hutool/db/SqlConnRunner.java | 24 ++--------------- .../java/cn/hutool/db/dialect/Dialect.java | 4 ++- .../java/cn/hutool/db/sql/SqlBuilder.java | 1 - .../main/java/cn/hutool/db/sql/Wrapper.java | 4 ++- 6 files changed, 31 insertions(+), 32 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 11ddb22fb..0fef330cd 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,7 +10,8 @@ * 【core 】 增加KetamaHash(issue#2084@Github) * 【crypto 】 增加SignUtil * 【json 】 JSONGetter增加getBeanList方法 -* 【core 】 ObjectUtil 添加三个defaultIfXxxx方法,用于节省CPU及内存损耗。(pr#2094@Github) +* 【core 】 ObjectUtil 添加三个defaultIfXxxx方法,用于节省CPU及内存损耗(pr#2094@Github) +* 【db 】 增加单条数据原生upsert语义支持(pr#501@Gitee) * ### 🐞Bug修复 * 【core 】 修复setter重载导致匹配错误(issue#2082@Github) diff --git a/hutool-db/src/main/java/cn/hutool/db/DialectRunner.java b/hutool-db/src/main/java/cn/hutool/db/DialectRunner.java index b1003b987..26e18bc09 100644 --- a/hutool-db/src/main/java/cn/hutool/db/DialectRunner.java +++ b/hutool-db/src/main/java/cn/hutool/db/DialectRunner.java @@ -96,6 +96,7 @@ public class DialectRunner implements Serializable { * @param keys 需要检查唯一性的字段 * @return 插入行数 * @throws SQLException SQL执行异常 + * @since 5.7.20 */ public int upsert(Connection conn, Entity record, String... keys) throws SQLException { PreparedStatement ps = getDialect().psForUpsert(conn, record, keys); @@ -106,12 +107,26 @@ public class DialectRunner implements Serializable { DbUtil.close(ps); } } else { - final Entity where = record.filter(keys); - if (MapUtil.isNotEmpty(where) && count(conn, where) > 0) { - return update(conn, record, where); - } else { - return insert(conn, record).length; - } + return insertOrUpdate(conn, record, keys); + } + } + + /** + * 插入或更新数据
+ * 此方法不会关闭Connection + * + * @param conn 数据库连接 + * @param record 记录 + * @param keys 需要检查唯一性的字段 + * @return 插入行数 + * @throws SQLException SQL执行异常 + */ + public int insertOrUpdate(Connection conn, Entity record, String... keys) throws SQLException { + final Entity where = record.filter(keys); + if (MapUtil.isNotEmpty(where) && count(conn, where) > 0) { + return update(conn, record, where); + } else { + return insert(conn, record)[0]; } } 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 95290d6db..28f1bfc2b 100644 --- a/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java +++ b/hutool-db/src/main/java/cn/hutool/db/SqlConnRunner.java @@ -1,7 +1,6 @@ package cn.hutool.db; import cn.hutool.core.collection.CollUtil; -import cn.hutool.core.map.MapUtil; import cn.hutool.db.dialect.Dialect; import cn.hutool.db.dialect.DialectFactory; import cn.hutool.db.handler.EntityListHandler; @@ -15,7 +14,6 @@ import cn.hutool.db.sql.SqlUtil; import javax.sql.DataSource; import java.sql.Connection; -import java.sql.PreparedStatement; import java.sql.SQLException; import java.util.Collection; import java.util.List; @@ -23,7 +21,8 @@ import java.util.List; /** * SQL执行类
* 此执行类只接受方言参数,不需要数据源,只有在执行方法时需要数据库连接对象
- * 此对象存在的意义在于,可以由使用者自定义数据库连接对象,并执行多个方法,方便事务的统一控制或减少连接对象的创建关闭 + * 此对象存在的意义在于,可以由使用者自定义数据库连接对象,并执行多个方法,方便事务的统一控制或减少连接对象的创建关闭
+ * 相比{@link DialectRunner},此类中提供了更多重载方法 * * @author Luxiaolei */ @@ -83,25 +82,6 @@ public class SqlConnRunner extends DialectRunner { //---------------------------------------------------------------------------- CRUD start - /** - * 插入或更新数据
- * 此方法不会关闭Connection - * - * @param conn 数据库连接 - * @param record 记录 - * @param keys 需要检查唯一性的字段 - * @return 插入行数 - * @throws SQLException SQL执行异常 - */ - public int insertOrUpdate(Connection conn, Entity record, String... keys) throws SQLException { - final Entity where = record.filter(keys); - if (MapUtil.isNotEmpty(where) && count(conn, where) > 0) { - return update(conn, record, where); - } else { - return insert(conn, record); - } - } - /** * 批量插入数据
* 需要注意的是,批量插入每一条数据结构必须一致。批量插入数据时会获取第一条数据的字段结构,之后的数据会按照这个格式插入。
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 16058bc4c..d37a7e867 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 @@ -144,13 +144,15 @@ public interface Dialect extends Serializable { } /** - * 构建用于upsert的PreparedStatement + * 构建用于upsert的PreparedStatement
+ * 方言实现需实现此默认方法,默认返回{@code null} * * @param conn 数据库连接对象 * @param entity 数据实体类(包含表名) * @param keys 查找字段 * @return PreparedStatement * @throws SQLException SQL执行异常 + * @since 5.7.20 */ default PreparedStatement psForUpsert(Connection conn, Entity entity, String... keys) throws SQLException { return null; 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 ac666054b..fcf09c671 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 @@ -310,7 +310,6 @@ public class SqlBuilder implements Builder { if (null != wrapper) { // 包装表名 - // entity = wrapper.wrap(entity); entity.setTableName(wrapper.wrap(entity.getTableName())); } diff --git a/hutool-db/src/main/java/cn/hutool/db/sql/Wrapper.java b/hutool-db/src/main/java/cn/hutool/db/sql/Wrapper.java index fd823804e..12ee7778d 100644 --- a/hutool-db/src/main/java/cn/hutool/db/sql/Wrapper.java +++ b/hutool-db/src/main/java/cn/hutool/db/sql/Wrapper.java @@ -7,6 +7,7 @@ import cn.hutool.core.util.CharUtil; import cn.hutool.core.util.StrUtil; import cn.hutool.db.Entity; +import java.io.Serializable; import java.util.Arrays; import java.util.Collection; import java.util.Map.Entry; @@ -17,7 +18,8 @@ import java.util.Map.Entry; * @author Looly * */ -public class Wrapper { +public class Wrapper implements Serializable { + private static final long serialVersionUID = 1L; /** 前置包装符号 */ private Character preWrapQuote; From 9da17cf6c4431e846063f31e6c4760a1fc1e77fc Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 15 Jan 2022 09:58:14 +0800 Subject: [PATCH 04/28] add doc --- CHANGELOG.md | 3 ++- hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java | 7 ++++++- .../src/main/java/cn/hutool/core/util/EscapeUtil.java | 1 + .../src/test/java/cn/hutool/core/io/FileUtilTest.java | 8 ++++++++ 4 files changed, 17 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 0fef330cd..4a327694d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.7.20 (2022-01-14) +# 5.7.20 (2022-01-15) ### 🐣新特性 * 【core 】 增加对null值友好的groupingBy操作的Collector实现,可指定map类型(pr#498@Gitee) @@ -18,6 +18,7 @@ * 【core 】 修复RegexPool汉字匹配范围小问题(pr#2081@Github) * 【core 】 修复OS中的拼写错误(pr#500@Gitee) * 【core 】 修复CustomKeyMap的merge失效问题(issue#2086@Github) +* 【core 】 修复FileUtil.appendLines换行问题(issue#I4QCEZ@Gitee) ------------------------------------------------------------------------------------------------------------- # 5.7.19 (2022-01-07) diff --git a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java index c658db8cb..3d4b80858 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/FileUtil.java @@ -2876,7 +2876,12 @@ public class FileUtil extends PathUtil { } /** - * 将列表写入文件,追加模式 + * 将列表写入文件,追加模式,策略为: + *
    + *
  • 当文件为空,从开头追加,尾部不加空行
  • + *
  • 当有内容,换行追加,尾部不加空行
  • + *
  • 当有内容,并末尾有空行,依旧换行追加
  • + *
* * @param 集合元素类型 * @param list 列表 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/EscapeUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/EscapeUtil.java index 05beb38ac..f2b3dee92 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/EscapeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/EscapeUtil.java @@ -10,6 +10,7 @@ import cn.hutool.core.text.escape.XmlUnescape; * 转义和反转义工具类Escape / Unescape
* escape采用ISO Latin字符集对指定的字符串进行编码。
* 所有的空格符、标点符号、特殊字符以及其他非ASCII字符都将被转化成%xx格式的字符编码(xx等于该字符在字符集表里面的编码的16进制数字)。 + * TODO 6.x迁移到core.text.escape包下 * * @author xiaoleilu */ diff --git a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java index 712b19b5a..13bf3382f 100644 --- a/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/io/FileUtilTest.java @@ -1,5 +1,6 @@ package cn.hutool.core.io; +import cn.hutool.core.collection.ListUtil; import cn.hutool.core.io.file.LineSeparator; import cn.hutool.core.lang.Console; import cn.hutool.core.util.CharsetUtil; @@ -444,4 +445,11 @@ public class FileUtilTest { File file2 = new File("."); Assert.assertTrue(FileUtil.isSub(file, file2)); } + + @Test + @Ignore + public void appendLinesTest(){ + List list = ListUtil.toList("a", "b", "c"); + FileUtil.appendLines(list, FileUtil.file("d:/test/appendLines.txt"), CharsetUtil.CHARSET_UTF_8); + } } From 78ac9fcdefb4a0b3693e020377fe537b65e081ec Mon Sep 17 00:00:00 2001 From: VampireAchao Date: Sat, 15 Jan 2022 11:48:51 +0800 Subject: [PATCH 05/28] =?UTF-8?q?=E6=8F=90=E4=BA=A4Collectors.toMap?= =?UTF-8?q?=E7=9A=84=E5=AF=B9null=E5=8F=8B=E5=A5=BD=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=EF=BC=8C=E9=81=BF=E5=85=8DNPE?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/collection/CollStreamUtil.java | 2 +- .../cn/hutool/core/stream/CollectorUtil.java | 83 +++++++++++++++++-- .../core/collection/CollStreamUtilTest.java | 7 ++ 3 files changed, 83 insertions(+), 9 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollStreamUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollStreamUtil.java index ef3893d6d..b70f24a62 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollStreamUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollStreamUtil.java @@ -199,7 +199,7 @@ public class CollStreamUtil { if (CollUtil.isEmpty(collection) || key1 == null || key2 == null) { return Collections.emptyMap(); } - return groupBy(collection, key1, Collectors.toMap(key2, Function.identity(), (l, r) -> l), isParallel); + return groupBy(collection, key1, CollectorUtil.toMap(key2, Function.identity(), (l, r) -> l), isParallel); } /** diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java index 55e96877c..15e0d6d02 100644 --- a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java @@ -7,6 +7,7 @@ import java.util.Collections; import java.util.EnumSet; import java.util.HashMap; import java.util.Map; +import java.util.Set; import java.util.StringJoiner; import java.util.function.BiConsumer; import java.util.function.BinaryOperator; @@ -22,6 +23,16 @@ import java.util.stream.Collector; */ public class CollectorUtil { + /** + * 说明已包含IDENTITY_FINISH特征 为 Characteristics.IDENTITY_FINISH 的缩写 + */ + public static final Set CH_ID + = Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH)); + /** + * 说明不包含IDENTITY_FINISH特征 + */ + public static final Set CH_NOID = Collections.emptySet(); + /** * 提供任意对象的Join操作的{@link Collector}实现,对象默认调用toString方法 * @@ -93,17 +104,12 @@ public class CollectorUtil { A container = m.computeIfAbsent(key, k -> downstreamSupplier.get()); downstreamAccumulator.accept(container, t); }; - BinaryOperator> merger = (m1, m2) -> { - for (Map.Entry e : m2.entrySet()) { - m1.merge(e.getKey(), e.getValue(), downstream.combiner()); - } - return m1; - }; + BinaryOperator> merger = mapMerger(downstream.combiner()); @SuppressWarnings("unchecked") Supplier> mangledFactory = (Supplier>) mapFactory; if (downstream.characteristics().contains(Collector.Characteristics.IDENTITY_FINISH)) { - return new SimpleCollector<>(mangledFactory, accumulator, merger, Collections.unmodifiableSet(EnumSet.of(Collector.Characteristics.IDENTITY_FINISH))); + return new SimpleCollector<>(mangledFactory, accumulator, merger, CH_ID); } else { @SuppressWarnings("unchecked") Function downstreamFinisher = (Function) downstream.finisher(); @@ -113,7 +119,7 @@ public class CollectorUtil { M castResult = (M) intermediate; return castResult; }; - return new SimpleCollector<>(mangledFactory, accumulator, merger, finisher, Collections.emptySet()); + return new SimpleCollector<>(mangledFactory, accumulator, merger, finisher, CH_NOID); } } @@ -134,4 +140,65 @@ public class CollectorUtil { return groupingBy(classifier, HashMap::new, downstream); } + + /** + * 对null友好的 toMap 操作的 {@link Collector}实现,默认使用HashMap + * + * @param keyMapper 指定map中的key + * @param valueMapper 指定map中的value + * @param mergeFunction 合并前对value进行的操作 + * @param 实体类型 + * @param map中key的类型 + * @param map中value的类型 + * @return 对null友好的 toMap 操作的 {@link Collector}实现 + */ + public static + Collector> toMap(Function keyMapper, + Function valueMapper, + BinaryOperator mergeFunction) { + return toMap(keyMapper, valueMapper, mergeFunction, HashMap::new); + } + + /** + * 对null友好的 toMap 操作的 {@link Collector}实现 + * + * @param keyMapper 指定map中的key + * @param valueMapper 指定map中的value + * @param mergeFunction 合并前对value进行的操作 + * @param mapSupplier 最终需要的map类型 + * @param 实体类型 + * @param map中key的类型 + * @param map中value的类型 + * @param map的类型 + * @return 对null友好的 toMap 操作的 {@link Collector}实现 + */ + public static > + Collector toMap(Function keyMapper, + Function valueMapper, + BinaryOperator mergeFunction, + Supplier mapSupplier) { + BiConsumer accumulator + = (map, element) -> map.put(Opt.ofNullable(element).map(keyMapper).get(), Opt.ofNullable(element).map(valueMapper).get()); + return new SimpleCollector<>(mapSupplier, accumulator, mapMerger(mergeFunction), CH_ID); + } + + /** + * 用户合并map的BinaryOperator,传入合并前需要对value进行的操作 + * + * @param mergeFunction 合并前需要对value进行的操作 + * @param key的类型 + * @param value的类型 + * @param map + * @return 用户合并map的BinaryOperator + */ + public static > BinaryOperator mapMerger(BinaryOperator mergeFunction) { + return (m1, m2) -> { + for (Map.Entry e : m2.entrySet()) { + m1.merge(e.getKey(), e.getValue(), mergeFunction); + } + return m1; + }; + } + + } diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/CollStreamUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/CollStreamUtilTest.java index 7c95075af..69da448ba 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/CollStreamUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/CollStreamUtilTest.java @@ -150,6 +150,13 @@ public class CollStreamUtilTest { compare.put(2L, map2); Assert.assertEquals(compare, map); + // 对null友好 + Map> termIdClassIdStudentMap = CollStreamUtil.group2Map(Arrays.asList(null, new Student(2, 2, 1, "王五")), Student::getTermId, Student::getClassId); + Map> termIdClassIdStudentCompareMap = new HashMap>() {{ + put(null, MapUtil.of(null, null)); + put(2L, MapUtil.of(2L, new Student(2, 2, 1, "王五"))); + }}; + Assert.assertEquals(termIdClassIdStudentCompareMap, termIdClassIdStudentMap); } @Test From 8e67251fd393b9ad955c2ca97d24b1db230ff7be Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 15 Jan 2022 13:08:46 +0800 Subject: [PATCH 06/28] fix upsert --- .../cn/hutool/core/collection/CollUtil.java | 12 +- .../main/java/cn/hutool/core/map/MapUtil.java | 19 ++ .../hutool/core/collection/CollUtilTest.java | 9 + .../main/java/cn/hutool/db/DialectRunner.java | 7 +- .../main/java/cn/hutool/db/StatementUtil.java | 2 +- .../java/cn/hutool/db/dialect/Dialect.java | 42 ++-- .../db/dialect/impl/AnsiSqlDialect.java | 11 +- .../cn/hutool/db/dialect/impl/H2Dialect.java | 51 +++-- .../hutool/db/dialect/impl/MysqlDialect.java | 50 ++++- .../hutool/db/dialect/impl/OracleDialect.java | 28 ++- .../db/dialect/impl/PhoenixDialect.java | 7 + .../db/dialect/impl/PostgresqlDialect.java | 56 ++++-- .../java/cn/hutool/db/sql/SqlBuilder.java | 187 ++++-------------- .../main/java/cn/hutool/db/sql/Wrapper.java | 56 ++++-- .../src/test/java/cn/hutool/db/H2Test.java | 2 +- 15 files changed, 298 insertions(+), 241 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java index 201308b2e..dd22d5275 100644 --- a/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/collection/CollUtil.java @@ -2343,11 +2343,7 @@ public class CollUtil { */ @SuppressWarnings("unchecked") public static ArrayList valuesOfKeys(Map map, K... keys) { - final ArrayList values = new ArrayList<>(); - for (K k : keys) { - values.add(map.get(k)); - } - return values; + return MapUtil.valuesOfKeys(map, new ArrayIter<>(keys)); } /** @@ -2377,11 +2373,7 @@ public class CollUtil { * @since 3.0.9 */ public static ArrayList valuesOfKeys(Map map, Iterator keys) { - final ArrayList values = new ArrayList<>(); - while (keys.hasNext()) { - values.add(map.get(keys.next())); - } - return values; + return MapUtil.valuesOfKeys(map, keys); } // ------------------------------------------------------------------------------------------------- sort diff --git a/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java b/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java index 7bbce4c4b..b0fb79250 100644 --- a/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/map/MapUtil.java @@ -1354,4 +1354,23 @@ public class MapUtil { } } } + + /** + * 从Map中获取指定键列表对应的值列表
+ * 如果key在map中不存在或key对应值为null,则返回值列表对应位置的值也为null + * + * @param 键类型 + * @param 值类型 + * @param map {@link Map} + * @param keys 键列表 + * @return 值列表 + * @since 5.7.20 + */ + public static ArrayList valuesOfKeys(Map map, Iterator keys) { + final ArrayList values = new ArrayList<>(); + while (keys.hasNext()) { + values.add(map.get(keys.next())); + } + return values; + } } diff --git a/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java index 4e866979d..ad0582e18 100644 --- a/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/collection/CollUtilTest.java @@ -4,6 +4,7 @@ import cn.hutool.core.comparator.ComparableComparator; import cn.hutool.core.date.DateUtil; import cn.hutool.core.lang.Dict; import cn.hutool.core.map.MapUtil; +import cn.hutool.core.util.StrUtil; import lombok.AllArgsConstructor; import lombok.Data; import org.junit.Assert; @@ -302,6 +303,14 @@ public class CollUtilTest { Assert.assertEquals(CollUtil.newArrayList("b", "c"), filtered); } + @Test + public void filterSetTest() { + Set set = CollUtil.newLinkedHashSet("a", "b", "", " ", "c"); + Set filtered = CollUtil.filter(set, StrUtil::isNotBlank); + + Assert.assertEquals(CollUtil.newLinkedHashSet("a", "b", "c"), filtered); + } + @Test public void filterRemoveTest() { ArrayList list = CollUtil.newArrayList("a", "b", "c"); diff --git a/hutool-db/src/main/java/cn/hutool/db/DialectRunner.java b/hutool-db/src/main/java/cn/hutool/db/DialectRunner.java index 26e18bc09..24c535c4b 100644 --- a/hutool-db/src/main/java/cn/hutool/db/DialectRunner.java +++ b/hutool-db/src/main/java/cn/hutool/db/DialectRunner.java @@ -99,7 +99,12 @@ public class DialectRunner implements Serializable { * @since 5.7.20 */ public int upsert(Connection conn, Entity record, String... keys) throws SQLException { - PreparedStatement ps = getDialect().psForUpsert(conn, record, keys); + PreparedStatement ps = null; + try{ + ps = getDialect().psForUpsert(conn, record, keys); + }catch (SQLException ignore){ + // 方言不支持,使用默认 + } if (null != ps) { try { return ps.executeUpdate(); 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 b3a6eed12..b821c819e 100644 --- a/hutool-db/src/main/java/cn/hutool/db/StatementUtil.java +++ b/hutool-db/src/main/java/cn/hutool/db/StatementUtil.java @@ -194,7 +194,7 @@ public class StatementUtil { * @throws SQLException SQL异常 * @since 4.6.7 */ - public static PreparedStatement prepareStatementForBatch(Connection conn, String sql, List fields, Entity... entities) throws SQLException { + public static PreparedStatement prepareStatementForBatch(Connection conn, String sql, Iterable fields, Entity... entities) throws SQLException { Assert.notBlank(sql, "Sql String must be not blank!"); sql = sql.trim(); 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 d37a7e867..6ad5922bd 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 @@ -37,7 +37,8 @@ public interface Dialect extends Serializable { // -------------------------------------------- Execute /** - * 构建用于插入的PreparedStatement + * 构建用于插入的{@link PreparedStatement}
+ * 用户实现需按照数据库方言格式,将{@link Entity}转换为带有占位符的SQL语句及参数列表 * * @param conn 数据库连接对象 * @param entity 数据实体类(包含表名) @@ -47,7 +48,8 @@ public interface Dialect extends Serializable { PreparedStatement psForInsert(Connection conn, Entity entity) throws SQLException; /** - * 构建用于批量插入的PreparedStatement + * 构建用于批量插入的PreparedStatement
+ * 用户实现需按照数据库方言格式,将{@link Entity}转换为带有占位符的SQL语句及参数列表 * * @param conn 数据库连接对象 * @param entities 数据实体,实体的结构必须全部一致,否则插入结果将不可预知 @@ -57,7 +59,9 @@ public interface Dialect extends Serializable { PreparedStatement psForInsertBatch(Connection conn, Entity... entities) throws SQLException; /** - * 构建用于删除的PreparedStatement + * 构建用于删除的{@link PreparedStatement}
+ * 用户实现需按照数据库方言格式,将{@link Query}转换为带有占位符的SQL语句及参数列表
+ * {@link Query}中包含了删除所需的表名、查询条件等信息,可借助SqlBuilder完成SQL语句生成。 * * @param conn 数据库连接对象 * @param query 查找条件(包含表名) @@ -67,7 +71,9 @@ public interface Dialect extends Serializable { PreparedStatement psForDelete(Connection conn, Query query) throws SQLException; /** - * 构建用于更新的PreparedStatement + * 构建用于更新的{@link PreparedStatement}
+ * 用户实现需按照数据库方言格式,将{@link Entity}配合{@link Query}转换为带有占位符的SQL语句及参数列表
+ * 其中{@link Entity}中包含需要更新的数据信息,{@link Query}包含更新的查找条件信息。 * * @param conn 数据库连接对象 * @param entity 数据实体类(包含表名) @@ -80,7 +86,9 @@ public interface Dialect extends Serializable { // -------------------------------------------- Query /** - * 构建用于获取多条记录的PreparedStatement + * 构建用于获取多条记录的{@link PreparedStatement}
+ * 用户实现需按照数据库方言格式,将{@link Query}转换为带有占位符的SQL语句及参数列表
+ * {@link Query}中包含了查询所需的表名、查询条件等信息,可借助SqlBuilder完成SQL语句生成。 * * @param conn 数据库连接对象 * @param query 查询条件(包含表名) @@ -90,7 +98,9 @@ public interface Dialect extends Serializable { PreparedStatement psForFind(Connection conn, Query query) throws SQLException; /** - * 构建用于分页查询的PreparedStatement + * 构建用于分页查询的{@link PreparedStatement}
+ * 用户实现需按照数据库方言格式,将{@link Query}转换为带有占位符的SQL语句及参数列表
+ * {@link Query}中包含了分页查询所需的表名、查询条件、分页等信息,可借助SqlBuilder完成SQL语句生成。 * * @param conn 数据库连接对象 * @param query 查询条件(包含表名) @@ -100,7 +110,7 @@ public interface Dialect extends Serializable { PreparedStatement psForPage(Connection conn, Query query) throws SQLException; /** - * 构建用于分页查询的PreparedStatement
+ * 构建用于分页查询的{@link PreparedStatement}
* 可以在此方法中使用{@link SqlBuilder#orderBy(Order...)}方法加入排序信息, * 排序信息通过{@link Page#getOrders()}获取 * @@ -114,7 +124,9 @@ public interface Dialect extends Serializable { PreparedStatement psForPage(Connection conn, SqlBuilder sqlBuilder, Page page) throws SQLException; /** - * 构建用于查询行数的PreparedStatement + * 构建用于查询行数的{@link PreparedStatement}
+ * 用户实现需按照数据库方言格式,将{@link Query}转换为带有占位符的SQL语句及参数列表
+ * {@link Query}中包含了表名、查询条件等信息,可借助SqlBuilder完成SQL语句生成。 * * @param conn 数据库连接对象 * @param query 查询条件(包含表名) @@ -127,7 +139,9 @@ public interface Dialect extends Serializable { } /** - * 构建用于查询行数的PreparedStatement + * 构建用于查询行数的{@link PreparedStatement}
+ * 用户实现需按照数据库方言格式,将{@link Query}转换为带有占位符的SQL语句及参数列表
+ * {@link Query}中包含了表名、查询条件等信息,可借助SqlBuilder完成SQL语句生成。 * * @param conn 数据库连接对象 * @param sqlBuilder 查询语句,应该包含分页等信息 @@ -144,18 +158,18 @@ public interface Dialect extends Serializable { } /** - * 构建用于upsert的PreparedStatement
- * 方言实现需实现此默认方法,默认返回{@code null} + * 构建用于upsert的{@link PreparedStatement}
+ * 方言实现需实现此默认方法,如果没有实现,抛出{@link SQLException} * * @param conn 数据库连接对象 * @param entity 数据实体类(包含表名) - * @param keys 查找字段 + * @param keys 查找字段,某些数据库此字段必须,如H2,某些数据库无需此字段,如MySQL(通过主键) * @return PreparedStatement - * @throws SQLException SQL执行异常 + * @throws SQLException SQL执行异常,或方言数据不支持此操作 * @since 5.7.20 */ default PreparedStatement psForUpsert(Connection conn, Entity entity, String... keys) throws SQLException { - return null; + throw new SQLException("Unsupported upsert operation of " + 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 3793d41f0..a615a45f3 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,5 +1,6 @@ package cn.hutool.db.dialect.impl; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; @@ -17,16 +18,17 @@ import cn.hutool.db.sql.Wrapper; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; +import java.util.Set; /** * ANSI SQL 方言 - * + * * @author loolly * */ public class AnsiSqlDialect implements Dialect { private static final long serialVersionUID = 2088101129774974580L; - + protected Wrapper wrapper = new Wrapper(); @Override @@ -53,7 +55,8 @@ public class AnsiSqlDialect implements Dialect { } // 批量,根据第一行数据结构生成SQL占位符 final SqlBuilder insert = SqlBuilder.create(wrapper).insert(entities[0], this.dialectName()); - return StatementUtil.prepareStatementForBatch(conn, insert.build(), insert.getFields(), entities); + final Set fields = CollUtil.filter(entities[0].keySet(), StrUtil::isNotBlank); + return StatementUtil.prepareStatementForBatch(conn, insert.build(), fields, entities); } @Override @@ -113,7 +116,7 @@ public class AnsiSqlDialect implements Dialect { /** * 根据不同数据库在查询SQL语句基础上包装其分页的语句
* 各自数据库通过重写此方法实现最小改动情况下修改分页语句 - * + * * @param find 标准查询语句 * @param page 分页对象 * @return 分页语句 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 917980049..110aea5f6 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 @@ -2,19 +2,16 @@ package cn.hutool.db.dialect.impl; import cn.hutool.core.lang.Assert; import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; import cn.hutool.db.Entity; import cn.hutool.db.Page; import cn.hutool.db.StatementUtil; import cn.hutool.db.dialect.DialectName; -import cn.hutool.db.sql.Condition; -import cn.hutool.db.sql.Query; import cn.hutool.db.sql.SqlBuilder; import java.sql.Connection; import java.sql.PreparedStatement; import java.sql.SQLException; -import java.util.Arrays; -import java.util.function.Function; /** * H2数据库方言 @@ -39,18 +36,42 @@ public class H2Dialect extends AnsiSqlDialect { return find.append(" limit ").append(page.getStartPosition()).append(" , ").append(page.getPageSize()); } - /** - * 构建用于upsert的PreparedStatement - * - * @param conn 数据库连接对象 - * @param entity 数据实体类(包含表名) - * @param keys 查找字段 如果不提供keys将自动使用主键 - * @return PreparedStatement - * @throws SQLException SQL执行异常 - */ @Override public PreparedStatement psForUpsert(Connection conn, Entity entity, String... keys) throws SQLException { - final SqlBuilder upsert = SqlBuilder.create(wrapper).upsert(entity, this.dialectName(),keys); - return StatementUtil.prepareStatement(conn, upsert); + Assert.notEmpty(keys, "Keys must be not empty for H2 MERGE SQL."); + SqlBuilder.validateEntity(entity); + final SqlBuilder builder = SqlBuilder.create(wrapper); + + final StringBuilder fieldsPart = new StringBuilder(); + final StringBuilder placeHolder = new StringBuilder(); + + // 构建字段部分和参数占位符部分 + entity.forEach((field, value)->{ + if (StrUtil.isNotBlank(field)) { + if (fieldsPart.length() > 0) { + // 非第一个参数,追加逗号 + fieldsPart.append(", "); + placeHolder.append(", "); + } + + fieldsPart.append((null != wrapper) ? wrapper.wrap(field) : field); + placeHolder.append("?"); + builder.addParams(value); + } + }); + + String tableName = entity.getTableName(); + if (null != this.wrapper) { + tableName = this.wrapper.wrap(tableName); + } + builder.append("MERGE INTO ").append(tableName) + // 字段列表 + .append(" (").append(fieldsPart) + // 更新关键字列表 + .append(") KEY(").append(ArrayUtil.join(keys, ", ")) + // 更新值列表 + .append(") VALUES (").append(placeHolder).append(")"); + + return StatementUtil.prepareStatement(conn, builder); } } 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 3ce7a199a..a52cfa759 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 @@ -1,5 +1,6 @@ package cn.hutool.db.dialect.impl; +import cn.hutool.core.util.StrUtil; import cn.hutool.db.Entity; import cn.hutool.db.Page; import cn.hutool.db.StatementUtil; @@ -34,17 +35,58 @@ public class MysqlDialect extends AnsiSqlDialect{ } /** - * 构建用于upsert的PreparedStatement + * 构建用于upsert的{@link PreparedStatement}
+ * MySQL通过主键方式实现Upsert,故keys无效,生成SQL语法为: + *
+	 *     INSERT INTO demo(a,b,c) values(?, ?, ?) ON DUPLICATE KEY UPDATE a=values(a), b=values(b), c=values(c);
+	 * 
* * @param conn 数据库连接对象 * @param entity 数据实体类(包含表名) - * @param keys 查找字段 + * @param keys 此参数无效 * @return PreparedStatement * @throws SQLException SQL执行异常 + * @since 5.7.20 */ @Override public PreparedStatement psForUpsert(Connection conn, Entity entity, String... keys) throws SQLException { - final SqlBuilder upsert = SqlBuilder.create(wrapper).upsert(entity, this.dialectName(),keys); - return StatementUtil.prepareStatement(conn, upsert); + SqlBuilder.validateEntity(entity); + final SqlBuilder builder = SqlBuilder.create(wrapper); + + final StringBuilder fieldsPart = new StringBuilder(); + final StringBuilder placeHolder = new StringBuilder(); + final StringBuilder updateHolder = new StringBuilder(); + + // 构建字段部分和参数占位符部分 + entity.forEach((field, value)->{ + if (StrUtil.isNotBlank(field)) { + if (fieldsPart.length() > 0) { + // 非第一个参数,追加逗号 + fieldsPart.append(", "); + placeHolder.append(", "); + updateHolder.append(", "); + } + + field = (null != wrapper) ? wrapper.wrap(field) : field; + fieldsPart.append(field); + updateHolder.append(field).append("=values(").append(field).append(")"); + placeHolder.append("?"); + builder.addParams(value); + } + }); + + String tableName = entity.getTableName(); + if (null != this.wrapper) { + tableName = this.wrapper.wrap(tableName); + } + builder.append("INSERT INTO ").append(tableName) + // 字段列表 + .append(" (").append(fieldsPart) + // 更新值列表 + .append(") VALUES (").append(placeHolder) + // 主键冲突后的更新操作 + .append(") ON DUPLICATE KEY UPDATE ").append(updateHolder); + + return StatementUtil.prepareStatement(conn, builder); } } 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 925889611..037aaf8a0 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 @@ -1,32 +1,44 @@ package cn.hutool.db.dialect.impl; +import cn.hutool.core.util.StrUtil; import cn.hutool.db.Page; import cn.hutool.db.dialect.DialectName; import cn.hutool.db.sql.SqlBuilder; /** * Oracle 方言 - * @author loolly * + * @author loolly */ -public class OracleDialect extends AnsiSqlDialect{ +public class OracleDialect extends AnsiSqlDialect { private static final long serialVersionUID = 6122761762247483015L; + /** + * 检查字段值是否为Oracle自增字段,自增字段以`.nextval`结尾 + * + * @param value 检查的字段值 + * @return 是否为Oracle自增字段 + * @since 5.7.20 + */ + public static boolean isNextVal(Object value) { + return (value instanceof CharSequence) && StrUtil.endWithIgnoreCase(value.toString(), ".nextval"); + } + public OracleDialect() { //Oracle所有字段名用双引号包围,防止字段名或表名与系统关键字冲突 //wrapper = new Wrapper('"'); } - + @Override protected SqlBuilder wrapPageSql(SqlBuilder find, Page page) { final int[] startEnd = page.getStartEnd(); return find - .insertPreFragment("SELECT * FROM ( SELECT row_.*, rownum rownum_ from ( ") - .append(" ) row_ where rownum <= ").append(startEnd[1])// - .append(") table_alias")// - .append(" where table_alias.rownum_ > ").append(startEnd[0]);// + .insertPreFragment("SELECT * FROM ( SELECT row_.*, rownum rownum_ from ( ") + .append(" ) row_ where rownum <= ").append(startEnd[1])// + .append(") table_alias")// + .append(" where table_alias.rownum_ > ").append(startEnd[0]);// } - + @Override public String dialectName() { return DialectName.ORACLE.name(); diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/PhoenixDialect.java b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/PhoenixDialect.java index c2ad5bdd5..8064e8982 100644 --- a/hutool-db/src/main/java/cn/hutool/db/dialect/impl/PhoenixDialect.java +++ b/hutool-db/src/main/java/cn/hutool/db/dialect/impl/PhoenixDialect.java @@ -24,6 +24,7 @@ public class PhoenixDialect extends AnsiSqlDialect { @Override public PreparedStatement psForUpdate(Connection conn, Entity entity, Query query) throws SQLException { // Phoenix的插入、更新语句是统一的,统一使用upsert into关键字 + // Phoenix只支持通过主键更新操作,因此query无效,自动根据entity中的主键更新 return super.psForInsert(conn, entity); } @@ -31,4 +32,10 @@ public class PhoenixDialect extends AnsiSqlDialect { public String dialectName() { return DialectName.PHOENIX.name(); } + + @Override + public PreparedStatement psForUpsert(Connection conn, Entity entity, String... keys) throws SQLException { + // Phoenix只支持通过主键更新操作,因此query无效,自动根据entity中的主键更新 + return psForInsert(conn, entity); + } } 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 82f5fe373..1f5a90122 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 @@ -1,5 +1,8 @@ package cn.hutool.db.dialect.impl; +import cn.hutool.core.lang.Assert; +import cn.hutool.core.util.ArrayUtil; +import cn.hutool.core.util.StrUtil; import cn.hutool.db.Entity; import cn.hutool.db.StatementUtil; import cn.hutool.db.dialect.DialectName; @@ -28,21 +31,48 @@ public class PostgresqlDialect extends AnsiSqlDialect{ return DialectName.POSTGREESQL.name(); } - /** - * 构建用于upsert的PreparedStatement - * - * @param conn 数据库连接对象 - * @param entity 数据实体类(包含表名) - * @param keys 查找字段 必须是有唯一索引的列且不能为空 - * @return PreparedStatement - * @throws SQLException SQL执行异常 - */ @Override public PreparedStatement psForUpsert(Connection conn, Entity entity, String... keys) throws SQLException { - if (null==keys || keys.length==0){ - throw new SQLException("keys不能为空"); + Assert.notEmpty(keys, "Keys must be not empty for Postgres."); + SqlBuilder.validateEntity(entity); + final SqlBuilder builder = SqlBuilder.create(wrapper); + + final StringBuilder fieldsPart = new StringBuilder(); + final StringBuilder placeHolder = new StringBuilder(); + final StringBuilder updateHolder = new StringBuilder(); + + // 构建字段部分和参数占位符部分 + entity.forEach((field, value)->{ + if (StrUtil.isNotBlank(field)) { + if (fieldsPart.length() > 0) { + // 非第一个参数,追加逗号 + fieldsPart.append(", "); + placeHolder.append(", "); + updateHolder.append(", "); + } + + final String wrapedField = (null != wrapper) ? wrapper.wrap(field) : field; + fieldsPart.append(wrapedField); + updateHolder.append(wrapedField).append("=EXCLUDED.").append(field); + placeHolder.append("?"); + builder.addParams(value); + } + }); + + String tableName = entity.getTableName(); + if (null != this.wrapper) { + tableName = this.wrapper.wrap(tableName); } - final SqlBuilder upsert = SqlBuilder.create(wrapper).upsert(entity, this.dialectName(),keys); - return StatementUtil.prepareStatement(conn, upsert); + builder.append("INSERT INTO ").append(tableName) + // 字段列表 + .append(" (").append(fieldsPart) + // 更新值列表 + .append(") VALUES (").append(placeHolder) + // 定义检查冲突的主键或字段 + .append(") ON CONFLICT (").append(ArrayUtil.join(keys,", ")) + // 主键冲突后的更新操作 + .append(") DO UPDATE SET ").append(updateHolder); + + return StatementUtil.prepareStatement(conn, builder); } } 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 fcf09c671..a2a52634d 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 @@ -7,13 +7,13 @@ import cn.hutool.core.util.StrUtil; import cn.hutool.db.DbRuntimeException; import cn.hutool.db.Entity; import cn.hutool.db.dialect.DialectName; +import cn.hutool.db.dialect.impl.OracleDialect; 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; /** * SQL构建器
@@ -57,6 +57,24 @@ public class SqlBuilder implements Builder { return create().append(sql); } + /** + * 验证实体类对象的有效性 + * + * @param entity 实体类对象 + * @throws DbRuntimeException SQL异常包装,获取元数据信息失败 + */ + public static void validateEntity(Entity entity) throws DbRuntimeException { + if (null == entity) { + throw new DbRuntimeException("Entity is null !"); + } + if (StrUtil.isBlank(entity.getTableName())) { + throw new DbRuntimeException("Entity`s table name is null !"); + } + if (entity.isEmpty()) { + throw new DbRuntimeException("No filed and value in this entity !"); + } + } + // --------------------------------------------------------------- Static methods end // --------------------------------------------------------------- Enums start @@ -87,10 +105,6 @@ public class SqlBuilder implements Builder { // --------------------------------------------------------------- Enums end private final StringBuilder sql = new StringBuilder(); - /** - * 字段列表(仅用于插入和更新) - */ - private final List fields = new ArrayList<>(); /** * 占位符对应的值列表 */ @@ -146,41 +160,29 @@ public class SqlBuilder implements Builder { // 验证 validateEntity(entity); - if (null != wrapper) { - // 包装表名 entity = wrapper.wrap(entity); - entity.setTableName(wrapper.wrap(entity.getTableName())); - } - final boolean isOracle = DialectName.ORACLE.match(dialectName);// 对Oracle的特殊处理 final StringBuilder fieldsPart = new StringBuilder(); final StringBuilder placeHolder = new StringBuilder(); - boolean isFirst = true; - String field; - Object value; - for (Entry entry : entity.entrySet()) { - field = entry.getKey(); - value = entry.getValue(); - if (StrUtil.isNotBlank(field) /* && null != value */) { - if (isFirst) { - isFirst = false; - } else { + entity.forEach((field, value) -> { + if (StrUtil.isNotBlank(field)) { + if (fieldsPart.length() > 0) { // 非第一个参数,追加逗号 fieldsPart.append(", "); placeHolder.append(", "); } - this.fields.add(field); fieldsPart.append((null != wrapper) ? wrapper.wrap(field) : field); - if (isOracle && value instanceof String && StrUtil.endWithIgnoreCase((String) value, ".nextval")) { + if (isOracle && OracleDialect.isNextVal(value)) { // Oracle的特殊自增键,通过字段名.nextval获得下一个值 placeHolder.append(value); } else { + // 普通字段使用占位符 placeHolder.append("?"); this.paramValues.add(value); } } - } + }); // issue#1656@Github Phoenix兼容 if (DialectName.PHOENIX.match(dialectName)) { @@ -189,94 +191,18 @@ public class SqlBuilder implements Builder { sql.append("INSERT INTO "); } - sql.append(entity.getTableName()) + String tableName = entity.getTableName(); + if (null != this.wrapper) { + // 包装表名 entity = wrapper.wrap(entity); + tableName = this.wrapper.wrap(tableName); + } + sql.append(tableName) .append(" (").append(fieldsPart).append(") VALUES (")// .append(placeHolder).append(")"); return this; } - /** - * 插入
- * 插入会忽略空的字段名及其对应值,但是对于有字段名对应值为{@code null}的情况不忽略 - * - * @param entity 实体 - * @param dialectName 方言名,用于对特殊数据库特殊处理 - * @param keys 根据何字段来确认唯一性,不传则用主键 - * @return 自己 - * @since 5.7.21 - */ - public SqlBuilder upsert(Entity entity, String dialectName, String... keys) { - // 验证 - validateEntity(entity); - - if (null != wrapper) { - // 包装表名 entity = wrapper.wrap(entity); - entity.setTableName(wrapper.wrap(entity.getTableName())); - } - - final boolean isOracle = DialectName.ORACLE.match(dialectName);// 对Oracle的特殊处理 - final StringBuilder fieldsPart = new StringBuilder(); - final StringBuilder placeHolder = new StringBuilder(); - - boolean isFirst = true; - String field; - Object value; - for (Entry entry : entity.entrySet()) { - field = entry.getKey(); - value = entry.getValue(); - if (StrUtil.isNotBlank(field) /* && null != value */) { - if (isFirst) { - isFirst = false; - } else { - // 非第一个参数,追加逗号 - fieldsPart.append(", "); - placeHolder.append(", "); - } - - this.fields.add(field); - fieldsPart.append((null != wrapper) ? wrapper.wrap(field) : field); - if (isOracle && value instanceof String && StrUtil.endWithIgnoreCase((String) value, ".nextval")) { - // Oracle的特殊自增键,通过字段名.nextval获得下一个值 - placeHolder.append(value); - } else { - placeHolder.append("?"); - this.paramValues.add(value); - } - } - } - - // issue#1656@Github Phoenix兼容 - if (DialectName.PHOENIX.match(dialectName)) { - sql.append("UPSERT INTO ").append(entity.getTableName()); - } else if (DialectName.MYSQL.match(dialectName)) { - sql.append("INSERT INTO "); - sql.append(entity.getTableName()) - .append(" (").append(fieldsPart).append(") VALUES (") - .append(placeHolder).append(") on duplicate key update ") - .append(ArrayUtil.join(ArrayUtil.map(entity.keySet().toArray(), String.class, (k) -> k + "=values(" + k + ")"), ",")); - } else if (DialectName.H2.match(dialectName)) { - sql.append("MERGE INTO ").append(entity.getTableName()); - if (null != keys && keys.length > 0) { - sql.append(" KEY(").append(ArrayUtil.join(keys, ",")) - .append(") VALUES (") - .append(placeHolder) - .append(")"); - } - } else if (DialectName.POSTGREESQL.match(dialectName)) { - sql.append("INSERT INTO "); - sql.append(entity.getTableName()) - .append(" (").append(fieldsPart).append(") VALUES (") - .append(placeHolder).append(") on conflict (") - .append(ArrayUtil.join(keys,",")) - .append(") do update set ") - .append(ArrayUtil.join(ArrayUtil.map(entity.keySet().toArray(), String.class, (k) -> k + "=excluded." + k ), ",")); - } else { - throw new RuntimeException(dialectName + " not support yet"); - } - return this; - } - /** * 删除 * @@ -308,25 +234,22 @@ public class SqlBuilder implements Builder { // 验证 validateEntity(entity); + String tableName = entity.getTableName(); if (null != wrapper) { // 包装表名 - entity.setTableName(wrapper.wrap(entity.getTableName())); + tableName = wrapper.wrap(tableName); } - sql.append("UPDATE ").append(entity.getTableName()).append(" SET "); - String field; - for (Entry entry : entity.entrySet()) { - field = entry.getKey(); + sql.append("UPDATE ").append(tableName).append(" SET "); + entity.forEach((field, value) -> { if (StrUtil.isNotBlank(field)) { if (paramValues.size() > 0) { sql.append(", "); } - this.fields.add(field); sql.append((null != wrapper) ? wrapper.wrap(field) : field).append(" = ? "); - this.paramValues.add(entry.getValue());// 更新不对空做处理,因为存在清空字段的情况 + this.paramValues.add(value);// 更新不对空做处理,因为存在清空字段的情况 } - } - + }); return this; } @@ -653,24 +576,6 @@ public class SqlBuilder implements Builder { } // --------------------------------------------------------------- Builder end - /** - * 获得插入或更新的数据库字段列表 - * - * @return 插入或更新的数据库字段列表 - */ - public String[] getFieldArray() { - return this.fields.toArray(new String[0]); - } - - /** - * 获得插入或更新的数据库字段列表 - * - * @return 插入或更新的数据库字段列表 - */ - public List getFields() { - return this.fields; - } - /** * 获得占位符对应的值列表
* @@ -725,23 +630,5 @@ public class SqlBuilder implements Builder { return ConditionBuilder.of(conditions).build(this.paramValues); } - - /** - * 验证实体类对象的有效性 - * - * @param entity 实体类对象 - * @throws DbRuntimeException SQL异常包装,获取元数据信息失败 - */ - private static void validateEntity(Entity entity) throws DbRuntimeException { - if (null == entity) { - throw new DbRuntimeException("Entity is null !"); - } - if (StrUtil.isBlank(entity.getTableName())) { - throw new DbRuntimeException("Entity`s table name is null !"); - } - if (entity.isEmpty()) { - throw new DbRuntimeException("No filed and value in this entity !"); - } - } // --------------------------------------------------------------- private method end } diff --git a/hutool-db/src/main/java/cn/hutool/db/sql/Wrapper.java b/hutool-db/src/main/java/cn/hutool/db/sql/Wrapper.java index 12ee7778d..8851b2eac 100644 --- a/hutool-db/src/main/java/cn/hutool/db/sql/Wrapper.java +++ b/hutool-db/src/main/java/cn/hutool/db/sql/Wrapper.java @@ -15,15 +15,19 @@ import java.util.Map.Entry; /** * 包装器
* 主要用于字段名的包装(在字段名的前后加字符,例如反引号来避免与数据库的关键字冲突) - * @author Looly * + * @author Looly */ public class Wrapper implements Serializable { private static final long serialVersionUID = 1L; - /** 前置包装符号 */ + /** + * 前置包装符号 + */ private Character preWrapQuote; - /** 后置包装符号 */ + /** + * 后置包装符号 + */ private Character sufWrapQuote; public Wrapper() { @@ -31,6 +35,7 @@ public class Wrapper implements Serializable { /** * 构造 + * * @param wrapQuote 单包装字符 */ public Wrapper(Character wrapQuote) { @@ -40,6 +45,7 @@ public class Wrapper implements Serializable { /** * 包装符号 + * * @param preWrapQuote 前置包装符号 * @param sufWrapQuote 后置包装符号 */ @@ -49,14 +55,17 @@ public class Wrapper implements Serializable { } //--------------------------------------------------------------- Getters and Setters start + /** * @return 前置包装符号 */ public char getPreWrapQuote() { return preWrapQuote; } + /** * 设置前置包装的符号 + * * @param preWrapQuote 前置包装符号 */ public void setPreWrapQuote(Character preWrapQuote) { @@ -69,8 +78,10 @@ public class Wrapper implements Serializable { public char getSufWrapQuote() { return sufWrapQuote; } + /** * 设置后置包装的符号 + * * @param sufWrapQuote 后置包装符号 */ public void setSufWrapQuote(Character sufWrapQuote) { @@ -81,26 +92,27 @@ public class Wrapper implements Serializable { /** * 包装字段名
* 有时字段与SQL的某些关键字冲突,导致SQL出错,因此需要将字段名用单引号或者反引号包装起来,避免冲突 + * * @param field 字段名 * @return 包装后的字段名 */ - public String wrap(String field){ - if(preWrapQuote == null || sufWrapQuote == null || StrUtil.isBlank(field)) { + public String wrap(String field) { + if (preWrapQuote == null || sufWrapQuote == null || StrUtil.isBlank(field)) { return field; } //如果已经包含包装的引号,返回原字符 - if(StrUtil.isSurround(field, preWrapQuote, sufWrapQuote)){ + if (StrUtil.isSurround(field, preWrapQuote, sufWrapQuote)) { return field; } //如果字段中包含通配符或者括号(字段通配符或者函数),不做包装 - if(StrUtil.containsAnyIgnoreCase(field, "*", "(", " ", " as ")) { + if (StrUtil.containsAnyIgnoreCase(field, "*", "(", " ", " as ")) { return field; } //对于Oracle这类数据库,表名中包含用户名需要单独拆分包装 - if(field.contains(StrUtil.DOT)){ + if (field.contains(StrUtil.DOT)) { final Collection target = CollUtil.edit(StrUtil.split(field, CharUtil.DOT, 2), t -> StrUtil.format("{}{}{}", preWrapQuote, t, sufWrapQuote)); return CollectionUtil.join(target, StrUtil.DOT); } @@ -111,16 +123,17 @@ public class Wrapper implements Serializable { /** * 包装字段名
* 有时字段与SQL的某些关键字冲突,导致SQL出错,因此需要将字段名用单引号或者反引号包装起来,避免冲突 + * * @param fields 字段名 * @return 包装后的字段名 */ - public String[] wrap(String... fields){ - if(ArrayUtil.isEmpty(fields)) { + public String[] wrap(String... fields) { + if (ArrayUtil.isEmpty(fields)) { return fields; } String[] wrappedFields = new String[fields.length]; - for(int i = 0; i < fields.length; i++) { + for (int i = 0; i < fields.length; i++) { wrappedFields[i] = wrap(fields[i]); } @@ -130,11 +143,12 @@ public class Wrapper implements Serializable { /** * 包装字段名
* 有时字段与SQL的某些关键字冲突,导致SQL出错,因此需要将字段名用单引号或者反引号包装起来,避免冲突 + * * @param fields 字段名 * @return 包装后的字段名 */ - public Collection wrap(Collection fields){ - if(CollectionUtil.isEmpty(fields)) { + public Collection wrap(Collection fields) { + if (CollectionUtil.isEmpty(fields)) { return fields; } @@ -142,13 +156,14 @@ public class Wrapper implements Serializable { } /** - * 包装字段名
+ * 包装表名和字段名,此方法返回一个新的Entity实体类
* 有时字段与SQL的某些关键字冲突,导致SQL出错,因此需要将字段名用单引号或者反引号包装起来,避免冲突 + * * @param entity 被包装的实体 - * @return 包装后的字段名 + * @return 新的实体 */ - public Entity wrap(Entity entity){ - if(null == entity) { + public Entity wrap(Entity entity) { + if (null == entity) { return null; } @@ -168,14 +183,15 @@ public class Wrapper implements Serializable { /** * 包装字段名
* 有时字段与SQL的某些关键字冲突,导致SQL出错,因此需要将字段名用单引号或者反引号包装起来,避免冲突 + * * @param conditions 被包装的实体 * @return 包装后的字段名 */ - public Condition[] wrap(Condition... conditions){ + public Condition[] wrap(Condition... conditions) { final Condition[] clonedConditions = new Condition[conditions.length]; - if(ArrayUtil.isNotEmpty(conditions)) { + if (ArrayUtil.isNotEmpty(conditions)) { Condition clonedCondition; - for(int i = 0; i < conditions.length; i++) { + for (int i = 0; i < conditions.length; i++) { clonedCondition = conditions[i].clone(); clonedCondition.setField(wrap(clonedCondition.getField())); clonedConditions[i] = clonedCondition; diff --git a/hutool-db/src/test/java/cn/hutool/db/H2Test.java b/hutool-db/src/test/java/cn/hutool/db/H2Test.java index 85bd27627..bc7e48dd7 100644 --- a/hutool-db/src/test/java/cn/hutool/db/H2Test.java +++ b/hutool-db/src/test/java/cn/hutool/db/H2Test.java @@ -1,6 +1,5 @@ package cn.hutool.db; -import com.alibaba.druid.support.json.JSONUtils; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; @@ -40,6 +39,7 @@ public class H2Test { List query = Db.use(DS_GROUP_NAME).find(Entity.create("test")); Assert.assertEquals(4, query.size()); } + @Test public void upsertTest() throws SQLException { Db db=Db.use(DS_GROUP_NAME); From da4d64fcbcd3f45aa5094f594f60a81b775ef9a5 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 15 Jan 2022 13:26:56 +0800 Subject: [PATCH 07/28] add xugu --- .../src/main/java/cn/hutool/db/dialect/DialectFactory.java | 3 +++ .../src/main/java/cn/hutool/db/dialect/DriverNamePool.java | 4 ++++ 2 files changed, 7 insertions(+) diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java b/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java index 5f6e76194..42d81cc58 100644 --- a/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java +++ b/hutool-db/src/main/java/cn/hutool/db/dialect/DialectFactory.java @@ -149,6 +149,9 @@ public class DialectFactory implements DriverNamePool{ } else if (nameContainsProductInfo.contains("sybase")) { // 神州数据库 driver = DRIVER_SYBASE; + } else if (nameContainsProductInfo.contains("xugu")) { + // 虚谷数据库 + driver = DRIVER_XUGO; } return driver; diff --git a/hutool-db/src/main/java/cn/hutool/db/dialect/DriverNamePool.java b/hutool-db/src/main/java/cn/hutool/db/dialect/DriverNamePool.java index 6da118859..56392945a 100644 --- a/hutool-db/src/main/java/cn/hutool/db/dialect/DriverNamePool.java +++ b/hutool-db/src/main/java/cn/hutool/db/dialect/DriverNamePool.java @@ -108,5 +108,9 @@ public interface DriverNamePool { * JDBC 驱动 Sybase */ String DRIVER_SYBASE = "com.sybase.jdbc4.jdbc.SybDriver"; + /** + * JDBC 驱动 虚谷 + */ + String DRIVER_XUGO = "com.xugu.cloudjdbc.Driver"; } From 4b44716b5fc78ef0dde8ec1febb4f2dfb30214d3 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 15 Jan 2022 13:30:44 +0800 Subject: [PATCH 08/28] add null ignore for CollectorUtil --- CHANGELOG.md | 1 + .../src/main/java/cn/hutool/core/stream/CollectorUtil.java | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4a327694d..68ca938c2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,7 @@ * 【json 】 JSONGetter增加getBeanList方法 * 【core 】 ObjectUtil 添加三个defaultIfXxxx方法,用于节省CPU及内存损耗(pr#2094@Github) * 【db 】 增加单条数据原生upsert语义支持(pr#501@Gitee) +* 【core 】 在CollectorUtil提交Collectors.toMap的对null友好实现,避免NPE(pr#502@Gitee) * ### 🐞Bug修复 * 【core 】 修复setter重载导致匹配错误(issue#2082@Github) diff --git a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java index 15e0d6d02..82821897d 100644 --- a/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/stream/CollectorUtil.java @@ -18,7 +18,7 @@ import java.util.stream.Collector; /** * 可变的汇聚操作{@link Collector} 相关工具封装 * - * @author looly + * @author looly, VampireAchao * @since 5.6.7 */ public class CollectorUtil { From 749c0f8727c0e7140ca1897bd1030a5bf37b1a30 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 15 Jan 2022 19:22:40 +0800 Subject: [PATCH 09/28] fix bug --- CHANGELOG.md | 1 + .../hutool/core/bean/copier/BeanCopier.java | 1 + .../core/date/TemporalAccessorUtil.java | 21 +++++++-- .../main/java/cn/hutool/json/JSONObject.java | 1 - .../java/cn/hutool/json/Issue2090Test.java | 43 +++++++++++++++++++ .../java/cn/hutool/json/JSONObjectTest.java | 1 + 6 files changed, 64 insertions(+), 4 deletions(-) create mode 100755 hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java diff --git a/CHANGELOG.md b/CHANGELOG.md index 68ca938c2..de4449f8f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -20,6 +20,7 @@ * 【core 】 修复OS中的拼写错误(pr#500@Gitee) * 【core 】 修复CustomKeyMap的merge失效问题(issue#2086@Github) * 【core 】 修复FileUtil.appendLines换行问题(issue#I4QCEZ@Gitee) +* 【core 】 修复java.time.Month解析问题(issue#2090@Github) ------------------------------------------------------------------------------------------------------------- # 5.7.19 (2022-01-07) diff --git a/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java b/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java index 1b9afc7cf..c3f4cb35c 100644 --- a/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java +++ b/hutool-core/src/main/java/cn/hutool/core/bean/copier/BeanCopier.java @@ -35,6 +35,7 @@ public class BeanCopier implements Copier, Serializable { private static final long serialVersionUID = 1L; /** 源对象 */ + @SuppressWarnings("NonSerializableFieldInSerializableClass") private final Object source; /** 目标对象 */ private final T dest; diff --git a/hutool-core/src/main/java/cn/hutool/core/date/TemporalAccessorUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/TemporalAccessorUtil.java index 1d9baef32..034f4868b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/TemporalAccessorUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/TemporalAccessorUtil.java @@ -7,6 +7,7 @@ import java.time.Instant; import java.time.LocalDate; import java.time.LocalDateTime; import java.time.LocalTime; +import java.time.Month; import java.time.OffsetDateTime; import java.time.OffsetTime; import java.time.ZoneId; @@ -40,7 +41,8 @@ public class TemporalAccessorUtil extends TemporalUtil{ } /** - * 格式化日期时间为指定格式 + * 格式化日期时间为指定格式
+ * 如果为{@link Month},调用{@link Month#toString()} * * @param time {@link TemporalAccessor} * @param formatter 日期格式化器,预定义的格式见:{@link DateTimeFormatter} @@ -52,6 +54,10 @@ public class TemporalAccessorUtil extends TemporalUtil{ return null; } + if(time instanceof Month){ + return time.toString(); + } + if(null == formatter){ formatter = DateTimeFormatter.ISO_LOCAL_DATE_TIME; } @@ -74,7 +80,8 @@ public class TemporalAccessorUtil extends TemporalUtil{ } /** - * 格式化日期时间为指定格式 + * 格式化日期时间为指定格式
+ * 如果为{@link Month},调用{@link Month#toString()} * * @param time {@link TemporalAccessor} * @param format 日期格式 @@ -86,6 +93,10 @@ public class TemporalAccessorUtil extends TemporalUtil{ return null; } + if(time instanceof Month){ + return time.toString(); + } + // 检查自定义格式 if(GlobalCustomFormat.isCustomFormat(format)){ return GlobalCustomFormat.format(time, format); @@ -98,13 +109,17 @@ public class TemporalAccessorUtil extends TemporalUtil{ } /** - * {@link TemporalAccessor}转换为 时间戳(从1970-01-01T00:00:00Z开始的毫秒数) + * {@link TemporalAccessor}转换为 时间戳(从1970-01-01T00:00:00Z开始的毫秒数)
+ * 如果为{@link Month},调用{@link Month#getValue()} * * @param temporalAccessor Date对象 * @return {@link Instant}对象 * @since 5.4.1 */ public static long toEpochMilli(TemporalAccessor temporalAccessor) { + if(temporalAccessor instanceof Month){ + return ((Month) temporalAccessor).getValue(); + } return toInstant(temporalAccessor).toEpochMilli(); } diff --git a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java index 403070853..a7582edd4 100644 --- a/hutool-json/src/main/java/cn/hutool/json/JSONObject.java +++ b/hutool-json/src/main/java/cn/hutool/json/JSONObject.java @@ -671,7 +671,6 @@ public class JSONObject implements JSON, JSONGetter, Map // 不支持对象类型转换为JSONObject throw new JSONException("Unsupported type [{}] to JSONObject!", source.getClass()); } - } /** diff --git a/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java new file mode 100755 index 000000000..f1f59b68e --- /dev/null +++ b/hutool-json/src/test/java/cn/hutool/json/Issue2090Test.java @@ -0,0 +1,43 @@ +package cn.hutool.json; + +import lombok.Data; +import org.junit.Assert; +import org.junit.Test; + +import java.time.LocalDate; +import java.time.Month; + +/** + * https://github.com/dromara/hutool/issues/2090 + */ +public class Issue2090Test { + + @Test + public void parseTest(){ + final TestBean test = new TestBean(); + test.setLocalDate(LocalDate.now()); + + final JSONObject json = JSONUtil.parseObj(test); + final TestBean test1 = json.toBean(TestBean.class); + Assert.assertEquals(test, test1); + } + + @Test + public void parseLocalDateTest(){ + LocalDate localDate = LocalDate.now(); + final JSONObject jsonObject = JSONUtil.parseObj(localDate); + Assert.assertNotNull(jsonObject.toString()); + } + + @Test + public void monthTest(){ + final JSONObject jsonObject = new JSONObject(); + jsonObject.set("month", Month.JANUARY); + Assert.assertEquals("{\"month\":1}", jsonObject.toString()); + } + + @Data + public static class TestBean{ + private LocalDate localDate; + } +} diff --git a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java index f3ae62388..3b18c8059 100644 --- a/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java +++ b/hutool-json/src/test/java/cn/hutool/json/JSONObjectTest.java @@ -30,6 +30,7 @@ import org.junit.Test; import java.math.BigDecimal; import java.sql.Timestamp; +import java.time.LocalDate; import java.util.Date; import java.util.HashMap; import java.util.List; From 15a34bcc24a14ef0214d34d1b6f60cbb811ba092 Mon Sep 17 00:00:00 2001 From: Looly Date: Sat, 15 Jan 2022 22:09:52 +0800 Subject: [PATCH 10/28] add method --- CHANGELOG.md | 1 + .../java/cn/hutool/http/HttpGlobalConfig.java | 25 +++++++++++++++++++ .../java/cn/hutool/http/HttpResponse.java | 3 ++- 3 files changed, 28 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index de4449f8f..bb0310d47 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ * 【core 】 ObjectUtil 添加三个defaultIfXxxx方法,用于节省CPU及内存损耗(pr#2094@Github) * 【db 】 增加单条数据原生upsert语义支持(pr#501@Gitee) * 【core 】 在CollectorUtil提交Collectors.toMap的对null友好实现,避免NPE(pr#502@Gitee) +* 【http 】 增加HttpGlobalConfig.setIgnoreEOFError(issue#2092@Github) * ### 🐞Bug修复 * 【core 】 修复setter重载导致匹配错误(issue#2082@Github) diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java index 918085592..99f28b1e1 100755 --- a/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpGlobalConfig.java @@ -32,6 +32,7 @@ public class HttpGlobalConfig implements Serializable { private static boolean isAllowPatch = false; private static String boundary = "--------------------Hutool_" + RandomUtil.randomString(16); private static int maxRedirectCount = 0; + private static boolean ignoreEOFError = true; /** * 获取全局默认的超时时长 @@ -99,6 +100,30 @@ public class HttpGlobalConfig implements Serializable { maxRedirectCount = customMaxRedirectCount; } + /** + * 获取是否忽略响应读取时可能的EOF异常。
+ * 在Http协议中,对于Transfer-Encoding: Chunked在正常情况下末尾会写入一个Length为0的的chunk标识完整结束。
+ * 如果服务端未遵循这个规范或响应没有正常结束,会报EOF异常,此选项用于是否忽略这个异常。 + * + * @return 是否忽略响应读取时可能的EOF异常 + * @since 5.7.20 + */ + public static boolean isIgnoreEOFError() { + return ignoreEOFError; + } + + /** + * 设置是否忽略响应读取时可能的EOF异常。
+ * 在Http协议中,对于Transfer-Encoding: Chunked在正常情况下末尾会写入一个Length为0的的chunk标识完整结束。
+ * 如果服务端未遵循这个规范或响应没有正常结束,会报EOF异常,此选项用于是否忽略这个异常。 + * + * @param customIgnoreEOFError 是否忽略响应读取时可能的EOF异常。 + * @since 5.7.20 + */ + synchronized public static void setIgnoreEOFError(boolean customIgnoreEOFError) { + ignoreEOFError = customIgnoreEOFError; + } + /** * 获取Cookie管理器,用于自定义Cookie管理 * diff --git a/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java b/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java index 03ae911da..d43ae1e6c 100644 --- a/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java +++ b/hutool-http/src/main/java/cn/hutool/http/HttpResponse.java @@ -588,7 +588,8 @@ public class HttpResponse extends HttpBase implements Closeable { copyLength = IoUtil.copy(in, out, IoUtil.DEFAULT_BUFFER_SIZE, contentLength, streamProgress); } catch (IORuntimeException e) { //noinspection StatementWithEmptyBody - if (e.getCause() instanceof EOFException || StrUtil.containsIgnoreCase(e.getMessage(), "Premature EOF")) { + if (HttpGlobalConfig.isIgnoreEOFError() + && (e.getCause() instanceof EOFException || StrUtil.containsIgnoreCase(e.getMessage(), "Premature EOF"))) { // 忽略读取HTTP流中的EOF错误 } else { throw e; From cacf025b2fd27b67964f31fed1d23d743a8eb12a Mon Sep 17 00:00:00 2001 From: dream <52assert@gmail.com> Date: Sun, 16 Jan 2022 00:09:44 +0800 Subject: [PATCH 11/28] =?UTF-8?q?fix:=20=E5=AF=B9RandomUtil.randomStringWi?= =?UTF-8?q?thoutStr=E6=8E=92=E9=99=A4=E5=AD=97=E7=AC=A6=E4=B8=B2=E5=85=BC?= =?UTF-8?q?=E5=AE=B9=E5=A4=A7=E5=86=99=E5=AD=97=E6=AF=8D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/cn/hutool/core/util/RandomUtil.java | 10 ++-------- .../test/java/cn/hutool/core/util/RandomUtilTest.java | 11 +++++++++++ 2 files changed, 13 insertions(+), 8 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java index 8d77384e7..3e1db16a0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java @@ -14,13 +14,7 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Date; -import java.util.LinkedHashSet; -import java.util.List; -import java.util.Random; -import java.util.Set; +import java.util.*; import java.util.concurrent.ThreadLocalRandom; /** @@ -524,7 +518,7 @@ public class RandomUtil { */ public static String randomStringWithoutStr(int length, String elemData) { String baseStr = BASE_CHAR_NUMBER; - baseStr = StrUtil.removeAll(baseStr, elemData.toCharArray()); + baseStr = StrUtil.removeAll(baseStr, elemData.toLowerCase(Locale.ROOT).toCharArray()); return randomString(baseStr, length); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/RandomUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/RandomUtilTest.java index 978fcd2ed..1472dcd79 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/RandomUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/RandomUtilTest.java @@ -8,6 +8,7 @@ import org.junit.Test; import java.math.RoundingMode; import java.util.List; +import java.util.Locale; import java.util.Set; public class RandomUtilTest { @@ -59,4 +60,14 @@ public class RandomUtilTest { char c = RandomUtil.randomChinese(); Assert.assertTrue(c > 0); } + @Test + public void randomStringWithoutStrTest() { + for (int i = 0; i < 100; i++) { + final String s = RandomUtil.randomStringWithoutStr(8, "0IPOL"); + System.out.println(s); + for (char c : "0IPOL".toCharArray()) { + Assert.assertFalse(s.contains((String.valueOf(c).toLowerCase(Locale.ROOT)))); + } + } + } } From dd560ce5e34c38eab9d58ba1ce9df864f95c11fd Mon Sep 17 00:00:00 2001 From: Looly Date: Sun, 16 Jan 2022 00:39:44 +0800 Subject: [PATCH 12/28] change code --- CHANGELOG.md | 3 ++- .../main/java/cn/hutool/core/util/RandomUtil.java | 14 ++++++++++---- .../java/cn/hutool/core/util/RandomUtilTest.java | 2 ++ 3 files changed, 14 insertions(+), 5 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index bb0310d47..370e2162a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.7.20 (2022-01-15) +# 5.7.20 (2022-01-16) ### 🐣新特性 * 【core 】 增加对null值友好的groupingBy操作的Collector实现,可指定map类型(pr#498@Gitee) @@ -14,6 +14,7 @@ * 【db 】 增加单条数据原生upsert语义支持(pr#501@Gitee) * 【core 】 在CollectorUtil提交Collectors.toMap的对null友好实现,避免NPE(pr#502@Gitee) * 【http 】 增加HttpGlobalConfig.setIgnoreEOFError(issue#2092@Github) +* 【core 】 RandomUtil.randomStringWithoutStr排除字符串兼容大写字母(pr#503@Gitee) * ### 🐞Bug修复 * 【core 】 修复setter重载导致匹配错误(issue#2082@Github) diff --git a/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java index 3e1db16a0..2c415b386 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/RandomUtil.java @@ -14,7 +14,13 @@ import java.math.BigDecimal; import java.math.RoundingMode; import java.security.NoSuchAlgorithmException; import java.security.SecureRandom; -import java.util.*; +import java.util.ArrayList; +import java.util.Collection; +import java.util.Date; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Random; +import java.util.Set; import java.util.concurrent.ThreadLocalRandom; /** @@ -510,15 +516,15 @@ public class RandomUtil { } /** - * 获得一个随机的字符串(只包含数字和字符) 并排除指定字符串 + * 获得一个随机的字符串(只包含数字和小写字母) 并排除指定字符串 * * @param length 字符串的长度 - * @param elemData 要排除的字符串,如:去重容易混淆的字符串,oO0、lL1、q9Q、pP + * @param elemData 要排除的字符串,如:去重容易混淆的字符串,oO0、lL1、q9Q、pP,不区分大小写 * @return 随机字符串 */ public static String randomStringWithoutStr(int length, String elemData) { String baseStr = BASE_CHAR_NUMBER; - baseStr = StrUtil.removeAll(baseStr, elemData.toLowerCase(Locale.ROOT).toCharArray()); + baseStr = StrUtil.removeAll(baseStr, elemData.toLowerCase().toCharArray()); return randomString(baseStr, length); } diff --git a/hutool-core/src/test/java/cn/hutool/core/util/RandomUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/RandomUtilTest.java index 1472dcd79..e61e5bcbf 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/RandomUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/RandomUtilTest.java @@ -60,7 +60,9 @@ public class RandomUtilTest { char c = RandomUtil.randomChinese(); Assert.assertTrue(c > 0); } + @Test + @Ignore public void randomStringWithoutStrTest() { for (int i = 0; i < 100; i++) { final String s = RandomUtil.randomStringWithoutStr(8, "0IPOL"); From cba165983323fe3ffa5576125605c8cf5c586b19 Mon Sep 17 00:00:00 2001 From: VampireAchao Date: Mon, 17 Jan 2022 14:40:16 +0800 Subject: [PATCH 13/28] =?UTF-8?q?=E4=BF=AE=E5=A4=8DofTry=E4=B8=AD=E5=B9=B6?= =?UTF-8?q?=E5=8F=91=E7=8E=AF=E5=A2=83=E4=B8=8B=E7=BA=BF=E7=A8=8B=E5=AE=89?= =?UTF-8?q?=E5=85=A8=E9=97=AE=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/cn/hutool/core/lang/Opt.java | 2 +- .../test/java/cn/hutool/core/lang/OptTest.java | 15 +++++++++++++++ 2 files changed, 16 insertions(+), 1 deletion(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java b/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java index e03ad64cd..dad1b5cd0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java +++ b/hutool-core/src/main/java/cn/hutool/core/lang/Opt.java @@ -122,7 +122,7 @@ public class Opt { try { return Opt.ofNullable(supplier.call()); } catch (Exception e) { - final Opt empty = Opt.empty(); + final Opt empty = new Opt<>(null); empty.exception = e; return empty; } diff --git a/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java b/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java index 0bdab907d..41d2d19c3 100644 --- a/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/lang/OptTest.java @@ -187,6 +187,21 @@ public class OptTest { Assert.assertEquals(indexOut, indexOutSituation); Assert.assertEquals("hutool", npe); Assert.assertEquals("hutool", indexOut); + + // 多线程下情况测试 + Stream.iterate(0, i -> ++i).limit(20000).parallel().forEach(i -> { + Opt opt = Opt.ofTry(() -> { + if (i % 2 == 0) { + throw new IllegalStateException(i + ""); + } else { + throw new NullPointerException(i + ""); + } + }); + Assert.assertTrue( + (i % 2 == 0 && opt.getException() instanceof IllegalStateException) || + (i % 2 != 0 && opt.getException() instanceof NullPointerException) + ); + }); } @Data From 91e2eabd040b9191f6b2f2f2bd11627649eefb52 Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 17 Jan 2022 18:05:32 +0800 Subject: [PATCH 14/28] fix bug --- CHANGELOG.md | 3 ++- .../src/main/java/cn/hutool/core/io/file/PathUtil.java | 6 ++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 370e2162a..fba4bd1f6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.7.20 (2022-01-16) +# 5.7.20 (2022-01-17) ### 🐣新特性 * 【core 】 增加对null值友好的groupingBy操作的Collector实现,可指定map类型(pr#498@Gitee) @@ -23,6 +23,7 @@ * 【core 】 修复CustomKeyMap的merge失效问题(issue#2086@Github) * 【core 】 修复FileUtil.appendLines换行问题(issue#I4QCEZ@Gitee) * 【core 】 修复java.time.Month解析问题(issue#2090@Github) +* 【core 】 修复PathUtil.moveContent移动覆盖导致的问题(issue#I4QV0L@Gitee) ------------------------------------------------------------------------------------------------------------- # 5.7.19 (2022-01-07) diff --git a/hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java b/hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java index 1c3ed2d92..7c1cd4955 100644 --- a/hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/io/file/PathUtil.java @@ -20,6 +20,7 @@ import java.nio.charset.Charset; import java.nio.file.AccessDeniedException; import java.nio.file.CopyOption; import java.nio.file.DirectoryStream; +import java.nio.file.FileAlreadyExistsException; import java.nio.file.FileVisitOption; import java.nio.file.FileVisitResult; import java.nio.file.FileVisitor; @@ -516,6 +517,11 @@ public class PathUtil { try { return Files.move(src, target, options); } catch (IOException e) { + if(e instanceof FileAlreadyExistsException){ + // 目标文件已存在,直接抛出异常 + // issue#I4QV0L@Gitee + throw new IORuntimeException(e); + } // 移动失败,可能是跨分区移动导致的,采用递归移动方式 try { Files.walkFileTree(src, new MoveVisitor(src, target, options)); From 727e561c1aae1020e45e9219bba0cd51deb7c88d Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 17 Jan 2022 18:09:56 +0800 Subject: [PATCH 15/28] fix bug --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index fba4bd1f6..8752ab457 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -24,6 +24,7 @@ * 【core 】 修复FileUtil.appendLines换行问题(issue#I4QCEZ@Gitee) * 【core 】 修复java.time.Month解析问题(issue#2090@Github) * 【core 】 修复PathUtil.moveContent移动覆盖导致的问题(issue#I4QV0L@Gitee) +* 【core 】 修复Opt.ofTry中并发环境下线程安全问题(pr#504@Gitee) ------------------------------------------------------------------------------------------------------------- # 5.7.19 (2022-01-07) From db5bd528ced063834784300fda53bd091c9f2d7d Mon Sep 17 00:00:00 2001 From: Looly Date: Mon, 17 Jan 2022 19:16:07 +0800 Subject: [PATCH 16/28] fix bug --- CHANGELOG.md | 1 + .../main/java/cn/hutool/core/net/NetUtil.java | 20 +++++++++---------- .../core/text/finder/PatternFinder.java | 2 +- .../cn/hutool/core/text/split/SplitIter.java | 3 +-- ...rSpliterTest.java => StrSplitterTest.java} | 20 ++++++++++++++++++- 5 files changed, 32 insertions(+), 14 deletions(-) rename hutool-core/src/test/java/cn/hutool/core/text/split/{StrSpliterTest.java => StrSplitterTest.java} (73%) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8752ab457..207651c81 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -25,6 +25,7 @@ * 【core 】 修复java.time.Month解析问题(issue#2090@Github) * 【core 】 修复PathUtil.moveContent移动覆盖导致的问题(issue#I4QV0L@Gitee) * 【core 】 修复Opt.ofTry中并发环境下线程安全问题(pr#504@Gitee) +* 【core 】 修复PatternFinder中end边界判断问题(issue#2099@Github) ------------------------------------------------------------------------------------------------------------- # 5.7.19 (2022-01-07) diff --git a/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java b/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java index aef83a6b8..3fded9388 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/NetUtil.java @@ -571,16 +571,6 @@ public class NetUtil { return null; } - /** - * 获得本机物理地址 - * - * @return 本机物理地址 - * @since 5.7.3 - */ - public static byte[] getLocalHardwareAddress() { - return getHardwareAddress(getLocalhost()); - } - /** * 获得指定地址信息中的硬件地址 * @@ -604,6 +594,16 @@ public class NetUtil { return null; } + /** + * 获得本机物理地址 + * + * @return 本机物理地址 + * @since 5.7.3 + */ + public static byte[] getLocalHardwareAddress() { + return getHardwareAddress(getLocalhost()); + } + /** * 获取主机名称,一次获取会缓存名称 * diff --git a/hutool-core/src/main/java/cn/hutool/core/text/finder/PatternFinder.java b/hutool-core/src/main/java/cn/hutool/core/text/finder/PatternFinder.java index 92afc408d..c258daed0 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/finder/PatternFinder.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/finder/PatternFinder.java @@ -66,7 +66,7 @@ public class PatternFinder extends TextFinder { }else{ limit = Math.min(endIndex, text.length()); } - return end < limit ? end : INDEX_NOT_FOUND; + return end <= limit ? end : INDEX_NOT_FOUND; } @Override diff --git a/hutool-core/src/main/java/cn/hutool/core/text/split/SplitIter.java b/hutool-core/src/main/java/cn/hutool/core/text/split/SplitIter.java index 6dc104800..b1cb444d9 100644 --- a/hutool-core/src/main/java/cn/hutool/core/text/split/SplitIter.java +++ b/hutool-core/src/main/java/cn/hutool/core/text/split/SplitIter.java @@ -87,9 +87,8 @@ public class SplitIter extends ComputeIter implements Serializable { } // 找到新的分隔符位置 - final int end = finder.end(start); final String result = text.substring(offset, start); - offset = end; + offset = finder.end(start); if (ignoreEmpty && result.isEmpty()) { // 发现空串且需要忽略时,跳过之 diff --git a/hutool-core/src/test/java/cn/hutool/core/text/split/StrSpliterTest.java b/hutool-core/src/test/java/cn/hutool/core/text/split/StrSplitterTest.java similarity index 73% rename from hutool-core/src/test/java/cn/hutool/core/text/split/StrSpliterTest.java rename to hutool-core/src/test/java/cn/hutool/core/text/split/StrSplitterTest.java index 8458f70f6..16f812f69 100644 --- a/hutool-core/src/test/java/cn/hutool/core/text/split/StrSpliterTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/text/split/StrSplitterTest.java @@ -11,7 +11,7 @@ import java.util.List; * @author Looly * */ -public class StrSpliterTest { +public class StrSplitterTest { @Test public void splitByCharTest(){ @@ -71,4 +71,22 @@ public class StrSpliterTest { Assert.assertNotNull(strings); Assert.assertEquals(0, strings.length); } + + /** + * https://github.com/dromara/hutool/issues/2099 + */ + @Test + public void splitByRegexTest(){ + String text = "01 821 34567890182345617821"; + List strings = StrSplitter.splitByRegex(text, "21", 0, false, true); + Assert.assertEquals(2, strings.size()); + Assert.assertEquals("01 8", strings.get(0)); + Assert.assertEquals(" 345678901823456178", strings.get(1)); + + strings = StrSplitter.splitByRegex(text, "21", 0, false, false); + Assert.assertEquals(3, strings.size()); + Assert.assertEquals("01 8", strings.get(0)); + Assert.assertEquals(" 345678901823456178", strings.get(1)); + Assert.assertEquals("", strings.get(2)); + } } From 4b02bc72877150eab22ff3ab056785676714a0f3 Mon Sep 17 00:00:00 2001 From: tjh Date: Tue, 18 Jan 2022 15:39:44 +0800 Subject: [PATCH 17/28] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=EF=BC=9A=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E5=8C=96=E4=B8=BA=E4=B8=AD=E6=96=87=E6=97=A5=E6=9C=9F?= =?UTF-8?q?=E6=97=B6=EF=BC=8C0=E8=A2=AB=E5=A4=84=E7=90=86=E4=B8=BA?= =?UTF-8?q?=E7=A9=BA=E4=B8=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/cn/hutool/core/convert/NumberChineseFormatter.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java b/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java index afbb9d472..cacd13000 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java @@ -164,6 +164,11 @@ public class NumberChineseFormatter { */ public static String formatThousand(int amount, boolean isUseTraditional){ Assert.checkBetween(amount, -999, 999, "Number support only: (-999 ~ 999)!"); + + // thousandToChinese方法对0不处理,此处直接返回"零" + if (amount == 0) { + return String.valueOf(DIGITS[0]); + } final String chinese = thousandToChinese(amount, isUseTraditional); if(amount < 20 && amount > 10){ // "十一"而非"一十一" From 8c20364c2c0f8a86c2607d9c4c56947b5c67b6dd Mon Sep 17 00:00:00 2001 From: tjh Date: Tue, 18 Jan 2022 16:12:03 +0800 Subject: [PATCH 18/28] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=EF=BC=9A=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E5=8C=96=E4=B8=BA=E4=B8=AD=E6=96=87=E6=97=A5=E6=9C=9F?= =?UTF-8?q?=E6=97=B6=EF=BC=8C0=E8=A2=AB=E5=A4=84=E7=90=86=E4=B8=BA?= =?UTF-8?q?=E7=A9=BA=E4=B8=B2?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/main/java/cn/hutool/core/date/CalendarUtil.java | 8 +++++++- .../src/test/java/cn/hutool/core/date/DateUtilTest.java | 3 +++ 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java index 752a052c8..43ef2d3b8 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java @@ -555,6 +555,12 @@ public class CalendarUtil { result.append(NumberChineseFormatter.formatThousand(day, false)); result.append('日'); + // 时分秒中零不需要替换 + String temp = result.toString().replace('零', '〇'); + result.delete(0, result.length()); + result.append(temp); + + if (withTime) { // 时 int hour = calendar.get(Calendar.HOUR_OF_DAY); @@ -570,7 +576,7 @@ public class CalendarUtil { result.append('秒'); } - return result.toString().replace('零', '〇'); + return result.toString(); } /** diff --git a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java index 6d3a2529e..24e7321a8 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/DateUtilTest.java @@ -289,6 +289,9 @@ public class DateUtilTest { public void formatChineseDateTimeTest() { String formatChineseDateTime = DateUtil.formatChineseDate(DateUtil.parse("2018-02-24 12:13:14"), true, true); Assert.assertEquals("二〇一八年二月二十四日十二时十三分十四秒", formatChineseDateTime); + + formatChineseDateTime = DateUtil.formatChineseDate(DateUtil.parse("2022-01-18 12:00:00"), true, true); + Assert.assertEquals("二〇二二年一月十八日十二时零分零秒", formatChineseDateTime); } @Test From 7ef7ac52f04d58264f5c4b1ff9b8636ce00d158c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E4=B8=8D=E5=BF=98=E5=88=9D=E5=BF=83?= Date: Tue, 18 Jan 2022 11:09:51 +0000 Subject: [PATCH 19/28] change ipv4util getBeginIpLong to public --- hutool-core/src/main/java/cn/hutool/core/net/Ipv4Util.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/net/Ipv4Util.java b/hutool-core/src/main/java/cn/hutool/core/net/Ipv4Util.java index 4a65ca3df..886f9c47f 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/Ipv4Util.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/Ipv4Util.java @@ -187,7 +187,7 @@ public class Ipv4Util { * @param maskBit 给定的掩码位,如30 * @return 起始IP的长整型表示 */ - private static Long getBeginIpLong(String ip, int maskBit) { + public static Long getBeginIpLong(String ip, int maskBit) { return ipv4ToLong(ip) & ipv4ToLong(getMaskByMaskBit(maskBit)); } @@ -348,7 +348,7 @@ public class Ipv4Util { * @param maskBit 给定的掩码位,如30 * @return 终止IP的长整型表示 */ - private static Long getEndIpLong(String ip, int maskBit) { + public static Long getEndIpLong(String ip, int maskBit) { return getBeginIpLong(ip, maskBit) + ~ipv4ToLong(getMaskByMaskBit(maskBit)); } From 9a8894538658ba315db12d1e0522511b41bcaa78 Mon Sep 17 00:00:00 2001 From: Looly Date: Wed, 19 Jan 2022 09:25:05 +0800 Subject: [PATCH 20/28] fix bug --- CHANGELOG.md | 3 ++- .../cn/hutool/core/convert/NumberChineseFormatter.java | 9 +++++---- .../src/main/java/cn/hutool/core/date/CalendarUtil.java | 2 +- 3 files changed, 8 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 207651c81..48a4866d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.7.20 (2022-01-17) +# 5.7.20 (2022-01-19) ### 🐣新特性 * 【core 】 增加对null值友好的groupingBy操作的Collector实现,可指定map类型(pr#498@Gitee) @@ -26,6 +26,7 @@ * 【core 】 修复PathUtil.moveContent移动覆盖导致的问题(issue#I4QV0L@Gitee) * 【core 】 修复Opt.ofTry中并发环境下线程安全问题(pr#504@Gitee) * 【core 】 修复PatternFinder中end边界判断问题(issue#2099@Github) +* 【core 】 修复格式化为中文日期时,0被处理为空串(pr#507@Gitee) ------------------------------------------------------------------------------------------------------------- # 5.7.19 (2022-01-07) diff --git a/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java b/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java index cacd13000..29c2405e1 100644 --- a/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java +++ b/hutool-core/src/main/java/cn/hutool/core/convert/NumberChineseFormatter.java @@ -165,10 +165,6 @@ public class NumberChineseFormatter { public static String formatThousand(int amount, boolean isUseTraditional){ Assert.checkBetween(amount, -999, 999, "Number support only: (-999 ~ 999)!"); - // thousandToChinese方法对0不处理,此处直接返回"零" - if (amount == 0) { - return String.valueOf(DIGITS[0]); - } final String chinese = thousandToChinese(amount, isUseTraditional); if(amount < 20 && amount > 10){ // "十一"而非"一十一" @@ -289,6 +285,11 @@ public class NumberChineseFormatter { * @return 转换后的汉字 */ private static String thousandToChinese(int amountPart, boolean isUseTraditional) { + if (amountPart == 0) { + // issue#I4R92H@Gitee + return String.valueOf(DIGITS[0]); + } + int temp = amountPart; StringBuilder chineseStr = new StringBuilder(); diff --git a/hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java index 43ef2d3b8..a1e2f176b 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/CalendarUtil.java @@ -555,7 +555,7 @@ public class CalendarUtil { result.append(NumberChineseFormatter.formatThousand(day, false)); result.append('日'); - // 时分秒中零不需要替换 + // 只替换年月日,时分秒中零不需要替换 String temp = result.toString().replace('零', '〇'); result.delete(0, result.length()); result.append(temp); From 41651781b163bb74778bae4219efa165abd271fc Mon Sep 17 00:00:00 2001 From: GL Date: Wed, 19 Jan 2022 20:50:11 +0800 Subject: [PATCH 21/28] =?UTF-8?q?fixed=20178b994=20from=20https://gitee.co?= =?UTF-8?q?m/huahua522/hutool/pulls/511=20fixed=204248036=20from=20https:/?= =?UTF-8?q?/gitee.com/huahua522/hutool/pulls/509=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=AF=B9=E6=97=B6=E9=97=B4=E6=AE=B5=E9=87=8D=E5=90=88=E7=9A=84?= =?UTF-8?q?=E5=88=A4=E6=96=AD=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../hutool/core/date/LocalDateTimeUtil.java | 50 +++++++++++++------ 1 file changed, 35 insertions(+), 15 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/date/LocalDateTimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/LocalDateTimeUtil.java index f41d4e781..ccb063c96 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/LocalDateTimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/LocalDateTimeUtil.java @@ -5,24 +5,13 @@ import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ReUtil; import cn.hutool.core.util.StrUtil; -import java.time.DayOfWeek; -import java.time.Duration; -import java.time.Instant; -import java.time.LocalDate; -import java.time.LocalDateTime; -import java.time.LocalTime; -import java.time.Period; -import java.time.ZoneId; -import java.time.ZonedDateTime; +import java.time.*; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; -import java.time.temporal.ChronoField; -import java.time.temporal.ChronoUnit; -import java.time.temporal.Temporal; -import java.time.temporal.TemporalAccessor; -import java.time.temporal.TemporalUnit; +import java.time.temporal.*; import java.util.Date; import java.util.TimeZone; +import java.util.function.Supplier; /** * JDK8+中的{@link LocalDateTime} 工具类封装 @@ -493,7 +482,7 @@ public class LocalDateTimeUtil { * @since 5.7.18 */ public static LocalDateTime endOfDay(LocalDateTime time, boolean truncateMillisecond) { - if(truncateMillisecond){ + if (truncateMillisecond) { return time.with(LocalTime.of(23, 59, 59)); } return time.with(LocalTime.MAX); @@ -544,4 +533,35 @@ public class LocalDateTimeUtil { public static Week dayOfWeek(LocalDate localDate) { return Week.of(localDate.getDayOfWeek()); } + + /** + * 第二个事件段是否在第一个时间段的内部 + * 需要注意的是比如第一个时间段的结尾是23:59:59 第二天开始需要是00:00:00 相同也是重复 + * + * @param realStartTime 第一个时间段的开始时间 + * @param realEndTime 第一个时间段的结束时间 + * @param startTime 第二个时间段的开始时间 + * @param endTime 第二个时间段的结束时间 + * @return true 表示时间有重合 + */ + public static boolean isOverlap(LocalDateTime realStartTime, LocalDateTime realEndTime, LocalDateTime startTime, LocalDateTime endTime) { + return startTime.isAfter(realEndTime) || endTime.isBefore(realStartTime); + } + + /** + * jdk新特新的支持,并没发现什么场合,因为拿不到泛型 + * 第二个事件段是否在第一个时间段的内部 + * 需要注意的是比如第一个时间段的结尾是23:59:59 第二天开始需要是00:00:00 相同也是重复 + * + * @param realStartTime 第一个时间段的开始时间 + * @param realEndTime 第一个时间段的结束时间 + * @param startTime 第二个时间段的开始时间 + * @param endTime 第二个时间段的结束时间 + * @return true 表示没有时间有重合 + */ + public static boolean isOverlap(Supplier realStartTime, Supplier realEndTime, Supplier startTime, Supplier endTime) { + return isOverlap(realStartTime.get(), realEndTime.get(), startTime.get(), endTime.get()); + } + + } From 03657b2eaf0006119d35e52b9c1352805b789fc3 Mon Sep 17 00:00:00 2001 From: GL Date: Wed, 19 Jan 2022 20:58:06 +0800 Subject: [PATCH 22/28] =?UTF-8?q?fixed=20c592aa6=20from=20https://gitee.co?= =?UTF-8?q?m/huahua522/hutool/pulls/511=20fixed=20d6b78ed=20from=20https:/?= =?UTF-8?q?/gitee.com/huahua522/hutool/pulls/509=20=E5=A2=9E=E5=8A=A0?= =?UTF-8?q?=E5=AF=B9=E6=97=B6=E9=97=B4=E6=AE=B5=E9=87=8D=E5=90=88=E7=9A=84?= =?UTF-8?q?=E5=88=A4=E6=96=AD=E6=96=B9=E6=B3=95=20=E6=B5=8B=E8=AF=95?= =?UTF-8?q?=E7=94=A8=E4=BE=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../core/date/LocalDateTimeUtilTest.java | 29 +++++++++++++++++++ 1 file changed, 29 insertions(+) diff --git a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java index f8455b941..ed835e123 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java @@ -177,4 +177,33 @@ public class LocalDateTimeUtilTest { final Week seven = LocalDateTimeUtil.dayOfWeek(LocalDate.of(2021, 9, 26)); Assert.assertEquals(Week.SUNDAY, seven); } + + @Test + public void isOverlapTest(){ + + + LocalDateTime oneStartTime = LocalDateTime.of(2022, 1, 1, 10, 10, 10); + LocalDateTime oneEndTime = LocalDateTime.of(2022, 1, 1, 11, 10, 10); + + + + LocalDateTime oneStartTime2 = LocalDateTime.of(2022, 1, 1, 11, 20, 10); + LocalDateTime oneEndTime2 = LocalDateTime.of(2022, 1, 1, 11, 30, 10); + + + + LocalDateTime oneStartTime3 = LocalDateTime.of(2022, 1, 1, 11, 40, 10); + LocalDateTime oneEndTime3 = LocalDateTime.of(2022, 1, 1, 11, 50, 10); + + + + //真实请假数据 + LocalDateTime realStartTime = LocalDateTime.of(2022, 1, 1, 11, 49, 10); + LocalDateTime realEndTime = LocalDateTime.of(2022, 1, 1, 12, 0, 10); + + Assert.assertTrue(LocalDateTimeUtil.isOverlap(oneStartTime,oneEndTime,realStartTime,realEndTime)); + Assert.assertTrue(LocalDateTimeUtil.isOverlap(oneStartTime2,oneEndTime2,realStartTime,realEndTime)); + Assert.assertFalse(LocalDateTimeUtil.isOverlap(oneStartTime3,oneEndTime3,realStartTime,realEndTime)); + + } } From 5c6e275750860ed83bbc5a43fe36a054e24e30f0 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 20 Jan 2022 09:57:24 +0800 Subject: [PATCH 23/28] fix bug --- CHANGELOG.md | 3 ++- .../java/cn/hutool/core/net/url/UrlPath.java | 17 ++++++++++++----- .../java/cn/hutool/core/net/UrlBuilderTest.java | 9 +++++++++ 3 files changed, 23 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 48a4866d5..50c167159 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,7 +3,7 @@ ------------------------------------------------------------------------------------------------------------- -# 5.7.20 (2022-01-19) +# 5.7.20 (2022-01-20) ### 🐣新特性 * 【core 】 增加对null值友好的groupingBy操作的Collector实现,可指定map类型(pr#498@Gitee) @@ -27,6 +27,7 @@ * 【core 】 修复Opt.ofTry中并发环境下线程安全问题(pr#504@Gitee) * 【core 】 修复PatternFinder中end边界判断问题(issue#2099@Github) * 【core 】 修复格式化为中文日期时,0被处理为空串(pr#507@Gitee) +* 【core 】 修复UrlPath转义冒号问题(issue#I4RA42@Gitee) ------------------------------------------------------------------------------------------------------------- # 5.7.19 (2022-01-07) diff --git a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java index 0ab1bef1d..8a968f1a7 100644 --- a/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java +++ b/hutool-core/src/main/java/cn/hutool/core/net/url/UrlPath.java @@ -117,7 +117,10 @@ public class UrlPath { } /** - * 构建path,前面带'/' + * 构建path,前面带'/'
+ *
+	 *     path = path-abempty / path-absolute / path-noscheme / path-rootless / path-empty
+	 * 
* * @param charset encode编码,null表示不做encode * @return 如果没有任何内容,则返回空字符串"" @@ -129,10 +132,14 @@ public class UrlPath { final StringBuilder builder = new StringBuilder(); for (String segment : segments) { - // 根据https://www.ietf.org/rfc/rfc3986.html#section-3.3定义 - // path的第一部分允许有":",其余部分不允许 - // 在此处的Path部分特指host之后的部分,即不包含第一部分 - builder.append(CharUtil.SLASH).append(RFC3986.SEGMENT_NZ_NC.encode(segment, charset)); + if(builder.length() == 0){ + // 根据https://www.ietf.org/rfc/rfc3986.html#section-3.3定义 + // path的第一部分不允许有":",其余部分允许 + // 在此处的Path部分特指host之后的部分,即不包含第一部分 + builder.append(CharUtil.SLASH).append(RFC3986.SEGMENT_NZ_NC.encode(segment, charset)); + } else { + builder.append(CharUtil.SLASH).append(RFC3986.SEGMENT.encode(segment, charset)); + } } if (StrUtil.isEmpty(builder)) { // 空白追加是保证以/开头 diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java index 07c6e28ba..937efab91 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java @@ -251,6 +251,15 @@ public class UrlBuilderTest { Assert.assertEquals(urlStr, urlBuilder.toString()); } + @Test + public void encodePathTest2(){ + // https://gitee.com/dromara/hutool/issues/I4RA42 + // Path中`:`在第一个segment需要转义,之后的不需要 + final String urlStr = "https://hutool.cn/aa/bb/Pre-K,Kindergarten,First,Second,Third,Fourth,Fifth/Page:3"; + final UrlBuilder urlBuilder = UrlBuilder.ofHttp(urlStr, CharsetUtil.CHARSET_UTF_8); + Assert.assertEquals(urlStr, urlBuilder.toString()); + } + @Test public void gimg2Test(){ String url = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic.jj20.com%2Fup%2Fallimg%2F1114%2F0H320120Z3%2F200H3120Z3-6-1200.jpg&refer=http%3A%2F%2Fpic.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1621996490&t=8c384c2823ea453da15a1b9cd5183eea"; From b9df76f92c3bedca70669f6269d80b42471304d2 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 20 Jan 2022 10:21:20 +0800 Subject: [PATCH 24/28] add method --- CHANGELOG.md | 1 + .../hutool/core/date/LocalDateTimeUtil.java | 42 +++++++++---------- .../core/date/LocalDateTimeUtilTest.java | 9 ---- 3 files changed, 21 insertions(+), 31 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 50c167159..add6a54a5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ * 【core 】 在CollectorUtil提交Collectors.toMap的对null友好实现,避免NPE(pr#502@Gitee) * 【http 】 增加HttpGlobalConfig.setIgnoreEOFError(issue#2092@Github) * 【core 】 RandomUtil.randomStringWithoutStr排除字符串兼容大写字母(pr#503@Gitee) +* 【core 】 LocalDateTime增加isOverlap方法(pr#512@Gitee) * ### 🐞Bug修复 * 【core 】 修复setter重载导致匹配错误(issue#2082@Github) diff --git a/hutool-core/src/main/java/cn/hutool/core/date/LocalDateTimeUtil.java b/hutool-core/src/main/java/cn/hutool/core/date/LocalDateTimeUtil.java index ccb063c96..102fdff4e 100644 --- a/hutool-core/src/main/java/cn/hutool/core/date/LocalDateTimeUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/date/LocalDateTimeUtil.java @@ -5,13 +5,25 @@ import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ReUtil; import cn.hutool.core.util.StrUtil; -import java.time.*; +import java.time.DayOfWeek; +import java.time.Duration; +import java.time.Instant; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.time.LocalTime; +import java.time.Period; +import java.time.ZoneId; +import java.time.ZonedDateTime; +import java.time.chrono.ChronoLocalDateTime; import java.time.format.DateTimeFormatter; import java.time.format.DateTimeFormatterBuilder; -import java.time.temporal.*; +import java.time.temporal.ChronoField; +import java.time.temporal.ChronoUnit; +import java.time.temporal.Temporal; +import java.time.temporal.TemporalAccessor; +import java.time.temporal.TemporalUnit; import java.util.Date; import java.util.TimeZone; -import java.util.function.Supplier; /** * JDK8+中的{@link LocalDateTime} 工具类封装 @@ -535,33 +547,19 @@ public class LocalDateTimeUtil { } /** - * 第二个事件段是否在第一个时间段的内部 - * 需要注意的是比如第一个时间段的结尾是23:59:59 第二天开始需要是00:00:00 相同也是重复 + * 检查两个时间段是否有时间重叠
+ * 重叠指两个时间段是否有交集 * * @param realStartTime 第一个时间段的开始时间 * @param realEndTime 第一个时间段的结束时间 * @param startTime 第二个时间段的开始时间 * @param endTime 第二个时间段的结束时间 * @return true 表示时间有重合 + * @since 5.7.20 */ - public static boolean isOverlap(LocalDateTime realStartTime, LocalDateTime realEndTime, LocalDateTime startTime, LocalDateTime endTime) { + public static boolean isOverlap(ChronoLocalDateTime realStartTime, ChronoLocalDateTime realEndTime, + ChronoLocalDateTime startTime, ChronoLocalDateTime endTime) { return startTime.isAfter(realEndTime) || endTime.isBefore(realStartTime); } - /** - * jdk新特新的支持,并没发现什么场合,因为拿不到泛型 - * 第二个事件段是否在第一个时间段的内部 - * 需要注意的是比如第一个时间段的结尾是23:59:59 第二天开始需要是00:00:00 相同也是重复 - * - * @param realStartTime 第一个时间段的开始时间 - * @param realEndTime 第一个时间段的结束时间 - * @param startTime 第二个时间段的开始时间 - * @param endTime 第二个时间段的结束时间 - * @return true 表示没有时间有重合 - */ - public static boolean isOverlap(Supplier realStartTime, Supplier realEndTime, Supplier startTime, Supplier endTime) { - return isOverlap(realStartTime.get(), realEndTime.get(), startTime.get(), endTime.get()); - } - - } diff --git a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java index ed835e123..8f35c59a5 100644 --- a/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/date/LocalDateTimeUtilTest.java @@ -180,23 +180,15 @@ public class LocalDateTimeUtilTest { @Test public void isOverlapTest(){ - - LocalDateTime oneStartTime = LocalDateTime.of(2022, 1, 1, 10, 10, 10); LocalDateTime oneEndTime = LocalDateTime.of(2022, 1, 1, 11, 10, 10); - - LocalDateTime oneStartTime2 = LocalDateTime.of(2022, 1, 1, 11, 20, 10); LocalDateTime oneEndTime2 = LocalDateTime.of(2022, 1, 1, 11, 30, 10); - - LocalDateTime oneStartTime3 = LocalDateTime.of(2022, 1, 1, 11, 40, 10); LocalDateTime oneEndTime3 = LocalDateTime.of(2022, 1, 1, 11, 50, 10); - - //真实请假数据 LocalDateTime realStartTime = LocalDateTime.of(2022, 1, 1, 11, 49, 10); LocalDateTime realEndTime = LocalDateTime.of(2022, 1, 1, 12, 0, 10); @@ -204,6 +196,5 @@ public class LocalDateTimeUtilTest { Assert.assertTrue(LocalDateTimeUtil.isOverlap(oneStartTime,oneEndTime,realStartTime,realEndTime)); Assert.assertTrue(LocalDateTimeUtil.isOverlap(oneStartTime2,oneEndTime2,realStartTime,realEndTime)); Assert.assertFalse(LocalDateTimeUtil.isOverlap(oneStartTime3,oneEndTime3,realStartTime,realEndTime)); - } } From 7c18710712276a2addfad8c3d0ede2810775eaa8 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 20 Jan 2022 10:39:29 +0800 Subject: [PATCH 25/28] add method --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index add6a54a5..df97fc9c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,7 @@ * 【http 】 增加HttpGlobalConfig.setIgnoreEOFError(issue#2092@Github) * 【core 】 RandomUtil.randomStringWithoutStr排除字符串兼容大写字母(pr#503@Gitee) * 【core 】 LocalDateTime增加isOverlap方法(pr#512@Gitee) +* 【core 】 Ipv4Util.getBeginIpLong、getEndIpLong改为public(pr#508@Gitee) * ### 🐞Bug修复 * 【core 】 修复setter重载导致匹配错误(issue#2082@Github) From a6e123108508a4637925a2b61f11a6a75c7033f3 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 20 Jan 2022 11:41:56 +0800 Subject: [PATCH 26/28] fix code --- .../main/java/cn/hutool/core/thread/ThreadUtil.java | 1 - .../src/main/java/cn/hutool/core/util/IdUtil.java | 12 +++++++++++- .../test/java/cn/hutool/core/util/IdUtilTest.java | 3 ++- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/hutool-core/src/main/java/cn/hutool/core/thread/ThreadUtil.java b/hutool-core/src/main/java/cn/hutool/core/thread/ThreadUtil.java index 0227ca523..3418bd7cb 100644 --- a/hutool-core/src/main/java/cn/hutool/core/thread/ThreadUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/thread/ThreadUtil.java @@ -27,7 +27,6 @@ public class ThreadUtil { * 1. 初始线程数为corePoolSize指定的大小 * 2. 没有最大线程数限制 * 3. 默认使用LinkedBlockingQueue,默认队列大小为1024 - * 4. 当运行线程大于corePoolSize放入队列,队列满后抛出异常 * * * @param corePoolSize 同时执行的线程数大小 diff --git a/hutool-core/src/main/java/cn/hutool/core/util/IdUtil.java b/hutool-core/src/main/java/cn/hutool/core/util/IdUtil.java index 9e9167b72..6ac904e3a 100644 --- a/hutool-core/src/main/java/cn/hutool/core/util/IdUtil.java +++ b/hutool-core/src/main/java/cn/hutool/core/util/IdUtil.java @@ -1,6 +1,7 @@ package cn.hutool.core.util; import cn.hutool.core.exceptions.UtilException; +import cn.hutool.core.lang.Assert; import cn.hutool.core.lang.ObjectId; import cn.hutool.core.lang.Singleton; import cn.hutool.core.lang.Snowflake; @@ -203,8 +204,17 @@ public class IdUtil { * @since 5.7.3 */ public static long getDataCenterId(long maxDatacenterId) { + Assert.isTrue(maxDatacenterId > 0, "maxDatacenterId must be > 0"); + if(maxDatacenterId == Long.MAX_VALUE){ + maxDatacenterId -= 1; + } long id = 1L; - final byte[] mac = NetUtil.getLocalHardwareAddress(); + byte[] mac = null; + try{ + mac = NetUtil.getLocalHardwareAddress(); + }catch (UtilException ignore){ + // ignore + } if (null != mac) { id = ((0x000000FF & (long) mac[mac.length - 2]) | (0x0000FF00 & (((long) mac[mac.length - 1]) << 8))) >> 6; diff --git a/hutool-core/src/test/java/cn/hutool/core/util/IdUtilTest.java b/hutool-core/src/test/java/cn/hutool/core/util/IdUtilTest.java index b68d2f20b..f0dd9c5ab 100644 --- a/hutool-core/src/test/java/cn/hutool/core/util/IdUtilTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/util/IdUtilTest.java @@ -137,7 +137,8 @@ public class IdUtilTest { @Test public void getDataCenterIdTest(){ + //按照mac地址算法拼接的算法,maxDatacenterId应该是0xffffffffL>>6-1此处暂时按照0x7fffffffffffffffL-1,防止最后取模溢出 final long dataCenterId = IdUtil.getDataCenterId(Long.MAX_VALUE); - Assert.assertTrue(dataCenterId > 1); + Assert.assertTrue(dataCenterId >= 0); } } From 4a351ec741003b29aa43d9609977fa54b67b2c35 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 20 Jan 2022 12:10:46 +0800 Subject: [PATCH 27/28] fix tes --- .../src/test/java/cn/hutool/core/net/UrlBuilderTest.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java b/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java index 937efab91..764167662 100644 --- a/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java +++ b/hutool-core/src/test/java/cn/hutool/core/net/UrlBuilderTest.java @@ -265,8 +265,9 @@ public class UrlBuilderTest { String url = "https://gimg2.baidu.com/image_search/src=http%3A%2F%2Fpic.jj20.com%2Fup%2Fallimg%2F1114%2F0H320120Z3%2F200H3120Z3-6-1200.jpg&refer=http%3A%2F%2Fpic.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1621996490&t=8c384c2823ea453da15a1b9cd5183eea"; final UrlBuilder urlBuilder = UrlBuilder.of(url); - - Assert.assertEquals(url, urlBuilder.toString()); + // PATH除了第一个path外,:是允许的 + String url2 = "https://gimg2.baidu.com/image_search/src=http:%2F%2Fpic.jj20.com%2Fup%2Fallimg%2F1114%2F0H320120Z3%2F200H3120Z3-6-1200.jpg&refer=http:%2F%2Fpic.jj20.com&app=2002&size=f9999,10000&q=a80&n=0&g=0n&fmt=jpeg?sec=1621996490&t=8c384c2823ea453da15a1b9cd5183eea"; + Assert.assertEquals(url2, urlBuilder.toString()); } @Test From 41bae74843ab18b10db8b8a04e35da6348539022 Mon Sep 17 00:00:00 2001 From: Looly Date: Thu, 20 Jan 2022 12:16:48 +0800 Subject: [PATCH 28/28] =?UTF-8?q?=F0=9F=A7=A3release=205.7.20?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- hutool-all/pom.xml | 2 +- hutool-aop/pom.xml | 2 +- hutool-bloomFilter/pom.xml | 2 +- hutool-bom/pom.xml | 2 +- hutool-cache/pom.xml | 2 +- hutool-captcha/pom.xml | 2 +- hutool-core/pom.xml | 2 +- hutool-cron/pom.xml | 2 +- hutool-crypto/pom.xml | 2 +- hutool-db/pom.xml | 2 +- hutool-dfa/pom.xml | 2 +- hutool-extra/pom.xml | 2 +- hutool-http/pom.xml | 2 +- hutool-json/pom.xml | 2 +- hutool-jwt/pom.xml | 2 +- hutool-log/pom.xml | 2 +- hutool-poi/pom.xml | 2 +- hutool-script/pom.xml | 2 +- hutool-setting/pom.xml | 2 +- hutool-socket/pom.xml | 2 +- hutool-system/pom.xml | 2 +- pom.xml | 2 +- 22 files changed, 22 insertions(+), 22 deletions(-) diff --git a/hutool-all/pom.xml b/hutool-all/pom.xml index 10a7032a4..511746da4 100644 --- a/hutool-all/pom.xml +++ b/hutool-all/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20-SNAPSHOT + 5.7.20 hutool-all diff --git a/hutool-aop/pom.xml b/hutool-aop/pom.xml index edc46b7a7..7ea45314f 100644 --- a/hutool-aop/pom.xml +++ b/hutool-aop/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20-SNAPSHOT + 5.7.20 hutool-aop diff --git a/hutool-bloomFilter/pom.xml b/hutool-bloomFilter/pom.xml index abeaec1c1..c8cdcd926 100644 --- a/hutool-bloomFilter/pom.xml +++ b/hutool-bloomFilter/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20-SNAPSHOT + 5.7.20 hutool-bloomFilter diff --git a/hutool-bom/pom.xml b/hutool-bom/pom.xml index 3b8ad4fd4..c3cee33b3 100644 --- a/hutool-bom/pom.xml +++ b/hutool-bom/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20-SNAPSHOT + 5.7.20 hutool-bom diff --git a/hutool-cache/pom.xml b/hutool-cache/pom.xml index d544b5527..062080971 100644 --- a/hutool-cache/pom.xml +++ b/hutool-cache/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20-SNAPSHOT + 5.7.20 hutool-cache diff --git a/hutool-captcha/pom.xml b/hutool-captcha/pom.xml index 5dd8f9f15..45754904a 100644 --- a/hutool-captcha/pom.xml +++ b/hutool-captcha/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20-SNAPSHOT + 5.7.20 hutool-captcha diff --git a/hutool-core/pom.xml b/hutool-core/pom.xml index 0129bcab8..995d36026 100644 --- a/hutool-core/pom.xml +++ b/hutool-core/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20-SNAPSHOT + 5.7.20 hutool-core diff --git a/hutool-cron/pom.xml b/hutool-cron/pom.xml index d95c455b4..f0929496c 100644 --- a/hutool-cron/pom.xml +++ b/hutool-cron/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20-SNAPSHOT + 5.7.20 hutool-cron diff --git a/hutool-crypto/pom.xml b/hutool-crypto/pom.xml index 50f7356ac..7105bd7f5 100644 --- a/hutool-crypto/pom.xml +++ b/hutool-crypto/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20-SNAPSHOT + 5.7.20 hutool-crypto diff --git a/hutool-db/pom.xml b/hutool-db/pom.xml index 92fe54fa8..4d6d83222 100644 --- a/hutool-db/pom.xml +++ b/hutool-db/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20-SNAPSHOT + 5.7.20 hutool-db diff --git a/hutool-dfa/pom.xml b/hutool-dfa/pom.xml index 183c0eff9..244b9c3fe 100644 --- a/hutool-dfa/pom.xml +++ b/hutool-dfa/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20-SNAPSHOT + 5.7.20 hutool-dfa diff --git a/hutool-extra/pom.xml b/hutool-extra/pom.xml index 7fca763ac..0a6a57284 100644 --- a/hutool-extra/pom.xml +++ b/hutool-extra/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20-SNAPSHOT + 5.7.20 hutool-extra diff --git a/hutool-http/pom.xml b/hutool-http/pom.xml index a27a4ac72..b4a57da4c 100644 --- a/hutool-http/pom.xml +++ b/hutool-http/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20-SNAPSHOT + 5.7.20 hutool-http diff --git a/hutool-json/pom.xml b/hutool-json/pom.xml index 5103dc136..a41748fd4 100644 --- a/hutool-json/pom.xml +++ b/hutool-json/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20-SNAPSHOT + 5.7.20 hutool-json diff --git a/hutool-jwt/pom.xml b/hutool-jwt/pom.xml index 7520a39c4..549b5b819 100644 --- a/hutool-jwt/pom.xml +++ b/hutool-jwt/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20-SNAPSHOT + 5.7.20 hutool-jwt diff --git a/hutool-log/pom.xml b/hutool-log/pom.xml index 075926147..ea661e721 100644 --- a/hutool-log/pom.xml +++ b/hutool-log/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20-SNAPSHOT + 5.7.20 hutool-log diff --git a/hutool-poi/pom.xml b/hutool-poi/pom.xml index f80d0c8e9..80d7ce519 100644 --- a/hutool-poi/pom.xml +++ b/hutool-poi/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20-SNAPSHOT + 5.7.20 hutool-poi diff --git a/hutool-script/pom.xml b/hutool-script/pom.xml index 21854b669..7fa28a4f5 100644 --- a/hutool-script/pom.xml +++ b/hutool-script/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20-SNAPSHOT + 5.7.20 hutool-script diff --git a/hutool-setting/pom.xml b/hutool-setting/pom.xml index 107bd6367..7bd5fdce5 100644 --- a/hutool-setting/pom.xml +++ b/hutool-setting/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20-SNAPSHOT + 5.7.20 hutool-setting diff --git a/hutool-socket/pom.xml b/hutool-socket/pom.xml index 26529bfd2..d8d684dfe 100644 --- a/hutool-socket/pom.xml +++ b/hutool-socket/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20-SNAPSHOT + 5.7.20 hutool-socket diff --git a/hutool-system/pom.xml b/hutool-system/pom.xml index ca87e5d2d..4105c3a27 100644 --- a/hutool-system/pom.xml +++ b/hutool-system/pom.xml @@ -9,7 +9,7 @@ cn.hutool hutool-parent - 5.7.20-SNAPSHOT + 5.7.20 hutool-system diff --git a/pom.xml b/pom.xml index baad04d0a..ec82d9de5 100644 --- a/pom.xml +++ b/pom.xml @@ -8,7 +8,7 @@ cn.hutool hutool-parent - 5.7.20-SNAPSHOT + 5.7.20 hutool Hutool是一个小而全的Java工具类库,通过静态方法封装,降低相关API的学习成本,提高工作效率,使Java拥有函数式语言般的优雅,让Java语言也可以“甜甜的”。 https://github.com/dromara/hutool