事务在逻辑上是一组操作,要么执行,要不都不执行。主要是针对数据库而言的
- 原子性(Atomicity):一个事务中的所有操作,要么全部完成,要么全部不完成,不会结束在中间某个环节。事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。
- 一致性(Consistency):在事务开始之前和事务结束以后,数据库的完整性没有被破坏。
- 事务隔离(Isolation):数据库允许多个并发事务同时对其数据进行读写和修改,隔离性可以防止多个事务并发执行时由于交叉执行而导致数据的不一致。
- 持久性(Durability):事务处理结束后,对数据的修改就是永久的,即便系统故障也不会丢失。
Spring 支持两种事务方式,分别是编程式事务和声明式事务。一般使用后者
1.事务管理器配置
PlatformTransactionManager
package com.bdbk.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.omg.CORBA.PUBLIC_MEMBER;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
public class JdbcConfig {
@Value("${jdbc.driver}")
private String driver;
@Value("${jdbc.url}")
private String url;
@Value("${jdbc.username}")
private String username;
@Value("${jdbc.password}")
private String password;
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(username);
ds.setPassword(password);
return ds;
}
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
2.开启事务管理器
package com.bdbk.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.PropertySource;
import org.springframework.transaction.annotation.EnableTransactionManagement;
@Configuration
@ComponentScan("com.bdbk")
@PropertySource("classpath:jdbc.properties")
@Import({JdbcConfig.class, MybatisConfig.class})
@EnableTransactionManagement
public class SpringConfig {
}
3.Dao 配置
package com.bdbk.dao;
import com.bdbk.domain.Account;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Update;
import java.util.List;
public interface AccountDao {
@Update("update account set money = money + #{money} where id = #{id}")
public void in(@Param("id") Integer id, @Param("money") Double money);
@Update("update account set money = money - #{money} where id = #{id}")
public void out(@Param("id") Integer id,@Param("money") Double money);
}
4.在 service 接口加事务注解
package com.bdbk.service;
import com.bdbk.domain.Account;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
public interface AccountService {
@Transactional
public void transfer(int out_uid, int in_uid, Double money);
}
5.service实现类
package com.bdbk.service.impl;
import com.bdbk.dao.AccountDao;
import com.bdbk.domain.Account;
import com.bdbk.service.AccountService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
public void transfer(int out_uid, int in_uid, Double money) {
accountDao.in(in_uid, money);
int a = 1 / 0; //此处报错 会回滚事务
accountDao.out(out_uid, money);
}
}
6.数据库需要支持事务
如果数据库是mysql,则引擎需要改为 InnoDb
7.事务角色
事务管理员
发起事务方,通常为事务发起的方法,比如 transfer事务协调员
加入事务方,通常为事务加入的方法,比如 in 和 out
8.事务传播
- 事务传播行为
事务协调员对事务管理员所携带事务的处理态度,是加入事务还是重新开启事务
比如在上面的需求新增一个功能,无论是否转账成功,都需要记录日志
public void transfer(int out_uid, int in_uid, Double money) {
try {
accountDao.in(in_uid, money);
int a = 1 / 0;
accountDao.out(out_uid, money);
}finally {
logService.log(out_uid, in_uid, money);
}
}
这时候会发现,所有事务都进行了回滚,但是记录日志不需要回滚,因此做如下配置。日志开启新事务
- @Transactional(propagation = Propagation.REQUIRES_NEW)
package com.bdbk.service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
public interface LogService {
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void log(int out_uid, int in_uid, Double money);
}