一.环境搭建
1)引入相关依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.14</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.3.14</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.7</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.3.14</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.3.14</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.23</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.14</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.3.14</version>
</dependency>
2)编写jdbc配置
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/test?useSSL=false
jdbc.username=root
jdbc.password=root
3)全局配置类
@PropertySource("classpath:jdbc.properties")
@Configuration
@ComponentScan(basePackages = {"transaction.service","transaction.proxy","transaction.config"})
@Import(JdbcConfig.class)
public class GlobalConfig {
}
二.实现思路
1)事务基本实现步骤
- 开始事务
- 提交事务
- 若出现异常回滚事务
- 关闭连接
伪代码
try{
beginTransaction();
CRUD
commitTransaction();
}catch(Exception e){
rollbackTransaction();
}finally{
closeTransaction();
}
要实现上诉功能最容易想到的方法就是每一个需要事务的方法里面都写一遍类似上诉代码,显然这是一种糟糕的设计,代码冗余太大。
2)方案实现
- Proxy动态代理
- AOP
- @Transaction注解
后面两种其实都是通过动态代理来实现的
三.案例
数据库表数据如下,要实现的目标就是刘备曹操孙权向自己手下员工发工资转账,这是一个事务的典型应用场景。 1)编写一个JavaBean类已经相关业务方法
public class Account {
private int aId;
private String aName;
private double aBalance;
private int cId;
}
public interface AccountService {
Account getAccountByName(String name);
int updateAccount(Account account);
void transfer(String sourceName,String objectName,double cash);
}
@Service("accountService")
public class AccountServiceImpl implements AccountService {
@Autowired
JdbcTemplate jdbcTemplate;
@Override
public Account getAccountByName(String name) {
String querySql="select * from t_account where a_name=?";
BeanPropertyRowMapper<Account> beanPropertyRowMapper = new BeanPropertyRowMapper<>(Account.class);
return jdbcTemplate.queryForObject(querySql, beanPropertyRowMapper, name);
}
@Override
public int updateAccount(Account account) {
String updateSql="update t_account set a_balance=? where a_id=?";
return jdbcTemplate.update(updateSql, account.getaBalance(), account.getaId());
}
@Override
public void transfer(String sourceName, String objectName, double cash) {
Account A = getAccountByName(sourceName);
Account B = getAccountByName(objectName);
A.setaBalance(A.getaBalance()-cash);
B.setaBalance(B.getaBalance()+cash);
updateAccount(A);
updateAccount(B);
}
}
2)配置数据源
最主要解决的是数据库连接问题需要保证每一个线程使用的是不同的数据库连接,可以使用ThreadLocal来保证每一个线程拥有不同的连接。不过spring已经帮我们做好了。
TransactionSynchronizationManager.initSynchronization();
return DataSourceUtils.getConnection(dataSource);
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.password}")
private String password;
@Value("${jdbc.username}")
private String username;
@Bean
public JdbcTemplate jdbcTemplate(DataSource dataSource){
return new JdbcTemplate(dataSource);
}
@Bean
public DataSource dataSource(){
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName(driver);
dataSource.setUrl(url);
dataSource.setPassword(password);
dataSource.setUsername(username);
return dataSource;
}
@Bean
public Connection connection(DataSource dataSource){
TransactionSynchronizationManager.initSynchronization();
return DataSourceUtils.getConnection(dataSource);
}
}
3)封装事务相关方法
@Component
public class TransactionManger {
@Autowired
private Connection connection;
public void beginTransaction(){
try {
connection.setAutoCommit(false);
} catch (SQLException e) {
e.printStackTrace();
}
}
public void commitTransaction(){
try {
connection.commit();
} catch (SQLException e) {
e.printStackTrace();
}
}
public void rollbackTransaction(){
try {
connection.rollback();
} catch (SQLException e) {
e.printStackTrace();
}
}
public void closeTransaction(){
try {
connection.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
}
4)编写一个代理类来实现对AccountService的代理增强
@Component
public class AccountServiceProxy {
@Qualifier("accountService")
@Autowired
private AccountService accountService;
@Autowired
private TransactionManger transactionManger;
@Bean("proxyAccountService")
public AccountService getAccountServiceProxy(){
ClassLoader classLoader = accountService.getClass().getClassLoader();
Class<?>[] interfaces = accountService.getClass().getInterfaces();
Object proxyInstance = Proxy.newProxyInstance(classLoader, interfaces, (proxy, method, args) ->{
Object val=null;
try {
transactionManger.beginTransaction();
val = method.invoke(accountService,args);
transactionManger.commitTransaction();
}catch (Throwable throwable){
transactionManger.rollbackTransaction();
throwable.printStackTrace();
}finally {
transactionManger.closeTransaction();
}
return val;
});
return (AccountService)proxyInstance;
}
}
5)测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = GlobalConfig.class)
public class TestTransaction {
@Qualifier("proxyAccountService")
@Autowired
private AccountService accountService;
@Test
public void getAccountByName(){
Account account = accountService.getAccountByName("刘备");
System.out.println(account);
}
@Test
public void updateAccount() throws Exception{
Account account = new Account();
account.setaId(1);
account.setaBalance(1100);
int result = accountService.updateAccount(account);
System.out.println(result);
}
@Test
public void testTransfer() throws Exception{
String A="刘备";
String B="关羽";
double cash=100d;
accountService.transfer(A,B,cash);
}
}
上面是自己通过代理来实现事务操作,可以通过AOP来改造上面代码
@Autowired
private TransactionManger transactionManger;
@Pointcut(value = "execution(* transaction.service.impl.AccountServiceImpl.transfer(..))")
public void pointcut(){}
@Before(value = "pointcut()")
public void beforeAdvice(){
System.out.println("事务开始===>");
transactionManger.beginTransaction();
}
@AfterReturning(value = "pointcut()")
public void afterReturning(){
System.out.println("提交事务===>");
transactionManger.commitTransaction();
}
@AfterThrowing(value = "pointcut()")
public void afterThrowing(){
System.out.println("回滚事务===>");
transactionManger.rollbackTransaction();
}
@After(value = "pointcut()")
public void afterAdvice(){
System.out.println("关闭事务===>");
transactionManger.closeTransaction();
}
使用环绕通知:
@Around(value = "pointcut()")
public Object transaction(ProceedingJoinPoint pjp){
try {
System.out.println("事务开始===>");
transactionManger.beginTransaction();
Object obj = pjp.proceed(pjp.getArgs());
System.out.println("提交事务===>");
transactionManger.commitTransaction();
return obj;
}catch (Throwable throwable){
throwable.printStackTrace();
System.out.println("回滚事务===>");
transactionManger.rollbackTransaction();
}finally {
System.out.println("关闭事务===>");
transactionManger.closeTransaction();
}
return null;
}
当然spring已经将所有的事务操作封装到@Transaction注解中了,不过Spring的注解也是通过AOP的环绕通知来实现的。
|