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. 这个便是业务的传达机制,看一个详细的事例(如图)

Spring 声明式事务

Spring 声明式事务

业务传达机制品种

● 业务传达的特点/品种一览图

Spring 声明式事务

● 业务传达的特点/品种机制剖析,

要点剖析了 REQUIRED 和 REQUIRED_NEW 两种业务 传达特点, 其它知道即可(看上图)

Spring 声明式事务

Spring 声明式事务

● 业务的传达机制的设置办法

Spring 声明式事务

● REQUIRES_NEW 和 REQUIRED 在处理业务的策略

Spring 声明式事务

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--------");
}

故意写错

Spring 声明式事务

修 改 GoodsService.java ,

将 传 播 机 制 改 成 REQUIRES_NEW 可 以 验 证 : 设 置 为 REQUIRES_NEW

buyGoodsByTx 假如过错,不会影响到 buyGoodsByTx02()反之亦然,也就 是说它们的业务是独立的 ,

将二个办法的@Transactional修正为下面的这种形式 完结测验

@Transactional(propagation = Propagation.REQUIRES_NEW)

业务的阻隔等级

业务阻隔等级阐明

● 业务阻隔等级的概念在这篇博客

【数据库和jdbc】

Spring 声明式事务

● 业务阻隔等级阐明

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

Spring 声明式事务

测验业务的阻隔等级

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);
}

Spring 声明式事务

完结测验

修正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 阻隔等级状况

Spring 声明式事务

Spring 声明式事务

完结测验

运用前面现已创立好的测验办法, 在 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. 能够经过设置业务超时回忆来完结

● 根本语法

Spring 声明式事务

超时回滚-代码完结

修正 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, 添加测验办法

Spring 声明式事务

@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>