Spring之事务

访问数据库的技术:

  1. JDBC访问数据库 Connection.commit()Connection.rollback()
  2. MyBatis框架 SqlSession.commit()SqlSession.rollback()
  3. Hibernate框架 Session.commit()Session.rollback()

spring事务主要用到两个接口

事务原本是数据库中的概念,在 Dao 层。但一般情况下,需要将事务提升到业务层,即 Service 层。这样做是为了能够使用事务的特性来管理具体的业务。

事务管理器接口 PlatformTransactionManager

PlatformTransactionManager:事务管理器,定义了spring提供的事务的通用方法

  1. commit()提交事务, spring会调用真正的数据库访问技术中的事务提交方法
  2. 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接口中定义的三类常量,控制方法的事务行为。

  1. 隔离级别,有5个值
  2. 传播行为,7个值,说明方法是否有事务的。
  3. 事务的超时时间,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

  1. 如果doSome()在事务中执行,doOther()没在事务中执行,则将doOther()加入到doSome()所在的事务中
  2. 如果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);	
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值