Skip to content
On this page

JdbcTemplate和声明式事务

JdbcTemplate基本介绍

spring 提供了一个操作数据库(表)功能强大的类JdbcTemplate 。我们可以同ioc 容器来配置一个 jdbcTemplate 对象,使用它来完成对数据库表的各种操作.

JdbcTemplate APIs : /spring-framework-5.3.8/docs/javadoc-api/index.html

img

JdbcTemplate 使用实例

引入所需jar包

image-20231126114836936

创建配置文件 src/jdbc.properties

properties
jdbc.userName=root
jdbc.password=hsp
jdbc.driverClass=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring

创建配置文件 src/JdbcTemplate_ioc.xml

xml
<!-- 引入外部属性文件 -->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!-- 配置数据源 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"><property name="user" value="${jdbc.userName}"></property>
  <property name="password" value="${jdbc.password}"></property>
  <property name="driverClass" value="${jdbc.driverClass}"></property>
  <property name="jdbcUrl" value="${jdbc.url}"></property>
</bean>

测试是否可以正确得到数据源

java
// 通过 spring 提供的 JdbcTemplate 获取连接
@Test
public void testDatasourceByJdbcTemplate() throws SQLException {
  //获取到容器
  ApplicationContext ioc =
    new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");

  DataSource dataSource = ioc.getBean(DataSource.class);
  Connection connection = dataSource.getConnection();
  System.out.println("获取到connection= " + connection);
  connection.close();
  System.out.println("ok");
}

配置 JdbcTemplate_ioc.xml,将数据源分配给 JdbcTemplate bean

xml
<!-- 配置 JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
  <!-- 将上面的数据源分配给 jdbcTemplate -->
  <property name="dataSource" ref="dataSource"/>
</bean>

测试

java
package com.hspedu.spring.test;

import com.hspedu.spring.bean.Monster;
import com.hspedu.spring.jdbctemplate.dao.MonsterDao;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.transaction.annotation.Transactional;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author 韩顺平
 * @version 1.0
 */
public class JdbcTemplateTest {

    @Test
    public void testDatasourceByJdbcTemplate() throws SQLException {
        //获取到容器
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");

        DataSource dataSource = ioc.getBean(DataSource.class);
        Connection connection = dataSource.getConnection();
        System.out.println("获取到connection= " + connection);
        connection.close();
        System.out.println("ok");
    }

    //测试通过JdbcTemplate对象完成添加数据
    @Test
    public void addDataByJdbcTemplate() {
        //获取到容器
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");

        //获取JdbcTemplate对象
        JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
        //1. 添加方式1
        //String sql = "INSERT INTO monster VALUES(400, '红孩儿', '枪法')";
        //jdbcTemplate.execute(sql);
        //2. 添加方式2
        String sql = "INSERT INTO monster VALUES(?, ?, ?)";
        //affected表示 执行后表受影响的记录数
        int affected = jdbcTemplate.update(sql, 500, "红孩儿2", "枪法2");
        System.out.println("add ok affected=" + affected);


    }

    //测试通过JdbcTemplate对象完成修改数据
    @Test
    public void updateDataByJdbcTemplate() {

        //获取到容器
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");

        //获取JdbcTemplate对象
        JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);

        //组织SQL
        String sql = "UPDATE monster SET skill=? WHERE id=?";
        int affected = jdbcTemplate.update(sql, "美女计", 300);
        System.out.println("update ok affected= " + affected);

    }

    //批量添加二个monster 白蛇精和青蛇精
    //这里有一个使用API的技巧
    /**
     * 老师说明
     * 1. 对于某个类, 有很多API, 使用的步骤
     * 2. 老韩的使用技巧(1) 先确定API名字 (2) 根据API提供相应的参数 [组织参数]
     *    (3) 把自己的调用思路清晰 (4) 根据API, 可以推测类似的用法和功能
     */

    /**
     * batch add data
     * 批量添加二个monster 白蛇精和青蛇精-update(sql,List<Object[]>)
     */
    @Test
    public void addBatchDataByJdbcTemplate() {
        ApplicationContext ioc = new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
        //得到JdbcTemplate bean
        JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);//添加..

        //1. 先确定,猜测API名称 batchUpdate[如果出现问题,才重新玩]
        //public int[] batchUpdate(String sql, List<Object[]> batchArgs){}
        //2. 准备参数
        String sql = "INSERT INTO monster VALUES(?, ?, ?)";
        List<Object[]> batchArgs = new ArrayList<>();
        batchArgs.add(new Object[]{600, "老鼠精", "偷吃粮食"});
        batchArgs.add(new Object[]{700, "老猫精", "抓老鼠"});
        //3. 调用
        //说明:返回结果是一个数组,每个元素对应上面的sql语句对表的影响记录数
        int[] ints = jdbcTemplate.batchUpdate(sql, batchArgs);
        //输出
        for (int anInt : ints) {
            System.out.println("anInt=" + anInt);
        }
        System.out.println("batch add ok..");
    }

    //查询id=100的monster并封装到Monster实体对象[在实际开发中,非常有用]

    @Test
    public void selectDataByJdbcTemplate() {
        ApplicationContext ioc = new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
        //得到JdbcTemplate bean
        JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
        //组织SQL
        //通过BeanPropertyRowMapper获取rowmapper 是一个接口,可以将查询的结果,封装到你指定的Monster对象中.

        //1. 确定API : queryForObject()
        //public <T> T queryForObject(String sql, RowMapper<T> rowMapper, @Nullable Object... args)
        //2.准备参数
        String sql = "SELECT id AS monsterId, NAME, skill FROM monster WHERE id = 100";
        //使用RowMapper 接口来对返回的数据,进行一个封装-》底层使用的反射->setter
        //这里有一个细节: 你查询的记录的表的字段需要和 Monster的对象字段名保持一致
        RowMapper<Monster> rowMapper = new BeanPropertyRowMapper<>(Monster.class);
        //jdbcTemplate
        Monster monster = jdbcTemplate.queryForObject(sql, rowMapper);
        System.out.println("monster= " + monster);
        System.out.println("查询ok");

    }
    //查询id>=200的monster并封装到Monster实体对象

    /**
     * 查询多条记录
     */
    @Test
    public void selectMulDataByJdbcTemplate() {
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
        //得到JdbcTemplate bean
        JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);
        //组织SQL
        //通过BeanPropertyRowMapper获取rowmapper 是一个接口,可以将查询的结果,封装到你指定的Monster对象中.

        //1.    确定API
        //public <T> T query(String sql, RowMapper<T> rowMapper, Object... args){}
        //2. 组织参数
        String sql = "SELECT id AS monsterId, NAME, skill FROM monster WHERE id >= ?";
        RowMapper<Monster> rowMapper = new BeanPropertyRowMapper<>(Monster.class);
        //3. 调用
        List<Monster> monsterList = jdbcTemplate.query(sql, rowMapper, 100);
        for (Monster monster : monsterList) {
            System.out.println("monster= " + monster);
        }
    }

    //查询返回结果只有一行一列的值,比如查询id=100的怪物名

    /**
     * 查询返回结果只有一行一列的值
     */
    @Test
    public void selectScalarByJdbcTemplate() {
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
        //得到JdbcTemplate bean
        JdbcTemplate jdbcTemplate = ioc.getBean(JdbcTemplate.class);

        //1. 确定API
        //public <T> T queryForObject(String sql, Class<T> requiredType)
        //2. 提供参数
        String sql = "SELECT NAME FROM monster WHERE id = 100";
        //Class<T> requiredType 表示你返回的单行单列的数据类型

        String name =
                jdbcTemplate.queryForObject(sql, String.class);
        System.out.println("返回name= " + name);

    }
}

使用 Map 传入具名参数完成操作

xml
<!-- 配置 NamedParameterJdbcTemplate,支持具名参数 -->
<bean id="namedParameterJdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate"><!-- 这里需要使用构造器关联数据源 -->
  <constructor-arg name="dataSource" ref="dataSource"/>
</bean>
java
//使用Map传入具名参数完成操作,比如添加 螃蟹精.:name 就是具名参数形式需要使用NamedParameterJdbcTemplate 类, 语句形式: String sql = "INSERT INTO monster VALUES(:my_id, :name, :skill)";

    /**
     * 使用Map传入具名参数完成操作,比如添加
     */
    @Test
    public void testDataByNamedParameterJdbcTemplate() {
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
        //得到NamedParameterJdbcTemplate bean
        NamedParameterJdbcTemplate namedParameterJdbcTemplate =
                ioc.getBean(NamedParameterJdbcTemplate.class);

        //1. 确定使用API
        //public int update(String sql, Map<String, ?> paramMap)
        //2. 准备参数 [:my_id, :name, :skill] 要求按照规定的名字来设置参数
        String sql = "INSERT INTO monster VALUES(:id, :name, :skill)";
        Map<String, Object> paramMap = new HashMap<>();
        //给paramMap填写数据
        paramMap.put("id", 800);
        paramMap.put("name", "蚂蚁精");
        paramMap.put("skill", "喜欢打洞");
        //3. 调用
        int affected = namedParameterJdbcTemplate.update(sql, paramMap);
        System.out.println("add ok affected=" + affected);


    }

    //使用sqlparametersoruce 来封装具名参数,还是添加一个Monster 狐狸精

    @Test
    public void operDataBySqlparametersoruce() {
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");
        //得到NamedParameterJdbcTemplate bean
        NamedParameterJdbcTemplate namedParameterJdbcTemplate =
                ioc.getBean(NamedParameterJdbcTemplate.class);

        //确定API
        //public int update(String sql, SqlParameterSource paramSource)
        //public BeanPropertySqlParameterSource(Object object)
        //准备参数
        String sql = "INSERT INTO monster VALUES(:monsterId, :name, :skill)";
        Monster monster = new Monster(900, "大象精", "搬运木头");
        SqlParameterSource sqlParameterSource =
                new BeanPropertySqlParameterSource(monster);
        //调用
        int affected =
                namedParameterJdbcTemplate.update(sql, sqlParameterSource);

        System.out.println("add ok affected= " + affected);
    }

Dao 对象中使用 JdbcTemplate 完成对数据的操作

xml
<context:component-scan base-package="com.hspedu.spring.jdbctemplate.dao"/>
java
package com.hspedu.spring.jdbctemplate.dao;

import com.hspedu.spring.bean.Monster;
import org.aspectj.lang.JoinPoint;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;

/**
 * @author 韩顺平
 * @version 1.0
 */
@Repository //将MonsterDao 注入到spring容器
public class MonsterDao {

    //注入一个属性
    @Resource
    private JdbcTemplate jdbcTemplate;

    //完成保存任务
    public void save(Monster monster) {
        //组织sql
        String sql = "INSERT INTO monster VALUES(?,?,?)";
        int affected = jdbcTemplate.update
                (sql, monster.getMonsterId(), monster.getName(), monster.getSkill());
        System.out.println("affected= " + affected);
    }
}
java
//测试MonsterDAO
@Test
public void monsterDaoSave() {
  ApplicationContext ioc =
    new ClassPathXmlApplicationContext("JdbcTemplate_ioc.xml");

  MonsterDao monsterDao = ioc.getBean(MonsterDao.class);
  Monster monster = new Monster(1000, "小鸭精", "吃鱼");
  monsterDao.save(monster);
  System.out.println("MonsterDAO保存 ok ..");
}

声明式事务

编程式事务

java
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. 使用传统的编程事务来处理,将代码写到一起[缺点: 代码冗余,效率低,不利于扩展, 优点是简单,好理解]
  2. 使用 Spring 的声明式事务处理, 可以将上面三个子步骤分别写成一个方法,然后统一管理. [这个是 Spring 很牛的地方,在开发使用的很多,优点是无代码冗余,效率高,扩展方便,缺点是理解较困难]==> 底层使用 AOP (动态代理+动态绑定+反射+注解) => 看 Debug 源码
java
package com.hspedu.spring.tx.dao;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;

import javax.annotation.Resource;

/**
 * @author 韩顺平
 * @version 1.0
 */
@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);
    }


    /**
     * 根据商品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);
    }
}
java
package com.hspedu.spring.tx.service;

import com.hspedu.spring.tx.dao.GoodsDao;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

/**
 * @author 韩顺平
 * @version 1.0
 */
@Service //将 GoodsService对象注入到spring容器
public class GoodsService {

    //定义属性GoodsDao
    @Resource
    private GoodsDao goodsDao;

    //编写一个方法,完成用户购买商品的业务, 这里主要是讲解事务管理

    /**
     * @param userId  用户id
     * @param goodsId 商品id
     * @param amount  购买数量
     */
    public void buyGoods(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("用户购买成功~");

    }

    /**
     * @param userId
     * @param goodsId
     * @param amount
     * @Transactional 注解解读
     * 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("用户购买成功~");

    }


    /**
     * 这个方法是第二套进行商品购买的方法
     *
     * @param userId
     * @param goodsId
     * @param amount
     */
    @Transactional(propagation = Propagation.REQUIRES_NEW, isolation = Isolation.DEFAULT)
    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("用户购买成功~");

    }

    /**
     * 老师说明
     * 1. 在默认情况下 声明式事务的隔离级别是 REPEATABLE_READ
     * 2. 我们将buyGoodsByTxISOLATION的隔离级别设置为 Isolation.READ_COMMITTED
     * ,表示只要是提交的数据,在当前事务是可以读取到最新数据
     */
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public void buyGoodsByTxISOLATION() {

        //查询两次商品的价格
        Float price = goodsDao.queryPriceById(1);
        System.out.println("第一次查询的price= " + price);

        Float price2 = goodsDao.queryPriceById(1);
        System.out.println("第二次查询的price= " + price2);

    }

    /**
     * 老韩解读
     * 1. @Transactional(timeout = 2)
     * 2. timeout = 2 表示 buyGoodsByTxTimeout 如果执行时间超过了2秒
     *    , 该事务就进行回滚.
     * 3. 如果你没有设置 timeout, 默认是 -1,表示使用事务的默认超时时间,
     *    或者不支持
     */
    @Transactional(timeout = 2)
    public void buyGoodsByTxTimeout(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);

        //模拟超时
        System.out.println("=====超时开始4s=====");
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("=====超时结束4s=====");

        //3. 减少库存量
        goodsDao.updateAmount2(goodsId, amount);

        System.out.println("用户购买成功~");


    }

}
java
package com.hspedu.spring.tx.service;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

/**
 * @author 韩顺平
 * @version 1.0
 */
@Service
public class MultiplyService {
    @Resource
    private GoodsService goodsService;


    /**
     * 老师解读
     * 1. multiBuyGoodsByTx 这个方法 有两次购买商品操作
     * 2. buyGoodsByTx 和 buyGoodsByTx2 都是声明式事务
     * 3. 当前buyGoodsByTx 和 buyGoodsByTx2 使用的传播属性是默认的 REQUIRED [这个含义老师前面讲过了
     *    即会当做一个整体事务进行管理 , 比如buyGoodsByTx方法成功,但是buyGoodsByTx2() 失败,会造成 整个事务的回滚
     *    即会回滚buyGoodsByTx]
     *
     * 4. 如果 buyGoodsByTx 和 buyGoodsByTx2 事务传播属性修改成 REQUIRES_NEW
     *    , 这时两个方法的事务是独立的,也就是如果 buyGoodsByTx成功 buyGoodsByTx2失败
     *    , 不会造成 buyGoodsByTx回滚.
     *
     */
    @Transactional
    public void multiBuyGoodsByTx() {

        goodsService.buyGoodsByTx(1, 1, 1);
        goodsService.buyGoodsByTx2(1, 1, 1);
    }
}
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.hspedu.spring.tx.dao"/>
    <context:component-scan base-package="com.hspedu.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>
java
package com.hspedu.spring.tx;

import com.hspedu.spring.tx.dao.GoodsDao;
import com.hspedu.spring.tx.service.GoodsService;
import com.hspedu.spring.tx.service.MultiplyService;
import com.sun.org.apache.xpath.internal.operations.Mult;
import org.junit.jupiter.api.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author 韩顺平
 * @version 1.0
 */
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("减少库存成功...");
    }

    //测试用户购买商品业务
    @Test
    public void buyGoodsTest() {
        //获取到容器
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("tx_ioc.xml");
        GoodsService goodsService = ioc.getBean(GoodsService.class);
        goodsService.buyGoods(1, 1, 1);
    }

    //测试用户购买商品业务
    @Test
    public void buyGoodsByTxTest() {
        //获取到容器
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("tx_ioc.xml");
        GoodsService goodsService = ioc.getBean(GoodsService.class);
        goodsService.buyGoodsByTx(1, 1, 1);//这里我们调用的是进行了事务声明的方法
    }

    //测试事务的传播机制
    @Test
    public void multiBuyGoodsByTxTest() {

        //获取到容器
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("tx_ioc.xml");

        MultiplyService multiplyService = ioc.getBean(MultiplyService.class);

        multiplyService.multiBuyGoodsByTx();
    }

    //测试声明式事务的隔离级别
    @Test
    public void buyGoodsByTxISOLATIONTest() {

        //获取到容器
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("tx_ioc.xml");

        GoodsService goodsService = ioc.getBean(GoodsService.class);

        goodsService.buyGoodsByTxISOLATION();
    }

    //测试timeout 属性
    @Test
    public void buyGoodsByTxTimeoutTest() {

        //获取到容器
        ApplicationContext ioc =
                new ClassPathXmlApplicationContext("tx_ioc.xml");

        GoodsService goodsService = ioc.getBean(GoodsService.class);

        goodsService.buyGoodsByTxTimeout(1,1,1);
    }
}

声明式事务机制

  1. 当有多个事务处理并存时,如何控制?
  2. 比如用户去购买两次商品(使用不同的方法), 每个方法都是一个事务,那么如何控制呢?

img

事务传播机制种类

image-20231127161616369

事务传播的属性/种类机制分析,重点分析了 REQUIRED 和REQUIRED_NEW两种事务传播属性, 其它知道即可(看上图)

img

img

事务的传播机制的设置方法

![img](file:////private/var/folders/yh/s7cn1qd143v9rg374q2rtf4r0000gn/T/com.kingsoft.wpsoffice.mac/wps-lijinshen/ksohtml//wps97.png)

img

REQUIRES_NEW 和 REQUIRED 在处理事务的策略

java
package com.hspedu.spring.tx.service;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

/**
 * @author 韩顺平
 * @version 1.0
 */
@Service
public class MultiplyService {
    @Resource
    private GoodsService goodsService;


    /**
     * 老师解读
     * 1. multiBuyGoodsByTx 这个方法 有两次购买商品操作
     * 2. buyGoodsByTx 和 buyGoodsByTx2 都是声明式事务
     * 3. 当前buyGoodsByTx 和 buyGoodsByTx2 使用的传播属性是默认的 REQUIRED [这个含义老师前面讲过了
     *    即会当做一个整体事务进行管理 , 比如buyGoodsByTx方法成功,但是buyGoodsByTx2() 失败,会造成 整个事务的回滚
     *    即会回滚buyGoodsByTx]
     *
     * 4. 如果 buyGoodsByTx 和 buyGoodsByTx2 事务传播属性修改成 REQUIRES_NEW
     *    , 这时两个方法的事务是独立的,也就是如果 buyGoodsByTx成功 buyGoodsByTx2失败
     *    , 不会造成 buyGoodsByTx回滚.
     *
     */
    @Transactional
    public void multiBuyGoodsByTx() {

        goodsService.buyGoodsByTx(1, 1, 1);
        goodsService.buyGoodsByTx2(1, 1, 1);
    }
}
  1. 如果设置为 REQUIRES_NEW buyGoods2 如果错误,不会影响到 buyGoods()反之亦然,即它们的事务是独立的.
  2. 如果设置为 REQUIRED buyGoods2 和 buyGoods 是一个整体,只要有方法的事务错误,那么两个方法都不

事务隔离级别说明

事务隔离级别的概念在 mysql 讲过, 就不再说了, 在 Mysql 的799 讲

img

  1. 默认的隔离级别, 就是 mysql 数据库默认的隔离级别 一般为REPEATABLE_READ
  2. 看源码可知 Isolation.DEFAULT 是 :Use the default isolation level of the underlyingdatastore
  3. 查看数据库默认的隔离级别 SELECT @@global.tx_isolation

img

事务的超时回滚

  1. 如果一个事务执行的时间超过某个时间限制,就让该事务回滚。
  2. 可以通过设置事务超时回顾来实现

img

java
/**
     * 老韩解读
     * 1. @Transactional(timeout = 2)
     * 2. timeout = 2 表示 buyGoodsByTxTimeout 如果执行时间超过了2秒
     *    , 该事务就进行回滚.
     * 3. 如果你没有设置 timeout, 默认是 -1,表示使用事务的默认超时时间,
     *    或者不支持
     */
    @Transactional(timeout = 2)
    public void buyGoodsByTxTimeout(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);

        //模拟超时
        System.out.println("=====超时开始4s=====");
        try {
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("=====超时结束4s=====");

        //3. 减少库存量
        goodsDao.updateAmount2(goodsId, amount);

        System.out.println("用户购买成功~");


    }

img

![img](file:////private/var/folders/yh/s7cn1qd143v9rg374q2rtf4r0000gn/T/com.kingsoft.wpsoffice.mac/wps-lijinshen/ksohtml//wps136.png)