spring 框架提供了能够方便操作数据库的 JdbcTemplate 类,以及简化事务开发的声明式事务。
JdbcTemplate
JdbcTemplate 是 Spring 对 JDBC 进行封装的结果,使用 JdbcTemplate 方便实现对数据库操作。
准备工作
-
创建数据库
-
创建子模块
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;
-
引入依赖
<dependencies> <!--spring jdbc Spring 持久化层支持jar包--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>6.0.6</version> </dependency> <!-- MySQL驱动 --> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.32</version> </dependency> <!-- 数据源 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.2.16</version> </dependency> </dependencies>
-
创建 jdbc.properties
jdbc.user=root jdbc.password=root jdbc.url=jdbc:mysql://localhost:3306/spring?serverTimezone=UTC jdbc.driver=com.mysql.cj.jdbc.Driver
-
创建 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>
-
准备测试类,按照上一篇文章的方法整合 JUnit
import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.test.context.junit.jupiter.SpringJUnitConfig; /** * @author hope * @date 2023/3/30 - 12:04 */ @SpringJUnitConfig(locations = "classpath:bean.xml") public class JdbcTemplateTest { @Autowired private JdbcTemplate jdbcTemplate; @Test public void updateTest() { // test code } }
实现 CRUD
在上一小节,我们获得了 JdbcTemplate 实例类,这节我将展示如何使用这个类。
添加操作
@Test
public void updateTest() {
// Spring.t_emp 的表结构:
// +-------+-------------+------+-----+---------+----------------+
// | Field | Type | Null | Key | Default | Extra |
// +-------+-------------+------+-----+---------+----------------+
// | id | int | NO | PRI | NULL | auto_increment |
// | name | varchar(20) | YES | | NULL | |
// | age | int | YES | | NULL | |
// | sex | varchar(2) | YES | | NULL | |
// +-------+-------------+------+-----+---------+----------------+
// 实现添加操作
// Step1. 编写 Sql 语句
String sql = "INSERT INTO `t_emp` VALUES(NULL, ?, ?, ?)";
// Step2. 调用 jdbcTemplate 的方法,执行 Sql 添加语句
// 返回值是影响的行数
int rows = jdbcTemplate.update(sql, "北原春希", 18, "男");
System.out.println(rows); // 1
// 另一种写法:
// Object[] params = {"北原春希", 18, "男"}
// jdbcTemplate.update(sql, params);
}
操作结果:
mysql> select * from t_emp;
+----+--------------+------+------+
| id | name | age | sex |
+----+--------------+------+------+
| 1 | 北原春希 | 18 | 男 |
+----+--------------+------+------+
1 row in set (0.00 sec)
现在,请你给数据库添加下面的条目:
name age sex 小木曾雪菜 19 女 冬马和纱 17 女 野原新之助 6 男 东方不败 40 未知
修改操作
// 现在的表:
//+----+-----------------+------+--------+
//| id | name | age | sex |
//+----+-----------------+------+--------+
//| 1 | 北原春希 | 18 | 男 |
//| 2 | 小木曾雪菜 | 19 | 女 |
//| 3 | 冬马和纱 | 17 | 女 |
//| 4 | 野原新之助 | 6 | 男 |
//| 5 | 东方不败 | 40 | 未知 |
//+----+-----------------+------+--------+
// 实现修改操作
// Step1. 编写 Sql 语句
String sql = "update t_emp set name=? where id=?";
// Step2. 调用 jdbcTemplate 的方法,执行 Sql 修改语句
int rows = jdbcTemplate.update(sql, "桐谷和人", 1);
System.out.println(rows);
// 修改后:
//+----+-----------------+------+--------+
//| id | name | age | sex |
//+----+-----------------+------+--------+
//| 1 | 桐谷和人 | 18 | 男 |
//| 2 | 小木曾雪菜 | 19 | 女 |
//| 3 | 冬马和纱 | 17 | 女 |
//| 4 | 野原新之助 | 6 | 男 |
//| 5 | 东方不败 | 40 | 未知 |
//+----+-----------------+------+--------+
删除操作
// 现在的表:
//+----+-----------------+------+--------+
//| id | name | age | sex |
//+----+-----------------+------+--------+
//| 1 | 桐谷和人 | 18 | 男 |
//| 2 | 小木曾雪菜 | 19 | 女 |
//| 3 | 冬马和纱 | 17 | 女 |
//| 4 | 野原新之助 | 6 | 男 |
//| 5 | 东方不败 | 40 | 未知 |
//+----+-----------------+------+--------+
// 实现删除操作
// Step1. 编写 Sql 语句
String sql = "delete from t_emp where id=?";
// Step2. 调用 jdbcTemplate 的方法,执行 Sql 删除语句
int rows = jdbcTemplate.update(sql, 5);
System.out.println(rows);
// 删除后:
//+----+-----------------+------+------+
//| id | name | age | sex |
//+----+-----------------+------+------+
//| 1 | 桐谷和人 | 18 | 男 |
//| 2 | 小木曾雪菜 | 19 | 女 |
//| 3 | 冬马和纱 | 17 | 女 |
//| 4 | 野原新之助 | 6 | 男 |
//+----+-----------------+------+------+
在使用 JdbcTemplate 进行数据库的增、删、改时,使用的都是其中的
update
方法。
查询操作
先准备好用于接收查询结果的对象类:
public class Emp {
private Integer id;
private String name;
private Integer age;
private String sex;
// toString() ...
// setter and getter ...
手动查询
@Test
public void selectTest() {
// 实现查询操作,为了性能和可读性,在实际开发中,最好不要使用 select * from... 的形式
String sql = "select id, name, age, sex from `t_emp` where id=?";
// queryForObject 包含三部分参数,
// 1. sql 语句
// 2. RowMapper 转换接口的实现类,用于实现查询行到对象的转换(不常用的转换可以用匿名类实现,常用的可以保留实现类)
// 3. Object... 用来替换?部分的参数
// 返回值和 RowMapper 的泛型有关
Emp emp = jdbcTemplate.queryForObject(sql, new RowMapper<Emp>() {
@Override
public Emp mapRow(ResultSet rs, int rowNum) throws SQLException {
Emp ans = new Emp();
ans.setId(rs.getInt("id"));
ans.setName(rs.getString("name"));
ans.setAge(rs.getInt("age"));
ans.setSex(rs.getString("sex"));
return ans;
}
}, 1);
System.out.println(emp); // Emp{id=1, name='桐谷和人', age=18, sex='男'}
}
自动查询
使用 spring 官方提供的 BeanPropertyRowMapper 实现自动查询,它是 RowMapper 的实现类,可以直接将数据库表的属性名和对象的属性名对应起来进行赋值。
String sql = "select id, name, age, sex from `t_emp` where id=?";
// 使用 BeanPropertyRowMapper 时,需要传入目标对象的对象类型
Emp emp = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Emp.class), 1);
System.out.println(emp); // Emp{id=1, name='桐谷和人', age=18, sex='男'}
通过 jdbcTemplate.query
可以获得符合条件的多个对象,参数和使用方式类似于 queryForObject
。
String sql = "select id, name, age, sex from `t_emp` where sex=?";
// 使用 query 方法可以得到查询结果的列表
List<Emp> empList = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Emp.class), "女");
System.out.println(empList);// [Emp{id=2, name='小木曾雪菜', age=19, sex='女'}, Emp{id=3, name='冬马和纱', age=17, sex='女'}]
另外,queryForObject 的另一种使用方式是在中间参数直接填入一个类型,表明返回单个该类型的值:
String sql = "select count(*) from `t_emp` where sex=?"; Integer count = jdbcTemplate.queryForObject(sql, Integer.class, "女"); System.out.println(count); // 2
声明式事务
事务的基本概念
事务(Transaction)就是一组数据库操作序列,这些操作是一个不可分割的工作单位,要么全部成功,要么全部失败。
事务的特性:ACID,这里不再赘述。
编程式事务
与声明式事务对应的是编程式事务。它的核心就是通过直接编码实现事务。典型的代码如下:
Connection conn = ...;
try {
// 开启事务:关闭事务的自动提交
conn.setAutoCommit(false);
// 核心操作
// 提交事务
conn.commit();
}catch(Exception e){
// 回滚事务
conn.rollBack();
}finally{
// 释放数据库连接
conn.close();
}
编程式事务的缺陷在于:
- 细节没有被屏蔽,细节较多,编码繁琐
- 代码复用性不高,没有实现有效的抽取
声明式事务
事务控制的代码有规律可循,代码的结构基本是确定的,所以框架就可以将固定模式的代码抽取出来,进行相关的封装。
基于注解的声明式事务
环境准备和说明
-
数据库准备(图书表、用户表)
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);
-
子环境搭建、自动扫描配置、JdbcTemplate 配置
-
创建组件
Controller 层:
package site.penghao.spring6.jdbc.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import site.penghao.spring6.jdbc.service.BookService; /** * @author hope * @date 2023/3/31 - 14:52 */ @Controller public class BookController { @Autowired private BookService bookService; public void buyBook(Integer bookId, Integer userId){ bookService.buyBook(bookId, userId); } }
Service 层:
package site.penghao.spring6.jdbc.service; /** * @author hope * @date 2023/3/31 - 14:53 */ public interface BookService { void buyBook(Integer bookId, Integer userId); }
package site.penghao.spring6.jdbc.service; import site.penghao.spring6.jdbc.dao.BookDao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; /** * @author hope * @date 2023/3/31 - 14:53 */ @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); } }
Dao 层:
package site.penghao.spring6.jdbc.dao; /** * @author hope * @date 2023/3/31 - 14:54 */ public interface BookDao { Integer getPriceByBookId(Integer bookId); void updateStock(Integer bookId); void updateBalance(Integer userId, Integer price); }
package site.penghao.spring6.jdbc.dao; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.jdbc.core.JdbcTemplate; import org.springframework.stereotype.Repository; /** * @author hope * @date 2023/3/31 - 14:54 */ @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); } }
不添加事务
上面的环境配置可以看出,用户 admin 的余额是 50 元,而《斗破苍穹》的价格是 80 元,如果按照上面的逻辑执行买书:
- 获得图书价格——正常
- 更新库存——正常
- 更新余额——出错,原因是用户的 balance 字段设置为 unsigned,50 - 80 的结果为负,会抛出 SQLException
我们测试看看:
@SpringJUnitConfig(locations = "classpath:bean.xml")
public class TransactionTest {
@Autowired
BookController bookController;
@Test
public void transactionTest() {
bookController.buyBook(1, 1); // org.springframework.dao.DataIntegrityViolationException ...
}
}
再看看数据库的情况,用户的余额不变,书籍的库存却被更新了:
mysql> select * from t_user;
+---------+----------+---------+
| user_id | username | balance |
+---------+----------+---------+
| 1 | admin | 50 |
+---------+----------+---------+
1 row in set (0.00 sec)
mysql> select *from t_book;
+---------+--------------+-------+-------+
| book_id | book_name | price | stock |
+---------+--------------+-------+-------+
| 1 | 斗破苍穹 | 80 | 99 |
| 2 | 斗罗大陆 | 50 | 100 |
+---------+--------------+-------+-------+
2 rows in set (0.00 sec)
这里的问题在于,前两步已经执行了,balance 却无法更新,这就导致数据库的信息出错。我们期望的是这些步骤要么全成功、要么全失败,所以需要给这些步骤添加事务。
加入事务
-
添加事务配置
在 spring 的配置文件中添加下面的配置(需要引入 tx 命名空间):
<!-- 配置事务的事件源,使用我们前面创建的 druidDataSource --> <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="druidDataSource"/> </bean> <!-- 开启事务的注解驱动:通过注解 @Transactional 所标识的方法或标识的类中所有的方法,都会被事务管理器管理事务 --> <!-- transaction-manager 属性的默认值是 transactionManager, 如果事务管理器 bean 的 id 正好就是这个默认值,则可以省略这个属性 --> <tx:annotation-driven transaction-manager="transactionManager" />
-
添加事务注解
一般来说,service 层代表业务层,一个方法就表示完成一个功能,因此处理事务一般在 service 层添加。在我们的例子中,只需要在 BookServiceImpl 的 buybook() 添加注解
@Transactional
即可。@Service public class BookServiceImpl implements BookService { ... @Transactional @Override public void buyBook(Integer bookId, Integer userId) { ...
@Transactional
注解也可以添加到类的前面,相当于对该类的所有方法添加@Transactional
-
执行测试
可以看到,结果是报错了,book 的库存并没有再次减少:
mysql> select * from t_user; +---------+----------+---------+ | user_id | username | balance | +---------+----------+---------+ | 1 | admin | 50 | +---------+----------+---------+ 1 row in set (0.00 sec) mysql> select *from t_book; +---------+--------------+-------+-------+ | book_id | book_name | price | stock | +---------+--------------+-------+-------+ | 1 | 斗破苍穹 | 80 | 99 | | 2 | 斗罗大陆 | 50 | 100 | +---------+--------------+-------+-------+ 2 rows in set (0.00 sec)
@Transactional
的属性
readOnly
如果设置为 true,则只能查询,不允许修改添加删除。默认值 false。
timeout
设置超时时长,单位为秒。例如设置为 timeout = 3,则在 3s 内没有完成,就抛出异常并回滚;默认值为 -1,不使用超时。
回滚策略
设置哪些情况回滚,哪些情况不回滚。
-
rollbackFor 属性:需要设置一个 Class 类型的对象
-
rollbackForClassName 属性:需要设置一个字符串类型的全类名
-
noRollbackFor 属性:需要设置一个 Class 类型的对象
-
noRollbackForClassName 属性:需要设置一个字符串类型的全类名
例如,设置出现 ArithmeticException 异常时不进行回滚。
@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);
}
测试结果:
@SpringJUnitConfig(locations = "classpath:bean.xml")
public class TransactionTest {
@Autowired
BookController bookController;
@Test
public void transactionTest() {
bookController.buyBook(2, 1);
}
}
mysql> select *from t_book;
+---------+--------------+-------+-------+
| book_id | book_name | price | stock |
+---------+--------------+-------+-------+
| 1 | 斗破苍穹 | 80 | 99 |
| 2 | 斗罗大陆 | 50 | 99 |
+---------+--------------+-------+-------+
2 rows in set (0.00 sec)
mysql> select * from t_user;
+---------+----------+---------+
| user_id | username | balance |
+---------+----------+---------+
| 1 | admin | 0 |
+---------+----------+---------+
1 row in set (0.00 sec)
isolation
可以设置数据库的隔离级别。有下面的隔离级别可选:
-
读未提交:READ UNCOMMITTED
允许 Transaction01 读取 Transaction02 未提交的修改。
-
读已提交:READ COMMITTED(Oracle 默认)
要求 Transaction01 只能读取 Transaction02 已提交的修改。
-
可重复读:REPEATABLE READ(Mysql 默认)
确保 Transaction01 可以多次从一个字段中读取到相同的值,即 Transaction01 执行期间禁止其它事务对这个字段进行更新。
-
串行化:SERIALIZABLE
确保 Transaction01 可以多次从一个表中读取到相同的行,在 Transaction01 执行期间,禁止其它事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下。
propagation
可以设置事务的传播行为。
在 service 类中有 a() 方法和 b() 方法,a() 方法上有事务,b() 方法上也有事务,当 a() 方法执行过程中调用了 b() 方法,事务是如何传递的?合并到一个事务里?还是开启一个新的事务?这就是事务传播行为。换言之,就是说如果一个事务方法调用了多个事务方法,被调用的某个方法执行失败了,是回退整个事务方法,还是仅回退失败的方法呢?
一共有七种传播行为:
- REQUIRED:支持当前事务,如果不存在就新建一个(默认)【没有就新建,有就加入】
- SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行**【有就加入,没有就不管了】**
- MANDATORY:必须运行在一个事务中,如果当前没有事务正在发生,将抛出一个异常**【有就加入,没有就抛异常】**
- REQUIRES_NEW:开启一个新的事务,如果一个事务已经存在,则将这个存在的事务挂起**【不管有没有,直接开启一个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务被挂起】**
- NOT_SUPPORTED:以非事务方式运行,如果有事务存在,挂起当前事务**【不支持事务,存在就挂起】**
- NEVER:以非事务方式运行,如果有事务存在,抛出异常**【不支持事务,存在就抛异常】**
- NESTED:如果当前正有一个事务在进行中,则该方法应当运行在一个嵌套式事务中。被嵌套的事务可以独立于外层事务进行提交或回滚。如果外层事务不存在,行为就像 REQUIRED 一样。【有事务的话,就在这个事务里再嵌套一个完全独立的事务,嵌套的事务可以独立的提交和回滚。没有事务就和 REQUIRED 一样。】
最常用的两个:REQUIRED(默认)、REQUIRES_NEW。
全注解的事务管理
和之前的思路一致,使用配置类替换 bean.xml
实现全注解。
/**
* @author hope
* @date 2023/3/31 - 16:17
*/
@Configuration
@ComponentScan("site.penghao.spring6.jdbc")
@EnableTransactionManagement // 开启事务管理
@PropertySource({"classpath:jdbc.properties"}) // 引入数据源
public class SpringConfig {
private final Environment env;
@Autowired // 注入环境,环境中包含数据源信息
public SpringConfig(Environment env) {
this.env = env;
}
@Bean // 声明 dataSource
public DataSource getDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername(env.getProperty("jdbc.user"));
dataSource.setPassword(env.getProperty("jdbc.password"));
dataSource.setUrl(env.getProperty("jdbc.url"));
dataSource.setDriverClassName(env.getProperty("jdbc.driver"));
return dataSource;
}
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
@Bean(name = "transactionManager") // 设置 bean id
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
DataSourceTransactionManager dataSourceTransactionManager = new DataSourceTransactionManager();
dataSourceTransactionManager.setDataSource(dataSource);
return dataSourceTransactionManager;
}
}
基于 XML 的声明式事务
在 XML 文件中,进行如下配置也可以实现声明式事务:
<!-- tx:advice 标签:配置事务通知 -->
<!-- id 属性:给事务通知标签设置唯一标识,便于引用 -->
<!-- transaction-manager 属性:关联事务管理器 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<tx:attributes>
<!-- tx:method 标签:配置具体的事务方法 -->
<!-- name 属性:指定方法名,可以使用星号代表多个字符 -->
<tx:method name="get*" read-only="true"/>
<tx:method name="query*" read-only="true"/>
<tx:method name="find*" read-only="true"/>
<!-- read-only 属性:设置只读属性 -->
<!-- rollback-for 属性:设置回滚的异常 -->
<!-- no-rollback-for 属性:设置不回滚的异常 -->
<!-- isolation 属性:设置事务的隔离级别 -->
<!-- timeout 属性:设置事务的超时属性 -->
<!-- propagation 属性:设置事务的传播行为 -->
<tx:method name="save*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
<tx:method name="update*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
<tx:method name="delete*" read-only="false" rollback-for="java.lang.Exception" propagation="REQUIRES_NEW"/>
</tx:attributes>
</tx:advice>
<aop:config>
<!-- 配置事务通知和切入点表达式的绑定 -->
<aop:advisor advice-ref="txAdvice" pointcut="execution(* site.penghao.spring6.jdbc.service.*.*(..))"></aop:advisor>
</aop:config>
注意:使用 XML 配置声明式事务时,需要引入依赖 AspectJ。
<dependency> <groupId>org.springframework</groupId> <artifactId>spring-aspects</artifactId> <version>6.0.2</version> </dependency>
小结
- spring 提供了导入外部属性文件的方法,可以将数据库的配置信息储存在一个配置文件中,需要时导入,这样就实现了数据库配置和工程文件的解耦。
- 使用 JdbcTemplate 能简化数据库的操作,对于增加、删除、修改操作统一使用 update() 方法,对于查询操作可以使用 queryForObject() 查询一个,或者使用 query() 查询多个。
- RowMapper 是 JdbcTemplate 查询可选的参数,它是一个接口,需要实现从数据库查询行到对象的转化逻辑。
- 声明式事务将事务控制的代码结构抽取出来,使得事务开发的代码更为简洁。使用声明式事务的基本步骤是:配置 transactionManager、开启事务的注解驱动 tx:annotation-driven、添加事务注解 @Transactional。
- 事务注解 @Transactional 可以设置多种属性,包括:设置只读、超时、回滚策略、隔离模式、传播行为等。
- 使用配置类也可以实现事务管理。