Spring 事务控制

1. Spring 事务控制说明

  1. JavaEE 体系进行分层开发,事务处理位于业务层,Spring 提供了分层设计业务层的事务处理解决方案。
  2. Spring 框架为我们提供了一组事务控制的接口。这组接口是在 spring-tx-5.0.2.RELEASE.jar 中。
  3. Spring 的事务控制都是基于 AOP 的,它既可以使用编程的方式实现,也可以使用配置的方式实现。我们学习的重点是使用配置的方式实现。

2. Spring 事务控制的 API

2.1 PlatformTransactionManager

  1. 此接口是 Spring 的事务管理器,提供了如下方法:

    • 获取事务状态信息
      • TransactionStatus getTransaction(TransactionDefinition definition)
    • 提交事务
      • void commit(TransactionStatus status)
    • 回滚事务
      • void rollback(TransactionStatus status)
  2. 我们在实际开发中都是使用它的实现类

    • org.springframework.jdbc.datasource.DataSourceTransactionManager

2.2 TransactionDefinition

  1. 此接口是事务的定义信息对象,提供了如下方法:

    • 获取事务对象名称
      • String getName()
    • 获取事务隔离级别
      • int getIsolationLevel()
    • 获取事务传播行为
      • int getPropagationBehavior()
    • 获取事务超时时间
      • int getTimeout()
    • 获取事务是否只读
      • boolean isReadOnly()
  2. 事务的隔离级别

    • 默认级别,归属下列某一种(和数据库一致)
      • ISOLATION_DEFAULT
    • 可以读取未提交数据
      • SOLATION_READ_UNCOMMITTED
    • 只能读取已提交数据,解决脏读问题(Oracle 默认级别)
      • ISOLATION_READ_COMMITTED
    • 在一次事务中,不能读到取其他事务提交的数据,保证了重复读数据的一致性,解决不可重复读问题(MySQL 默认级别)
      • ISOLATION_REPEATABLE_READ
  3. 事务的传播行为
    事务传播行为(propagation behavior)指的就是当一个事务方法被另一个事务方法调用时,这个事务方法应该如何进行。

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

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

  5. 是否是只读事务

    • 读写型事务配置(一般用于增删改方法)

    • 只读型事务配置(一般用于查询方法)

2.3 TransactionStatus

  1. 此接口提供的是事务具体的运行状态,提供了如下方法:
    • 刷新事务
      • void flush()
    • 获取是否存在存储点
      • boolean hasSavepoint()
    • 获取事务是否完成
      • boolean isCompleted()
    • 获取事务是否为新的事务
      • boolean isNewTransaction()
    • 获取事务是否回滚
      • boolean isRollbackOnly()
    • 设置事务回滚
      • void setRollbackOnly()

3. 基于 XML 的声明式事务控制

3.1 环境搭配

  1. 导入依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.32</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.13</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.9</version>
        </dependency>
    </dependencies>
    
  2. 准备数据库和表

    create table account(
    	id int primary key auto_increment,
    	name varchar(40),
    	money double
    )character set utf8 collate utf8_general_ci;
    
  3. 账户实体类

    /**
     * 账户实体类
     */
    public class Account implements Serializable {
        private Integer id;
        private String name;
        private Double money;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Double getMoney() {
            return money;
        }
    
        public void setMoney(Double money) {
            this.money = money;
        }
    
        @Override
        public String toString() {
            return "Account{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", money=" + money +
                    '}';
        }
    }
    
    
  4. 编写业务层接口和实现类

    AccountService 接口

    /**
     * 账户的业务层接口
     */
    public interface AccountService {
        /**
         * 查询所有
         * @return
         */
        List<Account> findAllAccount();
    
        /**
         * 查询一个
         * @param id
         * @return
         */
        Account findAccountById(Integer id);
    
    
        /**
         * 保存操作
         * @param account
         */
        void saveAccount(Account account);
    
        /**
         * 更新操作
         * @param account
         */
        void updateAccount(Account account);
    
        /**
         * 删除操作
         * @param id
         */
        void deleteAccount(Integer id);
    
        /**
         * 转账操作
         * @param sourceName 转出账户
         * @param targetName 转入账户
         * @param money 转账金额
         */
        void transfer(String sourceName, String targetName, double money);
    }
    
    

    AccountServiceImpl_OLD

    /**
     * 账户业务层实现类
     */
    public class AccountServiceImpl_OLD implements AccountService {
    
        private AccountDao accountDao;
    
        public void setAccountDao(AccountDao accountDao) {
            this.accountDao = accountDao;
        }
    
        public List<Account> findAllAccount() {
            return accountDao.findAllAccount();
        }
    
        public Account findAccountById(Integer id) {
            return accountDao.findAccountById(id);
        }
    
        public void saveAccount(Account account) {
            accountDao.saveAccount(account);
        }
    
        public void updateAccount(Account account) {
            accountDao.updateAccount(account);
        }
    
        public void deleteAccount(Integer id) {
            accountDao.deleteAccount(id);
    
        }
    
        public void transfer(String sourceName, String targetName, double money) {
            // 2.1 查询两个账户信息
            Account source = accountDao.findAccountByName(sourceName);
            Account target = accountDao.findAccountByName(targetName);
            // 2.2 转出账户减钱,转入帐户加钱
            source.setMoney(source.getMoney() - money);
            target.setMoney(target.getMoney() + money);
            // 2.3 更新操作
            accountDao.updateAccount(source);
            // 2.4 模拟转账异常
            int i = 3 / 0;
            accountDao.updateAccount(target);
        }
    
    }
    
    
  5. 编写 DAO 接口和实现类

    AccountDao 接口

    /**
     * 账户的持久层接口
     */
    public interface AccountDao {
        /**
         * 查询所有
         * @return
         */
        List<Account> findAllAccount();
    
        /**
         * 查询一个
         * @param id
         * @return
         */
        Account findAccountById(Integer id);
    
        /**
         * 根据名称查询账户
         * @param accountName
         * @return
         */
        Account findAccountByName(String accountName);
    
        /**
         * 保存操作
         * @param account
         */
        void saveAccount(Account account);
    
        /**
         * 更新操作
         * @param account
         */
        void updateAccount(Account account);
    
        /**
         * 删除操作
         * @param id
         */
        void deleteAccount(Integer id);
    
    
    }
    
    

    AccountDaoImpl

    /**
     * 账户的持久层实现类
     */
    public class AccountDaoImpl implements AccountDao {
     private JdbcTemplate jdbcTemplate;
    
        public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
            this.jdbcTemplate = jdbcTemplate;
        }
    
        public List<Account> findAllAccount() {
            String sql = "select * from account";
            List<Account> accounts = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Account>(Account.class));
            return accounts;
        }
    
        public Account findAccountById(Integer id) {
            String sql = "select * from account where id = ?";
            Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Account>(Account.class), id);
            return account;
        }
    
        public Account findAccountByName(String accountName) {
            String sql = "select * from account where name = ?";
            Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Account>(Account.class), accountName);
            return account;
        }
    
        public void saveAccount(Account account) {
            String sql = "insert into account values(null,?,?)";
            jdbcTemplate.update(sql, account.getName(), account.getMoney());
        }
    
        public void updateAccount(Account account) {
            String sql = "update account set name = ? , money = ? where id = ?";
            jdbcTemplate.update(sql, account.getName(), account.getMoney(), account.getId());
        }
    
        public void deleteAccount(Integer id) {
            String sql = "delete from account where id = ?";
            jdbcTemplate.update(sql,id);
        }
    }
    
    
  6. 配置文件

    <?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/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">
    
        <!--配置 IOC -->
        <!--配置 accountService_OLD 对象-->
        <bean id="accountService_OLD" class="com.zt.service.impl.AccountServiceImpl_OLD">
            <property name="accountDao" ref="accountDao"></property>
        </bean>
    
        <!--配置 dao 对象-->
        <bean id="accountDao" class="com.zt.dao.impl.AccountDaoImpl">
            <property name="jdbcTemplate" ref="jdbcTemplate"></property>
        </bean>
    
        <!--配置 jdbcTemplate 对象-->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" scope="prototype" >
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    
        <!--配置 dataSource 对象-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
            <property name="url" value="jdbc:mysql://127.0.0.1:3306/spring_day02"></property>
            <property name="username" value="root"></property>
            <property name="password" value="123456"></property>
            <property name="initialSize" value="5"></property>
            <property name="maxActive" value="10"></property>
            <property name="maxWait" value="3000"></property>
        </bean>
    </beans>
    

3.2 配置声明式事务

3.2.1 配置声明式事务的步骤

  1. 配置事务管理器

    使用 bean 标签配置事务管理器,并传入一个数据源

    • id:给事务管理器起一个唯一标识
    • class:一般使用 DataSourceTransactionManager
  2. 配置事务的通知
    使用 tx:advice 标签配置事务通知

    • id:给事务通知起一个唯一标识
    • transaction-manager:给事务通知提供一个事务管理器引用
  3. 配置 AOP,配置通用切入点表达式

  4. 建立事务通知和切入点表达式的对应关系

  5. 配置事务的属性
    是在事务的通知 tx:advice 标签的内部

    • isolation:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级
    • propagation:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择 SUPPORTS。
    • read-only:用于指定事务是否只读。只有查询方法才能设置为 true。默认值是 false,表示读写。
    • timeout:用于指定事务的超时时间,默认值是 -1,表示永不超时。如果指定了数值,以秒为单位。
    • rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
    • no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。

3.2.2 配置声明式事务的实例

<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!--配置事务的通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <!--配置事务的属性-->
    <tx:attributes>
        <tx:method name="*" propagation="REQUIRED" read-only="false"></tx:method>
        <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
    </tx:attributes>
</tx:advice>

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

3.3 测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations= {"classpath:bean.xml"})
public class AccountService_OLDTest {
    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer(){
        accountService.transfer("aaa","bbb",500);
    }
}

4. 基于注解的声明式事务控制

4.1 环境搭建

  1. 导入依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.32</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.13</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.9</version>
        </dependency>
    </dependencies>
    
  2. 准备数据库和表

    create table account(
    	id int primary key auto_increment,
    	name varchar(40),
    	money double
    )character set utf8 collate utf8_general_ci;
    
  3. 账户实体类

    /**
     * 账户实体类
     */
    public class Account implements Serializable {
        private Integer id;
        private String name;
        private Double money;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Double getMoney() {
            return money;
        }
    
        public void setMoney(Double money) {
            this.money = money;
        }
    
        @Override
        public String toString() {
            return "Account{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", money=" + money +
                    '}';
        }
    }
    
    
  4. 编写业务层接口和实现类

    AccountService 接口

    /**
     * 账户的业务层接口
     */
    public interface AccountService {
        /**
         * 查询所有
         * @return
         */
        List<Account> findAllAccount();
    
        /**
         * 查询一个
         * @param id
         * @return
         */
        Account findAccountById(Integer id);
    
    
        /**
         * 保存操作
         * @param account
         */
        void saveAccount(Account account);
    
        /**
         * 更新操作
         * @param account
         */
        void updateAccount(Account account);
    
        /**
         * 删除操作
         * @param id
         */
        void deleteAccount(Integer id);
    
        /**
         * 转账操作
         * @param sourceName 转出账户
         * @param targetName 转入账户
         * @param money 转账金额
         */
        void transfer(String sourceName, String targetName, double money);
    }
    
    

    AccountServiceImpl_OLD

    /**
     * 账户业务层实现类
     */
    @Service("accountService")
    public class AccountServiceImpl_OLD implements AccountService {
    
        @Autowired
        private AccountDao accountDao;
    
        public List<Account> findAllAccount() {
            return accountDao.findAllAccount();
        }
    
        public Account findAccountById(Integer id) {
            return accountDao.findAccountById(id);
        }
    
        public void saveAccount(Account account) {
            accountDao.saveAccount(account);
        }
    
        public void updateAccount(Account account) {
            accountDao.updateAccount(account);
        }
    
        public void deleteAccount(Integer id) {
            accountDao.deleteAccount(id);
    
        }
    
        public void transfer(String sourceName, String targetName, double money) {
            // 2.1 查询两个账户信息
            Account source = accountDao.findAccountByName(sourceName);
            Account target = accountDao.findAccountByName(targetName);
            // 2.2 转出账户减钱,转入帐户加钱
            source.setMoney(source.getMoney() - money);
            target.setMoney(target.getMoney() + money);
            // 2.3 更新操作
            accountDao.updateAccount(source);
            // 2.4 模拟转账异常
            // int i = 3 / 0;
            accountDao.updateAccount(target);
        }
    
    }
    
  5. 编写 DAO 接口和实现类

    AccountDao 接口

    /**
     * 账户的持久层接口
     */
    public interface AccountDao {
        /**
         * 查询所有
         * @return
         */
        List<Account> findAllAccount();
    
        /**
         * 查询一个
         * @param id
         * @return
         */
        Account findAccountById(Integer id);
    
        /**
         * 根据名称查询账户
         * @param accountName
         * @return
         */
        Account findAccountByName(String accountName);
    
        /**
         * 保存操作
         * @param account
         */
        void saveAccount(Account account);
    
        /**
         * 更新操作
         * @param account
         */
        void updateAccount(Account account);
    
        /**
         * 删除操作
         * @param id
         */
        void deleteAccount(Integer id);
    
    
    }
    

    AccountDaoImpl

    /**
     * 账户的持久层实现类
     */
    @Repository("accountDao")
    public class AccountDaoImpl implements AccountDao {
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        public List<Account> findAllAccount() {
            String sql = "select * from account";
            List<Account> accounts = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Account>(Account.class));
            return accounts;
        }
    
        public Account findAccountById(Integer id) {
            String sql = "select * from account where id = ?";
            Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Account>(Account.class), id);
            return account;
        }
    
        public Account findAccountByName(String accountName) {
            String sql = "select * from account where name = ?";
            Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Account>(Account.class), accountName);
            return account;
        }
    
        public void saveAccount(Account account) {
            String sql = "insert into account values(null,?,?)";
            jdbcTemplate.update(sql, account.getName(), account.getMoney());
        }
    
        public void updateAccount(Account account) {
            String sql = "update account set name = ? , money = ? where id = ?";
            jdbcTemplate.update(sql, account.getName(), account.getMoney(), account.getId());
        }
    
        public void deleteAccount(Integer id) {
            String sql = "delete from account where id = ?";
            jdbcTemplate.update(sql,id);
        }
    }
    
    
  6. 配置文件

    <?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"
           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/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">
    
        <!--配置 IOC -->
        <!--告知 spring 在创建容器时要扫描的包-->
        <context:component-scan base-package="com.zt"></context:component-scan>
    
        <!--配置 jdbcTemplate 对象-->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" scope="prototype" >
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    
        <!--配置 dataSource 对象-->
        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
            <property name="url" value="jdbc:mysql://127.0.0.1:3306/spring_day02"></property>
            <property name="username" value="root"></property>
            <property name="password" value="123456"></property>
            <property name="initialSize" value="5"></property>
            <property name="maxActive" value="10"></property>
            <property name="maxWait" value="3000"></property>
        </bean>
    </beans>
    

4.2 配置声明式事务

4.2.1 配置声明式事务的步骤

  1. 配置事务管理器
  2. 开启 spring 对注解事务的支持
  3. 在需要事务支持的地方使用 @Transactional 注解

4.2.2 配置声明式事务的实例

<!--配置事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"></property>
</bean>

<!-- 开启spring对注解事务的支持-->
<tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>

/**
 * 账户业务层实现类
 */
@Service("accountService")
@Transactional(propagation = Propagation.REQUIRED, readOnly = false)
public class AccountServiceImpl_OLD implements AccountService {

    @Autowired
    private AccountDao accountDao;

    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    public List<Account> findAllAccount() {
        return accountDao.findAllAccount();
    }

    @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
    public Account findAccountById(Integer id) {
        return accountDao.findAccountById(id);
    }

    public void saveAccount(Account account) {
        accountDao.saveAccount(account);
    }

    public void updateAccount(Account account) {
        accountDao.updateAccount(account);
    }

    public void deleteAccount(Integer id) {
        accountDao.deleteAccount(id);

    }

    public void transfer(String sourceName, String targetName, double money) {
        // 2.1 查询两个账户信息
        Account source = accountDao.findAccountByName(sourceName);
        Account target = accountDao.findAccountByName(targetName);
        // 2.2 转出账户减钱,转入帐户加钱
        source.setMoney(source.getMoney() - money);
        target.setMoney(target.getMoney() + money);
        // 2.3 更新操作
        accountDao.updateAccount(source);
        // 2.4 模拟转账异常
        // int i = 3 / 0;
        accountDao.updateAccount(target);
    }

}

4.3 测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations= {"classpath:bean.xml"})
public class AccountService_OLDTest {
    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer(){
        accountService.transfer("aaa","bbb",500);
    }
}

5. 基于纯注解的声明式事务控制

5.1 环境搭建

  1. 导入依赖

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.32</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.13</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.0.9</version>
        </dependency>
    </dependencies>
    
  2. 准备数据库和表

    create table account(
    	id int primary key auto_increment,
    	name varchar(40),
    	money double
    )character set utf8 collate utf8_general_ci;
    
  3. 账户实体类

    /**
     * 账户实体类
     */
    public class Account implements Serializable {
        private Integer id;
        private String name;
        private Double money;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Double getMoney() {
            return money;
        }
    
        public void setMoney(Double money) {
            this.money = money;
        }
    
        @Override
        public String toString() {
            return "Account{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", money=" + money +
                    '}';
        }
    }
    
    
  4. 编写业务层接口和实现类

    AccountService 接口

    /**
     * 账户的业务层接口
     */
    public interface AccountService {
        /**
         * 查询所有
         * @return
         */
        List<Account> findAllAccount();
    
        /**
         * 查询一个
         * @param id
         * @return
         */
        Account findAccountById(Integer id);
    
    
        /**
         * 保存操作
         * @param account
         */
        void saveAccount(Account account);
    
        /**
         * 更新操作
         * @param account
         */
        void updateAccount(Account account);
    
        /**
         * 删除操作
         * @param id
         */
        void deleteAccount(Integer id);
    
        /**
         * 转账操作
         * @param sourceName 转出账户
         * @param targetName 转入账户
         * @param money 转账金额
         */
        void transfer(String sourceName, String targetName, double money);
    }
    
    

    AccountServiceImpl_OLD

    /**
     * 账户业务层实现类
     */
    @Service("accountService")
    public class AccountServiceImpl_OLD implements AccountService {
    
        @Autowired
        private AccountDao accountDao;
    
        public List<Account> findAllAccount() {
            return accountDao.findAllAccount();
        }
    
        public Account findAccountById(Integer id) {
            return accountDao.findAccountById(id);
        }
    
        public void saveAccount(Account account) {
            accountDao.saveAccount(account);
        }
    
        public void updateAccount(Account account) {
            accountDao.updateAccount(account);
        }
    
        public void deleteAccount(Integer id) {
            accountDao.deleteAccount(id);
    
        }
    
        public void transfer(String sourceName, String targetName, double money) {
            // 2.1 查询两个账户信息
            Account source = accountDao.findAccountByName(sourceName);
            Account target = accountDao.findAccountByName(targetName);
            // 2.2 转出账户减钱,转入帐户加钱
            source.setMoney(source.getMoney() - money);
            target.setMoney(target.getMoney() + money);
            // 2.3 更新操作
            accountDao.updateAccount(source);
            // 2.4 模拟转账异常
            // int i = 3 / 0;
            accountDao.updateAccount(target);
        }
    
    }
    
  5. 编写 DAO 接口和实现类

    AccountDao 接口

    /**
     * 账户的持久层接口
     */
    public interface AccountDao {
        /**
         * 查询所有
         * @return
         */
        List<Account> findAllAccount();
    
        /**
         * 查询一个
         * @param id
         * @return
         */
        Account findAccountById(Integer id);
    
        /**
         * 根据名称查询账户
         * @param accountName
         * @return
         */
        Account findAccountByName(String accountName);
    
        /**
         * 保存操作
         * @param account
         */
        void saveAccount(Account account);
    
        /**
         * 更新操作
         * @param account
         */
        void updateAccount(Account account);
    
        /**
         * 删除操作
         * @param id
         */
        void deleteAccount(Integer id);
    
    
    }
    

    AccountDaoImpl

    /**
     * 账户的持久层实现类
     */
    @Repository("accountDao")
    public class AccountDaoImpl implements AccountDao {
        @Autowired
        private JdbcTemplate jdbcTemplate;
    
        public List<Account> findAllAccount() {
            String sql = "select * from account";
            List<Account> accounts = jdbcTemplate.query(sql, new BeanPropertyRowMapper<Account>(Account.class));
            return accounts;
        }
    
        public Account findAccountById(Integer id) {
            String sql = "select * from account where id = ?";
            Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Account>(Account.class), id);
            return account;
        }
    
        public Account findAccountByName(String accountName) {
            String sql = "select * from account where name = ?";
            Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<Account>(Account.class), accountName);
            return account;
        }
    
        public void saveAccount(Account account) {
            String sql = "insert into account values(null,?,?)";
            jdbcTemplate.update(sql, account.getName(), account.getMoney());
        }
    
        public void updateAccount(Account account) {
            String sql = "update account set name = ? , money = ? where id = ?";
            jdbcTemplate.update(sql, account.getName(), account.getMoney(), account.getId());
        }
    
        public void deleteAccount(Integer id) {
            String sql = "delete from account where id = ?";
            jdbcTemplate.update(sql,id);
        }
    }
    
    
  6. 在 java 包下创建 config 包

    1. 创建 SpringConfiguration

      /**
       * spring 的配置类,相当于 bean.xml
       */
      @Configuration
      @ComponentScan("com.zt")
      @Import({JdbcConfig.class})
      @PropertySource("druid.properties")
      public class SpringConfiguration {
      }
      
      
    2. 创建 JdbcConfig

      /**
       * 连接数据库相关的配置类
       */
      public class JdbcConfig {
          @Value("${driverClassName}")
          private String driverClassName;
          @Value("${url}")
          private String url;
          @Value("${name}")
          private String name;
          @Value("${password}")
          private String password;
          @Value("${initialSize}")
          private int initialSize;
          @Value("${maxActive}")
          private int maxActive;
          @Value("${maxWait}")
          private int maxWait;
      
          @Bean("jdbcTemplate")
          @Scope("prototype")
          public JdbcTemplate createJdbcTemplate(DataSource dataSource){
              return new JdbcTemplate(dataSource);
          }
      
          @Bean("dataSource")
          public DataSource createDataSource(){
              DruidDataSource druidDataSource = new DruidDataSource();
              druidDataSource.setDriverClassName(driverClassName);
              druidDataSource.setUrl(url);
              druidDataSource.setUsername(name);
              druidDataSource.setPassword(password);
              druidDataSource.setInitialSize(initialSize);
              druidDataSource.setMaxActive(maxActive);
              druidDataSource.setMaxWait(maxWait);
      
              return druidDataSource;
          }
      }
      
      
    3. 在 resouces 创建 druid.properties

      driverClassName=com.mysql.jdbc.Driver
      url=jdbc:mysql://127.0.0.1:3306/spring_day02
      name=root
      password=123456
      initialSize=5
      maxActive=10
      maxWait=3000
      

5.2 配置声明式事务

5.2.1 配置声明式事务的步骤

  1. 配置事务管理器
  2. 开启 spring 对注解事务的支持
  3. 在需要事务支持的地方使用 @Transactional 注解

5.2.2 配置声明式事务的实例

  1. 配置事务管理器

    在 config 包下创建 TransactionConfig,并在 SpringConfiguration 类中引入

    /**
     * 和事务相关的配置类
     */
    public class TransactionConfig {
        /**
         * 用于创建事务管理器对象
         * @param dataSource
         * @return
         */
        @Bean(name="transactionManager")
        public PlatformTransactionManager createTransactionManager(DataSource dataSource){
            return new DataSourceTransactionManager(dataSource);
        }
    }
    
    
  2. 开启 spring 对注解事务的支持

    在 SpringConfiguration 类上添加注解 @EnableTransactionManagement

    /**
     * spring 的配置类,相当于 bean.xml
     */
    @Configuration
    @ComponentScan("com.zt")
    @Import({JdbcConfig.class,TransactionConfig.class})
    @PropertySource("druid.properties")
    @EnableTransactionManagement
    public class SpringConfiguration {
    }
    
  3. 在需要事务支持的地方使用 @Transactional 注解

    /**
     * 账户业务层实现类
     */
    @Service("accountService")
    @Transactional(propagation = Propagation.REQUIRED, readOnly = false)
    public class AccountServiceImpl_OLD implements AccountService {
    
        @Autowired
        private AccountDao accountDao;
    
        @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
        public List<Account> findAllAccount() {
            return accountDao.findAllAccount();
        }
    
        @Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
        public Account findAccountById(Integer id) {
            return accountDao.findAccountById(id);
        }
    
        public void saveAccount(Account account) {
            accountDao.saveAccount(account);
        }
    
        public void updateAccount(Account account) {
            accountDao.updateAccount(account);
        }
    
        public void deleteAccount(Integer id) {
            accountDao.deleteAccount(id);
    
        }
    
        public void transfer(String sourceName, String targetName, double money) {
            // 2.1 查询两个账户信息
            Account source = accountDao.findAccountByName(sourceName);
            Account target = accountDao.findAccountByName(targetName);
            // 2.2 转出账户减钱,转入帐户加钱
            source.setMoney(source.getMoney() - money);
            target.setMoney(target.getMoney() + money);
            // 2.3 更新操作
            accountDao.updateAccount(source);
            // 2.4 模拟转账异常
            // int i = 3 / 0;
            accountDao.updateAccount(target);
        }
    
    }
    

5.3 测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountService_OLDTest {
    @Autowired
    private AccountService accountService;

    @Test
    public void testTransfer(){
        accountService.transfer("aaa","bbb",500);
    }
}
  • 1
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

bm1998

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值