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 | 无 | 无 | 无 |
各种数据库产品对事务隔离级别的支持程度:
隔离级别 | Oracle | MySQL |
---|---|---|
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的声明式事务,不符合当前全注解开发的趋势,此处省略。