笔记来历:尚硅谷Spring结构视频教程(spring5源码级讲解)

JdbcTemplate与声明式业务

1、JdbcTemplate

1.1、概述

前面咱们已经学习了 Spring 中的Core Container核心部分和AOPAspects等面向切面编程部分,接下来便是Data Access/Integration即数据拜访和集成部分

Spring 既能够独自运用,也能够集成其他结构,如HibernateMyBatis等。除此之外,其中对于JDBC也做了封装,即本章节的JdbcTemplate,用它能够比较便利地对数据库进行增删改查等操作

【Spring从入门到精通】03-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

【Spring从入门到精通】03-JdbcTemplate与声明式事务

  • 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()办法

【Spring从入门到精通】03-JdbcTemplate与声明式事务

这个办法是在其父类中定义了的

【Spring从入门到精通】03-JdbcTemplate与声明式事务

  • 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表数据,核验是否刺进成功

【Spring从入门到精通】03-JdbcTemplate与声明式事务

能够看到,表中成功新增了一条数据

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);

测验成果

【Spring从入门到精通】03-JdbcTemplate与声明式事务

测验删去

//删去图书
int result3 = bookService.deleteBook("1");
System.out.println(result3);

测验成果

【Spring从入门到精通】03-JdbcTemplate与声明式事务

1.5、查询操作

这儿演示三种查询操作:

  • 1)查询回来某个值
  • 2)查询回来目标
  • 3)查询回来调集

为了演示效果,需求先在数据库的t_book表中增加两条数据

【Spring从入门到精通】03-JdbcTemplate与声明式事务

接着咱们先将代码完结,最后再作进一步的分析阐明

代码完结

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

【Spring从入门到精通】03-JdbcTemplate与声明式事务

别的,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);	

测验成果

【Spring从入门到精通】03-JdbcTemplate与声明式事务

测验批量修正

// 批量修正
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);

测验成果

【Spring从入门到精通】03-JdbcTemplate与声明式事务

测验批量删去

// 批量删去
List<Object[]> bookList = new ArrayList<>();
Object[] book1 = {"3"};
Object[] book2 = {"4"};
bookList.add(book1);
bookList.add(book2);
bookService.batchDelBook(bookList);

测验成果

【Spring从入门到精通】03-JdbcTemplate与声明式事务

能够看出,上述测验都彻底符合咱们的预期

【Spring从入门到精通】03-JdbcTemplate与声明式事务

小结

简略总结下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层:与数据库进行交互

因而,咱们建立操作环境也依照典型的三层架构来完结,不过目前现阶段咱们只重视ServiceDao两层

【Spring从入门到精通】03-JdbcTemplate与声明式事务

咱们以银行转账为例,由于整个转账操作包含两个操作:出账的操作和入账的操作

进程概览

  • 1)创立数据库表结构,增加几条记载
  • 2)创立ServiceDao类,完结目标创立和联系注入
  • 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);

增加完结效果

【Spring从入门到精通】03-JdbcTemplate与声明式事务

**2)**创立ServiceDao类,完结目标创立和联系注入

Service中注入DaoDao中注入JdbcTemplateJdbcTemplate中注入DataSource

ServiceDao

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");

测验成果

【Spring从入门到精通】03-JdbcTemplate与声明式事务

能够发现,转账按期完结了。但真的没有一点问题么?

2.4、引进业务场景

咱们模仿下在转账中途发生网络反常,修正TransferRecordService中转账办法

public void transferAccounts(int amount, String fromUser, String toUser) {
    transferRecordDao.transferOut(amount, fromUser);
    //模仿网络反常而导致操作中止
    int i = 10 / 0;
    transferRecordDao.transferIn(amount, toUser);
}

为了更明晰直观地看到数据的改动,咱们还原数据表数据到开始状况

【Spring从入门到精通】03-JdbcTemplate与声明式事务

依照希望,转账应该失利,即两边账户不该该有任何改动。实际真的能够如咱们所料么?

咱们履行测验办法,按期抛出反常

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)

那数据表是否也按期改动呢?

【Spring从入门到精通】03-JdbcTemplate与声明式事务

咱们发现,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

供给了一个接口,代表业务办理器,针对不同结构存在不同完结类

【Spring从入门到精通】03-JdbcTemplate与声明式事务

主要把握

  • PlatformTransactionManager接口:即业务办理器,有多个不同的抽象类和详细完结类,可满足不同的结构
  • DataSourceTrasactionManager完结类:JdbcTemplateMyBatis结构运用到它
  • 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 {
    //...
}

首要还原数据表信息至初始状况

【Spring从入门到精通】03-JdbcTemplate与声明式事务

测验代码后改写数据表

【Spring从入门到精通】03-JdbcTemplate与声明式事务

这一次数据没有发生改动,阐明业务回滚了,符合咱们预期

3.4、业务参数

Service类上面增加注解@Transactional,在这个注解里边能够装备业务相关参数

【Spring从入门到精通】03-JdbcTemplate与声明式事务

主要介绍参数有

  • 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)指的是多业务之间互不影响。不考虑阻隔性,在并发时会发生一系列问题

有三个典型的“读”的问题:脏读、不可重复读、虚(幻)读

  • 脏读:一个未提交业务读取到了另一个未提交业务修正的数据

【Spring从入门到精通】03-JdbcTemplate与声明式事务

  • 不可重复读:一个未提交业务读取到了另一个已提交业务修正的数据(不能算问题,只是算现象)

【Spring从入门到精通】03-JdbcTemplate与声明式事务

  • 虚(幻)读:一个未提交业务读取到了另一个已提交业务增加的数据

经过设置阻隔等级,能够处理上述“读”的问题

业务阻隔等级 脏读 不可重复读 幻读
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反常,因而会进行回滚,转账进程不会成功

此刻数据库中的数据,就不会有任何改动

【Spring从入门到精通】03-JdbcTemplate与声明式事务

  • 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

此刻数据库中的数据,就会是下面状况(显然,这并不是咱们想要的)

【Spring从入门到精通】03-JdbcTemplate与声明式事务

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>

ServiceDao类去除注解,并对代码稍作修正

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

数据库成果

【Spring从入门到精通】03-JdbcTemplate与声明式事务

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从入门到精通】03-JdbcTemplate与声明式事务

小结

重点把握

  • Spring业务办理两种办法:编程式业务办理(不引荐)、声明式业务办理(引荐)
  • Spring业务办理API:PlatformTransactionManagerDataSourceTrasactionManagerHibernateTransactionManager
  • 声明式业务两种完结办法:注解办法XML办法
  • 业务相关参数有:传达行为、阻隔等级、超时时刻、是否只读、(不)回滚
    • 传达行为:有7种传达特点,REQUIREDREQUIRED_NEWSUPPORTSNOT_SOPPORTEDMANDATORYNEVERNESTED
    • 阻隔等级:有3种典型“读”的问题,脏读、不可重复读、虚(幻)读,可设置4种阻隔等级,READ UNCOMMITTEDREAD COMMITTEDREPEATABLE READSERIALIZABLE
    • 其他参数:timeoutreadOnlyrollbackFornoRollbackFor
  • 声明式业务(注解办法):@Transactional
  • 声明式业务(XML办法):装备业务办理器;装备业务告诉<tx:advice>;装备切入点和切面
  • 彻底注解开发:@EnableTransactionManagement@BeanAnnotationConfigApplicationContext

总结

  1. JdbcTemplateCRUD操作
  2. 业务ACID特性、Spring业务办理
  3. 声明式业务的注解办法和XML办法
  4. 业务相关特点:传达行为、阻隔等级、其他参数

下面思维导图经供参阅

【Spring从入门到精通】03-JdbcTemplate与声明式事务