笔记来历:尚硅谷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
<!--装备JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--特点注入dataSource-->
<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"/>
<!--装备dataSource-->
<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>
<!--装备JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--特点注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
</beans>
1.3、增加操作
进程预览
- 1)创立数据库中
t_book
表对应的实体目标 - 2)编写 Service 和 Dao 代码,增加增加图书的功用逻辑
- 3)代码测验
详细操作
- 1)创立数据库中
t_book
表对应的实体目标
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) {
//操作JdbcTemplate目标,运用update办法进行增加操作
String sql = "insert into t_book(bid,bname,bstatus) values(?,?,?)";
Object[] args = {book.getBid(), book.getBname(), book.getBstatus()};
return jdbcTemplate.update(sql, args);
}
}
- 3)代码测验
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"/>
<!--装备dataSource-->
<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>
<!--装备JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--特点注入dataSource-->
<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);
}
为了更明晰直观地看到数据的改动,咱们还原数据表数据到开始状况
依照希望,转账应该失利,即两边账户不该该有任何改动。实际真的能够如咱们所料么?
咱们履行测验办法,按期抛出反常
Exception in thread "main" java.lang.ArithmeticException: / by zero
at com.vectorx.spring5.s16_transaction.service.TransferRecordService.transferAccounts(TransferRecordService.java:15)
at com.vectorx.spring5.s16_transaction.TestTransfer.main(TestTransfer.java:11)
那数据表是否也按期改动呢?
咱们发现,Lucy
虽然成功转出了 100 元,但Mary
没有成功到账 100 元。从实际的视点来说,这个问题很严重!!!
从业务的视点来说,这个转账操作没有遵从业务的原子性、共同性,即没有做到“要么都成功,要么都失利”,也没有做到“操作前后的总量不变”
综上所述,咱们需求引进业务
2.5、业务根本操作
业务的根本操作进程如下
- Step1、敞开一个业务
- Step2、进行业务逻辑完结
- Step3、没有反常,则提交业务
- Step4、发生反常,则回滚业务
业务的一般完结如下
try {
// Step1、敞开一个业务
// Step2、进行业务逻辑完结
transferRecordDao.transferOut(amount, fromUser);
//模仿网络反常而导致操作中止
int i = 10 / 0;
transferRecordDao.transferIn(amount, toUser);
// Step3、没有反常,则提交业务
} catch (Exception e) {
// Step4、发生反常,则回滚业务
}
不过,在 Spring 结构中供给了更便利的办法完结业务。“欲知后事怎么,且听下回分解”
小结
本小结主要内容要害点
- 业务的根本概念:数据库操作的根本单元,逻辑上的一组操作,要么都成功,要么都失利
- 业务的四个根本特性:ACID,即原子性、共同性、阻隔性和持久性
3、声明式业务
3.1、Spring业务办理
业务一般增加到三层结构中的Service
层(业务逻辑层)
在 Spring 中进行业务办理操作有两种办法:编程式业务办理和声明式业务办理
- 编程式业务办理(不引荐):上述业务的一般完结便是典型的编程式业务办理完结。但这种办法虽然并不好,但仍然需求咱们有一定的了解,知道有这么一个进程即可。一般不引荐运用这种办法,主要原因如下
- 1)完结不便利
- 2)造成代码臃肿
- 3)保护起来麻烦
- 声明式业务办理(引荐运用):底层原理便是
AOP
,便是在不改动原代码基础上,扩展代码功用。有两种完结办法- 依据注解办法(引荐办法)
- 依据XML装备文件办法
3.2、Spring业务办理API
供给了一个接口,代表业务办理器,针对不同结构存在不同完结类
主要把握
-
PlatformTransactionManager
接口:即业务办理器,有多个不同的抽象类和详细完结类,可满足不同的结构 -
DataSourceTrasactionManager
完结类:JdbcTemplate
和MyBatis
结构运用到它 -
HibernateTransactionManager
完结类:Hibernate
结构运用到它
3.3、声明式业务(注解办法)
- 1)在 Spring 装备文件中装备业务办理器:装备
DataSourceTransactionManager
目标创立
<!--装备业务办理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
- 2)在 Spring 装备文件中敞开业务:引进
xmlns:tx
的称号空间,并装备<tx:annotation-driven>
标签以敞开业务注解
<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">
<!--其他装备信息略-->
<!--敞开业务注解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
- 3)在
Service
(或Service
的办法)上增加业务注解@Transactional
@Service
@Transactional
public class TransferRecordService {
//...
}
首要还原数据表信息至初始状况
测验代码后改写数据表
这一次数据没有发生改动,阐明业务回滚了,符合咱们预期
3.4、业务参数
在Service
类上面增加注解@Transactional
,在这个注解里边能够装备业务相关参数
主要介绍参数有
-
propagation
:业务传达行为 -
isolation
:业务阻隔等级 -
timeout
:超时时刻 -
readOnly
:是否只读 -
rollbackFor
:回滚 -
noRollbackFor
:不回滚
3.5、传达行为
- 业务传达行为:多业务办法直接进行调用,这个进程中业务是怎么进行办理的
- 业务办法:让数据表数据发生改动的操作
业务的传达行为能够有传达特点指定。Spring 结构中定义了 7 种类传达行为:
传达特点 | 描述 |
---|---|
REQUIRED |
假如有业务在运转,当时办法就在此业务内运转;不然,就发动一个新的业务,并在自己的业务内运转 |
REQUIRED_NEW |
当时办法有必要发动新业务,并在它自己的业务内运转;假如有业务正在运转,应该将它挂起 |
SUPPORTS |
假如有业务在运转,当时办法就在此业务内运转;不然,它能够不运转在业务中 |
NOT_SOPPORTED |
当时办法不该该运转在业务内部,假如有运转的业务,就将它挂起 |
MANDATORY |
当时办法有必要运转在业务内部,假如没有正在运转的业务,就抛出反常 |
NEVER |
当时办法不该该运转在业务中,假如有运转的业务,就抛出反常 |
NESTED |
假如有业务在运转,当时办法就应该在此业务的嵌套业务内运转;不然,就发动一个新的业务,并在它自己的业务内运转 |
举个例子:定义两个办法add()
和update()
@Transactional
public void add(){
// 调用update办法
update();
}
public void update(){
// do something
}
则依照不同的传达特点,能够有以下解说
-
REQUIRED
- 假如
add()
办法自身有业务,调用update()
办法之后,update()
运用当时add()
办法里边业务; - 假如
add()
办法自身没有业务,调用update()
办法之后,创立新的业务
- 假如
-
REQUIRED_NEW
- 运用
add()
办法调用update()
办法,无论add()
是否有业务,都创立新的业务
- 运用
代码完结
@Service
@Transactional(propagation = Propagation.REQUIRED)
public class TransferRecordService {
//...
}
等价于
@Service
@Transactional
public class TransferRecordService {
//...
}
即默许不写,其业务传达行为便是运用的REQUIRED
3.6、阻隔等级
在业务的四个特性中,阻隔性(isolation
)指的是多业务之间互不影响。不考虑阻隔性,在并发时会发生一系列问题
有三个典型的“读”的问题:脏读、不可重复读、虚(幻)读
- 脏读:一个未提交业务读取到了另一个未提交业务修正的数据
- 不可重复读:一个未提交业务读取到了另一个已提交业务修正的数据(不能算问题,只是算现象)
- 虚(幻)读:一个未提交业务读取到了另一个已提交业务增加的数据
经过设置阻隔等级,能够处理上述“读”的问题
业务阻隔等级 | 脏读 | 不可重复读 | 幻读 |
---|---|---|---|
READ UNCOMMITTED (读未提交) |
√ | √ | √ |
READ COMMITTED (读已提交) |
√ | √ | |
REPEATABLE READ (可重复读) |
√ | ||
SERIALIZABLE (串行化) |
代码完结
@Service
@Transactional(isolation = Isolation.REPEATABLE_READ)
public class TransferRecordService {
//...
}
小课堂:MySQL 中默许业务阻隔等级为REPEATABLE READ
(可重复读)
3.7、其他参数
-
timeout
:设置业务超时时刻。业务需求在一定的时刻内进行提交,若设定时刻内业务未完结提交,则对业务进行回滚。默许值为-1
,设置时刻以秒为单位
@Service
@Transactional(timeout = 5)
public class TransferRecordService {
@Autowired
private TransferRecordDao transferRecordDao;
public void transferAccounts(int amount, String fromUser, String toUser) {
transferRecordDao.transferOut(amount, fromUser);
//模仿处理超时
try {
Thread.sleep(6000);
} catch (InterruptedException e) {
e.printStackTrace();
}
transferRecordDao.transferIn(amount, toUser);
}
}
设置超时时刻后,履行测验代码,则日志信息会抛出TransactionTimedOutException
即业务超时反常
Exception in thread "main" org.springframework.transaction.TransactionTimedOutException: Transaction timed out: deadline was Wed Mar 09 21:30:33 CST 2022
at org.springframework.transaction.support.ResourceHolderSupport.checkTransactionTimeout(ResourceHolderSupport.java:155)
at org.springframework.transaction.support.ResourceHolderSupport.getTimeToLiveInMillis(ResourceHolderSupport.java:144)
at org.springframework.transaction.support.ResourceHolderSupport.getTimeToLiveInSeconds(ResourceHolderSupport.java:128)
at org.springframework.jdbc.datasource.DataSourceUtils.applyTimeout(DataSourceUtils.java:341)
...
-
readOnly
:是否只读。- 默许值为
false
,表明读写操作都允许,能够进行增、删、改、查等操作; - 可设置为
true
,表明只允许读操作,即只能进行查询操作
- 默许值为
@Service
@Transactional(readOnly = true)
public class TransferRecordService {
//...
}
设置只读后,履行测验代码,则日志信息会抛出TransientDataAccessResourceException
即瞬态数据拜访资源反常,一起还会抛出SQLException
,并指出“衔接是只读的,查询导致数据修正是不允许的”
Exception in thread "main" org.springframework.dao.TransientDataAccessResourceException: PreparedStatementCallback; SQL [update t_account set amount=amount-? where username=?]; Connection is read-only. Queries leading to data modification are not allowed; nested exception is java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
...
Caused by: java.sql.SQLException: Connection is read-only. Queries leading to data modification are not allowed
...
-
rollbackFor
:设置呈现哪些反常才进行回滚
@Service
@Transactional(rollbackFor = ArithmeticException.class)
public class TransferRecordService {
@Autowired
private TransferRecordDao transferRecordDao;
public void transferAccounts(int amount, String fromUser, String toUser) {
transferRecordDao.transferOut(amount, fromUser);
//模仿网络反常而导致操作中止
int i = 10 / 0;
transferRecordDao.transferIn(amount, toUser);
}
}
上述代码表明,只要在抛出的反常为ArithmeticException
时,才会进行业务的回滚操作
此刻运转测验代码,后台会抛出ArithmeticException
反常,因而会进行回滚,转账进程不会成功
此刻数据库中的数据,就不会有任何改动
-
noRollbackFor
:设置呈现哪些反常不进行回滚
@Service
@Transactional(noRollbackFor = ArithmeticException.class)
public class TransferRecordService {
@Autowired
private TransferRecordDao transferRecordDao;
public void transferAccounts(int amount, String fromUser, String toUser) {
transferRecordDao.transferOut(amount, fromUser);
//模仿网络反常而导致操作中止
int i = 10 / 0;
transferRecordDao.transferIn(amount, toUser);
}
}
由于设置了noRollbackFor = ArithmeticException.class
,即表明抛出ArithmeticException
反常时不会进行回滚
此刻运转测验代码,后台会抛出ArithmeticException
反常,但不会进行回滚,转账业务中只要出账操作会成功
Exception in thread "main" java.lang.ArithmeticException: / by zero
此刻数据库中的数据,就会是下面状况(显然,这并不是咱们想要的)
3.8、声明式业务(XML办法)
- Step1、装备业务办理器
<!--装备业务办理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
- Step2、装备业务告诉
<!--1、装备业务告诉-->
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="transferAccounts" propagation="REQUIRED" isolation="REPEATABLE_READ" read-only="false"
timeout="5" rollback-for="java.lang.ArithmeticException"/>
</tx:attributes>
</tx:advice>
- Step3、装备切入点和切面
<!--2、装备切入点和切面-->
<aop:config>
<aop:pointcut id="p"
expression="execution(* com.vectorx.spring5.s17_transaction_xml.service.TransferRecordService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="p"></aop:advisor>
</aop:config>
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"
xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:aop="http://www.springframework.org/schema/aop"
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
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
<!--装备dataSource-->
<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>
<!--装备JdbcTemplate-->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<!--特点注入dataSource-->
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--装备业务办理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource"></property>
</bean>
<!--装备Dao创立和特点注入-->
<bean id="transferRecordDao" class="com.vectorx.spring5.s17_transaction_xml.dao.TransferRecordDaoImpl">
<property name="jdbcTemplate" ref="jdbcTemplate"></property>
</bean>
<!--装备Service创立和特点注入-->
<bean id="transferRecordService" class="com.vectorx.spring5.s17_transaction_xml.service.TransferRecordService">
<property name="transferRecordDao" ref="transferRecordDao"></property>
</bean>
<!--1、装备业务告诉-->
<tx:advice id="txAdvice">
<tx:attributes>
<tx:method name="transferAccounts" propagation="REQUIRED" isolation="REPEATABLE_READ" read-only="false"
timeout="5" rollback-for="java.lang.ArithmeticException"/>
</tx:attributes>
</tx:advice>
<!--2、装备切入点和切面-->
<aop:config>
<aop:pointcut id="p"
expression="execution(* com.vectorx.spring5.s17_transaction_xml.service.TransferRecordService.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="p"></aop:advisor>
</aop:config>
</beans>
对Service
和Dao
类去除注解,并对代码稍作修正
public class TransferRecordDaoImpl implements TransferRecordDao {
private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
//...
}
public class TransferRecordService {
private TransferRecordDao transferRecordDao;
public void setTransferRecordDao(TransferRecordDao transferRecordDao) {
this.transferRecordDao = transferRecordDao;
}
//...
}
运转测验代码
后台成果
Exception in thread "main" java.lang.ArithmeticException: / by zero
数据库成果
3.9、彻底注解开发
// 表明此类为装备类
@Configuration
// 主动扫描包
@ComponentScan(basePackages = "com.vectorx.spring5.s18_transaction_annotation")
// 敞开业务
@EnableTransactionManagement
// 读取外部装备文件
@PropertySource(value = {"classpath:jdbc.properties"})
public class TxConfig {
@Value(value = "${mysql.driverClassName}")
private String driverClassName;
@Value(value = "${mysql.url}")
private String url;
@Value(value = "${mysql.username}")
private String username;
@Value(value = "${mysql.password}")
private String password;
//装备dataSource
@Bean
public DruidDataSource getDruidDataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setDriverClassName(driverClassName);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
return dataSource;
}
//装备JdbcTemplate
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {
//IOC容器会依据类型找到对应DataSource
JdbcTemplate jdbcTemplate = new JdbcTemplate();
jdbcTemplate.setDataSource(dataSource);
return jdbcTemplate;
}
//装备业务办理器
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {
DataSourceTransactionManager transactionManager = new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}
}
这儿咱们对各个注解进行一一阐明
-
@Configuration
:表明此类为一个装备类,其效果等同于创立一个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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
-
@ComponentScan
:主动扫描包,basePackages
特点装备为需求扫描的包途径,等价于<context:component-scan>
标签,即
<!--敞开注解扫描-->
<context:component-scan base-package="com.vectorx.spring5.s18_transaction_annotation"/>
-
@EnableTransactionManagement
:敞开业务办理,等价于<tx:annotation-driven>
标签,即
<!--敞开业务注解-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
-
@PropertySource
:引进外部文件,value
装备外部文件途径,等价于<context:property-placeholder>
标签
<context:property-placeholder location="classpath:jdbc.properties"/>
-
@Value
:对一般类型的特点进行注入,一起其特点值能够运用${}
表达式对外部文件装备信息进行获取 -
@Bean
:装备目标创立,等价于<bean>
标签。能够在被润饰的办法参数列表中传入受IOC容器办理的类型,IOC容器会主动依据类型找到对应目标并注入到此办法中。因而无论是装备JdbcTemplate还是装备业务办理器,都能够运用这种办法对外部Bean进行引用
//装备JdbcTemplate
@Bean
public JdbcTemplate getJdbcTemplate(DataSource dataSource) {...}
//装备业务办理器
@Bean
public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource) {...}
测验代码
需求注意的是,之前创立的目标是ClassPathXmlApplicationContext
,而现在是彻底注解开发,所以需求创立的目标是AnnotationConfigApplicationContext
,结构参数中传入装备类的class
类型即可,其他代码与之前共同
ApplicationContext context = new AnnotationConfigApplicationContext(TxConfig.class);
TransferRecordService transferRecordService = context.getBean("transferRecordService", TransferRecordService.class);
transferRecordService.transferAccounts(100, "Lucy", "Mary");
测验成果
小结
重点把握
- Spring业务办理两种办法:编程式业务办理(不引荐)、声明式业务办理(引荐)
- Spring业务办理API:
PlatformTransactionManager
、DataSourceTrasactionManager
、HibernateTransactionManager
- 声明式业务两种完结办法:注解办法和XML办法
- 业务相关参数有:传达行为、阻隔等级、超时时刻、是否只读、(不)回滚
- 传达行为:有7种传达特点,
REQUIRED
、REQUIRED_NEW
、SUPPORTS
、NOT_SOPPORTED
、MANDATORY
、NEVER
、NESTED
- 阻隔等级:有3种典型“读”的问题,脏读、不可重复读、虚(幻)读,可设置4种阻隔等级,
READ UNCOMMITTED
、READ COMMITTED
、REPEATABLE READ
、SERIALIZABLE
- 其他参数:
timeout
、readOnly
、rollbackFor
、noRollbackFor
- 传达行为:有7种传达特点,
- 声明式业务(注解办法):
@Transactional
- 声明式业务(XML办法):装备业务办理器;装备业务告诉
<tx:advice>
;装备切入点和切面 - 彻底注解开发:
@EnableTransactionManagement
、@Bean
、AnnotationConfigApplicationContext
总结
-
JdbcTemplate
的CRUD
操作 - 业务
ACID
特性、Spring业务办理 - 声明式业务的注解办法和XML办法
- 业务相关特点:传达行为、阻隔等级、其他参数
下面思维导图经供参阅