Spring<04>JdbcTemplate&声明式事务


typora-copy-images-to: images

Spring<04>JdbcTemplate&声明式事务

1、Spring JdbcTemplate

1.1 JdbcTemplate概述

以往使用jdbc时,每次都需要自己获取PreparedStatement,执行sql语句,关闭连接等操作。操作麻烦冗余,影响编码的效率。Spring把对数据库的操作在jdbc上面做了深层次的封装,使用spring的注入功能,可以把DataSource注册到JdbcTemplate(jdbc模板)之中,这样我们只需要做一些简单的操作(eg:编写SQL语句、传递参数)就可以了。

spring框架根据不同持久层方案为我们提供了不同的JdbcTemplate(jdbc模板类)。例如:操作关系型数据的JdbcTemplate和HibernateTemplate,操作nosql数据库的RedisTemplate,操作消息队列的JmsTemplate等等

1.2 QuickStart

1)JdbcTemplate基础开发步骤

① 导入spring-jdbc和spring-tx坐标

② 创建数据库表

③ 在测试类中创建JdbcTemplate对象(包含DataSource对象)并使用

JdbcTemplate位于spring-jdbc-x.x.x.RELEASE.jar中。其全限定命名为org.springframework.jdbc.core.JdbcTemplate。要使用JdbcTemlate还需要开启事务,所以需要Spring-tx-x.x.x.RELEASE.jar,该包包含了一下事务和异常管理。所以需要导入spring-jdbc和spring-tx的坐标。

2)JdbcTemplate基础开发代码实现

① 导入spring-jdbc和spring-tx坐标

<dependencies>

    <!-- Spring上下文坐标,如果使用到了Spring的上下文容器,需要导
 		 本次测试只是演示JdbcTemplate对象的创建和使用,不使用Spring容器进行Ioc,所以可以不导
	-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>

    <!-- 使用Spring JdbcTemplate需要导入下面两个坐标 -->
    <!-- 导入spring-jdbc坐标 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>

    <!-- 导入spring-tx事务管理坐标,如果不使用事务管理,可以不导。本测试未开启事务管理,可以不导 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-tx</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>

    <!-- 因为操作Mysql数据库,需要导入数据库驱动坐标 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.6</version>
    </dependency>

    <!-- 数据源选择使用c3p0连接池,所以导入其坐标,测试中用到了c3p0的连接池,必导 -->
    <dependency>
        <groupId>c3p0</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.1.2</version>
    </dependency>

    <!-- 使用单元测试,所以导入JUnit坐标 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
    </dependency>
</dependencies>

② 创建数据库表(和实体类)

CREATE TABLE account( username varchar(255),money double );
package com.itheima.jdbctemplate.domain;

/**
 * 本案例中未使用到该实体类,故在本案例中可以不创建该类
 */
public class Account {
    private String username;
    private double money;


    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public double getMoney() {
        return money;
    }

    public void setMoney(double money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "username='" + username + '\'' +
                ", money=" + money +
                '}';
    }
}

③ 在测试类中创建JdbcTemplate对象并使用

package com.itheima.jdbctemplate.test;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.junit.Test;
import org.springframework.jdbc.core.JdbcTemplate;

import java.beans.PropertyVetoException;

public class AccountTest {

    @Test
    public void test1() throws PropertyVetoException {

        //1. 创建JdbcTemplate模板对象
        JdbcTemplate jdbcTemplate = new JdbcTemplate();

        //2. 设置数据源ComboPooledDataSource对象
        //创建DataSource对象,并设置连接四要素
        ComboPooledDataSource dataSource = new ComboPooledDataSource();
        dataSource.setDriverClass("com.mysql.jdbc.Driver");
        dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/test");
        dataSource.setUser("root");
        dataSource.setPassword("135246");

        //JdbcTemplate依赖于DataSource
        jdbcTemplate.setDataSource(dataSource);

        //3. 执行增删改查操作
        int row = jdbcTemplate.update("insert into account values (?,?)", "hzheima", 50000);
        System.out.println(row);
    }
}

执行数据库操作并查看执行结果

Disconnected from the target VM, address: '127.0.0.1:58966', transport: 'socket'
1

Process finished with exit code 0
3)基础开发步骤总结

① 导入spring-jdbc[ 和spring-tx坐标 ](同时导入依赖mysql-connector-java、c3p0坐标和测试junit坐标)

② 创建数据库表

③ 在测试类中创建JdbcTemplate对象(包含DataSource对象)并使用

 1. 创建JdbcTemplate模板对象
 2. 设置数据源ComboPooledDataSource对象
 3. 执行增删改查操作

1.3 Spring容器创建并维护JdbcTemplate及相关对象

1)思路分析

Spring的核心之一就是Ioc,控制反转,也就是由Spring容器负责对象的创建和对象间依赖关系的管理。上述代码中DataSource、JdbcTemplate对象以及他们之间的关系都是由我们手动创建,并且手动设置依赖关系的。

经过观察,JdbcTemplate类可以通过无参构造创建该类对象,同时又setDataSource()方法用于设置数据源依赖,所以JdbcTemplate类的对象完全可以交给Spring容器管理,再加上我们之前学习过的数据源DataSource可以交给Spring容器管理,那我们就可以使用Spring容器管理并维护上述两个对象。

2)代码实现

导入坐标:因为要使用到Spring容器创建对象并维护对象依赖关系,所以要在pom.xml中确定导入了spring-context坐标。

    <!-- Spring上下文坐标,使用到了Spring的上下文容器,需要 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.0.5.RELEASE</version>
    </dependency>

Xml配置:在applicationContext.xml文件中先配置数据源,然后配置JdbcTemplate,最后将数据源注入到JdbcTemplate中。

<!-- DataSource对象 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> 
    <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
    <property name="jdbcUrl" value="jdbc:mysql:///test"></property>
    <property name="user" value="root"></property>
    <property name="password" value="135246"></property>
</bean>

<!-- JdbcTemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
</bean>

Java代码:通过Spring容器获得JdbcTemplate对象,调用JdbcTemplate对象的update方法,传入sql语句及参数完成插入数据的操作

@Test
public void test2() throws PropertyVetoException {

    //1. 获取JdbcTemplate模板对象(Spring已经将DataSource注入其中)
    ApplicationContext ac= new ClassPathXmlApplicationContext(
        "applicationContext.xml");
    JdbcTemplate jdbcTemplate = ac.getBean(JdbcTemplate.class);

    //2. 不需要设置数据源ComboPooledDataSource对象

    //3. 执行增删改查操作
    int row = jdbcTemplate.update("insert into account values (?,?)", "shheima", 60000);
    System.out.println(row);
}

1.4 抽取jdbc配置

目前的jdbc四要素已经脱离了代码,已经实现了解耦,之后如果数据库信息修改后直接修改配置文件即可。

但是目前数据库四要素的配置耦合进了Spring的配置文件,为了更方便的维护,建议将该信息抽取到一个独立properties文件中。

提取数据源的基本连接信息到配置文件jdbc.properties(名字任意,格式为properties)

jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql:///test
username=root
password=135246

引入properties文件,并修改DataSource注入基本参数的配置,具体如下:

<!-- 导入jdbc.properties文件。classpath表示文件在类路径下 -->
<context:property-placeholder location="classpath:jdbc.properties"/>

<!-- DataSource对象 -->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <!--
       property的name属性名称通过set方法截取;因为属性值为String,所以通过value属性设置
        通过${key}的方式可以从properties文件中读取key对应的值。
      -->
    <property name="driverClass" value="${jdbc.driver}"></property>
    <property name="jdbcUrl" value="${jdbc.url}"></property>
    <property name="user" value="${user}"></property>
    <property name="password" value="${password}"></property>
</bean>

<!-- JdbcTemplate对象 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"></property>
</bean>

​ 其他代码保持不变。

1.5 JdbcTemplate常用操作

jdbcTemplate更新数据库常用方法

  • update (更新数据,也就是增删改查)
  • batchUpdate (批量更新数据库,也就是增删改查)
  • queryForObject (单行查询)
  • query (多行查询)
  • queryForObject (单值查询)
  • 执行DDL语句

1.6 JdbcTemplate常用操作-增删改操作

JdbcTemplate对象的update方法可以完成增删改的操作,只是传入的sql语句及对应参数不同。

jdbcTemplate.update(sql语句, sql语句参数列表)	对数据库进行增删改的操作,返回影响的行数

代码如下:

因为要使用Spring整合JUnit测试,所以需要导入spring-test和junit(已经导入了)坐标

<!-- 导使用Spring整合JUnit测试,所以需要导入Spring-test及其依赖junit坐标 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-test</artifactId>
    <version>5.0.5.RELEASE</version>
</dependency> 
<!-- 使Spring-test依赖JUnit -->
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
</dependency>

applicationContext.xml文件

<!-- 内容无需修改 -->

使用Spring整合JUnit进行测试

@RunWith(SpringJUnit4ClassRunner.class)						//指定测试器
@ContextConfiguration("classpath:applicationContext.xml")	//指定Spring配置文件并加载接卸
public class JdbcTemplatetTest {

    @Autowired			//自动注入JdbcTemplate对象
    private JdbcTemplate jdbcTemplate;

    /**
     * testUpdate:测试删除
     */
    @Test
    public void testDelete(){
        int row = jdbcTemplate.update("delete from account where username=?","nanjingheima");
        System.out.println(row);
    }

    /**
     * testUpdate:测试修改
     */
    @Test
    public void testUpdate(){
        int row = jdbcTemplate.update("update account set money=? where username=?", 500000,"hzheima");
        System.out.println(row);
    }
}

1.7 JdbcTemplate常用操作-查询操作

JdbcTemplate对象的query**()方法可以完成查询的操作。

多行查询
List<T> query(sql语句, new BeanPropertyRowMapper<T>(T.class))	
				查询并将结果通过BeanPropertyRowMapper将查询的结果集封装到List集合中
List<T> query(sql语句, new BeanPropertyRowMapper<T>(T.class), sql语句参数列表)
				查询并将结果通过BeanPropertyRowMapper将查询的结果集封装到List集合中
单行查询
T queryForObject(sql语句, new BeanPropertyRowMapper<T>(T.class), sql语句参数列表)
    			单行查询
				查询并将结果通过BeanPropertyRowMapper将查询结果封装到javaBean对象中
单值查询
要求的查询结果类型 queryForObject(sql语句, 要求的查询结果类型.class)
				查询并将结果通过BeanPropertyRowMapper将聚合查询结果转化到要去的结果类型
要求的查询结果类型 queryForObject(sql语句, 要求的查询结果类型.class, sql语句参数列表)
				查询并将结果通过BeanPropertyRowMapper将聚合查询结果转化到要去的结果类型

代码如下:

导入依赖坐标

<!-- 内容无需修改 -->

applicationContext.xml文件

<!-- 内容无需修改 -->

编写JavaBean类

//已完成Account类编写

使用Spring整合JUnit进行测试

@RunWith(SpringJUnit4ClassRunner.class)						//指定测试器
@ContextConfiguration("classpath:applicationContext.xml")	//指定Spring配置文件并加载接卸
public class JdbcTemplatetTest {

    @Autowired			//自动注入JdbcTemplate对象
    private JdbcTemplate jdbcTemplate;
	/**
     * testQueryCount:聚合查询
     */
    @Test
    public void testQueryCount(){
        long count = jdbcTemplate.queryForObject("select count(*) from " +
                "account",long.class);
        System.out.println(count);
        count = jdbcTemplate.queryForObject("select count(*) from " +
                "account where money = ?",long.class,60000);
        System.out.println(count);
    }

    /**
     * testQueryOne:查询单个
     */
    @Test
    public void testQueryOne(){
        Account account = jdbcTemplate.queryForObject("select * from account " +
                "where username = ?",
                new BeanPropertyRowMapper<Account>(Account.class) ,
                "hzheima");
        System.out.println(account);
    }

    /**
     * testQuerySome:按条件查询部分
     */
    @Test
    public void testQuerySome(){
        List<Account> accounts = jdbcTemplate.query("select * from account where money = ?",
                new BeanPropertyRowMapper<Account>(Account.class), 60000);
        System.out.println(accounts);
    }

    /**
     * testQueryAll:查询所有
     */
    @Test
    public void testQueryAll(){
        List<Account> accounts = jdbcTemplate.query("select * from account",
                new BeanPropertyRowMapper<Account>(Account.class));
        System.out.println(accounts);
    }
}
补充讲解:

批量更新(增删改)

jdbcTemplate.batchUpdate(String sql, List batchArgs);	//批量增删改,batchArgs为批量更新的参数列表,类型为Object[]数组类型的List集合
/**
 *testBatechUpdate:批量更新,批量 insert,update,delete
 */
@Test
public void testBatechUpdate() {
    String sql = "insert into account(username, money) value(?,?)";
    List<Object[]> batchArgs = new ArrayList<Object[]>();
    batchArgs.add(new Object[] {"zhangsan",2000});
    batchArgs.add(new Object[] {"lisi",2000});
    batchArgs.add(new Object[] {"wangwu",2000});
    jdbcTemplate.batchUpdate(sql, batchArgs);
}

自定义绑定(使用场景较少)

除了使用系统提供的RowMapper的实现类之外,我们也可以自己手动编写RowMapper实现类,以实现特殊的结果集到javaBean的封装转化,实例代码如下:

Account account = jdbcTemplate.queryForObject(
    "select * from account where money = ?",
    new Object[]{60000L},
    new RowMapper<Account>() {
        //重写该方法时只需要将获取的结果集中单个结果封装到某个JavaBean中,query方法会根据实际情况选择选择返回一个javaBan对象还是一个List
        public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
            Account account = new Account();
            account.setUsername(rs.getString("username"));
            account.setMoney(rs.getString("money"));
            return account;
        }
    });

执行DDL语句(使用场景很少)

Spring的JdbcTemplate也可以使用execute(…)方法运行任意SQL语句。所以,该方法通常用于DDL语句。下面代码创建一个表:

jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");

详见Spring官方文档:data-access --> 3.3.1. Using JdbcTemplate

1.8 JdbcTemplate使用总结

使用步骤

① 导入spring-jdbc[ 和spring-tx ]坐标(同时导入依赖mysql-connector-java、c3p0和测试junit坐标;spring-context<SpringIoc容器>、spring-test<Spring整合JUnit>按需导入)

② 编写JavaBean类、创建数据库表

③ 在测试类中创建JdbcTemplate对象(包含DataSource对象)并使用

  1. 创建JdbcTemplate模板对象

  2. 设置数据源ComboPooledDataSource对象

    (如果使用了SpringIoc容器,上述1、 2步骤可以使用自动注入完成。)

  3. 使用JdbcTemplate对象执行增删改查操作

增删改查的常用方法
增删改
jdbcTemplate.update(sql语句, sql语句参数列表)	对数据库进行增删改的操作,返回影响的行数

查询
List<T> query(sql语句, new BeanPropertyRowMapper<T>(T.class))	
				查询并将结果通过BeanPropertyRowMapper将查询的结果集封装到List集合中
List<T> query(sql语句, new BeanPropertyRowMapper<T>(T.class), sql语句参数列表)	
				查询并将结果通过BeanPropertyRowMapper将查询的结果集封装到List集合中
T queryForObject(sql语句, new BeanPropertyRowMapper<T>(T.class), sql语句参数列表)
				查询并将结果通过BeanPropertyRowMapper将查询结果封装到javaBean对象中
要求的查询结果类型 queryForObject(sql语句, 要求的查询结果类型.class)
				查询并将结果通过BeanPropertyRowMapper将聚合查询结果转化到要去的结果类型
要求的查询结果类型 queryForObject(sql语句, 要求的查询结果类型.class, sql语句参数列表)
				查询并将结果通过BeanPropertyRowMapper将聚合查询结果转化到要去的结果类型

2、Spring事务管理

Spring事务管理主要应用在软件分层之后的业务层,也就是Service层。一个service层的操作依赖的多个dao层操作要被事务管理起来,保证数据安全准确。eg:转账。

2.1 Spring事务管理核心API(了解)☆

Spring事务管理的三个高层接口

  • PlatformTransactionManager:Spring提供的事务管理器接口,内部规定了常用的事务管理的规则。是整个Spring进行事务管理的核心接口。
  • TransactionDefinition:Spring提供的封装事务属性的接口,用于封装和操作事务的属性
  • TransactionStatus:Spring提供的封装事务状态的接口,用于封装和操作事务的状态

三者的关系平台事务管理器(PlatformTransactionManager)根据 事务属性管理器(TransactionDefinition)中定义的事务属性对事务进行管理;事务运行过程中,每一个时间点都有对应的事务状态信息(TransactionStatus)。

也就是说,干活的是Manager(PlatformTransactionManager),根据事务属性管理器(TransactionDefinition)的配置信息干活,在干活的过程中会有一些状态信息,通过TransactionStatus体现。

2.2 PlatformTransactionManager(了解)☆

Spring提供的事务管理器接口,整个Spring进行事务管理的核心接口,内部规定了常用的操作事务的规则。

a) 接口中规定的操作事务的方法:
操作事务的方法说明
TransactionStatus getTransaction
(TransactionDefination defination)
根据事务的属性信息对象
获取事务的状态信息
void commit(TransactionStatus status)提交事务
void rollback(TransactionStatus status)回滚事务

根据上述方法,

​ TransactionStatus getTransaction(TransactionDefination defination)

我们可以推断:

PlatformTransactionManager根据TransactionDefinition对象获得 TransactionStatus 对象

b) 不同的Dao层技术则有不同的实现类

PlatformTransactionManager是一个接口,只负责制定规范,那具体的实现是哪些类呢?Spring根据不同的数据持久层技术提供了不同实现类:

  • Dao 层技术是jdbc、 mybatis、Apache DBUtils时:org.springframework.jdbc.datasource.DataSourceTransactionManager

  • Dao层技术是hibernate时:

    org.springframework.orm.hibernate5.HibernateTransactionManager

  • 我们需要在开发中根据实际情况选择并配置对应的平台事务管理器。

2.3 TransactionDefinition(了解)

Spring提供的封装事务属性的接口,用于规定封装事务的属性,通过该接口实现类对象可以获取(子类对象可以设置)事务属性。

a) 接口中规定的获取事务属性的方法:
方法名说明
int getIsolationLevel()获取事务的隔离级别,解决事务并发访问隔离问题
int getPropagationBehavior()获取事务的传播行为,解决的就是两个事务方法事务统一性的问题
int getTimeout()获取超时时间
boolean isReadOnly()判断是否只读

*接口中只规定了获取的方法,在其实现类中有设置的方法,eg:DefaultTransactionDefinition

事务属性需要在进行声明式事务配置时,需要在配置文件中对于事务属性进行配置,以便Spring框架进行解析。

b) 事务的四个特性:

ACID:原子性、一致性、隔离性、持久性。

① Atomic(原子性):事务中包含的操作被看做一个逻辑单元,这个逻辑单元中的操作要么全部成 功,要么全部失败。

② Consistency(一致性):事务完成时,数据必须处于一致状态,数据的完整性约束没有被破坏,事务在执行过程中发生错误,会被回滚(Rollback)到事务开始前的状态,就像这个事务从来没有执行过一样。

③ Isolation(隔离性):事务允许多个用户对同一个数据进行并发访问,而不破坏数据的正确性 和完整性。同时,并行事务的修改必须与其他并行事务的修改相互独立。

④ Durability(持久性):事务结束后,事务处理的结果必须能够得到持久化。

c) 事务属性:隔离级别(理解)

在事务的隔离性在并发访问的时候,会出现一些读问题。

读问题

  • 脏读:一个事务读取了另外一个事务改写但还未提交的更新数据,如果这些数据被回滚,则读到的数据是无效的。
  • 不可重复读:在同一个事务中,多次读取同一个数据返回的结果有所不同。换句话说就是,后续读取可以读取到另一个事务已提交的更新数据。相反,“可重复读”在同一个事务中多次读取数据时,能保证数据时一样,也就是,后续读取不能读取到另一个事务已经提交的更新数据。
  • 幻读:一个事务读取了(按照某个规则的)全部数据后,另一个事务插入了一些记录,当后续再次查询的时候,幻读就发生了。在后续查询中,第一个事务就会发现有一些原来没有的记录。

设置隔离级别,可以解决事务并发产生的读问题,如脏读、不可重复读和虚读(幻读)。

  • ISOLATION_DEFAULT Spring事务管理提供的默认级别,使用数据库内部默认级别(mysql - Repeatable read,其他大多数是 - Read committed)

  • ISOLATION_READ_UNCOMMITTED 读未提交:一个事务可以读取另一个未提交事务的数据,该级别下容易上述所有问题。

  • ISOLATION_READ_COMMITTED 读提交:一个事务要等另一个事务提交后才能读取数据。能解决脏读问题,但是可能会有不可重复读、幻读的问题。

  • ISOLATION**_REPEATABLE_READ** 重复读,在开始读取数据(事务开启)时,不再允许修改操作。能解决脏读、不可重复读的问题,但是可能会有幻读的问题。

  • ISOLATION_SERIALIZABLE 序列化,解决所有三个问题,相当于锁表安全性高,串行化处理事务,但是非常耗数据库性能,效率低下,一般不用。

列表如下(√表示可能出现,×表示不会出现):

隔离级别\读问题脏读不可重复读幻读
Read uncommitted读未提交
Read commited读提交×
Repeatable read重复读××
Serializable×××

上述事务的四大特性、四个隔离级别是原来是在数据库内部规定的,Spring从数据库引入了 过来。但是接下来的七种传播行为和数据库没有直接关系,是Spring为了解决实际开发中的问题而引入的。

d) 事务属性:传播行为(理解)

即然是传播,那么至少有两个东西,才可以发生传播。单体不存在传播这个行为。

事务传播行为(propagation behavior)指的就是当一个事务方法A调用另一个事务方法B时,事务方法B应该以何种事务特性进行。是继续在调用者methodA的事务中运行呢,还是为自己开启一个新事务运行,这就是由methodB的事务传播行为决定的。

事务传播行为是为了解决业务方法相互调用时,事务如何管理的问题。

实际开发中,程序要进行事务管理,在程序分层之后,程序的事务控制通常在哪一层?


通常情况下,一个业务是一个事务。但是多个业务方法相互调用的时候可能会出现问题。

eg:取钱打印小票

七种事务传播行为(当前方法:被调用的方法):

  • PROPAGATION_REQUIRED:表示当前方法必须运行在事务中。如果调用者事务存在,当前方法就会在该事务中运行;否则,会启动一个新的事务。一般的选择(默认值)。
  • PROPAGATION_SUPPORTS:表示当前方法不需要事务上下文,但是如果当前事务存在的话,那么该方法会在这个事务中运行。
  • PROPAGATION_MANDATORY:表示当前方法必须在事务中运行,如果当前事务不存在,则会抛出一个异常。
  • PROPAGATION_REQUERS_NEW:表示当前方法必须运行在它自己的事务中,一个新的事务将被启动。如果存在当前事务,该方法执行期间,当前事务会被挂起。
  • PROPAGATION_NOT_SUPPORTED:表示当前方法不应该运行在事务中。如果存在当前事务,该方法运行期间,当前事务会被挂起。
  • PROPAGATION_NEVER:表示当前方法不应该运行在事务上下文中。如果存在当前事务,则会抛出异常。
  • PROPAGATION_NESTED:表示如果当前已经存在一个事务,那么该方法将会在嵌套事务中运行。嵌套的事务可以独立于当前事务进行独立的提交和回滚。如果当前事务不存在,那么其行为与PROPAGATION_REQUIRED一样。注意各厂商对这种传播行为的支持不同,可以参考资源管理器的文档来确认他们是否支持嵌套事务。
e) 事务其他属性

超时时间:默认值是-1,没有超时限制。如果有,以秒为单位进行设置。一般使用默认。

是否只读:建议查询操作时设置为只读,效率更高。

事务中的readOnly属性表示对应的事务会最优化为只读事务。如果值为true就会告诉Spring我这个方法里面没有insert或者update,你只需要提供只读的数据库Connection就行了,这种执行效率会比read-write的Connection高,所以这是一个最优化属性。

2.4 TransactionStatus

Spring提供的封装事务状态的接口,用于封装事务的状态。

事务的状态是事务自身的属性,在事务管理不同的时间段,事务会表现出不同的状态,所以不需要配置。

a) 接口中规定的判断事务状态的方法:
方法名说明
boolean isNewTransaction()是否是新事务。如果返回false,说明当前事务已存在,或者该操作没在事务环境中。
boolean hasSavepoint()是否存在回滚点
boolean isRollbackOnly()判断当前事务是否设置了rollback-only
boolean isCompleted()事务是否已结束

2.5 要点总结

Spring事务管理的三个高层接口

  • PlatformTransactionManager:Spring提供的事务管理器接口,内部规定了常用的操作事务的规则。我们需要在开发中根据实际情况选择并配置对应的平台事务管理器。

  • TransactionDefinition:Spring提供的定义事务参数的接口,用于封装事务的属性。

  • TransactionStatus:Spring提供的封装事务状态的接口,用于封装事务的状态。

三者关系:平台事务管理器(PlatformTransactionManager)根据 事务属性管理器(TransactionDefinition)中定义的事务属性对事务进行管理;事务运行过程中,每一个时间点都有一个对象的事务状态信息(TransactionStatus)。

需要配置的内容:PlatformTransactionManager、TransactionDefinition

不需要配置的内容:TransactionStatus

2.6 Spring事务管理分类 ☆

  • 编程式事务管理
  • 声明式事务管理
    • 基于XML的声明式事务管理
    • 基于注解的声明式事务管理

特点分析:

  • 编程式事务管理(了解)
    • 在实际开发中使用很少使用
    • 手动使用TransitionTemplate对象,编写代码手动管理事务
  • 声明式事务管理(基于AOP,掌握)
    • 无需修改业务代码,通过配置的方式,就可以在业务代码上添加事务管理相关的功能

2.7 准备案例环境

事务管理非常常见的场景就是转账业务,那我们就搭建一个转账业务的环境。

  1. 使用sql创建表,录入测试数据
  2. 使用SpringIoc的规范编写service、dao层相关代码
  3. 在转账业务添加异常代码,观看演示效果

详细代码见模块itheima_spring_tx_oraginal

2.8 编程式事务 ☆

使用java代码进行事务管理就是编程式事务管理。

在之前学习jdbc的时候:

​ connection.setAutoCommit(false) 开启事务

​ connection.commit() 提交事务

​ connection.rollback() 回滚事务

在Spring中使用,使用事务管理工具类TransactionTemplate进行编程式事务管理。

  1. 导入itheima_spring_tx_oraginal模块(已经导入spring-tx坐标)

  2. 因为一般在Service中进行事务管理,故将TransactionTemplate注入到service中.

    public class ServiceImpl{
    
    	//其他原有代码
    	
    	
    	//注入务管理工具类TransactionTemplate
    	private TransactionTemplate transactionTemplate;
        
        public void setTransactionTemplate(TransactionTemplate transactionTemplate){
            this.transactionTemplate = transactionTemplate;
        }
    	
    	// 其他原有代码
    }
    
       <!--装配TransactionTemplate-->
        <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate">
    
            <!--
               真正进行事务管理的不是transactionTemplate,而是我们之前讲的PlatformTransactionManager
               因为我们使用的是jdbc技术,所有选用DataSourceTransactionManager
            -->
            <property name="transactionManager"
                      ref="dataSourceTransactionManager"></property>
        </bean>
    
        <!--
            装配DataSourceTransactionManager到Spring容器
            DataSourceTransactionManager需要注入DataSource
         -->
    
        <bean id="dataSourceTransactionManager"
              class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"/>
        </bean>
    
        <!-- 将transactionTemplate注入到service中 -->
        <!--目标对象  内部的方法就是切点-->
        <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
            <property name="accountDao" ref="accountDao"/>
            <property name="transactionTemplate" ref="transactionTemplate"></property>
        </bean>
    
  3. 在业务代码中添加事务管理代码,使用transactionTemplate进行事务管理。

    package com.itheima.service.impl;
    
    import com.itheima.dao.AccountDao;
    import com.itheima.service.AccountService;
    import org.springframework.transaction.TransactionStatus;
    import org.springframework.transaction.support.TransactionCallbackWithoutResult;
    import org.springframework.transaction.support.TransactionTemplate;
    
    
    public class AccountServiceImpl implements AccountService {
    
        // 编程式事务管理,要用到事务模板工具类
        private TransactionTemplate transactionTemplate;
    
        public void setTransactionTemplate(TransactionTemplate transactionTemplate) {
            this.transactionTemplate = transactionTemplate;
        }
    
        private AccountDao accountDao;
    
        public void setAccountDao(AccountDao accountDao) {
            this.accountDao = accountDao;
        }
    
        /*
            编程式事务管理,需要将下面的需要被事务管理的代码写到指定方法中
            transactionTemplate对象的execute()
            方法的参数对象transactionCallbackWithoutResult的doInTransactionWithoutResult方法中
    
            内部类中的变量需要用final修饰
        */
        public void transfer(final String outMan, final String inMan, final double money) {
            transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                protected void doInTransactionWithoutResult(TransactionStatus transactionStatus) {
                    //业务代码
                    accountDao.out(outMan, money);
                    int i = 1 / 0;
                    accountDao.in(inMan, money);
                }
            });
    
        }
    }
    

上面代码就是通过编码完成事务管理,就是编程式事务管理。我们的编码使用的都是默认的事务参数(管理级别、传播行为;可以使用transactionTemplate设置不同的参数)。

编程式事务管理缺点:

1.(编码时)事务管理严重侵入业务组件。造成两者强耦合,但是事务管理是属于系统层面的,业务组件是业务逻辑的一部分,所以应该解耦。

2.代码不易维护。事务管理策略代码书写复杂,两者耦合在一起不易维护。

2.9 声明式事务概述

1. 概念:

Spring 的声明式事务就是采用声明的方式来设置事务的属性参数,事务会按照指定的规则自动管理运行。

这里所说的声明,指在配置文件中配置(或添加注解)<事务的属性参数>,用在Spring 配置文件(注解)中声明的事务来代替用代码编写的事务。

2. 原理:

编写业务代码的时候并没有体现事务管理的代码

业务代码运行过程中,Spring容器(中的事务管理器对象)跟据配置文件中的配置参数,动态的把事务加入到业务代码中。(AOP思想)

3. 特点:

**(编码时)事务管理不侵入业务组件。**因为事务管理是属于系统层面的,业务组件是业务逻辑的一部分,所以应该解耦。

**事务管理策略维护方便。**如果需要修改事务管理策略,只要在配置文件上修改,无需改变代码重新编译。

2.10基于XML的声明式事务管理快速入门

a) 编码前明确事项
切点 – 目标方法 -  业务层中的方法
通知 – 增强方法 –  事务管理器的事务管理方法
切面 – 目标方法 + 增强方法
织入 – 让目标方法和增强方法结合的过程
b) 编码步骤:
1. 引入tx命名空间
2. 配置切点
3. 配置通知(事务管理器)
4. 配置事务织入
5. 测试转账业务是否被正确被事务控制

c) 实现代码:
  1. 引入tx命名空间

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:aop="http://www.springframework.org/schema/aop"
           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/aop http://www.springframework.org/schema/aop/spring-aop.xsd
           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
    ">
    
  2. 配置切点

    <!--pointcut 目标对象  内部的方法就是切点-->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl">
    	<property name="accountDao" ref="accountDao"/>
    </bean>
    
  3. 配置通知(事务管理器)

    <!-- advice 配置通知-事务管理器就是通知(增强)所在的类
    	tx:attribute 内部参数稍后再讲 
    -->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
    	<tx:attributes>
    		<tx:method name="*"/>
    	</tx:attributes>
    </tx:advice>
    
    <!--
    	jdbc 使用的(平台)事务管理器是 DataSourceTransactionManager
    
    	事务管理管理的是Service层,service层需要调用dao层操作数据库,dao层依赖DataSource管理连接池并连接数据库
    	所以事务管理器中需要注入DataSource
    -->
    <bean id="transactionManager"
    class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    	<property name="dataSource" ref="dataSource"></property>
    </bean>
    
    
  4. 配置织入

    <!-- weaving 配置事务的织入。 增强方法 + 目标方法 -->
    <aop:config>
        <aop:advisor advice-ref="txAdvice"
                     pointcut="execution(* com.itheima.service.impl.*.*(..))"/>
    </aop:config>
    
  5. 测试转账业务是否被正确被事务控制

    //转账过程中如果发生异常,所有账户中金额不发生变动,就表示转账方法已经被事务管理起来了。
    

2.10基于XML的声明式事务管理参数详解

重点分析如下配置:

<!-- advice  配置通知-事务管理器就是通知(增强)所在的类 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <tx:method name="*"/>
    </tx:attributes>
</tx:advice>

其中:

tx:advice		
		advice,增强方法所在的类(transactionManager的class)
		因为要增强的内容是事务(为业务逻辑添加的事务),所以里面可以配置多个事务,
		以及这些事务都分别匹配到哪些业务逻辑(不同的事务分别增强哪些方法)
				
tx:attributes	
		具体的事务需要配到这个标签里面

tx:method		
		每一个tx:method代表一个事务,在这个标签里面的属性上会配置某些业务逻辑的方法(目标方法),并同时配置对应的事务管理逻辑。
		
tx:nethod ~	name
		name属性,用于配置哪些业务逻辑方法被当前事务增强。
		该属性并非Spring事务本身的属性,而是为了让该事务匹配关联到某些业务逻辑方法。
		符合切点表达式的所有方法会被拿过来进行同一个事务管理。
		如果name值为*,则表示所有符合切点表达式的方法都被当前事务管理
		如果是其他的值,则会根据规则匹配,比方说name="saveUser",就是匹配saveUser方法
									name="save*",就是匹配所有save开头的方法
		
tx:nethod ~	isolation				
		isolation属性,配置当前事务的隔离级别;如不设置或设置DEFAULT,使用默认的隔离级别。
		mysql默认的Repeatable read,其他大多数是Read committed。
		
tx:method ~ propagation
		propagation属性,配置当前事务的传播行为。不设置则使用默认值REQUIRED。
		
tx:method ~ timeout
		timeout属性,配置当前事务的超时时间,不设置则使用默认值-1,表示不限制。一般不设置。
tx:method ~ readonly
		readonly属性,值为boolean类型。默认值为false。
		如果是查询操作,建议设为true,优化查询性能;其他设为false或者不设置使用默认值。
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值