一、事务隔离级别
1、事务特性(ACID 原子性、一致性、隔离性、持久性 )
(1) 原子性Atomicity
事务的原子性指的是,事务中包含的程序作为数据库的逻辑工作单位,它所做的对数据修改操作要么全部执行,要么完全不执行。这种特性称为原子性。
(2) 一致性Consistency
事务的一致性指的是在一个事务执行之前和执行之后数据库都必须处于一致性状态。这种特性称为事务的一致性。假如数据库的状态满足所有的完整性约束,就说该数据库是一致的。
(3) 分离性Isolation
分离性指并发的事务是相互隔离的。即一个事务内部的操作及正在操作的数据必须封锁起来,不被其它企图进行修改的事务看到。
(4)持久性Durability
持久性意味着当系统或介质发生故障时,确保已提交事务的更新不能丢失。即一旦一个事务提交,DBMS保证它对数据库中数据的改变应该是永久性的,耐得住任何系统故障。
2、事务隔离性引发问题(脏读、不可重复读、 虚读 ), 数据库为了解决事务隔离性导致问题,引入四个隔离级别 :
DEFAULT 每种数据库提供默认隔离级别 mysql REPEATABLE_READ、oracle READ_COMMITTED
READ_UNCOMMITTED:引发所有隔离问题(脏读、不可重复读、虚读)
READ_COMMITTED:解决脏读, 引发(不可重复读、虚读)
REPEATABLE_READ : 解决脏读、不可重复读、 引发虚读
SERIALIZABLE :事务不存在并发性,阻止所有并发问题发生 (不推荐)
3、何为脏读、不可重复读、 虚读?
脏读:一个事务读取另一个事务改写但还没提交的数据,如果这些数据被回滚,则读到的数据是无效的;
不可重复读:在同一事务中,多次读取同一 数据返回的结果有所不同。也就是说,后续读取的数据是读取到了另一事务已提交的更新数据。
虚读:一个事务读取了几行记录后,另一个事务插入了一些记录,虚读就产生了!
二、事务传播行为
传播行为不是数据库特性, 而是为了解决企业实际开发中事务隔离问题,解决两个事务互相调用的问题,总共有7种行为:
PROPAGATION_REQUIRED :支持当前事务,如果当前没有事务,就重启一个事务
PROPAGATION_SUPPORTS :支持当前事务,如果当前没有事务,就以非事务进行
PROPAGATION_MANDATORY(强制的):支持当前事务,如果当前没有事务,就会抛出异常
PROPAGATION_REQUIRES_NEW:不支持当前事务,如果当前有事务,则将当前事务挂起,重启一个事务
PROPAGATION_NOT_SUPPORTED:不支持当前事务,如果当前有事务,就将当前事务挂起,以非事务进行
PROPAGATION_NEVER : 不支持当前事务,如果当前有事务,就会抛出异常
PROPAGATION_NESTED : 如果当前存在事务,则嵌套事务执行,只对DataSourceTransactionManager起效
三、事务的非注解配置应用
1、建表
CREATE TABLE `account` (
`id` int(11) NOT NULL,
`money` double default NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `account` VALUES ('1', '1000');
INSERT INTO `account` VALUES ('2', '900');
2、demo类
package top.einino.demo;
public class Account {
private int id;
private double money;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public double getMoney() {
return money;
}
public void setMoney(double money) {
this.money = money;
}
}
3、dao层
package top.einino.dao;
import top.einino.demo.Account;
public interface AccountDao {
void add(Account account, double money);
void reduce(Account account, double money);
}
package top.einino.dao;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import top.einino.demo.Account;
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
@Override
public void add(Account account, double money) {
String sql = "update account set money = money + ? where id = ?";
this.getJdbcTemplate().update(sql, money,account.getId());
}
@Override
public void reduce(Account account, double money) {
String sql = "update account set money = money - ? where id = ?";
this.getJdbcTemplate().update(sql, money,account.getId());
}
}
4、service层,事务处理层
package top.einino.service;
import top.einino.demo.Account;
public interface AccountService {
void transfer(Account a, Account b, double money);
}
package top.einino.service;
import top.einino.dao.AccountDao;
import top.einino.demo.Account;
public class AccountServiceImpl implements AccountService{
private AccountDao accountDao;
public void setAccountDao(AccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(Account a, Account b, double money) {
accountDao.reduce(a, money);
//int i=10/0;
accountDao.add(b, money);
}
}
5、配置事务,需要配置数据源,dao层,service层,事务管理器,事务增强,切面
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--c3p0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_tx_wp"></property>
<property name="user" value="用户名"></property>
<property name="password" value="密码"></property>
</bean>
<!--将dataSource注入accountDao中,并自动创建jdbcTemplate -->
<bean id="accountDao" class="top.einino.dao.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 将accountDao注入到accountService中 -->
<bean id="accountService" class="top.einino.service.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置事务管理器 在jdbc包中 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 事务管理器需要从数据源获取连接,才能开启事务,提交事务,回滚事务 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 需要advice进行事务增强 -->
<tx:advice id="transactionAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!--
对transfer方法进行事务增强
isolation隔离级别,因为使用的数据库为mysql,所以默认为REPEATABLE_READ
propagation传播行为,REQUIRED表示支持当前事务,如果当前没有事务,就重启事务
timeout表示超时时间
read-only设置为false表示可读可写,如果设置为true,则不能进行写操作,不然会报错
-->
<tx:method name="transfer" isolation="DEFAULT" propagation="REQUIRED" timeout="-1"
read-only="false" />
</tx:attributes>
</tx:advice>
<!-- 配置切面 -->
<aop:config>
<aop:advisor advice-ref="transactionAdvice"
pointcut="execution(* top.einino.service.AccountServiceImpl.*(..))"/>
</aop:config>
</beans>
6、测试
package top.einino.junit;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import top.einino.demo.Account;
import top.einino.service.AccountService;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="classpath:applicationContext.xml")
public class TestTransaction {
@Autowired
AccountService accountService;
@Test
public void testTransfer(){
Account a = new Account();
a.setId(1);
Account b = new Account();
a.setId(2);
accountService.transfer(a, b, 100);
}
}
四、事务的注解应用
1、更改dao层代码
package top.einino.dao;
import javax.annotation.Resource;
import javax.sql.DataSource;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import org.springframework.stereotype.Repository;
import top.einino.demo.Account;
@Repository("accountDao")
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
@Resource(name="dataSource")
public void setDs(DataSource dataSource){
this.setDataSource(dataSource);
}
@Override
public void add(Account account, double money) {
String sql = "update account set money = money + ? where id = ?";
this.getJdbcTemplate().update(sql, money,account.getId());
}
@Override
public void reduce(Account account, double money) {
String sql = "update account set money = money - ? where id = ?";
this.getJdbcTemplate().update(sql, money,account.getId());
}
}
2、更改service层代码
package top.einino.service;
import javax.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import top.einino.dao.AccountDao;
import top.einino.demo.Account;
@Service("accountService")
public class AccountServiceImpl implements AccountService{
@Resource(name="accountDao")
private AccountDao accountDao;
@Override
@Transactional(isolation=Isolation.DEFAULT,propagation=Propagation.REQUIRED,readOnly=false)
public void transfer(Account a, Account b, double money) {
accountDao.reduce(a, money);
int i=10/0;
accountDao.add(b, money);
}
}
3、更改applicationContext.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--c3p0连接池 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="com.mysql.jdbc.Driver"></property>
<property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring_tx_wp"></property>
<property name="user" value="root"></property>
<property name="password" value="bingo0922"></property>
</bean>
<context:component-scan base-package="top.einino"></context:component-scan>
<!-- 配置事务管理器 在jdbc包中 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 事务管理器需要从数据源获取连接,才能开启事务,提交事务,回滚事务 -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 注解进行事务管理 -->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
五、小结
本博文介绍了事务的四个特性,隔离级别,传播行为,并分别简单应用了事务的非注解配置和注解配置,其中用到c3p0连接池,还有spring的jdbcTemplate对数据库进行增删改查。
如果有疑问或者对本博文有何看法或建议或有问题的,欢迎评论,恳请指正!