Spring之事务(包含一的内容)

Spring之事务

前言

这是我看尚硅谷视频的学习笔记,结合了尚硅谷的PPT和我自己认为的侧重点和疑问。

事务的基本概念

一组操作数据库的操作序列。这些操作具有原子性,要么全部执行,要么全部不执行,是一个不可分割的执行单位。

事务的特性:

  • 原子性:~ ~ ~ ~ ~
  • 一致性:执行事务前后数据库都处于一致的状态。如果事务提交成功,数据库更新成功,处于有效状态。如 果事务更新失败,则回滚到事务提交前状态。
  • 隔离性:两个事务之间不会相互干扰。(并发事务,就是两个并发事务之间不相互干扰)
  • 持久性:事务一但成功提交,数据库保存的就是数据库更新后的数据。

编程时事务:自己写的事务

声明式事务:框架对固定代码封装,通过框架实现功能,我们只需要在配置文件中进行简单的配置即可。

JDBC简述

我们通过java代码对数据库进行操作(一般是以事务提交)是调用java提供的API——JDBC,JDBC是sun公司定义的一组数据库操作的规则,各个数据库公司根据这套规则制定配套的jar包。

javaWeb中原生调用JDBC的代码太过于繁琐,所以在javaSpring框架中对JDBC进行了封装,spring提供了一个实用类——JdbcTemplate。它简化了使用 JDBC(Java 数据库连接)进行数据库操作的过程。它是一个在 Java 应用程序中使用 JDBC 访问关系型数据库的工具类,提供了很多便利的方法和模板代码,可以简化数据库访问的代码和开发工作。它支持各种主流数据库,如 Oracle、MySQL、SQL Server 等,并提供了丰富的功能,如批处理操作、事务管理、参数绑定等。

具体操作

​ 主要叙述的是spring框架后封装的操作,因为正在学这个。前面的javaWeb的JDBC操作,等我有空了再给它补上。

一、准备工作

老规矩先加入依赖。

1、加入依赖
<!--spring jdbc  Spring 持久化层支持jar包-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>6.0.2</version>
    </dependency>
    <!-- MySQL驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.30</version>
    </dependency>
    <!-- 数据源 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.2.15</version>
    </dependency>

chatGpt是真的好用,直接搜相关依赖,然后CV就可以了。比我自己检索整理出来的依赖文件还省事儿。就是有时候容易一本正经的胡说八道,上午问Junit相关注解,他把切面的相关的注解给我了。

2、准备spring配置文件

bean.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.xsd">

    <!-- 导入外部属性文件 -->
    <context:property-placeholder location="classpath:jdbc.properties" />
    
    <!-- 配置数据源 -->
    <bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="url" value="${jdbc.url}"/>
        <property name="driverClassName" value="${jdbc.driver}"/>
        <property name="username" value="${jdbc.user}"/>
        <property name="password" value="${jdbc.password}"/>
    </bean>
    
    <!-- 配置 JdbcTemplate -->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <!-- 装配数据源 -->
        <property name="dataSource" ref="druidDataSource"/>
    </bean>

</beans>
3、准备个数据库表

Navicat for MySQL可视化操作确实不错,比我上学的时候学的在shell中操作可好太多了。

CREATE DATABASE `spring`;

use `spring`;

CREATE TABLE `t_emp` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(20) DEFAULT NULL COMMENT '姓名',
  `age` int(11) DEFAULT NULL COMMENT '年龄',
  `sex` varchar(2) DEFAULT NULL COMMENT '性别',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
4、实现CRUD

CV一个GPT的CRUD的介绍,CRUD操作可以定义为:

  • Create:创建新的数据记录或对象并将其添加到数据库或存储中。
  • Read:从数据库或存储中检索数据记录或对象。
  • Update:更新现有数据记录或对象的信息,并将其保存回数据库或存储中。
  • Delete:从数据库或存储中删除现有数据记录或对象。

这四个操作是许多应用程序的核心部分,它们可以用于管理和操作数据,让应用程序能够有效地管理和使用数据

​ 为什么把这段代码放在准备环境中,因为这段代码并不是事务,只是教会我正确使用spring框架对数据库增删改查。

先来个JdbcTemplate的测试类

@SpringJUnitConfig(locations = "classpath:beans.xml")
public class JDBCTemplateTest {
//字段注入
    @Autowired
    private JdbcTemplate jdbcTemplate;
    
}

在实际应用中,建议尽可能使用构造函数注入或setter方法注入,而避免使用字段注入。改为下列代码

@SpringJUnitConfig(locations = "classpath:beans.xml")
public class JDBCTemplateTest {

    
    private JdbcTemplate jdbcTemplate;
    
    //setter方法注入
    @Autowired
    public void setPeopleDao(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

}

setter注入一般是选择配置,用构造器就是强配置

1、INSERT
@Test
//测试增删改功能
public void testUpdate(){
    //添加功能
	String sql = "insert into t_emp values(null,?,?,?)";
	int result = jdbcTemplate.update(sql, "张三", 23, "男");
    
    //修改功能
	//String sql = "update t_emp set name=? where id=?";
    //int result = jdbcTemplate.update(sql, "张三atguigu", 1);

    //删除功能
	//String sql = "delete from t_emp where id=?";
	//int result = jdbcTemplate.update(sql, 1);
}
2、SELECT
//返回对象类
public class Emp {

    private Integer id;
    private String name;
    private Integer age;
    private String sex;

    //生成get和set方法
    //......

    @Override
    public String toString() {
        return "Emp{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", sex='" + sex + '\'' +
                '}';
    }
}

//查询:返回对象
@Test
public void testSelectObject() {
    //写法一
//        String sql = "select * from t_emp where id=?";
//        Emp empResult = jdbcTemplate.queryForObject(sql,
//                (rs, rowNum) -> {
//                    Emp emp = new Emp();
//                    emp.setId(rs.getInt("id"));
//                    emp.setName(rs.getString("name"));
//                    emp.setAge(rs.getInt("age"));
//                    emp.setSex(rs.getString("sex"));
//                    return emp;
//                }, 1);
//        System.out.println(empResult);

    //写法二
    String sql = "select * from t_emp where id=?";
    Emp emp = jdbcTemplate.queryForObject(sql,
                  new BeanPropertyRowMapper<>(Emp.class),1);
    System.out.println(emp);
}

@Test
//查询多条数据为一个list集合
public void testSelectList(){
    String sql = "select * from t_emp";
    List<Emp> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Emp.class));
    System.out.println(list);
}

@Test
//查询单行单列的值
public void selectCount(){
    String sql = "select count(id) from t_emp";
    Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
    System.out.println(count);
}

二、实现

主要是介绍声明式事务,包括两种:基于注解的声明式~ 和 基于XML~

基于注解~
1、在beans.xml添加配置
<!--扫描组件-->
<context:component-scan base-package="com.lyz.spring6"></context:component-scan>
2、创建表
CREATE TABLE `t_book` (
  `book_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `book_name` varchar(20) DEFAULT NULL COMMENT '图书名称',
  `price` int(11) DEFAULT NULL COMMENT '价格',
  `stock` int(10) unsigned DEFAULT NULL COMMENT '库存(无符号)',
  PRIMARY KEY (`book_id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;
insert  into `t_book`(`book_id`,`book_name`,`price`,`stock`) values (1,'斗破苍穹',80,100),(2,'斗罗大陆',50,100);
CREATE TABLE `t_user` (
  `user_id` int(11) NOT NULL AUTO_INCREMENT COMMENT '主键',
  `username` varchar(20) DEFAULT NULL COMMENT '用户名',
  `balance` int(10) unsigned DEFAULT NULL COMMENT '余额(无符号)',
  PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
insert  into `t_user`(`user_id`,`username`,`balance`) values (1,'admin',50);
3、创建组件

创建接口BookDao:

package com.atguigu.spring6.dao;
public interface BookDao {
    Integer getPriceByBookId(Integer bookId);

    void updateStock(Integer bookId);

    void updateBalance(Integer userId, Integer price);
}

创建实现类BookDaoImpl:

package com.atguigu.spring6.dao.impl;
@Repository
public class BookDaoImpl implements BookDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public Integer getPriceByBookId(Integer bookId) {
        String sql = "select price from t_book where book_id = ?";
        return jdbcTemplate.queryForObject(sql, Integer.class, bookId);
    }

    @Override
    public void updateStock(Integer bookId) {
        String sql = "update t_book set stock = stock - 1 where book_id = ?";
        jdbcTemplate.update(sql, bookId);
    }

    @Override
    public void updateBalance(Integer userId, Integer price) {
        String sql = "update t_user set balance = balance - ? where user_id = ?";
        jdbcTemplate.update(sql, price, userId);
    }
}

创建接口BookService:

package com.atguigu.spring6.service;
public interface BookService {
    void buyBook(Integer bookId, Integer userId);
}

创建实现类BookServiceImpl:

package com.atguigu.spring6.service.impl;
@Service
public class BookServiceImpl implements BookService {

    @Autowired
    private BookDao bookDao;

    @Override
    public void buyBook(Integer bookId, Integer userId) {
        //查询图书的价格
        Integer price = bookDao.getPriceByBookId(bookId);
        //更新图书的库存
        bookDao.updateStock(bookId);
        //更新用户的余额
        bookDao.updateBalance(userId, price);
    }
}

创建BookController:

package com.atguigu.spring6.controller;

@Controller
public class BookController {

    @Autowired
    private BookService bookService;

    public void buyBook(Integer bookId, Integer userId){
        bookService.buyBook(bookId, userId);
    }
}
4、加入事务 @Transactional

​ @Transactional标识在方法上,则只会影响该方法

​ @Transactional标识的类上,则会影响类中所有的方法

①添加事务配置

在spring配置文件中引入tx命名空间

<?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
       http://www.springframework.org/schema/context/spring-context.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd">

在Spring的配置文件中添加配置:

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="druidDataSource"></property>
</bean>

<!--
    开启事务的注解驱动
    通过注解@Transactional所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务
-->
<!-- transaction-manager属性的默认值是transactionManager,如果事务管理器bean的id正好就是这个默认值,则可以省略这个属性 -->
<tx:annotation-driven transaction-manager="transactionManager" />

②添加事务注解

因为service层表示业务逻辑层,一个方法表示一个完成的功能,因此处理事务一般在service层处理

在BookServiceImpl的buybook()添加注解@Transactional

③观察结果

由于使用了Spring的声明式事务,更新库存和更新余额都没有执行

5、事务属性

事务属性有:只读、超时、回滚策略、隔离级别、传播行为。

只读属性:

告诉数据库这个事务只涉及读取,不涉及更新操作。

@Transactional(readOnly = true)//只读操作
public void buyBook(Integer bookId, Integer userId) {
    //查询图书的价格
    Integer price = bookDao.getPriceByBookId(bookId);
    //更新图书的库存
    bookDao.updateStock(bookId);
    //更新用户的余额
    bookDao.updateBalance(userId, price);
    //System.out.println(1/0);
}

超时:给事务执行时间设限,超过限定时间,回复未操作前的数据库状态。

//超时时间单位秒
@Transactional(timeout = 3)
public void buyBook(Integer bookId, Integer userId) {
    try {
        TimeUnit.SECONDS.sleep(5);
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
    //查询图书的价格
    Integer price = bookDao.getPriceByBookId(bookId);
    //更新图书的库存
    bookDao.updateStock(bookId);
    //更新用户的余额
    bookDao.updateBalance(userId, price);
    //System.out.println(1/0);
}
回滚策略:

默认只针对运行时异常回滚,编译时异常不回滚。

在Java编程语言中,异常被分为两种类型:编译时异常和运行时异常。

编译时异常是指在编译Java程序时就能检测到的异常,它们通常与I/O操作、网络连接、数据库操作等有关,可能会发生错误或异常情况。编译时异常必须在代码中显式地捕获并处理,否则编译器将会抛出异常,导致程序无法编译通过。常见的编译时异常包括IOException、SQLException、ClassNotFoundException等。这些异常通常需要在代码中使用try-catch语句捕获并处理,或者在方法声明中使用throws关键字声明可能抛出的异常类型。

运行时异常是 指在程序运行时可能会发生的异常,通常与代码逻辑错误有关,例如数组越界、空指针引用等。与编译时异常不同的是,运行时异常不需要在代码中显式地捕获和处理。如果未在代码中处理,运行时异常将导致程序运行时崩溃,并抛出异常信息。常见的运行时异常包括NullPointerException、ArrayIndexOutOfBoundsException、ArithmeticException等。在Java程序中,如果不想处理运行时异常,可以使用throws关键字在方法声明中声明可能抛出的异常类型,或者直接忽略异常,让程序自动终止并输出异常信息。

可以通过@Transactional中相关属性设置回滚策略

  • rollbackFor:用于指定当哪些异常发生时需要回滚事务,默认情况下,只有RuntimeException和Error会回滚事务。可以使用rollbackFor属性来指定其他异常类型,多个异常类型可以使用逗号隔开。
  • rollbackForClassName:与rollbackFor属性作用相同,都是用于指定异常类型,不同的是,rollbackForClassName属性需要指定异常类的全限定名。
  • noRollbackFor:用于指定哪些异常发生时不需要回滚事务,多个异常类型可以使用逗号隔开。
  • noRollbackForClassName:与noRollbackFor属性作用相同,都是用于指定不需要回滚事务的异常类型,需要指定异常类的全限定名。

需要注意的是,rollbackFor和noRollbackFor属性不能同时使用,如果同时使用,将会产生异常。

@Transactional(noRollbackFor = ArithmeticException.class)
//@Transactional(noRollbackForClassName = "java.lang.ArithmeticException")
public void buyBook(Integer bookId, Integer userId) {
    //查询图书的价格
    Integer price = bookDao.getPriceByBookId(bookId);
    //更新图书的库存
    bookDao.updateStock(bookId);
    //更新用户的余额
    bookDao.updateBalance(userId, price);
    System.out.println(1/0);
}
隔离级别:

一个事务与其它事务的隔离程度叫隔离级别。隔离级别越高,数据一致性越好,但代价是并发性越弱。

隔离级别一共有四种:(小写字母仅用于记忆,笔者对于大小写的英文字母隔离级别是天花板级别。无奈出此下策)

  • 读未提交:READ UNCOMMITTED( read uncommitted )允许Transaction01读取Transaction02未提交的修改。

  • 读已提交:READ COMMITTED( read committed )、要求Transaction01只能读取Transaction02已提交的修改。

  • 可重复读:REPEATABLE READ ( repeatable read ),确保Transaction01可以多次从一个字段中读取到相同的值,即Transaction01执行期间禁止其它事务对这个字段进行更新。

  • 串行化:SERIALIZABLE( serializable )确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。

各个隔离级别解决并发问题的能力见下表:

( **“脏读”**是数据库中的一种数据不一致性问题,指的是在并发事务执行过程中,一个事务读取到了另一个未提交的事务中的数据。在事务隔离级别中,脏读是最低级别的问题,也是最容易出现的问题。

**“幻读”**是数据库中的一种数据不一致性问题,指的是在并发事务执行过程中,一个事务执行了一次查询操作,但是随后另一个事务插入了符合该查询条件的数据,导致该事务再次执行同样的查询操作时,返回了不同的结果,就好像发生了幻觉一样,因此称为“幻读”。

幻读和脏读区别,脏读指的是读取到了未提交的数据,而幻读指的是读取到了其他事务插入的数据。例如,一个事务在某个表中查询了一些数据,并且得到了符合条件的结果集,但在这个事务还没有提交的时候,另外一个事务插入了符合该条件的数据,此时原来的事务再次执行同样的查询操作时,发现有新插入的数据符合该条件,导致查询结果与之前不一致。

避免幻读的问题,数据库中提供了多种方法,其中最常见的是在事务中使用锁定机制。锁定机制可以通过悲观锁和乐观锁来实现。悲观锁在事务中加锁,从而阻止其他事务修改和插入数据,以避免幻读的问题。而乐观锁则通过版本号等方式来判断数据是否被其他事务修改,以及修改的次数等信息,从而避免幻读的问题。

需要注意的是,使用锁定机制会降低数据库的并发性能,因此需要在保证数据一致性和可靠性的前提下,尽量减少锁定操作的次数和时间,从而提高系统的性能。)

隔离级别脏读不可重复读幻读
READ UNCOMMITTED
READ COMMITTED
REPEATABLE READ
SERIALIZABLE

各种数据库产品对事务隔离级别的支持程度:

隔离级别OracleMySQL
READ UNCOMMITTED×
READ COMMITTED√(默认)
REPEATABLE READ×√(默认)
SERIALIZABLE

使用方式

@Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
@Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
@Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
@Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
@Transactional(isolation = Isolation.SERIALIZABLE)//串行化
传播行为:

在service类中有a()方法和b()方法,a()方法上有事务,b()方法上也有事务,当a()方法执行过程中调用了b()方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。

一共有七种传播行为:

  • REQUIRED( required ):支持当前事务,如果不存在就新建一个(默认)【没有就新建,有就加入】
  • SUPPORTS( supports ):支持当前事务,如果当前没有事务,就以非事务方式执行**【有就加入,没有就不管了】**
  • MANDATORY( mandatory ):必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常**【有就加入,没有就抛异常】**
  • REQUIRES_NEW( requires_new ):开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起**【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】**
  • NOT_SUPPORTED( not_supported ):以非事务方式运行,如果有事务存在,挂起当前事务**【不支持事务,存在就挂起】**
  • NEVER( never ):以非事务方式运行,如果有事务存在,抛出异常**【不支持事务,存在就抛异常】**
  • NESTED( nested ):如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像REQUIRED(required)一样。【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和REQUIRED一样。】

测试

创建接口CheckoutService:

package com.atguigu.spring6.service;

public interface CheckoutService {
    void checkout(Integer[] bookIds, Integer userId);
}

创建实现类CheckoutServiceImpl:

package com.atguigu.spring6.service.impl;

@Service
public class CheckoutServiceImpl implements CheckoutService {

    @Autowired
    private BookService bookService;

    @Override
    @Transactional
    //一次购买多本图书
    public void checkout(Integer[] bookIds, Integer userId) {
        for (Integer bookId : bookIds) {
            bookService.buyBook(bookId, userId);
        }
    }
}

在BookController中添加方法:

@Autowired
private CheckoutService checkoutService;

public void checkout(Integer[] bookIds, Integer userId){
    checkoutService.checkout(bookIds, userId);
}

在数据库中将用户的余额修改为100元

观察结果

可以通过@Transactional中的propagation属性设置事务传播行为

修改BookServiceImpl中buyBook()上,注解@Transactional的propagation属性

@Transactional(propagation = Propagation.REQUIRED),默认情况,表示如果当前线程上有已经开启的事务可用,那么就在这个事务中运行。经过观察,购买图书的方法buyBook()在checkout()中被调用,checkout()上有事务注解,因此在此事务中执行。所购买的两本图书的价格为80和50,而用户的余额为100,因此在购买第二本图书时余额不足失败,导致整个checkout()回滚,即只要有一本书买不了,就都买不了

@Transactional(propagation = Propagation.REQUIRES_NEW),表示不管当前线程上是否有已经开启的事务,都要开启新事务。同样的场景,每次购买图书都是在buyBook()的事务中执行,因此第一本图书购买成功,事务结束,第二本图书购买失败,只在第二次的buyBook()中回滚,购买第一本图书不受影响,即能买几本就买几本。

6、全注解配置事务

创建一个配置类用来替代:JDBC属性文件:jdbc.properties ,配置 JdbcTemplate的xml,配置管理数据库事务的xml。

jdbc.properties

jdbc.user=root
jdbc.password=1234
jdbc.url=jdbc:mysql://localhost:3306/db1?characterEncoding=utf8&useSSL=false
jdbc.driver=com.mysql.cj.jdbc.Driver

补充:useSSL=false是MySQL JDBC驱动程序中的一个连接参数,用于指示是否使用安全套接字层(SSL)来加密与MySQL服务器之间的通信。

如果将useSSL设置为false,则意味着连接不使用SSL协议进行加密,这通常在开发和测试环境中使用。但是,在生产环境中,应该使用SSL协议来加密连接以确保通信的安全性和保密性。

需要注意的是,如果MySQL服务器已经配置为强制要求SSL连接,则无论您在JDBC连接字符串中是否设置了useSSL=false参数,都需要使用SSL协议进行加密连接。此外,还应该根据实际情况进行适当的配置,例如,指定SSL证书的位置、密码等信息,以确保安全性和可靠性。

配置 JdbcTemplate的xml ( beans.xml )

<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"/>
</bean>

配置管理数据库事务的xml

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

替代的配置类

package com.atguigu.spring6.config;

import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;

@Configuration
@ComponentScan("com.atguigu.spring6")
@EnableTransactionManagement
public class SpringConfig {

    @Bean
    public DataSource getDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/spring?characterEncoding=utf8&useSSL=false");
        dataSource.setUsername("root");
        dataSource.setPassword("root");
        return dataSource;
    }

    @Bean(name = "jdbcTemplate")
    public JdbcTemplate getJdbcTemplate(DataSource dataSource){
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    @Bean
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
        DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
        dataSourceTransactionManager.setDataSource(dataSource);
        return dataSourceTransactionManager;
    }
}

基于xml的声明式事务,不符合当前全注解开发的趋势,此处省略。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值