九点钟☆方向

记录成长点滴

欢迎来到我的个人博客~


Spring笔记(十一) Spring中的事务控制

目录

Spring 事务控制我们要明确的

  1. JavaEE 体系进行分层开发,事务处理位于业务层(Service层)
  2. spring 框架为我们提供了一组事务控制的接口。这组接口是在spring-tx-5.2.8.RELEASE.jar 中
  3. spring 的事务控制都是基于 AOP 的,它既可以使用编程的方式实现,也可以使用配置的方式实现。实际开发中很少使用编程方式。

Spring 中事务控制的 API 介绍

PlatformTransactionManager

此接口是 spring 的事务管理器,它里面提供了我们常用的操作事务的方法

8

我们在开发中都是使用上述接口的实现类:真正管理事务的对象

org.springframework.jdbc.datasource.DataSourceTransactionManager 使用 SpringJDBC 或 iBatis 进行持久化数据时使用

org.springframework.orm.hibernate5.HibernateTransactionManager 使用Hibernate 版本进行持久化数据时使用

TransactionDefinition

它是事务的定义信息对象,里面有如下方法:

5

事务的隔离级别

6

并发安全:ISOLATION_READ_COMMITTED < ISOLATION_REPEATABLE_READ < ISOLATION_SERIALIZABLE

并发效率:ISOLATION_READ_COMMITTED > ISOLATION_REPEATABLE_READ > ISOLATION_SERIALIZABLE

事务的传播行为

  1. REQUIRED:如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选 择(默认值)。增删改一般选择这个
  2. SUPPORTS:支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)。查询一般选择这个
  3. MANDATORY:使用当前的事务,如果当前没有事务,就抛出异常
  4. REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
  5. NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
  6. NEVER:以非事务方式运行,如果当前存在事务,抛出异常
  7. NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作

实际开发中一般只用1、2

超时时间

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

是否是只读事务

建议查询时设置为只读。 默认为false

TransactionStatus

此接口提供的是事务具体的运行状态, 方法介绍如下图

7

基于 XML 的声明式事务控制(配置方式)

以银行账户之间转账为例

引入依赖

spring-context,spring-jdbc,spring-tx,mysql,aspectjweaver

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.6.6</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.6</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.2.8.RELEASE</version>
</dependency>

数据库表和实体类

sql语句:
create table account (
	id int primary key auto_increment,
	name varchar (40),
	money float
	)character set utf8 collate utf8_general_ci;

	insert into account(name, money) values('aaa', 1000);
	insert into account (name,money) values ('bbb',1000);
	insert into account (name, money) values('ccc', 1000) ;
实体类:
public class Account implements Serializable {
    private Integer id;
    private String name;
    private Float money;
    //生成set/get
}

业务层接口和实现类

//接口
public interface IAccountService {
    void transfer(String sourceName,String targeName,Float money);
}

//实现类
public class AccountServiceImpl implements IAccountService {
    private IAccountDao accountDao;//生成set/get

    //转账方法
    public void transfer(String sourceName, String targeName, Float money) {
        //1.根据名称查询两个账户
        Account source = accountDao.findAccountByName(sourceName);
        Account target = accountDao.findAccountByName(targeName);
		//2.修改两个账户的金额
        source.setMoney(source.getMoney()-money);//转出账户减钱
        target.setMoney(target.getMoney()+money);//转入账户加钱
		//3.更新两个账户
        accountDao.updateAccount(source);
        int i=1/0;   //抛出异常测试事务是否起作用
        accountDao.updateAccount(target);
    }
}

Dao 接口和实现类

//接口
public interface IAccountDao {
    Account findAccountByName(String name);
    void updateAccount(Account account);
}

//实现类   继承JdbcDaoSupport的方式
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {
    //根据账户名称查询
    public Account findAccountByName(String name) {
        List<Account> list = getJdbcTemplate().query("select * from account where name = ? ",
                                                     new AccountRowMapper(),name);
        if(list.isEmpty()){
            return null;
        }
        if(list.size()>1){
            throw new RuntimeException("结果集不唯一,不是只有一个账户对象");
        }
        return list.get(0);
    }

    //更新账户
    public void updateAccount(Account account) {
        super.getJdbcTemplate().update("update account set money = ? where id = ? ",
                                       account.getMoney(),account.getId());
    }
}


//账户的封装类 RowMapper 的实现类
public class AccountRowMapper implements RowMapper {
    public Object mapRow(ResultSet resultSet, int i) throws SQLException {
        Account account = new Account();
        account.setId(resultSet.getInt("id"));
        account.setName(resultSet.getString("name"));
        account.setMoney(resultSet.getFloat("money"));
        return account;
    }
}

Spring配置文件

#数据库配置文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring
jdbc.username=root
jdbc.password=root
<?xml version="1.0" encoding="UTF-8"?>
<!--导入相关名称空间和约束  tx的,aop的-->
<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: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/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
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd">

    <!--引入配置文件-->
    <context:property-placeholder location="classpath:jdbcConfig.properties"/>

    <!--配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

    <!--配置service-->
    <bean id="accountService" class="com.mg.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>

    <!--配置dao-->
    <bean id="accountDao" class="com.mg.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--Spring 基于xml的声明式事务控制配置步骤-->
    <!--1.配置事务管理器-->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--2.配置事务通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--3.配置事务属性-->
        <tx:attributes>
           	<!--name属性为方法名字,可以使用通配符。find*定义后所有的查询方法都要以find开头,方便管理-->
            <!--以下配置意思为:查询方法执行一种事务策略,其他方法执行另一种事务策略-->
            <tx:method name="*" propagation="REQUIRED"/>
            <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
        </tx:attributes>
    </tx:advice>

    <!--4.配置AOP切入点表达式-->
    <aop:config>
        <aop:pointcut id="pt1" expression="execution(* com.mg.service.impl.*.*(..))"/>
        <!--5.建立事务的通知和切入点表达式的关系-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
    </aop:config>

</beans>

细节:
1. 配置事务通知
<tx:advice id="txAdvice" transaction-manager="transactionManager">
属性:  
id:给事务通知一个唯一标识
transaction-manager:给事务通知一个事务管理器的引用
    
2.配置事务属性
<tx:attributes>
	<tx:method name="*" propagation="REQUIRED"/>
	<tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
</tx:attributes>
属性:
name:方法名字,可以使用通配符
read-only:是否是只读事务。默认 false,不只读。
isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。
propagation:指定事务的传播行为。
timeout:指定超时时间。默认值为: -1。永不超时。
rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。没有默认值,任何异常都回滚。
no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回滚。没有默认值,任何异常都回滚

基于注解的声明式事务

以银行账户之间转账为例

引入依赖(同XML方式)

数据库表和实体类(同XML方式)

业务层接口(同XML方式)和实现类

//实现类  加入相应注解
@Service("accountService")
@Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
public class AccountServiceImpl implements IAccountService {

    @Autowired
    private IAccountDao accountDao;

    @Transactional(readOnly=false,propagation=Propagation.REQUIRED)
    public void transfer(String sourceName, String targeName, Float money) {
        //1.根据名称查询两个账户
        Account source = accountDao.findAccountByName(sourceName);
        Account target = accountDao.findAccountByName(targeName);
		//2.修改两个账户的金额
        source.setMoney(source.getMoney()-money);//转出账户减钱
        target.setMoney(target.getMoney()+money);//转入账户加钱
		//3.更新两个账户
        accountDao.updateAccount(source);
        int i=1/0;  //抛出异常测试事务是否起作用
        accountDao.updateAccount(target);
    }
}

细节
该注解的属性和 xml 中的属性含义一致该注解可以出现在接口上类上和方法上
出现接口上表示该接口的所有实现类都有事务支持
出现在类上表示类中所有方法有事务支持
出现在方法上表示方法有事务支持
以上三个位置的优先级方法>>接口

Dao 接口(同XML)和实现类

//实现类,加入相应注解。不能使用继承JdbcDaoSupport的方式了
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    public Account findAccountByName(String name) {
        List<Account> list = jdbcTemplate.query("select * from account where name = ? ",
                                                new AccountRowMapper(),name
                                               );
        if(list.isEmpty()){
            return null;
        }
        if(list.size()>1){
            throw new RuntimeException("结果集不唯一,不是只有一个账户对象");
        }
        return list.get(0);
    }

    public void updateAccount(Account account) {
        jdbcTemplate.update("update account set money = ? where id = ? ",
                            account.getMoney(),account.getId()
                           );
    }
}

//账户的封装类 RowMapper 的实现类  AccountRowMapper也和XML方式相同
public class AccountRowMapper implements RowMapper {
    public Object mapRow(ResultSet resultSet, int i) throws SQLException {
        Account account = new Account();
        account.setId(resultSet.getInt("id"));
        account.setName(resultSet.getString("name"));
        account.setMoney(resultSet.getFloat("money"));
        return account;
    }
}

Spring配置文件

#数据库配置文件  同xml方式
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring
jdbc.username=root
jdbc.password=root
<?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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                           http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/tx
                           http://www.springframework.org/schema/tx/spring-tx.xsd
                           http://www.springframework.org/schema/context
                           http://www.springframework.org/schema/context/spring-context.xsd">
	<!-- 配置 spring 创建容器时要扫描的包 -->
    <context:component-scan base-package="com.mg"></context:component-scan>
    <!--引入配置文件-->
    <context:property-placeholder location="classpath:jdbcConfig.properties"/>
	<!--配置jdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
	<!--配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${jdbc.driver}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

	<!--Spring 基于xml的声明式事务控制配置步骤-->
	<!--1.配置事务管理器-->
    <bean id="transactionManager"
          class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
	<!-- 2.开启 spring 对注解事务的支持 -->
    <tx:annotation-driven transaction-manager="transactionManager"/>
	<!-- 3.在业务层使用@Transactional 注解,见service层代码 -->
</beans>

纯注解方式

基于上述注解方式,利用之前所学的知识,继续改造代码,去除Spring配置文件

引入依赖(同注解方式)

数据库表和实体类(同注解方式)

业务层接口和实现类(同注解方式)

Dao 接口和实现类 (同注解方式)

创建配置类,替换Spring的配置文件

#数据库配置文件  同注解方式
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring
jdbc.username=root
jdbc.password=root

JDBC相关配置类

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import javax.sql.DataSource;

@PropertySource("classpath:jdbcConfig.properties")
public class JdbcConfig {
    @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;

    @Bean(name = "dataSource")
    public DriverManagerDataSource createDataSource() {
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        return ds;
    }

    @Bean(name = "jdbcTemplate")
    public JdbcTemplate createJdbcTemplate(DataSource dataSource) {
        return new JdbcTemplate(dataSource);
    }
}

事务相关配置类

import org.springframework.context.annotation.Bean;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

import javax.sql.DataSource;

public class TransactionConfig {
    @Bean(name = "transactionManager")
    public PlatformTransactionManager createTransactionManager(DataSource dataSource) {
        return new DataSourceTransactionManager(dataSource);
    }
}

Spring 主配置类

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
@ComponentScan(basePackages = {"com.mg"})
@Import({JdbcConfig.class,TransactionConfig.class})
@EnableTransactionManagement //开启Spring注解事务的支持
public class SpringConfig {

}

打赏一个呗

取消

感谢您的支持,我会继续努力的!

扫码支持
扫码支持
扫码打赏,你说多少就多少

打开支付宝扫一扫,即可进行扫码打赏哦