笔记来源:尚硅谷Spring框架视频教程(spring5源码级讲解)
JdbcTemplate与声明式事务
1、JdbcTemplate
1.1、概述
前面我们已经学习了 Spring 中的Core Container 核心部分和AOP 、Aspects 等面向切面编程部分,接下来就是Data Access/Integration 即数据访问和集成部分
Spring 既可以单独使用,也可以集成其他框架,如Hibernate 、MyBatis 等。除此之外,其中对于JDBC 也做了封装,即本章节的JdbcTemplate ,用它可以比较方便地对数据库进行增删改查等操作
总结一下:
JdbcTemplate 就是 Spring 框架对JDBC 技术进行的二次封装模板,能够简化对数据库的操作
1.2、准备工作
步骤预览
- 1)引入相关
jar 包 - 2)Spring 配置文件配置
Druid 连接池信息 - 3)配置
JdbcTemplate 对象,注入dataSource - 4)创建 Service 和 Dao 类,在 Dao 类中注入
JdbcTemplate 对象
详细操作
- 1)引入相关
jar 包(或依赖)
druid mysql-connector-java spring-jdbc spring-orm spring-tx
- 2)Spring 配置文件配置
Druid 连接池信息
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${mysql.driverClassName}"/>
<property name="url" value="${mysql.url}"/>
<property name="username" value="${mysql.username}"/>
<property name="password" value="${mysql.password}"/>
</bean>
沿用之前章节的Jdbc.properties 配置信息,但稍作修改
mysql.driverClassName=com.mysql.jdbc.Driver
mysql.url=jdbc:mysql:///book_db
mysql.username=root
mysql.password=root
- 3)配置
JdbcTemplate 对象,注入dataSource
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
为何使用属性注入?
JdbcTemplate 虽然含有DataSource 的有参构造,但其调用了setDataSource() 方法
这个方法是在其父类中定义了的
- 4)创建 Service 和 Dao 类,在 Dao 类中注入
JdbcTemplate 对象
Dao 类
public interface BookDao {
}
@Repository
public class BookDaoImpl implements BookDao {
@Autowired
private JdbcTemplate jdbcTemplate;
}
Service 类
@Service
public class BookService {
@Autowired
private BookDao bookDao;
}
别忘了开启注解扫描
<context:component-scan base-package="com.vectorx.spring5.s15_jdbctemplate"/>
配置文件整体结构
<?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:component-scan base-package="com.vectorx.spring5.s15_jdbctemplate"/>
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${mysql.driverClassName}"/>
<property name="url" value="${mysql.url}"/>
<property name="username" value="${mysql.username}"/>
<property name="password" value="${mysql.password}"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
1.3、添加操作
步骤预览
- 1)创建数据库中
t_book 表对应的实体对象 - 2)编写 Service 和 Dao 代码,增加_添加图书_的功能逻辑
- 3)代码测试
详细操作
public class Book {
private String bid;
private String bname;
private String bstatus;
public String getBid() {
return bid;
}
public void setBid(String bid) {
this.bid = bid;
}
public String getBname() {
return bname;
}
public void setBname(String bname) {
this.bname = bname;
}
public String getBstatus() {
return bstatus;
}
public void setBstatus(String bstatus) {
this.bstatus = bstatus;
}
}
- 2)编写 Service 和 Dao 代码,增加_添加图书_的功能逻辑
Service 类:添加addBook() 方法
@Service
public class BookService {
@Autowired
private BookDao bookDao;
public int addBook(Book book) {
return bookDao.add(book);
}
}
Dao 类:通过操作JdbcTemplate 对象的update() 方法可实现插入,其中两个参数分别是
- 第一个参数
sql :编写插入数据对应的sql 语句,可使用通配符? 做占位符 - 第二个参数
args :可变参数列表,设置占位符对应的参数值
public interface BookDao {
int add(Book book);
}
@Repository
public class BookDaoImpl implements BookDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public int add(Book book) {
String sql = "insert into t_book(bid,bname,bstatus) values(?,?,?)";
Object[] args = {book.getBid(), book.getBname(), book.getBstatus()};
return jdbcTemplate.update(sql, args);
}
}
ApplicationContext context = new ClassPathXmlApplicationContext("bean13.xml");
BookService bookService = context.getBean("bookService", BookService.class);
Book book = new Book();
book.setBid("1");
book.setBname("Spring JdbcTemplate");
book.setBstatus("1");
int result = bookService.addBook(book);
System.out.println(result);
测试结果
Loading class `com.mysql.jdbc.Driver'. This is deprecated. The new driver class is `com.mysql.cj.jdbc.Driver'. The driver is automatically registered via the SPI and manual loading of the driver class is generally unnecessary.
三月 06, 2022 10:25:49 下午 com.alibaba.druid.pool.DruidDataSource info
信息: {dataSource-1} inited
1
刷新数据库中t_book 表数据,核验是否插入成功
可以看到,表中成功新增了一条数据
1.4、修改和删除
修改、删除操作和添加操作代码逻辑基本一致
BookService 类:添加updateBook() 和deleteBook() 方法
public int updateBook(Book book) {
return bookDao.update(book);
}
public int deleteBook(String id) {
return bookDao.delete(id);
}
BookDao 类:添加update() 和delete() 方法
int update(Book book);
int delete(String id);
BookDaoImpl 类:实现update() 和delete() 方法
@Override
public int update(Book book) {
String sql = "update t_book set bname=?,bstatus=? where bid=?";
Object[] args = {book.getBname(), book.getBstatus(), book.getBid()};
return jdbcTemplate.update(sql, args);
}
@Override
public int delete(String id) {
String sql = "delete from t_book where bid=? ";
return jdbcTemplate.update(sql, id);
}
测试修改
Book book = new Book();
book.setBid("1");
book.setBname("JdbcTemplate");
book.setBstatus("update");
int result2 = bookService.updateBook(book);
System.out.println(result2);
测试结果
测试删除
int result3 = bookService.deleteBook("1");
System.out.println(result3);
测试结果
1.5、查询操作
这里演示三种查询操作:
- 1)查询返回某个值
- 2)查询返回对象
- 3)查询返回集合
为了演示效果,需要先在数据库的t_book 表中添加两条数据
接着我们先将代码完成,最后再作进一步的分析说明
代码实现
BookService 类:添加findCount() 、findById() 和findAll() 方法
public int findCount() {
return bookDao.selectCount();
}
public Book findById(String id) {
return bookDao.selectById(id);
}
public List<Book> findAll() {
return bookDao.selectAll();
}
BookDao 类:添加selectCount() 、selectById() 和selectAll() 方法
int selectCount();
Book selectById(String id);
List<Book> selectAll();
BookDaoImpl 类:实现selectCount() 、selectById() 和selectAll() 方法
@Override
public int selectCount() {
String sql = "select count(0) from t_book";
return jdbcTemplate.queryForObject(sql, Integer.class);
}
@Override
public Book selectById(String id) {
String sql = "select * from t_book where bid=?";
return jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Book.class), id);
}
@Override
public List<Book> selectAll() {
String sql = "select * from t_book where 1=1";
return jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Book.class));
}
测试代码
int count = bookService.findCount();
System.out.println(count);
Book book = bookService.findById("1");
System.out.println(book);
List<Book> bookList = bookService.findAll();
System.out.println(bookList);
测试结果
2
Book{bid='1', bname='Spring', bstatus='add'}
[Book{bid='1', bname='Spring', bstatus='add'}, Book{bid='2', bname='SpringMVC', bstatus='add'}]
代码分析
上述代码逻辑中使用到了queryForObject() 和query() 方法
jdbcTemplate.queryForObject(sql, Integer.class);
jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Book.class), id);
jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(Book.class));
分别对应JdbcTemplate 中的三个方法
public <T> T queryForObject(String sql, Class<T> requiredType);
public <T> T queryForObject(String sql, RowMapper<T> rowMapper, Object... args);
public <T> List<T> query(String sql, RowMapper<T> rowMapper);
其中,有两个参数值得关注,一个是Class<T> requiredType ,另一个是RowMapper<T> rowMapper
Class<T> requiredType :返回值的Class 类型RowMapper<T> rowMapper :是一个接口,返回不同类型数据,可以使用其实现类进行数据的封装。其实现类有很多,因为我们需要返回一个数据库实体对象,所以可以选择使用BeanPropertyRowMapper
另外,queryForObject(String sql, RowMapper<T> rowMapper, Object... args) 和query(String sql, RowMapper<T> rowMapper) 的
区别在于
queryForObject 返回一个对象query 返回一个集合
1.6、批量操作
JdbcTemplate 中提供了batchUpdate() 可供我们进行批量操作,如:批量添加、批量修改、批量删除等,代码实现上大同小异,我们对代码进行快速实现
代码实现
BookService 类:添加batchAddBook() 、batchUpdateBook() 和batchDelBook() 方法
public void batchAddBook(List<Object[]> bookList) {
bookDao.batchAdd(bookList);
}
public void batchUpdateBook(List<Object[]> bookList) {
bookDao.batchUpdate(bookList);
}
public void batchDelBook(List<Object[]> bookList) {
bookDao.batchDel(bookList);
}
BookDao 类:添加batchAdd() 、batchUpdate() 和batchDel() 方法
void batchAdd(List<Object[]> bookList);
void batchUpdate(List<Object[]> bookList);
void batchDel(List<Object[]> bookList);
BookDaoImpl 类:实现batchAdd() 、batchUpdate() 和batchDel() 方法
@Override
public void batchAdd(List<Object[]> bookList) {
String sql = "insert into t_book(bid,bname,bstatus) values(?,?,?)";
extractBatch(sql, bookList);
}
@Override
public void batchUpdate(List<Object[]> bookList) {
String sql = "update t_book set bname=?,bstatus=? where bid=?";
extractBatch(sql, bookList);
}
@Override
public void batchDel(List<Object[]> bookList) {
String sql = "delete from t_book where bid=? ";
extractBatch(sql, bookList);
}
private void extractBatch(String sql, List<Object[]> bookList,) {
int[] ints = jdbcTemplate.batchUpdate(sql, bookList);
System.out.println(ints);
}
代码测试
测试批量添加
List<Object[]> bookList = new ArrayList<>();
Object[] book1 = {"3", "Java", "batchAdd"};
Object[] book2 = {"4", "Python", "batchAdd"};
Object[] book3 = {"5", "C#", "batchAdd"};
bookList.add(book1);
bookList.add(book2);
bookList.add(book3);
bookService.batchAddBook(bookList);
测试结果
测试批量修改
List<Object[]> bookList = new ArrayList<>();
Object[] book1 = {"Java++", "batchUpdate", "3"};
Object[] book2 = {"Python++", "batchUpdate", "4"};
Object[] book3 = {"C#++", "batchUpdate", "5"};
bookList.add(book1);
bookList.add(book2);
bookList.add(book3);
bookService.batchUpdateBook(bookList);
测试结果
测试批量删除
List<Object[]> bookList = new ArrayList<>();
Object[] book1 = {"3"};
Object[] book2 = {"4"};
bookList.add(book1);
bookList.add(book2);
bookService.batchDelBook(bookList);
测试结果
可以看出,上述测试都完全符合我们的预期
小结
简单总结下JdbcTemplate 操作数据库的各个方法
- 添加、修改、删除操作:
update() 方法 - 查询操作:
queryForObject() 和query() 方法,关注两个参数:
Class<T> requiredType :返回值的Class 类型RowMapper<T> rowMapper :接口,具体实现类BeanPropertyRowMapper ,封装对象实体 - 批量操作:
batchUpdate() 方法
2、事务
2.1、事务概念
- 1)事务是数据库操作的最基本单元,是逻辑上的一组操作。这一组操作,要么都成功,要么都失败(只要有一个操作失败,所有操作都失败)
- 2)典型场景:银行转账。Lucy 转账 100 元给 Mary,Lucy 少 100,Mary 多 100。转账过程中若出现任何问题,双方都不会多钱或少钱,转账就不会成功
2.2、事务四个特性(ACID)
- 原子性(Atomicity):一个事务中的所有操作,要么都成功,要么都失败,整个过程不可分割
- 一致性(Consistency):事务操作之前和操作之后,总量保持不变
- 隔离性(Isolation):多事务操作时,相互之间不会产生影响
- 持久性(Durability):事务最终提交后,数据库表中数据才会真正发生变化
2.3、搭建事务操作环境
我们知道 JavaEE 中的三层架构分为:表示层(web 层)、业务逻辑层(service 层)、数据访问层(dao 层)
web 层:与客户端进行交互service 层:处理业务逻辑dao 层:与数据库进行交互
因此,我们搭建操作环境也按照典型的三层架构来实现,不过目前现阶段我们只关注Service 和Dao 两层
我们以银行转账为例,因为整个转账操作包括两个操作:出账的操作和入账的操作
过程概览
- 1)创建数据库表结构,添加几条记录
- 2)创建
Service 和Dao 类,完成对象创建和关系注入 - 3)
Dao 中创建两个方法:出账的方法、入账的方法;Service 中创建转账的方法
过程详解
**1)**创建数据库表结构,添加几条记录
create table t_account
(
id varchar(20) not null,
username varchar(50) null,
amount int null,
constraint transfer_record_pk
primary key (id)
);
INSERT INTO book_db.t_account (id, username, amount) VALUES ('1', 'Lucy', 1000);
INSERT INTO book_db.t_account (id, username, amount) VALUES ('2', 'Mary', 1000);
添加完成效果
**2)**创建Service 和Dao 类,完成对象创建和关系注入
Service 中注入Dao ,Dao 中注入JdbcTemplate ,JdbcTemplate 中注入DataSource
Service 和Dao 类
public interface TransferRecordDao {
}
@Repository
public class TransferRecordDaoImpl implements TransferRecordDao {
@Autowired
private JdbcTemplate jdbcTemplate;
}
@Service
public class TransferRecordService {
@Autowired
private TransferRecordDao transferRecordDao;
}
Spring 配置文件
<?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:component-scan base-package="com.vectorx.spring5.s16_transaction"/>
<context:property-placeholder location="classpath:jdbc.properties"/>
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${mysql.driverClassName}"/>
<property name="url" value="${mysql.url}"/>
<property name="username" value="${mysql.username}"/>
<property name="password" value="${mysql.password}"/>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
3)Dao 中创建两个方法:出账的方法、入账的方法;Service 中创建转账的方法
Dao 负责数据库操作,所以需要创建两个方法:出账的方法、入账的方法
public interface TransferRecordDao {
void transferOut(int amount, String username);
void transferIn(int amount, String username);
}
@Repository
public class TransferRecordDaoImpl implements TransferRecordDao {
@Autowired
private JdbcTemplate jdbcTemplate;
@Override
public void transferOut(int amount, String username) {
String sql = "update t_account set amount=amount-? where username=?";
Object[] args = {amount, username};
jdbcTemplate.update(sql, args);
}
@Override
public void transferIn(int amount, String username) {
String sql = "update t_account set amount=amount+? where username=?";
Object[] args = {amount, username};
jdbcTemplate.update(sql, args);
}
}
Service 负责业务操作,所以需要创建一个方法,来调用Dao 中两个方法
@Service
public class TransferRecordService {
@Autowired
private TransferRecordDao transferRecordDao;
public void transferAccounts(int amount, String fromUser, String toUser) {
transferRecordDao.transferOut(amount, fromUser);
transferRecordDao.transferIn(amount, toUser);
}
}
测试代码
ApplicationContext context = new ClassPathXmlApplicationContext("bean14.xml");
TransferRecordService transferRecordService = context.getBean("transferRecordService", TransferRecordService.class);
transferRecordService.transferAccounts(100, "Lucy", "Mary");
测试结果
可以发现,转账如期完成了。但真的没有一点问题么?
2.4、引入事务场景
我们模拟下在转账中途发生网络异常,修改TransferRecordService 中转账方法
public void transferAccounts(int amount, String fromUser, String toUser) {
transferRecordDao.transferOut(amount, fromUser);
int i = 10 / 0;
transferRecordDao.transferIn(amount, toUser);
}
为了更清晰直观地看到数据的变化,我们还原数据表数据到最初状态
按照期望,转账应该失败,即双方账户不应该有任何变化。事实真的能够如我们所料么?
我们执行测试方法,如期抛出异常
|