• JDBCTemple

  • 声明式事务

  • 支持REQUIRED传播模式

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

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

1
2
3
4
5
# 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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@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)为基础

1
2
3
4
5
6
7
8
public  T execute(ConnectionCallback action) {
try (Connection newConn = dataSource.getConnection()) {
T result = action.doInConnection(newConn);
return result;
} catch (SQLException e) {
throw new DataAccessException(e);
}
}

基本上都是实现execute的

实现声明式事务
首先定义声明式事务

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

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

1
2
public interface PlatformTransactionManager {
}

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

1
2
3
4
5
6
7
public class TransactionStatus {
final Connection connection;

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

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

1
2
3
4
5
6
7
8
9
10
public class DataSourceTransactionManager implements
PlatformTransactionManager, InvocationHandler
{
static final ThreadLocal transactionStatus = new ThreadLocal<>();
final DataSource dataSource;

public DataSourceTransactionManager(DataSource dataSource) &#123;
this.dataSource = dataSource;
&#125;
&#125;

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable &#123;
TransactionStatus ts = transactionStatus.get();
if (ts == null) &#123;
// 当前无事务,开启新事务:
try (Connection connection = dataSource.getConnection()) &#123;
final boolean autoCommit = connection.getAutoCommit();
if (autoCommit) &#123;
connection.setAutoCommit(false);
&#125;
try &#123;
// 设置ThreadLocal状态:
transactionStatus.set(new TransactionStatus(connection));
// 调用业务方法:
Object r = method.invoke(proxy, args);
// 提交事务:
connection.commit();
// 方法返回:
return r;
&#125; catch (InvocationTargetException e) &#123;
// 回滚事务:
TransactionException te = new TransactionException(e.getCause());
try &#123;
connection.rollback();
&#125; catch (SQLException sqle) &#123;
te.addSuppressed(sqle);
&#125;
throw te;
&#125; finally &#123;
// 删除ThreadLocal状态:
transactionStatus.remove();
if (autoCommit) &#123;
connection.setAutoCommit(true);
&#125;
&#125;
&#125;
&#125; else &#123;
// 当前已有事务,加入当前事务执行:
return method.invoke(proxy, args);
&#125;
&#125;

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

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

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

1
2
3
4
5
6
7
public class TransactionalUtils &#123;
@Nullable
public static Connection getCurrentConnection() &#123;
TransactionStatus ts = DataSourceTransactionManager.transactionStatus.get();
return ts == null ? null : ts.connection;
&#125;
&#125;

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
//取消
// TransactionStatus ts = transactionStatus.get();
// 尝试获取当前事务连接:
Connection current = TransactionalUtils.getCurrentConnection();
if (current != null) &#123;
try &#123;
return action.doInConnection(current);
&#125; catch (SQLException e) &#123;
throw new DataAccessException(e);
&#125;
&#125;
// 无事务,从DataSource获取新连接:
try (Connection newConn = dataSource.getConnection()) &#123;
return action.doInConnection(newConn);
&#125; catch (SQLException e) &#123;
throw new DataAccessException(e);
&#125;

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

1
2
public class TransactionalBeanPostProcessor extends AnnotationProxyBeanPostProcessor &#123;
&#125;

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

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Configuration
public class JdbcConfiguration &#123;

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

@Bean
JdbcTemplate jdbcTemplate(@Autowired DataSource dataSource) &#123;
return new JdbcTemplate(dataSource);
&#125;

@Bean
TransactionalBeanPostProcessor transactionalBeanPostProcessor() &#123;
return new TransactionalBeanPostProcessor();
&#125;

@Bean
PlatformTransactionManager platformTransactionManager(@Autowired DataSource dataSource) &#123;
return new DataSourceTransactionManager(dataSource);
&#125;
&#125;