简介
事务:是数据库操作的最小工作单元,是作为单个逻辑工作单元执行的一系列操作;这些操作作为一个整体一起向系统提交,要么都执行、要么都不执行;事务是一组不可再分割的操作集合(工作逻辑单元);
事务的四个特征
- 原子性 : 事务是数据库的逻辑工作单位,事务中包含的各操作要么都做,要么都不做
- 一致性 : 事务执行的结果必须是使数据库从一个一致性状态变到另一个一致性状态。因此当数据库只包含成功事务提交的结果时,就说数据库处于一致性状态。如果数据库系统 运行中发生故障,有些事务尚未完成就被迫中断,这些未完成事务对数据库所做的修改有一部分已写入物理数据库,这时数据库就处于一种不正确的状态,或者说是 不一致的状态。
- 隔离性: 一个事务的执行不能其它事务干扰。即一个事务内部的操作及使用的数据对其它并发事务是隔离的,并发执行的各个事务之间不能互相干扰。
- 持续性 : 也称永久性,指一个事务一旦提交,它对数据库中的数据的改变就应该是永久性的。接下来的其它操作或故障不应该对其执行结果有任何影响。
事务的隔离级别
事务隔离级别越高,在并发下会产生的问题就越少,但同时付出的性能消耗也将越大,因此很多时候必须在并发性和性能之间做一个权衡
。
-
DEFAULT
默认隔离级别,每种数据库支持的事务隔离级别不一样,如果Spring配置事务时将isolation设置为这个值的话,那么将使用底层数据库的默认事务隔离级别。顺便说一句,如果使用的MySQL,可以使用"select @@tx_isolation"来查看默认的事务隔离级别 -
READ_UNCOMMITTED
读未提交,即能够读取到没有被提交的数据,所以很明显这个级别的隔离机制无法解决脏读、不可重复读、幻读中的任何一种,因此很少使用 -
READ_COMMITED
读已提交,即能够读到那些已经提交的数据,自然能够防止脏读,但是无法限制不可重复读和幻读 -
REPEATABLE_READ
重复读取,即在数据读出来之后加锁,类似"select * from XXX for update",明确数据读取出来就是为了更新用的,所以要加一把锁,防止别人修改它。REPEATABLE_READ的意思也类似,读取了一条数据,这个事务不结束,别的事务就不可以改这条记录,这样就解决了脏读、不可重复读的问题,但是幻读的问题还是无法解决 -
SERLALIZABLE
串行化,最高的事务隔离级别,不管多少事务,挨个运行完一个事务的所有子事务之后才可以执行另外一个事务里面的所有子事务,这样就解决了脏读、不可重复读和幻读的问题了
脏读、幻读、不可重复读
- 脏读(dirty read):事务A读到了事务B还没有提交的数据。
时间 | 取款事物 | 存款事物 |
---|---|---|
T1 | 开始事物 | |
T2 | 开始事物 | |
T3 | 查询账户余额为1000元 | |
T4 | 汇入100元把余额改为1100元 | |
T5 | 查询账户余额为1100元(读取脏数据) | |
T6 | 回滚 | |
T7 | 取款1100 | |
T8 | 提交事物失败 |
- 幻读( phantom read ):在一个事务里面的操作中发现了未被操作的数据。
时间 | 查询学生事物 | 插入新学生事物 |
---|---|---|
T1 | 开始事物 | |
T2 | 开始事物 | |
T3 | 查询学生为10人 | |
T4 | 插入一个新学生 | |
T5 | 查询学生为11人 | |
T6 | 提交事物 | |
T7 | 提交事物 |
- 不可重复读(non-repeatable read):在一个事务里面读取了两次某个数据,读出来的数据不一致。
时间 | 取款事物 | 存款事物 |
---|---|---|
T1 | 开始事物 | |
T2 | 开始事物 | |
T3 | 查询账户余额为1000元 | |
T4 | 汇入100元把余额改为1100元 | |
T5 | 提交事物 | |
T6 | 查询账户余额为1100元(读取脏数据) | |
T8 | 提交事物 |
第一类丢失更新(lest update)
时间 | 取款事物 | 存款事物 |
---|---|---|
T1 | 开始事物 | |
T2 | 开始事物 | |
T3 | 查询账户余额为1000元 | |
T4 | 查询账户余额为1000元 | |
T5 | 汇入100元把余额改为1100元 | |
T6 | 提交事物 | |
T7 | 取出100元把余额改为900元 | |
T8 | 撤销事物 | |
T9 | 余额恢复为1000元(丢失更新) |
第二类丢失更新(Second lost update problem)【不可重复读的特殊情况】
时间 | 转账事物 | 取款事物 |
---|---|---|
T1 | 开始事物 | |
T2 | 开始事物 | |
T3 | 查询账户余额为1000元 | |
T4 | 查询账户余额为1000元 | |
T5 | 取100元把余额改为900元 | |
T6 | 提交事物 | |
T7 | 汇入100元 | |
T8 | 提交事物 | |
T9 | 把余额改成1100元(丢失更新) |
Spring-tx
1、导入maven依赖
<!--Spring-webmvc -->
<!-- mysql -->
<!-- spring-tx -->
<!-- aspectj -->
<!-- jdbc -->
<!-- dbcp -->
2、创建实体类
private int id;
private double money;
3、添加数据访问层
@Repository
public class UserDaoImpl implements UserDao{
@Autowired
private DataSource dataSource;
@Autowired
private JdbcTemplate jdbcTemplate;
//操作数据库完成数据查找
public double select(int id) {
// TODO Auto-generated method stub
return jdbcTemplate.queryForObject("select money from t_u where id = ?",Integer.class,id);
}
//操作数据库完成数据更新
public void update(int id, double money) {
// TODO Auto-generated method stub
jdbcTemplate.update("update t_u set money = ? where id = ?", new Object[]{money ,id});
}
}
4、添加数据业务层
@Service
public class UserServiceImpl {
@Autowired
private UserDao userDao;
public void transfer(int fromId, int toId, double money) {
double money1=userDao.select(fromId);
userDao.update(fromId, money1-money);
double money2=userDao.select(toId);
userDao.update(toId, money2+money);
}
}
5、配置文件config.xml
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns="http://www.springframework.org/schema/beans"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans-4.2.xsd
http://www.springframework.org/schema/context
http://www.springframework.org/schema/context/spring-context-4.2.xsd
http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx-4.2.xsd
http://www.springframework.org/schema/aop
http://www.springframework.org/schema/aop/spring-aop-4.2.xsd">
<!-- 扫描注解 -->
<context:annotation-config/>
<context:component-scan base-package="spring"></context:component-scan>
<!-- 导入资源文件 -->
<context:property-placeholder location="jdbc.properties"/>
<!-- 数据源-->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${d}"></property>
<property name="url" value="${url}"></property>
<property name="username" value="${u}"></property>
<property name="password" value="${p}"></property>
</bean>
<!-- 配置事务管理器e -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- aop -->
<bean id="userService" class="spring.service.UserServiceImpl"></bean>
<aop:config>
<aop:pointcut expression="execution(* spring.service.*.*(..))" id="cut"/>
<aop:advisor advice-ref="tx" pointcut-ref="cut"/>
</aop:config>
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- tx -->
<tx:advice id="tx" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
</beans>
6、测试类
public class Test {
public static void main(String[] args) {
// TODO Auto-generated method stub
ApplicationContext context =new ClassPathXmlApplicationContext("config.xml");
UserServiceImpl service = context.getBean("userService",UserServiceImpl.class);
service.transfer(1, 2, 200);
}
}