Spring声明式业务
业务分类
1.编程式业务:
暗示代码, 传统方式
Connection connection = JdbcUtils.getConnection();
try {
//1. 先设置业务不要自动提交
connection.setAutoCommint(false);
//2. 进行各种 crud
//多个表的修正,添加 ,删去
//3. 提交
connection.commit();
} catch (Exception e) {
//4. 回滚
conection.rollback();
}
声明式业务-运用实例
需求阐明-用户购买产品
咱们需求去处理用户购买产品的业务逻辑:剖析:当一个用户要去购买产品应该包括三个过程
1. 经过产品 id 获取价格. 2. 购买产品(某人购买产品,修正用户的余额)
3. 修正库存量
4. 其实大家能够看到,这时,咱们需求涉及到三张表产品表,用户表,产品存量表。 应该运用业务处理
解决方案剖析
1. 运用传统的编程业务来处理,将代码写到一同[缺陷: 代码冗余,效率低,不利于扩展, 优点是简单,好了解]
Connection connection = JdbcUtils.getConnection();
try {
//1. 先设置业务不要自动提交
connection.setAutoCommit(false);
//2. 进行各种 crud
//多个表的修正,添加 ,删去
select from 产品表 => 获取价格
修正用户余额 update ... 修正库存量 update
//3. 提交
connection.commit();
} catch (Exception e) {
//4. 回滚
conection.rollback();
}
2. 运用 Spring 的声明式业务处理,
能够将上面三个子过程分别写成一个办法,然后统一管理.
[这个是 Spring 很牛的当地,在开发运用的很多,优点是无代码冗余,效率高,扩展方便,缺陷是了解较困难]==> 底层运用 AOP (动态署理+动态绑定+反射+注解)
声明式业务运用-代码完结
1. 先创立产品系统的数据库和表
-- 演示声明式业务创立的表
CREATE TABLE `user_account`(
user_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
user_name VARCHAR(32) NOT NULL DEFAULT '',
money DOUBLE NOT NULL DEFAULT 0.0
)CHARSET=utf8;
INSERT INTO `user_account` VALUES(NULL,'张三', 1000);
INSERT INTO `user_account` VALUES(NULL,'李四', 2000);
CREATE TABLE `goods`(
goods_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
goods_name VARCHAR(32) NOT NULL DEFAULT '',
price DOUBLE NOT NULL DEFAULT 0.0
)CHARSET=utf8 ;
INSERT INTO `goods` VALUES(NULL,'小风扇', 10.00);
INSERT INTO `goods` VALUES(NULL,'小台灯', 12.00);
INSERT INTO `goods` VALUES(NULL,'可口可乐', 3.00);
CREATE TABLE `goods_amount`(
goods_id INT UNSIGNED PRIMARY KEY AUTO_INCREMENT,
goods_num INT UNSIGNED DEFAULT 0
)CHARSET=utf8 ;
INSERT INTO `goods_amount` VALUES(1,200);
INSERT INTO `goods_amount` VALUES(2,20);
INSERT INTO `goods_amount` VALUES(3,15);
创立GoodsDao类
@Repository //将 GoodsDao-目标 注入到spring容器
public class GoodsDao {
@Resource
private JdbcTemplate jdbcTemplate;
/**
* 依据产品id,回来对应的价格
* @param id
* @return
*/
public Float queryPriceById(Integer id) {
String sql = "SELECT price From goods Where goods_id=?";
Float price = jdbcTemplate.queryForObject(sql, Float.class, id);
return price;
}
/**
* 修正用户的余额 [削减用户余额]
* @param user_id
* @param money
*/
public void updateBalance(Integer user_id, Float money) {
String sql = "UPDATE user_account SET money=money-? Where user_id=?";
jdbcTemplate.update(sql, money, user_id);
}
/**
* 修正产品库存 [削减]
* @param goods_id
* @param amount
*/
public void updateAmount(Integer goods_id, int amount){
String sql = "UPDATE goods_amount SET goods_num=goods_num-? Where goods_id=?";
jdbcTemplate.update(sql, amount , goods_id);
}
}
创立 src\tx_ioc.xm
<?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: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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--装备要扫描的包-->
<context:component-scan base-package="com.spring.tx.dao"/>
<!--引进外部的jdbc.properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--装备数据源目标-DataSoruce-->
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
<!--给数据源目标装备特点值-->
<property name="user" value="${jdbc.user}"/>
<property name="password" value="${jdbc.pwd}"/>
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
</bean>
<!--装备JdbcTemplate目标-->
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<!--给JdbcTemplate目标装备dataSource-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--装备业务管理器-目标
1. DataSourceTransactionManager 这个目标是进行业务管理-debug源码
2. 一定要装备数据源特点,这样指定该业务管理器 是对哪个数据源进行业务操控
-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--装备发动依据注解的声明式业务管理功用-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
创立TxTest类
public class TxTest {
@Test
public void queryPriceByIdTest() {
//获取到容器
ApplicationContext ioc =
new ClassPathXmlApplicationContext("tx_ioc.xml");
GoodsDao goodsDao = ioc.getBean(GoodsDao.class);
Float price = goodsDao.queryPriceById(1);
System.out.println("id=100 的price=" + price);
}
@Test
public void updateBalance() {
//获取到容器
ApplicationContext ioc =
new ClassPathXmlApplicationContext("tx_ioc.xml");
GoodsDao goodsDao = ioc.getBean(GoodsDao.class);
goodsDao.updateBalance(1, 1.0F);
System.out.println("削减用户余额成功~");
}
@Test
public void updateAmount() {
//获取到容器
ApplicationContext ioc =
new ClassPathXmlApplicationContext("tx_ioc.xml");
GoodsDao goodsDao = ioc.getBean(GoodsDao.class);
goodsDao.updateAmount(1, 1);
System.out.println("削减库存成功...");
}
}
创立GoodsService类
编写办法,验证不运用业务就会出现数据不一致现象.
@Service
public class GoodsService {
@Autowired
private GoodsDao goodsDao;
/**
* 购买产品[没有运用业务]
* @param user_id
* @param goods_id
* @param num
*/
public void buyGoods(int user_id, int goods_id, int num) {
//查询到产品价格
Float goods_price = goodsDao.queryPriceById(goods_id);
//购买产品,减去余额
goodsDao.updateBalance(user_id, goods_price * num);
// //: 模拟一个反常, 会发生数据库数据不一致现象
// int i = 10 / 0;
//更新库存
goodsDao.updateAmount(goods_id, num);
}
}
修正tx_ioc.xml, 参加对 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: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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--装备要扫描的包-->
<context:component-scan base-package="com.spring.tx.dao"/>
<context:component-scan base-package="com.spring.tx.service"/>
<!--引进外部的jdbc.properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--装备数据源目标-DataSoruce-->
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
<!--给数据源目标装备特点值-->
<property name="user" value="${jdbc.user}"/>
<property name="password" value="${jdbc.pwd}"/>
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
</bean>
<!--装备JdbcTemplate目标-->
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<!--给JdbcTemplate目标装备dataSource-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--装备业务管理器-目标
1. DataSourceTransactionManager 这个目标是进行业务管理-debug源码
2. 一定要装备数据源特点,这样指定该业务管理器 是对哪个数据源进行业务操控
-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--装备发动依据注解的声明式业务管理功用-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>
测验 TxTest类
@Test
public void buyGoodsTest() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("tx_ioc.xml");
GoodsService bean = ioc.getBean(GoodsService.class);
bean.buyGoods(1, 2, 1);
System.out.println("====购买产品成功====");
}
修正 GoodsService.java, 添加测验办法,参加声明式业务注解
@Transactional
public void buyGoodsByTx(int user_id, int goods_id, int num) {
//查询到产品价格
Float goods_price = goodsDao.queryPriceById(goods_id);
//购买产品,减去余额
goodsDao.updateBalance(user_id, goods_price * num);
// // 模拟一个反常, 会发生数据库数据不一致现象
// int i = 10 / 0;
//更新库存
goodsDao.updateAmount(goods_id, num);
}
修正 TxTest.java, 添加测验办法, 对声明式业务进行测验,看看是否确保了数据一致性
/**
* 测验购买产品(运用了声明式业务)
*/
@Test
public void buyGoodsByTxTest() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("tx_ioc.xml");
GoodsService bean = ioc.getBean(GoodsService.class);
//运用 buyGoodsByTx()
bean.buyGoodsByTx(1, 2, 1);
System.out.println("====购买产品成功====");
}
声明式业务机制-Debug
业务的传达机制
业务的传达机制阐明
1. 当有多个业务处理并存时,怎么操控?
2. 比方用户去购买两次产品(运用不同的办法), 每个办法都是一个业务,那么怎么操控呢?
3. 这个便是业务的传达机制,看一个详细的事例(如图)
业务传达机制品种
● 业务传达的特点/品种一览图
● 业务传达的特点/品种机制剖析,
要点剖析了 REQUIRED 和 REQUIRED_NEW 两种业务 传达特点, 其它知道即可(看上图)
● 业务的传达机制的设置办法
● REQUIRES_NEW 和 REQUIRED 在处理业务的策略
1. 假如设置为 REQUIRES_NEW
buyGoods2 假如过错,不会影响到 buyGoods()反之亦然,即它们的业务是独立的.
2. 假如设置为 REQUIRED
buyGoods2 和 buyGoods 是一个全体,只要有办法的业务过错,那么两个办法都不会履行成功.!
业务的传达机制-应用实例
● 业务的传达机制需求阐明
1. 比方用户去购买两次产品(运用不同的办法), 每个办法都是一个业务,那么怎么操控呢?
=>这个便是业务的传达机制
2. 看一个详细的事例(用 required/requires_new 来测验):
修正 GoodsDao.java, 添加办法
public class GoodsDao {
/**
* 依据产品id,回来对应的价格
* @param id
* @return
*/
public Float queryPriceById2(Integer id) {
String sql = "SELECT price From goods Where goods_id=?";
Float price = jdbcTemplate.queryForObject(sql, Float.class, id);
return price;
}
/**
* 修正用户的余额 [削减用户余额]
* @param user_id
* @param money
*/
public void updateBalance2(Integer user_id, Float money) {
String sql = "UPDATE user_account SET money=money-? Where user_id=?";
jdbcTemplate.update(sql, money, user_id);
}
/**
* 修正产品库存 [削减]
* @param goods_id
* @param amount
*/
public void updateAmount2(Integer goods_id, int amount){
String sql = "UPDATE goods_amount SET goods_num=goods_num-? Where goods_id=?";
jdbcTemplate.update(sql, amount , goods_id);
}
}
修正 GoodsService.java 添加 buyGoodsByTx02(), 运用默许的传达机制
注解解读
1. 运用@Transactional 能够进行声明式业务操控
2. 行将标识的办法中的,对数据库的操作作为一个业务管理
3. @Transactional 底层运用的仍然是AOP机制
4. 底层是运用动态署理目标来调用buyGoodsByTx
5. 在履行buyGoodsByTx() 办法 先调用 业务管理器的 doBegin() , 调用 buyGoodsByTx()
假如履行没有发生反常,则调用 业务管理器的 doCommit(), 假如发生反常 调用业务管理器的 doRollback()
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void buyGoodsByTx(int userId, int goodsId, int amount) {
//输出购买的相关信息
System.out.println("用户购买信息 userId=" + userId
+ " goodsId=" + goodsId + " 购买数量=" + amount);
//1.得到产品的价格
Float price = goodsDao.queryPriceById(userId);
//2. 削减用户的余额
goodsDao.updateBalance(userId, price * amount);
//3. 削减库存量
goodsDao.updateAmount(goodsId, amount);
System.out.println("用户购买成功~");
}
@Transactional
public void buyGoodsByTx2(int userId, int goodsId, int amount) {
//输出购买的相关信息
System.out.println("用户购买信息 userId=" + userId
+ " goodsId=" + goodsId + " 购买数量=" + amount);
//1.得到产品的价格
Float price = goodsDao.queryPriceById2(userId);
//2. 削减用户的余额
goodsDao.updateBalance2(userId, price * amount);
//3. 削减库存量
goodsDao.updateAmount2(goodsId, amount);
System.out.println("用户购买成功~");
}
创立MultiplyService类
解读
1. multiBuyGoodsByTx 这个办法 有两次购买产品操作
2. buyGoodsByTx 和 buyGoodsByTx2 都是声明式业务
3. 当时buyGoodsByTx 和 buyGoodsByTx2 运用的传达特点是默许的 REQUIRED [这个意义前面讲过了
即会当做一个全体业务进行管理 , 比方buyGoodsByTx办法成功,可是buyGoodsByTx2() 失利,会造成 整个业务的回滚即会回滚buyGoodsByTx
4. 假如 buyGoodsByTx 和 buyGoodsByTx2 业务传达特点修正成 REQUIRES_NEW
这时两个办法的业务是独立的,也便是假如 buyGoodsByTx成功 buyGoodsByTx2失利, 不会造成 buyGoodsByTx回滚.
@Service
public class MultiplyService {
@Resource
private GoodsService goodsService;
@Transactional
public void multiBuyGoodsByTx() {
goodsService.buyGoodsByTx(1, 1, 1);
goodsService.buyGoodsByTx2(1, 1, 1);
}
}
测验 TxTest.java,
能够验证:为 REQUIRED buyGoodsByTx 和 buyGoodsByTx02 是全体,只要有办法的业务过错,那么两个办法都不会履行成功
@Test
public void buyGoodsByMulTxTest() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("tx_ioc.xml");
MultiplyTxService bean = ioc.getBean(MultiplyTxService.class);
bean.multiTxTest();
System.out.println("------ok--------");
}
故意写错
修 改 GoodsService.java ,
将 传 播 机 制 改 成 REQUIRES_NEW 可 以 验 证 : 设 置 为 REQUIRES_NEW
buyGoodsByTx 假如过错,不会影响到 buyGoodsByTx02()反之亦然,也就 是说它们的业务是独立的 ,
将二个办法的@Transactional修正为下面的这种形式 完结测验
@Transactional(propagation = Propagation.REQUIRES_NEW)
业务的阻隔等级
业务阻隔等级阐明
● 业务阻隔等级的概念在这篇博客
【数据库和jdbc】
● 业务阻隔等级阐明
1. 默许的阻隔等级, 便是 mysql 数据库默许的阻隔等级 一般为 REPEATABLE_READ
2. 看源码可知 Isolation.DEFAULT 是 :Use the default isolation level of the underlying datastore
3. 查看数据库默许的阻隔等级 SELECT @@global.tx_isolation
业务阻隔等级的设置和测验
1. 修正 GoodsService.java , 先测默许阻隔等级,添加办法 buyGoodsByTxISOLATIO
测验业务的阻隔等级
1. 默许的阻隔等级, 便是 mysql 数据库默许的阻隔等级 一般为 REPEATABLE_READ
2. 看源码可知 Use the default isolation level of the underlying datastore
public enum Isolation {
<p>
Use the default isolation level of the underlying datastore.
All other levels correspond to the JDBC isolation levels.
@see java.sql.Connection
DEFAULT(TransactionDefinition.ISOLATION_DEFAULT)}
3. 查看数据库默许的阻隔等级 SELECT @@global.tx_isolation
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void buyGoodsByTxISOLATION(int user_id, int goods_id, int num) {
//查询到产品价格
Float goods_price = goodsDao.queryPriceById(goods_id);
System.out.println("第一次读取的价格 = " + goods_price);
//测验一下阻隔等级,在同一个业务中,查询一下价格
goods_price = goodsDao.queryPriceById(goods_id);
System.out.println("第二次读取的价格 = " + goods_price);
}
完结测验
修正TxTest增 加测验办法, 默许阻隔等级 下, 两次读取到的价格是相同的,不会遭到 SQLyog 修正影响
@Test
public void buyGoodsByTxISOLATIONTest() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("tx_ioc.xml");
GoodsService bean = ioc.getBean(GoodsService.class);
bean.buyGoodsByTxISOLATION(1, 1, 1);
System.out.println("------ok--------");
}
修正 GoodsService.java , 测验 READ_COMMITTED 阻隔等级状况
完结测验
运用前面现已创立好的测验办法, 在 READ_COMMITTED 阻隔等级 下, 两次 读取到的价格会遭到 SQLyog 修正
@Test
public void buyGoodsByTxISOLATIONTest() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("tx_ioc.xml");
GoodsService bean = ioc.getBean(GoodsService.class);
bean.buyGoodsByTxISOLATION(1, 1, 1);
System.out.println("------ok--------");
}
业务的超时回滚
● 根本介绍
1. 假如一个业务履行的时刻超越某个时刻限制,就让该业务回滚。
2. 能够经过设置业务超时回忆来完结
● 根本语法
超时回滚-代码完结
修正 GoodsService.java ,添加 buyGoodsByTxTimeout()
@Transactional(timeout = 2)
public void buyGoodsByTxTimeout(int user_id, int goods_id, int num) {
//查询到产品价格
Float goods_price = goodsDao.queryPriceById02(goods_id);
//购买产品,减去余额
goodsDao.updateBalance02(user_id, goods_price * num);
System.out.println("====超时 start====");
try {
Thread.sleep(4000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("====超时 end====");
//更新库存
goodsDao.updateAmount02(goods_id, num);
}
测验 TxTest.java, 添加测验办法
@Test
public void buyGoodsByTxTimeoutTest() {
ApplicationContext ioc = new ClassPathXmlApplicationContext("tx_ioc.xml");
GoodsService bean = ioc.getBean(GoodsService.class);
bean.buyGoodsByTxTimeout(1, 1, 1);
System.out.println("------ok--------");
}
注意
上面一切的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: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 https://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
<!--装备要扫描的包-->
<context:component-scan base-package="com.spring.tx.dao"/>
<context:component-scan base-package="com.spring.tx.service"/>
<!--引进外部的jdbc.properties文件-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--装备数据源目标-DataSoruce-->
<bean class="com.mchange.v2.c3p0.ComboPooledDataSource" id="dataSource">
<!--给数据源目标装备特点值-->
<property name="user" value="${jdbc.user}"/>
<property name="password" value="${jdbc.pwd}"/>
<property name="driverClass" value="${jdbc.driver}"/>
<property name="jdbcUrl" value="${jdbc.url}"/>
</bean>
<!--装备JdbcTemplate目标-->
<bean class="org.springframework.jdbc.core.JdbcTemplate" id="jdbcTemplate">
<!--给JdbcTemplate目标装备dataSource-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--装备业务管理器-目标
1. DataSourceTransactionManager 这个目标是进行业务管理-debug源码
2. 一定要装备数据源特点,这样指定该业务管理器 是对哪个数据源进行业务操控
-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--装备发动依据注解的声明式业务管理功用-->
<tx:annotation-driven transaction-manager="transactionManager"/>
</beans>