文章目录
访问数据库的技术:
- JDBC访问数据库
Connection.commit()
;Connection.rollback()
; - MyBatis框架
SqlSession.commit()
;SqlSession.rollback()
; - Hibernate框架
Session.commit()
;Session.rollback()
;
spring事务主要用到两个接口
事务原本是数据库中的概念,在 Dao 层。但一般情况下,需要将事务提升到业务层,即 Service 层。这样做是为了能够使用事务的特性来管理具体的业务。
事务管理器接口 PlatformTransactionManager
PlatformTransactionManager
:事务管理器,定义了spring提供的事务的通用方法
commit()
提交事务, spring会调用真正的数据库访问技术中的事务提交方法rollback()
回滚事务,spring会调用真正的数据库访问技术中的事务回滚方法
PlatformTransactionManager
接口有两个常用的实现类:
➢ DataSourceTransactionManager
:使用 JDBC 或 MyBatis 进行数据库操作时使用。
➢ HibernateTransactionManager
:使用 Hibernate 进行持久化数据时使用。
例如你现在使用MyBatis访问数据库,spring
提供了处理MyBatis事务的实现类 DataSourceTransactionManager
,在DataSourceTransactionManager
中实现了SqlSession.commit()
;SqlSession.rollback()
;
你的程序调用spring的commit时,spring会调用DataSourceTransactionManager的commit();在DataSourceTransactionManager的commit()方法中是执行 SqlSession.commit();
你使用Hibernate
框架访问数据, spring提供的HibernateTransactionManager
的实现类
你的程序调用spring的commit时,spring会调用HibernateTransactionManager的commit();
在HibernateTransactionManager的commit()方法中是执行 Session.commit()
;
spring默认的事务处理:当你的应用程序中发生运行时异常或者是error时,回滚事务。发生受查异常时,提交事务(当然手工设置为回滚)。如果你的程序没有异常就是提交事务。
事务定义接口 TransactionDefinition
接口中有很多常量值,这些常量值是用来控制方法的事务行为的。可以控制方法是否有事务,有什么类型的事务,什么样的隔离级别等等。
TransactionDefinition
接口中定义的三类常量,控制方法的事务行为。
- 隔离级别,有5个值
- 传播行为,7个值,说明方法是否有事务的。
- 事务的超时时间,1个值
5个事务隔离级别常量
这些常量均是以 ISOLATION_开头。即形如 ISOLATION_XXX。
DEFAULT:采用DB默认的事务隔离级别。MySQL默认REPEATABLE_READ,Oracle默认READ_COMMITTED
READ_UNCOMMITTED:读未提交。未解决任何并发问题
READ_COMMITTED:读已提交。解决脏读,存在不可重复读、幻读的问题
REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读问题
SERIALIZABLE:串行化。不存在并发问题
脏读:设A表的外键是B表的主键,当两个表合并时,出现呢A表的外键与B表的主键不匹配的情况,这就是脏数据,也叫脏读
不可重复读:不能重复读取。以转账为例,A查询账户额,之后,B在同账户取钱,数据库数据改变,A再次查询,数据改变,读取到最新的数据。同时操作,事务一分别读取事务二操作时和提交后的数据,读取的记录内容不一致
幻读:和不可重复读类似,但是事务二的数据操作仅仅是插入和删除,不是修改数据,读取的记录数量前后不一致
7个事务传播行为常量
所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情况。
如,A 事务中的方法 doSome()调用 B 事务中的方法 doOther(),在调用执行期间事务的维护情况,就称为事务传播行为。
事务传播行为是加在方法上的。
事务传播行为常量都是以 PROPAGATION_ 开头,形如 PROPAGATION_XXX。
以下三个掌握:(假设存在两个方法doSome() 和 doOther(),在doSome()中调用doOther()方法)
PROPAGATION_REQUIRED
- 如果doSome()在事务中执行,doOther()没在事务中执行,则将doOther()加入到doSome()所在的事务中
- 如果doSome()和doOther()都没在事务中执行,则doOther()会创建一个事务,并在其中执行
PROPAGATION_REQUIRES_NEW
指定的方法支持当前事务,若当前没有事务,也可以以非事务的方式执行下去
PROPAGATION_SUPPORTS
每次都会创建一个新的事务。若当前存在事务,就将当前事务挂起,直到新事物执行完毕。
PROPAGATION_MANDATORY
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED
事务的超时时间
常量 TIMEOUT_DEFAULT
定义了事务底层默认的超时时限,sql 语句的执行时长。默认时间单位:秒
事务应用案例
建数据库表
sale 销售表
CREATE TABLE sale
(
id
int(11) NOT NULL ,
gid
int(11) ,
nums
int(11) DEFAULT NULL,
PRIMARY KEY (id
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
goods 商品表
CREATE TABLE goods
(
id
int(11) NOT NULL ,
name
varchar(100) DEFAULT NULL,
amount
int(11) DEFAULT NULL,
price
float(11) DEFAULT NULL,
PRIMARY KEY (id
)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
添加依赖
pom.xml
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--spring框架依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.3.16.RELEASE</version>
</dependency>
<!--Spring有关事务的依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.3.16.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.3.16.RELEASE</version>
</dependency>
<!--Mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<!--Spring整合Mybatis的依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<!--MySQL驱动的依赖-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.9</version>
</dependency>
<!--druid连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
定义实体类
Goods.java
public class Goods{
private Integer id;
private String name;
private Integer amount;
private float price;
//生成set/get/toString方法
}
Sale.java
public class Sale{
private Integer id;
private Integer gid;
private Integer nums;
//生成set/get/toString方法
}
定义dao接口和对应的mapper.xml映射文件
//saleDao接口
public interface SaleDao{
int insertSale(Sale sale);
}
//goods 接口
public interface Goods{
int updateGoods(Goods goods);
Goods selectGoods(Integer goodsId);
}
映射文件
SaleDao.xml
<mapper namespace="com.ljf.dao.SaleDao">
<insert id="insertSale">
insert into sale(gid,nums) values(#{gid},#{nums})
</insert>
</mapper>
GoodsDao.xml
<mapper namespace="com.ljf.dao.GoodsDao">
<update id="updateGoods">
update goods set amount = amount - #{amount} where id=#{id}
</update>
<select id="selectGoods" resultType="Goods">
select id,name,price,amount from goods where id=#{goodsId}
</select>
</mapper>
定义异常类
自定义异常类,在service层可能抛出的异常
public class NotEnoughException extends RuntimeException{
public NotEnoughException(){
super();
}
public NotEnoughException(String msg){
super(msg);
}
}
定义service接口和service实现类
public interface BuyGoodsService{
void buy(Integer goodsId,Integer amount);
}
public class BuyGoodsServiceImpl{
private SaleDao saleDao;
private GoodsDao goodsDao;
public void setGoodsDao(GoodsDao goodsDao){
this.goodsDao = goodsDao;
}
public void setSaleDao(SaleDao saleDao){
this.saleDao = saleDao;
}
public void buy(Integer goodsId,Integer amount){
Sale sale = new Sale();
sale.setGid(goodsId);
sale.setNums(amount);
saleDao.insertSale(sale);
Goods goods = goodsDao.selectGoods(goodsId);
if(goods==null){
throw new NotPointException("无此商品");
}
if(goods.getAmount()<amount){
throw new NotEnoughException("库存不足");
}
goods = new Goods();
goods.setAmount(amount);
goods.setId(goodsId);
goodsDao.updateGoods(goods);
}
}
修改spring配置文件内容
mybatis.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!--定义别名-->
<typeAliases>
<package name="com.ljf.domain" />
</typeAliases>
<!--指定mapper文件的位置-->
<mappers>
<package name="com.ljf.dao" />
</mappers>
</configuration>
jdbc.properties
jdbc.url=jdbc:mysql://localhost:3306/springdb
jdbc.user=root
jdbc.pwd=123
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:coontext="http://www.springframework.org/schema/context"
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">
<coontext:property-placeholder location="classpath:jdbc.properties" />
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource"
init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.pwd}" />
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="myDataSource" />
<property name="configLocation" value="classpath:mybatis.xml" />
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
<property name="basePackage" value="com.bjpowernode.dao" />
</bean>
<bean id="buyService" class="com.bjpowernode.service.impl.BuyGoodsServiceImpl">
<property name="saleDao" ref="saleDao" />
<property name="goodsDao" ref="goodsDao" />
</bean>
</beans>
测试类
TransTest .java
public class TransTest {
@Test
public void test01(){
System.out.println("===测试购买开始===");
String config="applicationContext.xml";
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
BuyGoodsService service = (BuyGoodsService) ctx.getBean("buyService");
//service.buy(1001,10);
service.buy(1005, 10);
System.out.println("===测试购买完成===");
}
}
使用spring的事务注解管理事务
通过@Transactional 注解方式,可将事务织入到相应 public 方法中(只能用于public修饰的方法上),实现事务管理。
@Transactional 的所有可选属性如下所示:
➢ propagation:用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为Propagation.REQUIRED。
➢ isolation:用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认值为Isolation.DEFAULT。
➢ readOnly:用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值为 false。
➢ timeout:用于设置本操作与数据库连接的超时时限。单位为秒,类型为 int,默认值为-1,即没有时限。
➢ rollbackFor:指定需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
➢ rollbackForClassName:指定需要回滚的异常类类名。类型为 String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
➢ noRollbackFor:指定不需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
➢ noRollbackForClassName:指定不需要回滚的异常类类名。类型为 String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
applicationContext.xml
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransantionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />
事务层(service层):
@Transactional(propagation=Propagation.REQUIRED,rollbackFor={NotPointerException.class})
public void buy(int goodsId,int amount){
...
}
使用AspectJ的AOP配置管理事务
参考上面案例进行修改
添加需要的依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.16.RELEASE</version>
</dependency>
添加事务管理器
applicationContext.xml
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
配置事务通知
为事务通知设置相关属性。用于指定要将事务以什么方式织入给哪些方法。
例如,应用到 buy 方法上的事务要求是必须的,且当 buy 方法发生异常后要回滚业务。
applicationContext.xml
<!--事务通知(切面) -->
<tx:advice id="buyAdvice" transaction-manager="transactionManager" >
<tx:attributes>
<tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.NullPointerWxception,com.ljf.exceptions.NotEnoughException" />
<tx:method name="add" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.Exception" />
<tx:method name="*" propagation="SUPPORTS" />
</tx:attributes>
</tx:advice>
配置增强器
指定将配置好的事务通知,织入给谁。
applicationContext.xml
<aop:config>
<aop:pointcut expression="execution(* *..service..*.*(..))" id="servicePt" />
<!--声明增强器:通知和切入点的组合-->
<aop:advisor advice-ref="buyAdvice" pointcut-ref="servicePt" />
</aop:config>
修改测试类
测试类中要从容器中获取的是目标对象。
@Test
public void testBuy(){
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BuyGoodsService service = (BuyGoodsService)crx.getBean("buyService");
service.buy(1001,20);
}