事务是一个原子操作。是一个最小执行单元。可以由一个或多个SQL语句组成,在同一个事务当中,所有的SQL语句都成功执行时,整个事务成功,有一个SQL语句执行失败,整个事务都执行失败。
- Atomicity(原子性)
一个事务中的事务操作,不可再分,要一起成功要么一起失败
- Consistency(一致性)
一个或多个事务的操作,在操作前后数据是一致的
- Isolation(隔离性)
一个事务的操作不能影响到另外一个事务
- Durability(持久性)
一旦事务条件,将会永久保存到数据库中
事务并发时的安全问题
问题 | 描述 |
---|---|
脏读 | 一个事务读取到另一个事务还未提交的数据。大于等于 read-commited 可防止 |
不可重复读 | 一个事务内多次读取一行数据的相同内容,其结果不一致。大于等于 repeatable-read 可防止 |
幻读 | 一个事务内多次读取一张表中的相同内容,其结果不一致。serialized-read 可防止 |
名称 | 描述 |
---|---|
default | (默认值)(采用数据库的默认的设置) (建议) |
read-uncommited | 读未提交 |
read-commited | 读提交 (Oracle数据库默认的隔离级别) |
repeatable-read | 可重复读 (MySQL数据库默认的隔离级别) |
serialized-read | 序列化读 |
隔离级别由低到高为:read-uncommited < read-commited < repeatable-read < serialized-read
安全性:级别越高,多事务并发时,越安全。因为共享的数据越来越少,事务间彼此干扰减少。
并发性:级别越高,多事务并发时,并发越差。因为共享的数据越来越少,事务间阻塞情况增多。
技术:Spring+DBUtils、AOP
1)增强类 (负责对方法进行增强,开启事务、提交事务、回滚事务、关闭资源)
2)配置切面(增强+切入点)
注意:要保证这个操作中的Connetion是同一个
public class TXAdvice {
@Autowired
TXUtils txUtils;
public Object around(ProceedingJoinPoint joinPoint){
try {
//开启事务
txUtils.start();
//调用目标对象的方法
Object obj = joinPoint.proceed();
//提交事务
txUtils.commit();
return obj;
} catch (Throwable throwable) {
throwable.printStackTrace();
//回滚事务
txUtils.rollback();
}finally {
//释放资源
txUtils.close();
}
return null;
}
}
//事务工具类
@Component
public class TXUtils {
@Autowired
DataSource dataSource;
ThreadLocal<Connection> tl = new ThreadLocal<>();
public Connection getConnection(){
try {
Connection conn = tl.get();
if(conn == null){
conn = dataSource.getConnection();
//保存ThreadLocal中
tl.set(conn);
}
return conn;
} catch (SQLException throwables) {
throwables.printStackTrace();
}
return null;
}
public void start(){
Connection connection = getConnection();
try {
connection.setAutoCommit(false);
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
public void commit(){
Connection connection = getConnection();
try {
connection.commit();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
public void rollback(){
Connection connection = getConnection();
try {
connection.rollback();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
public void close(){
Connection connection = getConnection();
try {
connection.close();
//在ThreadLocal移除Connection对象
tl.remove();
} catch (SQLException throwables) {
throwables.printStackTrace();
}
}
}
@Repository
public class AccountDaoImpl implements AccountDao {
@Autowired
private QueryRunner qr;
@Autowired
TXUtils txUtils;
@Override
public Account selectAccount(String name) throws SQLException {
return qr.query(txUtils.getConnection(),
"select * from tb_account where name = ?",
new BeanHandler<Account>(Account.class),
name);
}
@Override
public int updateAccount(String name, double money) throws SQLException {
String sql = "update tb_account set money = money + ? where name = ?";
return qr.update(txUtils.getConnection(),
sql,
money,
name);
}
}
@Service
public class AccountServiceImpl implements AccountService {
@Autowired
private AccountDao accountDao;
@Override
public String zhuanzhang(String fromName, String toName, double money)
throws SQLException {
//1、判断用户是否存在
Account account = accountDao.selectAccount(fromName);
if(account == null){
return "用户不存在";
}
//2、判断余额是否足够
if(account.getMoney() < money){
return "余额不足";
}
//3、判断对方账户是否存在
Account toAccount = accountDao.selectAccount(toName);
if(toAccount == null){
return "对方用户不存在";
}
//4、我方扣钱
accountDao.updateAccount(fromName,-money);
System.out.println(10/0);
//5、敌方加钱
accountDao.updateAccount(toName,money);
return "转账成功";
}
}
<bean id="advice" class="com.ying.advice.TXAdvice">bean>
<aop:config>
<aop:aspect ref="advice">
<aop:pointcut id="pc" expression="execution(* com.ying.service..*.*(..))"/>
<aop:around method="around" pointcut-ref="pc"/>
aop:aspect>
aop:config>
<aop:aspectj-autoproxy/>
@Component
@Aspect
public class TXAdvice { //通知增强
@Autowired
TXUtils txUtils;
@Pointcut("execution(* com.ying.service..*.*(..))")
public void pc(){}
@Around("pc()")
public Object around(ProceedingJoinPoint joinPoint){
try {
//开启事务
txUtils.start();
//调用目标对象的方法
Object obj = joinPoint.proceed();
//提交事务
txUtils.commit();
return obj;
} catch (Throwable throwable) {
throwable.printStackTrace();
//回滚事务
txUtils.rollback();
}finally {
//释放资源
txUtils.close();
}
return null;
}
}
<dependency>
<groupId>org.springframeworkgroupId>
<artifactId>spring-txartifactId>
<version>5.3.6version>
dependency>
<dependency>
<groupId>org.aspectjgroupId>
<artifactId>aspectjweaverartifactId>
<version>1.9.6version>
dependency>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
注意:DataSourceTransactionManager 和 SqlSessionFactoryBean 要注入同一个DataSource,否则事务控制失败!!!
<tx:advice id="txAdvice" transaction-manager="transactionManager" >
<tx:attributes>
<tx:method name="get*" isolation="DEFAULT"
propagation="SUPPORTS" read-only="true" timeout="-1"/>
<tx:method name="select*" isolation="DEFAULT"
propagation="SUPPORTS" read-only="true" timeout="-1"/>
<tx:method name="find*" isolation="DEFAULT"
propagation="SUPPORTS" read-only="true" timeout="-1"/>
<tx:method name="query*" isolation="DEFAULT"
propagation="SUPPORTS" read-only="true" timeout="-1"/>
<tx:method name="*" isolation="DEFAULT"
propagation="REQUIRED" read-only="false" timeout="-1"/>
tx:attributes>
tx:advice>
isolation
隔离级别
名称 | 描述 |
---|---|
default | (默认值)(采用数据库的默认的设置) (建议) |
read-uncommited | 读未提交 |
read-commited | 读提交 (Oracle数据库默认的隔离级别) |
repeatable-read | 可重复读 (MySQL数据库默认的隔离级别) |
serialized-read | 序列化读 |
propagation
事务传播行为
名称 | 作用 |
---|---|
PROPAGATION_REQUIRED | 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。这是最常见的选择。 |
PROPAGATION_SUPPORTS | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
PROPAGATION_MANDATORY | 使用当前的事务,如果当前没有事务,就抛出异常。 |
PROPAGATION_REQUIRES_NEW | 新建事务,如果当前存在事务,把当前事务挂起。 |
PROPAGATION_NOT_SUPPORTED | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
PROPAGATION_NEVER | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
PROPAGATION_NESTED | 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行与PROPAGATION_REQUIRED类似的操作。 |
当涉及到事务嵌套(Service调用Service)时,可以设置:
SUPPORTS = 不存在外部事务,则不开启新事务;存在外部事务,则合并到外部事务中。(适合查询)
REQUIRED = 不存在外部事务,则开启新事务;存在外部事务,则合并到外部事务中。 (默认值)(适合增删改)
readonly
读写性
- true:只读,可提高查询效率。(适合查询)
- false:可读可写。 (默认值)(适合增删改)
timeout
事务超时时间
当前事务所需操作的数据被其他事务占用,则等待。
- 1000:自定义等待时间1000(秒)。
- -1:由数据库指定等待时间,默认值。(建议)
rollbackFor
配置回滚的异常
noRollbackFor
配置不回滚的异常
<aop:config>
<aop:pointcut id="pc" expression="execution(* com.ying.service..*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="pc"/>
aop:config>
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"/>
bean>
<tx:annotation-driven transaction-manager="transactionManager"/>
在service层的方法或者类上添加注解
注解加在类上表示所有的方法都使用事务管理
注解加在方法上仅表示当前方法使用事务管理
@Transactional(isolation = Isolation.DEFAULT,
propagation = Propagation.REQUIRED,readOnly = false,timeout = -1)