refactor: 提取 TransactionTemplate,分离事务管理职责

将 executeTransaction / commitIfTrue 及 TransactionJdbcExecutor
从 SimpleJdbcTemplate 移至独立的 TransactionTemplate 类:

- 新增 TransactionTemplate,封装事务生命周期(开启/提交/回滚)
- SimpleJdbcTemplate 新增 transaction() 入口
- 更新 TransactionTest 适配新 API:template.transaction().execute()
- 更新 README.md 事务章节,说明 TransactionTemplate 使用方式
This commit is contained in:
2026-05-31 05:48:51 +08:00
parent b639daca30
commit 1a308ed30e
4 changed files with 304 additions and 221 deletions

View File

@@ -68,7 +68,9 @@ Maven 依赖:
## 4. 事务
- **`executeTransaction(consumer)`**:执行事务。传入 `ThrowingConsumer<JdbcOperations>`,若内部无异常则提交,有异常则回滚
通过 `TransactionTemplate` 管理事务,可直接创建或通过 `SimpleJdbcTemplate.transaction()` 获取
- **`execute(consumer)`**:执行事务。传入 `ThrowingConsumer<JdbcOperations>`,若内部无异常则提交,有异常则回滚。
- **`commitIfTrue(predicate)`**:执行事务。传入 `ThrowingPredicate<JdbcOperations>`,返回 `true` 提交,返回 `false` 或抛异常则回滚。
## 5. 参数构建
@@ -246,7 +248,7 @@ if (result.getStatus() == BatchUpdateStatus.COMPLETED_WITH_ERRORS) {
### 6.4 事务
```java
jdbcTemplate.executeTransaction(jdbc -> {
jdbcTemplate.transaction().execute(jdbc -> {
...
jdbc.update(...);
...
@@ -255,7 +257,7 @@ jdbcTemplate.executeTransaction(jdbc -> {
// 无异常则自动提交
});
jdbcTemplate.commitIfTrue(jdbc -> {
jdbcTemplate.transaction().commitIfTrue(jdbc -> {
...
jdbc.update(...);
...

View File

@@ -26,8 +26,6 @@ import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.sql.DataSource;
import xyz.zhouxy.plusone.commons.function.ThrowingConsumer;
import xyz.zhouxy.plusone.commons.function.ThrowingPredicate;
import xyz.zhouxy.plusone.commons.util.AssertTools;
/**
@@ -35,19 +33,23 @@ import xyz.zhouxy.plusone.commons.util.AssertTools;
*
* <p>
* 对 JDBC 的简单封装,方便数据库操作,支持事务,支持批量操作,支持自定义结果集映射
* </p>
*
* @author ZhouXY
* @since 1.0.0
* @see TransactionTemplate
*/
public class SimpleJdbcTemplate implements JdbcOperations {
@Nonnull
private final DataSource dataSource;
@Nonnull
private final TransactionTemplate transactionTemplate;
public SimpleJdbcTemplate(@Nonnull DataSource dataSource) {
AssertTools.checkNotNull(dataSource);
this.dataSource = dataSource;
this.transactionTemplate = new TransactionTemplate(dataSource);
}
// #region - query
@@ -184,203 +186,10 @@ public class SimpleJdbcTemplate implements JdbcOperations {
// #region - transaction
/**
* 执行事务。如果未发生异常,则提交事务;当有异常发生时,回滚事务
*
* <p>
* operations 中使用 JdbcExecutor 实参进行 JDBC 操作,这些操作在一个连接中
* </p>
*
* @param <E> 异常类型
* @param operations 事务操作
* @throws SQLException SQL 异常
* @throws TransactionException 事务异常。事务中的异常会包装在该异常中。
*/
public <E extends Exception> void executeTransaction(
@Nonnull final ThrowingConsumer<JdbcOperations, E> operations)
throws TransactionException, SQLException {
AssertTools.checkNotNull(operations, "Operations can not be null.");
try (Connection conn = this.dataSource.getConnection()) {
final boolean autoCommit = conn.getAutoCommit();
try {
conn.setAutoCommit(false);
operations.accept(new TransactionJdbcExecutor(conn));
conn.commit();
}
catch (Exception e) {
try {
conn.rollback();
}
catch (SQLException ex) {
e.addSuppressed(ex);
}
throw new TransactionException(e);
}
finally {
conn.setAutoCommit(autoCommit);
}
}
}
/**
* 执行事务。
* 如果 {@code operations} 返回 {@code true},则提交事务;
* 如果抛出异常,或返回 {@code false},则回滚事务
*
* @param <E> 事务中的异常
* @param operations 事务操作
* @throws SQLException 数据库异常
* @throws TransactionException 事务异常。事务中的异常会包装在该异常中。
*/
public <E extends Exception> void commitIfTrue(
@Nonnull final ThrowingPredicate<JdbcOperations, E> operations)
throws SQLException, TransactionException {
AssertTools.checkNotNull(operations, "Operations can not be null.");
try (Connection conn = this.dataSource.getConnection()) {
final boolean autoCommit = conn.getAutoCommit();
try {
conn.setAutoCommit(false);
if (operations.test(new TransactionJdbcExecutor(conn))) {
conn.commit();
}
else {
conn.rollback();
}
}
catch (Exception e) {
try {
conn.rollback();
}
catch (SQLException ex) {
e.addSuppressed(ex);
}
throw new TransactionException(e);
}
finally {
conn.setAutoCommit(autoCommit);
}
}
public TransactionTemplate transaction() {
return this.transactionTemplate;
}
// #endregion
private static final class TransactionJdbcExecutor implements JdbcOperations {
private final Connection conn;
private TransactionJdbcExecutor(Connection conn) {
this.conn = conn;
}
// #region - query
/** {@inheritDoc} */
@Override
public <T> T query(String sql, Object[] params, ResultHandler<T> resultHandler)
throws SQLException {
return JdbcOperationSupport.query(this.conn, sql, params, resultHandler);
}
// #endregion
// #region - queryList
/** {@inheritDoc} */
@Override
public <T> List<T> queryList(String sql, Object[] params, RowMapper<T> rowMapper)
throws SQLException {
return JdbcOperationSupport.queryList(this.conn, sql, params, rowMapper);
}
/** {@inheritDoc} */
@Override
public <T> List<T> queryList(String sql, Object[] params, Class<T> clazz)
throws SQLException {
return JdbcOperationSupport.queryList(this.conn, sql, params, clazz);
}
/** {@inheritDoc} */
@Override
public List<Map<String, Object>> queryList(String sql, Object[] params)
throws SQLException {
return JdbcOperationSupport.queryList(this.conn, sql, params, RowMapper.HASH_MAP_MAPPER);
}
// #endregion
// #region - queryFirst
/** {@inheritDoc} */
@Override
public <T> Optional<T> queryFirst(String sql, Object[] params, RowMapper<T> rowMapper)
throws SQLException {
final T result = JdbcOperationSupport.queryFirst(this.conn, sql, params, rowMapper);
return Optional.ofNullable(result);
}
/** {@inheritDoc} */
@Override
public <T> Optional<T> queryFirst(String sql, Object[] params, Class<T> clazz)
throws SQLException {
final T result = JdbcOperationSupport.queryFirst(this.conn, sql, params, clazz);
return Optional.ofNullable(result);
}
/** {@inheritDoc} */
@Override
public Optional<Map<String, Object>> queryFirst(String sql, Object[] params)
throws SQLException {
final Map<String, Object> result = JdbcOperationSupport
.queryFirst(this.conn, sql, params, RowMapper.HASH_MAP_MAPPER);
return Optional.ofNullable(result);
}
/** {@inheritDoc} */
@Override
public boolean queryBoolean(String sql, Object[] params)
throws SQLException {
final Boolean result = JdbcOperationSupport
.queryFirst(this.conn, sql, params, Boolean.class);
return Boolean.TRUE.equals(result);
}
// #endregion
// #region - update & batchUpdate
/** {@inheritDoc} */
@Override
public int update(String sql, Object[] params)
throws SQLException {
return JdbcOperationSupport.update(this.conn, sql, params);
}
/** {@inheritDoc} */
@Override
public <T> List<T> updateAndReturnKeys(String sql, Object[] params, RowMapper<T> rowMapper)
throws SQLException {
return JdbcOperationSupport.updateAndReturnKeys(this.conn, sql, params, rowMapper);
}
/** {@inheritDoc} */
@Override
public BatchUpdateResult batchUpdate(String sql, @Nullable Collection<Object[]> params, int batchSize)
throws SQLException {
return JdbcOperationSupport.batchUpdate(this.conn, sql, params, batchSize, false);
}
/** {@inheritDoc} */
@Override
public BatchUpdateResult batchUpdate(String sql,
@Nullable Collection<Object[]> params,
int batchSize,
boolean quietly) throws SQLException {
return JdbcOperationSupport
.batchUpdate(this.conn, sql, params, batchSize, quietly);
}
// #endregion
}
}

View File

@@ -0,0 +1,271 @@
/*
* Copyright 2026-present ZhouXY
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package xyz.zhouxy.jdbc;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.sql.DataSource;
import xyz.zhouxy.plusone.commons.function.ThrowingConsumer;
import xyz.zhouxy.plusone.commons.function.ThrowingPredicate;
import xyz.zhouxy.plusone.commons.util.AssertTools;
/**
* 事务模板,提供事务执行能力。
*
* <p>
* 负责管理事务的生命周期:开启、提交、回滚、恢复自动提交。
* 事务内的 JDBC 操作通过 {@link JdbcOperations} 接口进行,
* 所有操作共享同一个数据库连接。
* </p>
*
* <p>使用示例:</p>
* <pre>{@code
* TransactionTemplate tx = new TransactionTemplate(dataSource);
*
* // 消费者模式:无异常自动提交
* tx.execute(ops -> {
* ops.update("INSERT INTO ...", buildParams(...));
* ops.update("UPDATE ...", buildParams(...));
* });
*
* // 谓词模式:返回 true 提交false 回滚
* tx.commitIfTrue(ops -> {
* ops.update("UPDATE ...", buildParams(...));
* return ops.queryBoolean("SELECT ...", buildParams(...));
* });
* }</pre>
*
* @author ZhouXY
* @since 1.1.0
*/
public class TransactionTemplate {
@Nonnull
private final DataSource dataSource;
public TransactionTemplate(@Nonnull DataSource dataSource) {
AssertTools.checkNotNull(dataSource);
this.dataSource = dataSource;
}
/**
* 执行事务。如果未发生异常,则提交事务;当有异常发生时,回滚事务
*
* <p>
* operations 中使用 JdbcExecutor 实参进行 JDBC 操作,这些操作在一个连接中
* </p>
*
* @param <E> 异常类型
* @param operations 事务操作
* @throws SQLException SQL 异常
* @throws TransactionException 事务异常。事务中的异常会包装在该异常中。
*/
public <E extends Exception> void execute(
@Nonnull final ThrowingConsumer<JdbcOperations, E> operations)
throws TransactionException, SQLException {
AssertTools.checkNotNull(operations, "Operations can not be null.");
try (Connection conn = this.dataSource.getConnection()) {
final boolean autoCommit = conn.getAutoCommit();
try {
conn.setAutoCommit(false);
operations.accept(new TransactionJdbcExecutor(conn));
conn.commit();
}
catch (Exception e) {
rollbackSilently(conn, e);
throw new TransactionException(e);
}
finally {
conn.setAutoCommit(autoCommit);
}
}
}
/**
* 执行事务。
* 如果 {@code operations} 返回 {@code true},则提交事务;
* 如果抛出异常,或返回 {@code false},则回滚事务
*
* @param <E> 事务中的异常
* @param operations 事务操作
* @throws SQLException 数据库异常
* @throws TransactionException 事务异常。事务中的异常会包装在该异常中。
*/
public <E extends Exception> void commitIfTrue(
@Nonnull final ThrowingPredicate<JdbcOperations, E> operations)
throws SQLException, TransactionException {
AssertTools.checkNotNull(operations, "Operations can not be null.");
try (Connection conn = this.dataSource.getConnection()) {
final boolean autoCommit = conn.getAutoCommit();
try {
conn.setAutoCommit(false);
if (operations.test(new TransactionJdbcExecutor(conn))) {
conn.commit();
}
else {
conn.rollback();
}
}
catch (Exception e) {
rollbackSilently(conn, e);
throw new TransactionException(e);
}
finally {
conn.setAutoCommit(autoCommit);
}
}
}
private void rollbackSilently(Connection conn, Exception e) {
try {
conn.rollback();
}
catch (SQLException ex) {
e.addSuppressed(ex);
}
}
// #region - TransactionJdbcExecutor
private static final class TransactionJdbcExecutor implements JdbcOperations {
private final Connection conn;
private TransactionJdbcExecutor(Connection conn) {
this.conn = conn;
}
// #region - query
/** {@inheritDoc} */
@Override
public <T> T query(String sql, Object[] params, ResultHandler<T> resultHandler)
throws SQLException {
return JdbcOperationSupport.query(this.conn, sql, params, resultHandler);
}
// #endregion
// #region - queryList
/** {@inheritDoc} */
@Override
public <T> List<T> queryList(String sql, Object[] params, RowMapper<T> rowMapper)
throws SQLException {
return JdbcOperationSupport.queryList(this.conn, sql, params, rowMapper);
}
/** {@inheritDoc} */
@Override
public <T> List<T> queryList(String sql, Object[] params, Class<T> clazz)
throws SQLException {
return JdbcOperationSupport.queryList(this.conn, sql, params, clazz);
}
/** {@inheritDoc} */
@Override
public List<Map<String, Object>> queryList(String sql, Object[] params)
throws SQLException {
return JdbcOperationSupport.queryList(this.conn, sql, params, RowMapper.HASH_MAP_MAPPER);
}
// #endregion
// #region - queryFirst
/** {@inheritDoc} */
@Override
public <T> Optional<T> queryFirst(String sql, Object[] params, RowMapper<T> rowMapper)
throws SQLException {
final T result = JdbcOperationSupport.queryFirst(this.conn, sql, params, rowMapper);
return Optional.ofNullable(result);
}
/** {@inheritDoc} */
@Override
public <T> Optional<T> queryFirst(String sql, Object[] params, Class<T> clazz)
throws SQLException {
final T result = JdbcOperationSupport.queryFirst(this.conn, sql, params, clazz);
return Optional.ofNullable(result);
}
/** {@inheritDoc} */
@Override
public Optional<Map<String, Object>> queryFirst(String sql, Object[] params)
throws SQLException {
final Map<String, Object> result = JdbcOperationSupport
.queryFirst(this.conn, sql, params, RowMapper.HASH_MAP_MAPPER);
return Optional.ofNullable(result);
}
/** {@inheritDoc} */
@Override
public boolean queryBoolean(String sql, Object[] params)
throws SQLException {
final Boolean result = JdbcOperationSupport
.queryFirst(this.conn, sql, params, Boolean.class);
return Boolean.TRUE.equals(result);
}
// #endregion
// #region - update & batchUpdate
/** {@inheritDoc} */
@Override
public int update(String sql, Object[] params)
throws SQLException {
return JdbcOperationSupport.update(this.conn, sql, params);
}
/** {@inheritDoc} */
@Override
public <T> List<T> updateAndReturnKeys(String sql, Object[] params, RowMapper<T> rowMapper)
throws SQLException {
return JdbcOperationSupport.updateAndReturnKeys(this.conn, sql, params, rowMapper);
}
/** {@inheritDoc} */
@Override
public BatchUpdateResult batchUpdate(String sql, @Nullable Collection<Object[]> params, int batchSize)
throws SQLException {
return JdbcOperationSupport.batchUpdate(this.conn, sql, params, batchSize, false);
}
/** {@inheritDoc} */
@Override
public BatchUpdateResult batchUpdate(String sql,
@Nullable Collection<Object[]> params,
int batchSize,
boolean quietly) throws SQLException {
return JdbcOperationSupport
.batchUpdate(this.conn, sql, params, batchSize, quietly);
}
// #endregion
}
// #endregion
}

View File

@@ -16,9 +16,10 @@ import xyz.zhouxy.jdbc.SimpleJdbcTemplate;
import xyz.zhouxy.jdbc.TransactionException;
/**
* 事务 API 测试:executeTransaction、commitIfTrue。
* 事务 API 测试:通过 {@link xyz.zhouxy.jdbc.TransactionTemplate#execute} 和
* {@link xyz.zhouxy.jdbc.TransactionTemplate#commitIfTrue} 测试事务提交与回滚。
*/
@DisplayName("SimpleJdbcTemplate 事务操作")
@DisplayName("TransactionTemplate 事务操作")
class TransactionTest extends BaseH2Test {
private static final Logger logger = LoggerFactory.getLogger(TransactionTest.class);
@@ -28,14 +29,14 @@ class TransactionTest extends BaseH2Test {
resetDatabase();
}
// ==================== executeTransaction 正常提交 ====================
// ==================== execute 正常提交 ====================
@Test
@DisplayName("executeTransaction:正常提交,数据持久化")
@DisplayName("execute正常提交数据持久化")
void testExecuteTransactionCommit() throws Exception {
SimpleJdbcTemplate template = createTemplate();
template.executeTransaction((JdbcOperations ops) -> {
template.transaction().execute((JdbcOperations ops) -> {
ops.update("INSERT INTO users (username, email, age, balance, active) VALUES (?, ?, ?, ?, ?)",
buildParams("txUser1", "tx1@test.com", 25, 1000L, true));
ops.update("UPDATE users SET balance = ? WHERE username = ?",
@@ -56,10 +57,10 @@ class TransactionTest extends BaseH2Test {
logger.info("事务提交验证通过");
}
// ==================== executeTransaction 异常回滚 ====================
// ==================== execute 异常回滚 ====================
@Test
@DisplayName("executeTransaction:异常回滚,数据恢复原状")
@DisplayName("execute异常回滚数据恢复原状")
void testExecuteTransactionRollback() throws Exception {
SimpleJdbcTemplate template = createTemplate();
@@ -69,7 +70,7 @@ class TransactionTest extends BaseH2Test {
buildParams("alice"), Long.class);
TransactionException ex = assertThrows(TransactionException.class, () ->
template.executeTransaction((JdbcOperations ops) -> {
template.transaction().execute((JdbcOperations ops) -> {
ops.update("UPDATE users SET balance = ? WHERE username = ?",
buildParams(0L, "alice"));
ops.update("INSERT INTO users (username, email) VALUES (?, ?)",
@@ -99,12 +100,12 @@ class TransactionTest extends BaseH2Test {
}
@Test
@DisplayName("executeTransactionSQL 异常触发回滚")
@DisplayName("executeSQL 异常触发回滚")
void testExecuteTransactionSqlExceptionRollback() {
SimpleJdbcTemplate template = createTemplate();
assertThrows(TransactionException.class, () ->
template.executeTransaction((JdbcOperations ops) -> {
template.transaction().execute((JdbcOperations ops) -> {
ops.update("INSERT INTO users (username) VALUES (?)",
buildParams("validUser"));
// 错误的 SQL
@@ -120,14 +121,14 @@ class TransactionTest extends BaseH2Test {
});
}
// ==================== commitIfTrue 返回 true 提交 ====================
// ==================== commitIfTrue返回 true 提交 ====================
@Test
@DisplayName("commitIfTrue返回 true 提交事务")
void testCommitIfTrueCommit() throws Exception {
SimpleJdbcTemplate template = createTemplate();
template.commitIfTrue((JdbcOperations ops) -> {
template.transaction().commitIfTrue((JdbcOperations ops) -> {
ops.update("INSERT INTO users (username, email) VALUES (?, ?)",
buildParams("cftUser", "cft@test.com"));
return true;
@@ -147,7 +148,7 @@ class TransactionTest extends BaseH2Test {
void testCommitIfFalseRollback() throws Exception {
SimpleJdbcTemplate template = createTemplate();
template.commitIfTrue((JdbcOperations ops) -> {
template.transaction().commitIfTrue((JdbcOperations ops) -> {
ops.update("INSERT INTO users (username, email) VALUES (?, ?)",
buildParams("cffUser", "cff@test.com"));
return false;
@@ -168,7 +169,7 @@ class TransactionTest extends BaseH2Test {
SimpleJdbcTemplate template = createTemplate();
assertThrows(TransactionException.class, () ->
template.commitIfTrue((JdbcOperations ops) -> {
template.transaction().commitIfTrue((JdbcOperations ops) -> {
ops.update("INSERT INTO users (username) VALUES (?)",
buildParams("exUser"));
throw new IllegalStateException("条件不满足");
@@ -186,11 +187,11 @@ class TransactionTest extends BaseH2Test {
// ==================== 事务内查询可见性 ====================
@Test
@DisplayName("executeTransaction:事务内可查询到未提交的数据")
@DisplayName("execute事务内可查询到未提交的数据")
void testTransactionVisibility() throws Exception {
SimpleJdbcTemplate template = createTemplate();
template.executeTransaction((JdbcOperations ops) -> {
template.transaction().execute((JdbcOperations ops) -> {
ops.update("INSERT INTO users (username, email) VALUES (?, ?)",
buildParams("visible", "visible@test.com"));
@@ -207,13 +208,13 @@ class TransactionTest extends BaseH2Test {
// ==================== 边界情况 ====================
@Test
@DisplayName("executeTransaction:空操作(无异常)正常提交")
@DisplayName("execute空操作无异常正常提交")
void testExecuteTransactionEmpty() throws Exception {
SimpleJdbcTemplate template = createTemplate();
// 空操作不应抛异常
assertDoesNotThrow(() ->
template.executeTransaction(ops -> { /* no-op */ }));
template.transaction().execute(ops -> { /* no-op */ }));
// 数据应保持不变
int count = template.query("SELECT COUNT(*) FROM users",
@@ -222,12 +223,12 @@ class TransactionTest extends BaseH2Test {
}
@Test
@DisplayName("executeTransactionnull 操作抛异常")
@DisplayName("executenull 操作抛异常")
@SuppressWarnings("null")
void testExecuteTransactionNullOps() {
SimpleJdbcTemplate template = createTemplate();
assertThrows(Exception.class, () ->
template.executeTransaction(null));
template.transaction().execute(null));
}
}