Spring声明式事务
环境搭建
数据表生成
/*
SQLyog Ultimate v9.20
MySQL - 5.1.37-community : Database - tx
*********************************************************************
*/
/*!40101 SET NAMES utf8 */;
/*!40101 SET SQL_MODE=''*/;
/*!40014 SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0 */;
/*!40014 SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0 */;
/*!40101 SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='NO_AUTO_VALUE_ON_ZERO' */;
/*!40111 SET @OLD_SQL_NOTES=@@SQL_NOTES, SQL_NOTES=0 */;
CREATE DATABASE /*!32312 IF NOT EXISTS*/`tx` /*!40100 DEFAULT CHARACTER SET gb2312 */;
USE `tx`;
/*Table structure for table `account` */
DROP TABLE IF EXISTS `account`;
CREATE TABLE `account` (
`username` varchar(50) NOT NULL,
`balance` int(11) DEFAULT NULL,
PRIMARY KEY (`username`)
) ENGINE=InnoDB DEFAULT CHARSET=gb2312;
/*Data for the table `account` */
insert into `account`(`username`,`balance`) values ('Jerry',800),('Tom',100000);
/*Table structure for table `book` */
DROP TABLE IF EXISTS `book`;
CREATE TABLE `book` (
`isbn` varchar(50) NOT NULL,
`book_name` varchar(100) DEFAULT NULL,
`price` int(11) DEFAULT NULL,
PRIMARY KEY (`isbn`)
) ENGINE=InnoDB DEFAULT CHARSET=gb2312;
/*Data for the table `book` */
insert into `book`(`isbn`,`book_name`,`price`) values ('ISBN-001','book01',100),('ISBN-002','book02',200),('ISBN-003','book03',300),('ISBN-004','book04',400),('ISBN-005','book05',500);
/*Table structure for table `book_stock` */
DROP TABLE IF EXISTS `book_stock`;
CREATE TABLE `book_stock` (
`isbn` varchar(50) NOT NULL,
`stock` int(11) DEFAULT NULL,
PRIMARY KEY (`isbn`)
) ENGINE=InnoDB DEFAULT CHARSET=gb2312;
/*Data for the table `book_stock` */
insert into `book_stock`(`isbn`,`stock`) values ('ISBN-001',1000),('ISBN-002',2000),('ISBN-003',3000),('ISBN-004',4000),('ISBN-005',5000);
/*!40101 SET SQL_MODE=@OLD_SQL_MODE */;
/*!40014 SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS */;
/*!40014 SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS */;
/*!40111 SET SQL_NOTES=@OLD_SQL_NOTES */;
account:
book:
book_stock:
java代码构建
-
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" 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-4.0.xsd"> <!-- 开启组件扫描 --> <context:component-scan base-package="com.atguigu"></context:component-scan> <!-- 读取配置文件 --> <context:property-placeholder location="classpath:c3p0.properties"/> <!-- 创建连接池对象 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <!-- 创建JdbcTemplate对象 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <constructor-arg name="dataSource" ref="dataSource"></constructor-arg> </bean> </beans>
-
导入相关jar包,创建BookDao层
package com.atguigu.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class BookDap {
@Autowired
JdbcTemplate jdbcTemplate;
/**
*减去某个用户余额
*/
public void updateBalance(String userName,int price){
String sql = "update account set balance = balance - ? where username = ?";
jdbcTemplate.update(sql,price,userName);
}
/**
* 获取某本图书的价格
*/
public double getPrice(String isbn){
String sql = "select price from book where isdn = ?";
Integer price = jdbcTemplate.queryForObject(sql, Integer.class,isbn);
return price;
}
/**
* 减某本书的库存,为了简单起见每次减1
*/
public void updateStock(String isbn){
String sql = "update book_stock set stock = stock - 1 where isbn = ?";
jdbcTemplate.update(sql,isbn);
}
}
-
创建BookServicel层
package com.atguigu.bookService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import com.atguigu.dao.BookDao; @Service public class bookService { @Autowired BookDao bookDao; /** * 结账:对指定用户扣除买书费用 * @param username 用户 * @param isbn 图书编号 */ public void checkout(String username,String isbn){ //减库存 bookDao.updateStock(isbn); //查询书本价格 Integer price = bookDao.getPrice(isbn); //减余额 bookDao.updateBalance(username, price); } }
-
测试
package com.atguigu.test; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.atguigu.bookService.bookService; public class bookServiceTest { ApplicationContext context = new ClassPathXmlApplicationContext("tx.xml"); @Test public void test() { bookService bookService = context.getBean(bookService.class); bookService.checkout("Tom", "ISBN-001"); } }
此时测试一切正常,数据库中的数据会相应改变。
模拟在程序执行中发生错误
修改BookSevice
package com.atguigu.bookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.atguigu.dao.BookDao;
@Service
public class bookService {
@Autowired
BookDao bookDao;
/**
* 结账:对指定用户扣除买书费用
* @param username 用户
* @param isbn 图书编号
*/
public void checkout(String username,String isbn){
//减库存
bookDao.updateStock(isbn);
//手动设置异常模拟错误
int a = 1/0;
//查询书本价格
Integer price = bookDao.getPrice(isbn);
//减余额
bookDao.updateBalance(username, price);
}
}
此时再次测试结果只是减了库存,但是并没有减去Tom的余额。
解决数据不统一的问题
编程式事务
TransictionFilter{
try{
//获取连接
//设置非自动提交
chain.dofIlter();
//提交事务
}catch{
//回滚
}
}
声明式事务
以前通过复杂的编程来编写一个事务,只需要告诉Spring哪个方法是事务方法即可,Spring自动进行事务控制。原理就是AOP的环绕通知,最终效果如下
class BookSerive{
@Thransaction
public void checkout(){
}
}
这个事务管理器可以在目标方法运行前后进行事务控制(事务切面),目前使用的是DataSourceTransactionManager。
声明式事务实例
-
配置出这个事务管理器让他工作
<?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" 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-4.0.xsd"> <!-- 开启组件扫描 --> <context:component-scan base-package="com.atguigu"></context:component-scan> <!-- 读取配置文件 --> <context:property-placeholder location="classpath:c3p0.properties"/> <!-- 创建连接池对象 --> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="jdbcUrl" value="${jdbc.jdbcUrl}"></property> <property name="driverClass" value="${jdbc.driverClass}"></property> <property name="user" value="${jdbc.user}"></property> <property name="password" value="${jdbc.password}"></property> </bean> <!-- 创建JdbcTemplate对象 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate"> <constructor-arg name="dataSource" ref="dataSource"></constructor-arg> </bean> <!-- 1、创建事务管理器对象让其进行事务控制 --> <bean id="dataSourceTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <!-- 控制住数据源 --> <property name="dataSource" ref="dataSource"></property> </bean> <!-- 2、开启基于注解的事务控制模式,依赖名称空间tx,导入面向切面的jar包 com.springsource.net.sf.cglib-2.2.0.jar com.springsource.org.aopalliance-1.0.0.jar com.springsource.org.aspectj.weaver-1.6.8.RELEASE.jar spring-aspects-4.0.0.RELEASE.jar --> <tx:annotation-driven transaction-manager="dataSourceTransactionManager"/> </beans>
-
给事务方法加注解@Transactional
package com.atguigu.bookService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.atguigu.dao.BookDao; @Service public class bookService { @Autowired BookDao bookDao; /** * 结账:对指定用户扣除买书费用 * @param username 用户 * @param isbn 图书编号 */ @Transactional//给事务方法加注解 public void checkout(String username,String isbn){ //减库存 bookDao.updateStock(isbn); //手动设置异常模拟错误 int a = 1/0; //查询书本价格 Integer price = bookDao.getPrice(isbn); //减余额 bookDao.updateBalance(username, price); } }
-
测试
package com.atguigu.test; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; import com.atguigu.bookService.bookService; public class bookServiceTest { ApplicationContext context = new ClassPathXmlApplicationContext("tx.xml"); @Test public void test() { bookService bookService = context.getBean(bookService.class); bookService.checkout("Tom", "ISBN-001"); } }
此时错误依旧存在,但是数据库中的数据并没有发生改变,说明事务进行了回滚。
import com.atguigu.bookService.bookService;
public class bookServiceTest {
ApplicationContext context = new ClassPathXmlApplicationContext(“tx.xml”);
@Test
public void test() {
bookService bookService = context.getBean(bookService.class);
bookService.checkout(“Tom”, “ISBN-001”);
}}
此时错误依旧存在,但是数据库中的数据并没有发生改变,说明事务进行了回滚。