1、事务
一组业务ACID操作,要么全部成功,要么全部不成功。
事务特性:①原子性,针对整体而言(一个事务不可以被拆分);②一致性,针对数据而言(一个事务执行之前和执行之后必须处于一致性状态,一个事务包含的所以操作要么全部成功,要么全部失败。比如转账前,A和B各有250元,A转250给B,结果A有0元,B有500元,A和B相加的钱,不管转账前还是转账后都是500元,这就是一致性);③隔离性,针对并发而言(对任意两个并发的事务T1和T2,T2要么在T1开始之前就已经结束,要么在T1结束之后才开始,这样每个事务都感觉不到有其他事务在并发地执行)④持久性,针对结果而言(指一个事务一旦被提交了,那么对数据库中的数据的改变就是永久性的)
(1)隔离问题(事务并发问题):
①脏读:一个事务读到另一个事务未提交的内容(读取未提交内容);就是一个事务读到了别的事务回滚前的脏数据,比如事务B执行过程中修改了数据X,在未提交前(即未保存到数据库前),事务A读取了X,而事务B却回滚了,这样事务A就形成了脏读。
②不可重复读:指在一个事务多次读取同一数据,读出来的数据不一致。比如库存数为100,事务A读取库存数为100,切换到事务B事务B开启事务–>事务B减库存50–>提交,数据库里面库存变为50元,此时切换回事务A,事务A再查一次查出库存为50,这样对事务A而言,在同一个事务内两次读取账户余额数据不一致,这就是不可重复读。
③幻读:两个事务,第一个事务将所有行的数据都修改了,第二个事务将插入一条数据提交,第1个事务提交发现有一条数据并没有修改。比如系统管理员A将数据库中所有学生的成绩从具体分数改为ABCDE等级,但是系统管理员B就在这个时候插入了一条具体分数的记录,当系统管理员A修改结束后发现还有一条记录没有改过来,就好像发生了幻觉一样,这就叫幻读。
(2)隔离级别--决绝问题
隔离级别 | 脏读 | 不可重复读 | 幻读 | 加锁读 |
Read Uncommitted(读取未提交内容) | 是 | 是 | 是 | 否 |
Read Committed(读取提交内容) | 否 | 是 | 是 | 否 |
Repeatable Read(可重复读) | 否 | 否 | 是 | 否 |
Serializable(可串行化) | 否 | 否 | 否 | 是 |
① Read Uncommitted(读取未提交内容): 会出现脏读、不可重复读、幻读 ( 隔离级别最低,并发性能高 )[保证了读取过程中不会读取到非法数据。隔离级别在于处理多事务的并发问题]
②Read Committed(读取提交内容):会出现不可重复读、幻读问题(锁定正在读取的行) [大多数主流数据库的默认事务等级,保证了一个事务不会读到另一个并行事务已修改但未提交的数据,避免了“脏读取”。该级别适用于大多数系统。]
③Repeatable Read(可重复读): 会出幻读(锁定所读取的所有行)[保证了一个事务不会修改已经由另一个事务读取但未提交(回滚)的数据。避免了“脏读取”和“不可重复读取”的情况,但是带来了更多的性能损失。]
④Serializable(可串行化): 保证所有的情况不会发生(锁表)[最严格的级别,事务串行执行,资源消耗最大。]
不可重复读的重点是修改 :
同样的条件 , 你读取过的数据 , 再次读取出来发现值不一样了
幻读的重点在于新增或者删除
同样的条件 , 第 1 次和第 2 次读出来的记录数不一样
解决不可重复读的问题只需锁住满足条件的行,解决幻读需要锁表。
2、PlatformTransactionManager 事务管理器
(1)事务管理器是什么?为什么需要管理事务?
事务是对一系列的数据库操作(比如插入多条数据)进行统一的提交或回滚操作,如果插入成功,那么一起成功,如果中间有一条出现异常,那么回滚之前的所有操作,这样可以防止出现脏数据,防止数据库数据出现问题。这一系列处理需要我们对其进行管理。
JDBC中是通过Connection对象进行事务管理,默认是自动提交事务,可以手工将自动提交关闭,通过commit方法进行提交,rollback方法进行回滚,如果不提交,则数据不会真正的插入到数据库中。Hibernate中是通过Transaction进行事务管理,处理方法与JDBC中类似。
Spring中也有自己的事务管理机制,一般是使用TransactionManager(事务管理器)进行管理,可以通过Spring的注入来完成此功能。
(2)先导入两个包:spring-jdbc-3.2.0.RELEASE.jar和spring-tx-3.2.0.RELEASE.jar,如果用了Hibernate还要导入spring-orm-3.2.0.RELEASE.jar
3、转账案例(实现事务的4种方式)
(1)无事务管理
①创建数据库表account:
并插入两条数据:id=1,username="jack",money=1000和id=2,username="rose",money=1000。
②dao层:接口IAccountDao
package com.gyf.dao;
public interface IAccountDao {
//扣钱
public void out(String outer,Integer money);
//进账
public void in(String inner,Integer money);
}
实现类:AccountDaoImpl
package com.gyf.dao.impl;
import org.springframework.jdbc.core.support.JdbcDaoSupport;
import com.gyf.dao.IAccountDao;
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
@Override
public void out(String outer, Integer money) {
getJdbcTemplate().update("update account set money = money-? where username = ?", money,outer);
}
@Override
public void in(String inner, Integer money) {
getJdbcTemplate().update("update account set money = money+ ? where username =?", money,inner);
}
}
③service层:接口 IAccountService
package com.gyf.service;
public interface IAccountService {
/*转账
* @param outer 转出账号
* @param inner 转入账号
* @param money 转入金额
*/
public void transfer(String outer,String inner,Integer money);
}
实现类:AccountServiceImpl
package com.gyf.service.impl;
import org.springframework.transaction.annotation.Transactional;
import com.gyf.dao.IAccountDao;
import com.gyf.service.IAccountService;
public class AccountServiceImpl2 implements IAccountService {
private IAccountDao accountDao;//由spring注入
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(String outer, String inner, Integer money) {
accountDao.out(outer, money);//扣钱
accountDao.in(inner, money);//进账
}
}
④web层,beans09.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 读取db.properties文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置C3P0数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driverClass}" />
<property name="jdbcUrl" value="${jdbcUrl}" />
<property name="user" value="${user}" />
<property name="password" value="${password}" />
</bean>
<!-- 配置dao -->
<bean id="accountDao" class="com.gyf.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置service -->
<bean id="accountService" class="com.gyf.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
</bean>
</beans>
⑤test层:lesson09
package com.gyf.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.gyf.service.IAccountService;
public class lesson09 {
@Test
public void test1() {
//转账测试
//获取service
ApplicationContext context=new ClassPathXmlApplicationContext("beans09.xml");
IAccountService accountService= (IAccountService) context.getBean("accountService");
accountService.transfer("jack", "rose", 100);
}
}
执行结果:
(2)加上事务(手动管理事务)(了解)
①修改service层实现类AccountServiceImpl
package com.gyf.service.impl;
import org.springframework.transaction.TransactionStatus;
import org.springframework.transaction.support.TransactionCallbackWithoutResult;
import org.springframework.transaction.support.TransactionTemplate;
import com.gyf.dao.IAccountDao;
import com.gyf.service.IAccountService;
public class AccountServiceImpl implements IAccountService {
private IAccountDao accountDao;//由spring注入
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
//spring配置事务模板【由spring注入】
private TransactionTemplate transactionTemplate;
public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
this.transactionTemplate = transactionTemplate;
}
@Override
public void transfer(String outer, String inner, Integer money) {
this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
@Override
protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
accountDao.out(outer, money);//扣钱
int i=10/0;//此时执行的话,转账会失败,因为转账的过程出现了错误,事务会回滚
accountDao.in(inner, money);//进账
}
});
}
}
②修改web层,beans09.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 读取db.properties文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置C3P0数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driverClass}" />
<property name="jdbcUrl" value="${jdbcUrl}" />
<property name="user" value="${user}" />
<property name="password" value="${password}" />
</bean>
<!-- 配置dao -->
<bean id="accountDao" class="com.gyf.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 配置dataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务模板 -->
<bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
<!-- 事务管理器 -->
<property name="transactionManager" ref="txManager"></property>
</bean>
<!-- 配置service -->
<bean id="accountService" class="com.gyf.service.impl.AccountServiceImpl">
<property name="accountDao" ref="accountDao"></property>
<!-- 事务模板 -->
<property name="transactionTemplate" ref="transactionTemplate"></property>
</bean>
</beans>
执行结果:
报错了,要想转账成功,就把service层实现类AccountServiceImpl中的int i=10/0;删了或者注销掉。
(3)工厂bean生成代理,半自动管理事务
①在service层,新建AccountServiceImpl2类
package com.gyf.service.impl;
import org.springframework.transaction.annotation.Transactional;
import com.gyf.dao.IAccountDao;
import com.gyf.service.IAccountService;
public class AccountServiceImpl2 implements IAccountService {
private IAccountDao accountDao;//由spring注入
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(String outer, String inner, Integer money) {
accountDao.out(outer, money);//扣钱
//int i=10/0;
accountDao.in(inner, money);//进账
}
}
②在web层新建beans10.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!-- 读取db.properties文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置C3P0数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driverClass}" />
<property name="jdbcUrl" value="${jdbcUrl}" />
<property name="user" value="${user}" />
<property name="password" value="${password}" />
</bean>
<!-- 配置dao -->
<bean id="accountDao" class="com.gyf.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 配置dataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置service -->
<bean id="accountService" class="com.gyf.service.impl.AccountServiceImpl2">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 配置工厂代理 -->
<bean id="proxyService" class="org.springframework.transaction.interceptor.TransactionProxyFactoryBean">
<!-- 接口 -->
<property name="proxyInterfaces" value="com.gyf.service.IAccountService"></property>
<!-- 目标对象 -->
<property name="target" ref="accountService"></property>
<!-- 事务管理器 -->
<property name="transactionManager" ref="txManager"></property>
<!--transactionAttributes:事务属性/详情配置
key:写方法名;value写事务配置;格式:PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly,-Exception,+Exception
传播行为 隔离级别 是否只读 异常回滚 异常提交
-->
<property name="transactionAttributes">
<props>
<prop key="transfer">PROPAGATION_REQUIRED,ISOLATION_DEFAULT</prop>
<prop key="add">PROPAGATION_REQUIRED,ISOLATION_DEFAULT</prop>
<prop key="delete">PROPAGATION_REQUIRED,ISOLATION_DEFAULT</prop>
<prop key="update">PROPAGATION_REQUIRED,ISOLATION_DEFAULT</prop>
<prop key="find">PROPAGATION_REQUIRED,ISOLATION_DEFAULT</prop>
</props>
</property>
</bean>
</beans>
③在test层新建lesson10类
package com.gyf.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.gyf.service.IAccountService;
public class lesson10 {
@Test
public void test1() {
//转账测试
//获取service
ApplicationContext context=new ClassPathXmlApplicationContext("beans10.xml");
IAccountService accountService= (IAccountService) context.getBean("proxyService");
accountService.transfer("jack", "rose", 100);
}
}
(4)基本aop的事务配置
①新建web层beans11.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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 读取db.properties文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置C3P0数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driverClass}" />
<property name="jdbcUrl" value="${jdbcUrl}" />
<property name="user" value="${user}" />
<property name="password" value="${password}" />
</bean>
<!-- 配置dao -->
<bean id="accountDao" class="com.gyf.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 配置dataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置service -->
<bean id="accountService" class="com.gyf.service.impl.AccountServiceImpl2">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 使用aop标签来配置 -->
<!--1、 配置事务通知管理器 -->
<tx:advice id="txAdvice" transaction-manager="txManager">
<!-- 事务详情 :传播行为,隔离级别-->
<tx:attributes>
<!-- 传播行为,隔离级别可以不用配置,但方法要配置-->
<tx:method name="transfer" propagation="REQUIRED" isolation="DEFAULT"/>
</tx:attributes>
</tx:advice>
<!-- 2、事务通知 与 切入点 关联 -->
<aop:config>
<!-- <aop:pointcut expression="execution(* com.gyf.service..*.*(..))" id="myPointcut"/>
<aop:advisor advice-ref="" pointcut-ref="myPointcut"/> 等价于下面的代码 -->
<aop:advisor advice-ref="txAdvice" pointcut="execution(* com.gyf.service..*.*(..))"/>
</aop:config>
</beans>
②新建test层lesson11
package com.gyf.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.gyf.service.IAccountService;
public class lesson11 {
@Test
public void test1() {
//转账测试
//获取service
ApplicationContext context=new ClassPathXmlApplicationContext("beans11.xml");
IAccountService accountService= (IAccountService) context.getBean("accountService");
accountService.transfer("jack", "rose", 100);
}
}
(5)基本注解的事务配置
①修改service层的 AccountServiceImpl2
package com.gyf.service.impl;
import org.springframework.transaction.annotation.Transactional;
import com.gyf.dao.IAccountDao;
import com.gyf.service.IAccountService;
//@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.DEFAULT)功能和下面的一样
@Transactional
public class AccountServiceImpl2 implements IAccountService {
private IAccountDao accountDao;//由spring注入
public void setAccountDao(IAccountDao accountDao) {
this.accountDao = accountDao;
}
@Override
public void transfer(String outer, String inner, Integer money) {
accountDao.out(outer, money);//扣钱
//int i=10/0;
accountDao.in(inner, money);//进账
}
}
②新建web层beans12.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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx
https://www.springframework.org/schema/tx/spring-tx.xsd">
<!-- 读取db.properties文件 -->
<context:property-placeholder location="classpath:db.properties"/>
<!-- 配置C3P0数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<property name="driverClass" value="${driverClass}" />
<property name="jdbcUrl" value="${jdbcUrl}" />
<property name="user" value="${user}" />
<property name="password" value="${password}" />
</bean>
<!-- 配置dao -->
<bean id="accountDao" class="com.gyf.dao.impl.AccountDaoImpl">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 配置service -->
<bean id="accountService" class="com.gyf.service.impl.AccountServiceImpl2">
<property name="accountDao" ref="accountDao"></property>
</bean>
<!-- 注解的事务配置 -->
<!--1、 配置事务管理器 -->
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 配置dataSource -->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!-- 2、开启事务注解驱动 -->
<tx:annotation-driven transaction-manager="txManager"/>
</beans>
③新建test层的lesson12
package com.gyf.test;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import com.gyf.service.IAccountService;
public class lesson12 {
@Test
public void test1() {
//转账测试
//获取service
ApplicationContext context=new ClassPathXmlApplicationContext("beans12.xml");
IAccountService accountService= (IAccountService) context.getBean("accountService");
accountService.transfer("jack", "rose", 100);
}
}