From 3ca9ad2be16f88e28ee2d73e38dc4fa4882f3489 Mon Sep 17 00:00:00 2001 From: ZhouXY108 Date: Thu, 3 Oct 2024 08:46:24 +0800 Subject: [PATCH] =?UTF-8?q?=E6=B7=BB=E5=8A=A0=20commitIfTrue=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 +- pom.xml | 6 + .../java/xyz/zhouxy/jdbc/ParamBuilder.java | 58 ++++++ src/main/java/xyz/zhouxy/jdbc/ResultMap.java | 3 - .../xyz/zhouxy/jdbc/SimpleJdbcTemplate.java | 117 +++++------- .../zhouxy/jdbc/SimpleJdbcTemplateTests.java | 166 +++++++++++++----- 6 files changed, 233 insertions(+), 119 deletions(-) create mode 100644 src/main/java/xyz/zhouxy/jdbc/ParamBuilder.java diff --git a/.gitignore b/.gitignore index 5ff6309..af665ab 100644 --- a/.gitignore +++ b/.gitignore @@ -35,4 +35,4 @@ build/ .vscode/ ### Mac OS ### -.DS_Store \ No newline at end of file +.DS_Store diff --git a/pom.xml b/pom.xml index 8526a62..5ac365c 100644 --- a/pom.xml +++ b/pom.xml @@ -21,6 +21,12 @@ 0.1.0-SNAPSHOT + + org.apache.commons + commons-lang3 + 3.16.0 + + org.junit.jupiter junit-jupiter-api diff --git a/src/main/java/xyz/zhouxy/jdbc/ParamBuilder.java b/src/main/java/xyz/zhouxy/jdbc/ParamBuilder.java new file mode 100644 index 0000000..90b3e51 --- /dev/null +++ b/src/main/java/xyz/zhouxy/jdbc/ParamBuilder.java @@ -0,0 +1,58 @@ +package xyz.zhouxy.jdbc; + +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.OptionalDouble; +import java.util.OptionalInt; +import java.util.OptionalLong; +import java.util.function.Function; +import java.util.stream.Collectors; + +import com.google.common.base.Preconditions; + +import xyz.zhouxy.plusone.commons.collection.CollectionTools; +import xyz.zhouxy.plusone.commons.util.ArrayTools; +import xyz.zhouxy.plusone.commons.util.OptionalTools; + +public class ParamBuilder { + public static final Object[] EMPTY_OBJECT_ARRAY = {}; + + public static Object[] buildParams(final Object... params) { + if (ArrayTools.isNullOrEmpty(params)) { + return EMPTY_OBJECT_ARRAY; + } + return Arrays.stream(params) + .map(param -> { + if (param instanceof Optional) { + return OptionalTools.orElseNull((Optional) param); + } + if (param instanceof OptionalInt) { + return OptionalTools.toInteger(((OptionalInt) param)); + } + if (param instanceof OptionalLong) { + return OptionalTools.toLong(((OptionalLong) param)); + } + if (param instanceof OptionalDouble) { + return OptionalTools.toDouble(((OptionalDouble) param)); + } + return param; + }) + .toArray(); + } + + public static List buildBatchParams(final Collection c, final Function func) { + Preconditions.checkNotNull(c, "The collection can not be null."); + Preconditions.checkNotNull(func, "The func can not be null."); + if (CollectionTools.isEmpty(c)) { + return Collections.emptyList(); + } + return c.stream().map(func).collect(Collectors.toList()); + } + + private ParamBuilder() { + throw new IllegalStateException("Utility class"); + } +} diff --git a/src/main/java/xyz/zhouxy/jdbc/ResultMap.java b/src/main/java/xyz/zhouxy/jdbc/ResultMap.java index 005efbd..a009497 100644 --- a/src/main/java/xyz/zhouxy/jdbc/ResultMap.java +++ b/src/main/java/xyz/zhouxy/jdbc/ResultMap.java @@ -16,15 +16,12 @@ package xyz.zhouxy.jdbc; -import com.google.common.annotations.Beta; - import java.sql.ResultSet; import java.sql.ResultSetMetaData; import java.sql.SQLException; import java.util.HashMap; import java.util.Map; -@Beta @FunctionalInterface public interface ResultMap { T map(ResultSet rs, int rowNumber) throws SQLException; diff --git a/src/main/java/xyz/zhouxy/jdbc/SimpleJdbcTemplate.java b/src/main/java/xyz/zhouxy/jdbc/SimpleJdbcTemplate.java index cb3c00c..aadf449 100644 --- a/src/main/java/xyz/zhouxy/jdbc/SimpleJdbcTemplate.java +++ b/src/main/java/xyz/zhouxy/jdbc/SimpleJdbcTemplate.java @@ -25,27 +25,19 @@ import java.sql.Statement; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Optional; import java.util.OptionalDouble; import java.util.OptionalInt; import java.util.OptionalLong; -import java.util.function.Function; -import java.util.stream.Collectors; - import javax.annotation.Nonnull; -import com.google.common.annotations.Beta; import com.google.common.base.Preconditions; import com.google.common.collect.Lists; -import xyz.zhouxy.plusone.commons.collection.CollectionTools; -import xyz.zhouxy.plusone.commons.util.ArrayTools; import xyz.zhouxy.plusone.commons.util.OptionalTools; -@Beta public class SimpleJdbcTemplate { public static JdbcExecutor connect(final Connection conn) { @@ -86,7 +78,7 @@ public class SimpleJdbcTemplate { private final Connection conn; - public JdbcExecutor(Connection conn) { + private JdbcExecutor(Connection conn) { this.conn = conn; } @@ -109,46 +101,46 @@ public class SimpleJdbcTemplate { return query(sql, params, resultMap).stream().findFirst(); } - public List> query(String sql, Object... params) throws SQLException { + public List> query(String sql, Object[] params) throws SQLException { return query(sql, params, ResultMap.mapResultMap); } - public Optional> queryFirst(String sql, Object... params) throws SQLException { + public Optional> queryFirst(String sql, Object[] params) throws SQLException { return queryFirst(sql, params, ResultMap.mapResultMap); } - public List queryToRecordList(String sql, Object... params) throws SQLException { + public List queryToRecordList(String sql, Object[] params) throws SQLException { return query(sql, params, ResultMap.recordResultMap); } - public Optional queryFirstRecord(String sql, Object... params) throws SQLException { + public Optional queryFirstRecord(String sql, Object[] params) throws SQLException { return queryFirst(sql, params, ResultMap.recordResultMap); } - public Optional queryToString(String sql, Object... params) throws SQLException { + public Optional queryToString(String sql, Object[] params) throws SQLException { return queryFirst(sql, params, (rs, rowNumber) -> rs.getString(1)); } - public OptionalInt queryToInt(String sql, Object... params) throws SQLException { + public OptionalInt queryToInt(String sql, Object[] params) throws SQLException { Optional result = queryFirst(sql, params, (rs, rowNumber) -> rs.getInt(1)); return OptionalTools.toOptionalInt(result); } - public OptionalLong queryToLong(String sql, Object... params) throws SQLException { + public OptionalLong queryToLong(String sql, Object[] params) throws SQLException { Optional result = queryFirst(sql, params, (rs, rowNumber) -> rs.getLong(1)); return OptionalTools.toOptionalLong(result); } - public OptionalDouble queryToDouble(String sql, Object... params) throws SQLException { + public OptionalDouble queryToDouble(String sql, Object[] params) throws SQLException { Optional result = queryFirst(sql, params, (rs, rowNumber) -> rs.getDouble(1)); return OptionalTools.toOptionalDouble(result); } - public Optional queryToBigDecimal(String sql, Object... params) throws SQLException { + public Optional queryToBigDecimal(String sql, Object[] params) throws SQLException { return queryFirst(sql, params, (rs, rowNumber) -> rs.getBigDecimal(1)); } - public int update(String sql, Object... params) throws SQLException { + public int update(String sql, Object[] params) throws SQLException { try (PreparedStatement stmt = this.conn.prepareStatement(sql)) { fillStatement(stmt, params); return stmt.executeUpdate(); @@ -185,7 +177,7 @@ public class SimpleJdbcTemplate { } } - public int[] batchUpdate(String sql, Collection params, int batchSize) throws SQLException { + public List batchUpdate(String sql, Collection params, int batchSize) throws SQLException { int executeCount = params.size() / batchSize; executeCount = (params.size() % batchSize == 0) ? executeCount : (executeCount + 1); List result = Lists.newArrayListWithCapacity(executeCount); @@ -194,9 +186,7 @@ public class SimpleJdbcTemplate { int i = 0; for (Object[] ps : params) { i++; - for (int j = 0; j < ps.length; j++) { - stmt.setObject(j + 1, ps[j]); - } + fillStatement(stmt, ps); stmt.addBatch(); if (i % batchSize == 0 || i >= params.size()) { int[] n = stmt.executeBatch(); @@ -204,16 +194,17 @@ public class SimpleJdbcTemplate { stmt.clearBatch(); } } - return ArrayTools.concatIntArray(result); + return result; } } - public void tx(final IAtom atom) throws SQLException, E { - Preconditions.checkNotNull(atom, "Atom can not be null."); + public void executeTransaction(@Nonnull final DbOperations operations) + throws SQLException, E { + Preconditions.checkNotNull(operations, "Operations can not be null."); final boolean autoCommit = this.conn.getAutoCommit(); try { this.conn.setAutoCommit(false); - atom.execute(this); + operations.execute(this); this.conn.commit(); } catch (Exception e) { @@ -225,9 +216,36 @@ public class SimpleJdbcTemplate { } } + public void commitIfTrue(@Nonnull final PredicateWithThrowable operations) + throws SQLException, E { + Preconditions.checkNotNull(operations, "Operations can not be null."); + final boolean autoCommit = this.conn.getAutoCommit(); + try { + this.conn.setAutoCommit(false); + if (operations.test(this)) { + this.conn.commit(); + } + else { + this.conn.rollback(); + } + } + catch (Exception e) { + this.conn.rollback(); + throw e; + } + finally { + this.conn.setAutoCommit(autoCommit); + } + } + @FunctionalInterface - public interface IAtom { - void execute(JdbcExecutor jdbcExecutor) throws SQLException, E; + public interface DbOperations { + void execute(JdbcExecutor jdbcExecutor) throws E; + } + + @FunctionalInterface + public interface PredicateWithThrowable { + boolean test(JdbcExecutor jdbcExecutor) throws E; } private static void fillStatement(PreparedStatement stmt, Object[] params) throws SQLException { @@ -251,45 +269,4 @@ public class SimpleJdbcTemplate { } } } - - public static class ParamBuilder { - - public static final Object[] EMPTY_OBJECT_ARRAY = {}; - - public static Object[] buildParams(final Object... params) { - if (ArrayTools.isNullOrEmpty(params)) { - return EMPTY_OBJECT_ARRAY; - } - return Arrays.stream(params) - .map(param -> { - if (param instanceof Optional) { - return OptionalTools.orElseNull((Optional) param); - } - if (param instanceof OptionalInt) { - return OptionalTools.toInteger(((OptionalInt) param)); - } - if (param instanceof OptionalLong) { - return OptionalTools.toLong(((OptionalLong) param)); - } - if (param instanceof OptionalDouble) { - return OptionalTools.toDouble(((OptionalDouble) param)); - } - return param; - }) - .toArray(); - } - - public static List buildBatchParams(final Collection c, final Function func) { - Preconditions.checkNotNull(c, "The collection can not be null."); - Preconditions.checkNotNull(func, "The func can not be null."); - if (CollectionTools.isEmpty(c)) { - return Collections.emptyList(); - } - return c.stream().map(func).collect(Collectors.toList()); - } - - private ParamBuilder() { - throw new IllegalStateException("Utility class"); - } - } } diff --git a/src/test/java/xyz/zhouxy/jdbc/SimpleJdbcTemplateTests.java b/src/test/java/xyz/zhouxy/jdbc/SimpleJdbcTemplateTests.java index 2df1a2d..b5a0654 100644 --- a/src/test/java/xyz/zhouxy/jdbc/SimpleJdbcTemplateTests.java +++ b/src/test/java/xyz/zhouxy/jdbc/SimpleJdbcTemplateTests.java @@ -3,16 +3,18 @@ package xyz.zhouxy.jdbc; import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertNotNull; import static org.junit.jupiter.api.Assertions.assertTrue; -import static xyz.zhouxy.jdbc.SimpleJdbcTemplate.ParamBuilder.*; +import static xyz.zhouxy.jdbc.ParamBuilder.*; import static xyz.zhouxy.plusone.commons.sql.JdbcSql.IN; import java.sql.Connection; import java.sql.SQLException; +import java.time.LocalDate; import java.time.LocalDateTime; -import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Optional; +import java.util.Random; +import java.util.concurrent.ThreadLocalRandom; import javax.sql.DataSource; @@ -20,13 +22,16 @@ import org.junit.jupiter.api.Test; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.collect.Lists; import com.zaxxer.hikari.HikariConfig; import com.zaxxer.hikari.HikariDataSource; import xyz.zhouxy.jdbc.SimpleJdbcTemplate.JdbcExecutor; import xyz.zhouxy.plusone.commons.sql.SQL; +import xyz.zhouxy.plusone.commons.util.ArrayTools; import xyz.zhouxy.plusone.commons.util.IdGenerator; import xyz.zhouxy.plusone.commons.util.IdWorker; +import xyz.zhouxy.plusone.commons.util.Numbers; class SimpleJdbcTemplateTests { @@ -48,26 +53,26 @@ class SimpleJdbcTemplateTests { config.setJdbcUrl("jdbc:postgresql://localhost:5432/plusone"); config.setUsername("postgres"); config.setPassword("zhouxy108"); - config.setMaximumPoolSize(800); + config.setMaximumPoolSize(8); config.setConnectionTimeout(1000000); dataSource = new HikariDataSource(config); } @Test void testQuery() throws SQLException { + Object[] ids = buildParams("501533", "501554", "544599"); + String sql = SQL.newJdbcSql() + .SELECT("*") + .FROM("test_table") + .WHERE(IN("id", ids)) + .toString(); + log.info(sql); try (Connection conn = dataSource.getConnection()) { - Object[] ids = buildParams("501533", "501554", "544599"); - String sql = SQL.newJdbcSql() - .SELECT("*") - .FROM("test_table") - .WHERE(IN("id", ids)) - .toString(); - log.info(sql); List rs = SimpleJdbcTemplate.connect(conn) .queryToRecordList(sql, ids); assertNotNull(rs); for (DbRecord baseEntity : rs) { - // log.info("id: {}", baseEntity.getValueAsString("id")); + // log.info("id: {}", baseEntity.getValueAsString("id")); // NOSONAR log.info(baseEntity.toString()); assertEquals(Optional.empty(), baseEntity.getValueAsString("updated_by")); } @@ -77,19 +82,26 @@ class SimpleJdbcTemplateTests { @Test void testInsert() throws SQLException { try (Connection conn = dataSource.getConnection()) { - List> keys = new ArrayList<>(); - SimpleJdbcTemplate.connect(conn).update("INSERT INTO base_table(status, created_by) VALUES (?, ?)", - buildParams(1, 886), keys); + List keys = SimpleJdbcTemplate.connect(conn).update( + "INSERT INTO base_table(status, created_by) VALUES (?, ?)", + buildParams(1, 886L), + ResultMap.recordResultMap); log.info("keys: {}", keys); + assertEquals(1, keys.size()); + DbRecord result = keys.get(0); + assertEquals(1, result.getValueAsInt("status").getAsInt()); + assertEquals(886L, result.getValueAsLong("created_by").getAsLong()); + assertTrue(result.get("id").isPresent()); } } @Test void testUpdate() throws SQLException { try (Connection conn = dataSource.getConnection()) { - List> keys = new ArrayList<>(); - SimpleJdbcTemplate.connect(conn).update("UPDATE base_table SET status = ?, version = version + 1, update_time = now(), updated_by = ? WHERE id = ?", - buildParams(2, 886, 9), keys); + List keys = SimpleJdbcTemplate.connect(conn).update( + "UPDATE base_table SET status = ?, version = version + 1, update_time = now(), updated_by = ? WHERE id = ? AND version = ?", + buildParams(2, 886, 9, 0), + ResultMap.recordResultMap); log.info("keys: {}", keys); } } @@ -101,21 +113,13 @@ class SimpleJdbcTemplateTests { try (Connection conn = dataSource.getConnection()) { long id = this.idGenerator.nextId(); JdbcExecutor jdbcExecutor = SimpleJdbcTemplate.connect(conn); - try { - jdbcExecutor.tx(jdbc -> { - jdbc.update("INSERT INTO base_table (id, created_by, create_time, status) VALUES (?, ?, ?, ?)", - buildParams(id, 585757, LocalDateTime.now(), 0)); - throw new NullPointerException(); - }); - } - catch (NullPointerException e) { - // ignore - } - catch (Exception e) { - e.printStackTrace(); - } + jdbcExecutor.executeTransaction(jdbc -> { + jdbc.update("INSERT INTO base_table (id, created_by, create_time, status) VALUES (?, ?, ?, ?)", + buildParams(id, 585757, LocalDateTime.now(), 0)); + throw new NullPointerException(); + }); Optional> first = jdbcExecutor - .queryFirst("SELECT * FROM base_table WHERE id = ?", id); + .queryFirst("SELECT * FROM base_table WHERE id = ?", buildParams(id)); log.info("first: {}", first); assertTrue(!first.isPresent()); } @@ -123,23 +127,95 @@ class SimpleJdbcTemplateTests { try (Connection conn = dataSource.getConnection()) { long id = this.idGenerator.nextId(); JdbcExecutor jdbcExecutor = SimpleJdbcTemplate.connect(conn); - try { - jdbcExecutor.tx(jdbc -> { - jdbc.update("INSERT INTO base_table (id, created_by, create_time, status) VALUES (?, ?, ?, ?)", - buildParams(id, 585757, LocalDateTime.now(), 0)); - // throw new NullPointerException(); - }); - } - catch (NullPointerException e) { - // ignore - } - catch (Exception e) { - e.printStackTrace(); - } + jdbcExecutor.executeTransaction(jdbc -> { + jdbc.update("INSERT INTO base_table (id, created_by, create_time, status) VALUES (?, ?, ?, ?)", + buildParams(id, 585757, LocalDateTime.now(), 0)); + // throw new NullPointerException(); // NOSONAR + }); Optional> first = jdbcExecutor - .queryFirst("SELECT * FROM base_table WHERE id = ?", id); + .queryFirst("SELECT * FROM base_table WHERE id = ?", buildParams(id)); + log.info("first: {}", first); + assertTrue(first.isPresent()); + } + + try (Connection conn = dataSource.getConnection()) { + long id = this.idGenerator.nextId(); + JdbcExecutor jdbcExecutor = SimpleJdbcTemplate.connect(conn); + jdbcExecutor.commitIfTrue(jdbc -> { + jdbc.update("INSERT INTO base_table (id, created_by, create_time, status) VALUES (?, ?, ?, ?)", + buildParams(id, 585757, LocalDateTime.now(), 0)); + throw new NullPointerException(); + }); + Optional> first = jdbcExecutor + .queryFirst("SELECT * FROM base_table WHERE id = ?", buildParams(id)); + log.info("first: {}", first); + assertTrue(!first.isPresent()); + } + + try (Connection conn = dataSource.getConnection()) { + long id = this.idGenerator.nextId(); + JdbcExecutor jdbcExecutor = SimpleJdbcTemplate.connect(conn); + jdbcExecutor.commitIfTrue(jdbc -> { + jdbc.update("INSERT INTO base_table (id, created_by, create_time, status) VALUES (?, ?, ?, ?)", + buildParams(id, 585757, LocalDateTime.now(), 0)); + return false; + }); + Optional> first = jdbcExecutor + .queryFirst("SELECT * FROM base_table WHERE id = ?", buildParams(id)); + log.info("first: {}", first); + assertTrue(!first.isPresent()); + } + + try (Connection conn = dataSource.getConnection()) { + long id = this.idGenerator.nextId(); + JdbcExecutor jdbcExecutor = SimpleJdbcTemplate.connect(conn); + jdbcExecutor.commitIfTrue(jdbc -> { + jdbc.update("INSERT INTO base_table (id, created_by, create_time, status) VALUES (?, ?, ?, ?)", + buildParams(id, 585757, LocalDateTime.now(), 0)); + return true; + }); + Optional> first = jdbcExecutor + .queryFirst("SELECT * FROM base_table WHERE id = ?", buildParams(id)); log.info("first: {}", first); assertTrue(first.isPresent()); } } + + @Test + void testBatch() throws Exception { + + Random random = ThreadLocalRandom.current(); + + LocalDate handleDate = LocalDate.of(1949, 10, 1); + List datas = Lists.newArrayList(); + while (handleDate.isBefore(LocalDate.of(2008, 8, 8))) { + DbRecord r1 = new DbRecord(); + r1.put("username", "张三2"); + r1.put("usage_date", handleDate); + r1.put("usage_duration", random.nextInt(500)); + datas.add(r1); + DbRecord r2 = new DbRecord(); + r2.put("username", "李四2"); + r2.put("usage_date", handleDate); + r2.put("usage_duration", random.nextInt(500)); + datas.add(r2); + + handleDate = handleDate.plusDays(1L); + } + try (Connection conn = dataSource.getConnection()) { + List result = SimpleJdbcTemplate.connect(conn) + .batchUpdate("insert into test_table (username, usage_date, usage_duration) values (?,?,?)", + buildBatchParams(datas, item -> buildParams( + item.getValueAsString("username"), + item.getValueAsString("usage_date"), + item.getValueAsString("usage_duration"))), + 400); + long sum = Numbers.sum(ArrayTools.concatIntArray(result)); + assertEquals(datas.size(), sum); + log.info("sum: {}", sum); + } catch (Exception e) { + e.printStackTrace(); + throw e; + } + } }