支持

  1. JDBCTemple
  2. 声明式事务
  3. 支持REQUIRED传播模式

首先配置DataSource

因为我们先前已经实现了读取xml和yaml文件的方法,所以,对于JDBC的配置,我们只需要从properties中拿东西就好了

在我们的配置文件中配置文件内容

# application.properties
summer.datasource.url=jdbc:mysql://localhost:3306/summer_jdbc?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
summer.datasource.username=root
summer.datasource.password=your_password
summer.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

然后实现一个由HikariCP连接池库支持的DataSource

@Configuration
public class JdbcConfiguration {

@Bean(destroyMethod = "close")
DataSource dataSource(
// properties:
@Value("${summer.datasource.url}") String url,
@Value("${summer.datasource.username}") String username,
@Value("${summer.datasource.password}") String password,
@Value("${summer.datasource.driver-class-name:}") String driver,
@Value("${summer.datasource.maximum-pool-size:20}") int maximumPoolSize,
@Value("${summer.datasource.minimum-pool-size:1}") int minimumPoolSize,
@Value("${summer.datasource.connection-timeout:30000}") int connTimeout
) {
var config = new HikariConfig();
config.setAutoCommit(false);
config.setJdbcUrl(url);
config.setUsername(username);
config.setPassword(password);
if (driver != null) {
config.setDriverClassName(driver);
}
config.setMaximumPoolSize(maximumPoolSize);
config.setMinimumIdle(minimumPoolSize);
config.setConnectionTimeout(connTimeout);
return new HikariDataSource(config);
}
}

定义JdbcTemplate,基于Template模式,提供了大量以回调作为参数的模板方法,其中以execute(ConnectionCallback)为基础

public <T> T execute(ConnectionCallback<T> action) {
try (Connection newConn = dataSource.getConnection()) {
T result = action.doInConnection(newConn);
return result;
} catch (SQLException e) {
throw new DataAccessException(e);
}
}

基本上都是实现execute的

实现声明式事务

首先定义声明式事务

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)//运行时
@Documented
@Inherited //支持子类继承该注解
public @interface Transactional {
String value() default "platformTransactionManager";
}

我们需要一个类来协助bean管理事务
定义一个接口PlatformTransactionManager,来标识管理的类型。

public interface PlatformTransactionManager {
}

然后定义TransactionStatus来管理事务状态,将来如果扩展,则可以将事务的传播模式存储在里面。

public class TransactionStatus {
final Connection connection;

public TransactionStatus(Connection connection) {
this.connection = connection;
}
}

最后加上一个DataSourceTransactionManager,他用的是ThreadLocal存储的TransactionStatus,和一个DataSource

public class DataSourceTransactionManager implements
PlatformTransactionManager, InvocationHandler
{
static final ThreadLocal<TransactionStatus> transactionStatus = new ThreadLocal<>();
final DataSource dataSource;

public DataSourceTransactionManager(DataSource dataSource) {
this.dataSource = dataSource;
}
}

这里的实现是和AOP那一节一样的,只是对于事务来说来说,他做的是对该类的所有方法都进行一个代理。苏所以,无需在加任何的方法注解,只需要在invoke内部处理就好

@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
TransactionStatus ts = transactionStatus.get();
if (ts == null) {
// 当前无事务,开启新事务:
try (Connection connection = dataSource.getConnection()) {
final boolean autoCommit = connection.getAutoCommit();
if (autoCommit) {
connection.setAutoCommit(false);
}
try {
// 设置ThreadLocal状态:
transactionStatus.set(new TransactionStatus(connection));
// 调用业务方法:
Object r = method.invoke(proxy, args);
// 提交事务:
connection.commit();
// 方法返回:
return r;
} catch (InvocationTargetException e) {
// 回滚事务:
TransactionException te = new TransactionException(e.getCause());
try {
connection.rollback();
} catch (SQLException sqle) {
te.addSuppressed(sqle);
}
throw te;
} finally {
// 删除ThreadLocal状态:
transactionStatus.remove();
if (autoCommit) {
connection.setAutoCommit(true);
}
}
}
} else {
// 当前已有事务,加入当前事务执行:
return method.invoke(proxy, args);
}
}

如此,我们就实现了声明式的事务。

但是,如果我们在一个声明式的事务里调用了另一个声明式事务,这两个事务怎么合并到同一个事务里的呢?

首先,我们要知晓当前的事务连接

public class TransactionalUtils {
@Nullable
public static Connection getCurrentConnection() {
TransactionStatus ts = DataSourceTransactionManager.transactionStatus.get();
return ts == null ? null : ts.connection;
}
}

然后更新一下JdbcTemplate的代码,拿去事务的时候是从这个线程的状态判断的

//取消
// TransactionStatus ts = transactionStatus.get();
// 尝试获取当前事务连接:
Connection current = TransactionalUtils.getCurrentConnection();
if (current != null) {
try {
return action.doInConnection(current);
} catch (SQLException e) {
throw new DataAccessException(e);
}
}
// 无事务,从DataSource获取新连接:
try (Connection newConn = dataSource.getConnection()) {
return action.doInConnection(newConn);
} catch (SQLException e) {
throw new DataAccessException(e);
}

准备好AnnotationProxyBeanPostProcessor来使得AOP机制生效

public class TransactionalBeanPostProcessor extends AnnotationProxyBeanPostProcessor<Transactional> {
}

然后在配置类中配置所有的内容基本上就可以了

@Configuration
public class JdbcConfiguration {

@Bean(destroyMethod = "close")
DataSource dataSource(
// properties:
@Value("${summer.datasource.url}") String url,
@Value("${summer.datasource.username}") String username,
@Value("${summer.datasource.password}") String password,
@Value("${summer.datasource.driver-class-name:}") String driver,
@Value("${summer.datasource.maximum-pool-size:20}") int maximumPoolSize,
@Value("${summer.datasource.minimum-pool-size:1}") int minimumPoolSize,
@Value("${summer.datasource.connection-timeout:30000}") int connTimeout
) {
...
return new HikariDataSource(config);
}

@Bean
JdbcTemplate jdbcTemplate(@Autowired DataSource dataSource) {
return new JdbcTemplate(dataSource);
}

@Bean
TransactionalBeanPostProcessor transactionalBeanPostProcessor() {
return new TransactionalBeanPostProcessor();
}

@Bean
PlatformTransactionManager platformTransactionManager(@Autowired DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}