Spring --15.Spring中基于xml的声明事务控制

版权声明:转载请注明原始链接 https://blog.csdn.net/sswqzx/article/details/83989650

开发环境:

jdk1.8

Idea 2017 :Maven工程、引入父工程

Tomcat:apache-tomcat-8

Spring:5.0.7

一、事务控制

1、概述

事务的概念:

事务是逻辑上一组操作、组成这组操作各个逻辑单元、要么一起成功、要么一起失败。

事务的特性:

原子性:事务不可分割

一致性:事务执行前后数据完整性保持一致

隔离性:一个事务的执行不应该受到其他事条的干扰

持久性:一旦事务结束、数据就持久化到数据库

2、事务并发读问题

脏读:一个事务读取到另一个事务未提交的数据

不可重复读:一个事务读取到另一个事务已提交的update的数据、导致一个事务中多次查询结果不一致

虚读(幻读):一个事务读取到另一个事务已提交的insert的数据、导致一个事务中多次查询结果不一致

解决读问题:设置事务隔离级别:

read uncommitted:未提交读、什么也解决不了

read committed :已提交读、解决脏读、解决不了不可重复读和虚读

repeatable  read :重复读、解决脏读和不可重复读

Serializable:串行化、解决所有读问题

3、Spring事务管理的API

3.1、PlatformTransactionManager:平台事务管理器

platformTransactionManager接口提供事务操作的方法包含3个具体的方法:

--获取事务状态信息:TransactionStatus  getTransaction(TransactionDefinition   definition)

--提交事务:void  commit(TransactionStatus  status)

--回滚事务:void  rollback(TransactionStatus  status)

platformTransactionManager实现类:

DataSourceTransactionManager --使用Spring JDBC或iBatis进行持久化数据时使用

扫描二维码关注公众号,回复: 4146320 查看本文章

3.2、TransactionDefinition:事务定义信息(接口)

事务定义:用于定义事务的相关的信息、隔离级别、超时信息、传播行为、是否可读

有如下方法:

(1)--String  getName()  -- 获取事务对象名称

(2)--int  getlsolationLevel() --获取事务的隔离级别

(3)--int  getPropagationBehavior()-- 获取事务传播行为 

(4)--int getTimeout()  --获取事务超时时间

(5)--boolean  isReadOnly() -- 获取事务是否只读

==================================================

--int  getlsolationLevel() --获取事务的隔离级别

--int  getPropagationBehavior()-- 获取事务传播行为 

Spring中提供了七种事务的传播行为:

      保证多个操作在同一个事务中

PROPAGATION_REQUIRED    :默认值,如果A中有事务,使用A中的事务,如果A没有,创建一个新的事务,将操作包含进来

PROPAGATION_SUPPORTS    :支持事务,如果A中有事务,使用A中的事务。如果A没有事务,不使用事务。

PROPAGATION_MANDATORY   :如果A中有事务,使用A中的事务。如果A没有事务,抛出异常。

     保证多个操作不在同一个事务中

PROPAGATION_REQUIRES_NEW    :如果A中有事务,将A的事务挂起(暂停),创建新事务,只包含自身操作。如果A中没有事务,创建一个新事务,包含自身操作。

PROPAGATION_NOT_SUPPORTED   :如果A中有事务,将A的事务挂起。不使用事务管理。

PROPAGATION_NEVER          :如果A中有事务,报异常。

    嵌套式事务

PROPAGATION_NESTED         :嵌套事务,如果A中有事务,按照A的事务执行,执行完成后,设置一个保存点,执行B中的操作,如果没有异常,执行通过,如果有异常,可以选择回滚到最初始位置,也可以回滚到保存点。

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

--boolean  isReadOnly() -- 建议查询设置为只读

3.3、TransactionStatus:事务的状态(接口)

事务状态:用于记录在事务管理过程中、事务的状态的对象

包含6个具体的方法:

--void  flush()  --刷新事务

--boolean hasSavePoint() -- 获取是否是存在存储点

--boolean  isCompleted() -- 获取事务是否完成

--boolean  isNewTransaction() --获取事务是否为新的事务

--boolean  isRollbackOnly() --获取事务是否回滚

--void  setRollbackOnly() --设置事务回滚

3.4、事务管理的API关系

Spring进行事务管理的时候、首先平台事务管理器根据事务定义信息进行事务的管理、在事务管理过程中、产生各种状态、

将这些状态的信息记录到事务状态的对象中

二、Spring基于xml的声明事条控制

1、创建工程、引入依赖

创建maven子模块、

导入依赖:Ioc依赖、aop依赖、jdbc模板+数据库连接池的依赖、Spring整合Junit单元测试依赖

pom.xml

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
    </dependency>
    <dependency>
        <groupId>org.slf4j</groupId>
        <artifactId>slf4j-log4j12</artifactId>
    </dependency>
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
    </dependency>
    <!-- jdbc模板 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
    </dependency>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
    </dependency>

    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    </dependencies>

2、编写相关类

domain/Account.java

package com.day04_tx.domain;

import java.io.Serializable;

/**
 * @ Author     :ShaoWei Sun.
 * @ Date       :Created in 8:50 2018/11/15
 */
public class Account implements Serializable {
    private Long id;
    private String name;
    private Double money;

    public Account(Long id, String name, Double money) {
        this.id = id;
        this.name = name;
        this.money = money;
    }

    public Account() {
    }

    public Long getId() {
        return id;
    }

    public void setId(Long 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 + '\'' +
                '}';
    }
}

重写RowMapper接口的实现类AccountRowMapper类、把数据库表中的一行数据形成帐户对象

AccouontRowMapper.java

package com.day04_tx.rowmapper;

import com.day04_tx.domain.Account;
import org.springframework.jdbc.core.RowMapper;

import java.sql.ResultSet;
import java.sql.SQLException;

/**
*把数据库表中的一行数据形成帐户对象
 * @ Author     :ShaoWei Sun.
 * @ Date       :Created in 8:51 2018/11/15
 */
public class AccountRowMapper implements RowMapper<Account> {
    @Override
    public Account mapRow(ResultSet resultSet, int row) throws SQLException {
        Account account = new Account();
        account.setId(resultSet.getLong("id"));
        account.setName(resultSet.getString("name"));
        account.setMoney(resultSet.getDouble("money"));
        return account;
    }
}

AccountDao.java接口

package com.day04_tx.dao;

import com.day04_tx.domain.Account;

/**
 * @ Author     :ShaoWei Sun.
 * @ Date       :Created in 9:36 2018/11/15
 */
public interface AccountDao {

    /**
     * 根据id查询用户对象
     * @param id
     * @return
     */
    public Account findById(Long id);

    /**
     * 更新用户对象
     * @param account
     */
    public void update(Account account);
}

编写AccountDaoImpl实现类、继承JdbcDaoSupport(xml中就不用注入jdbc了)

package com.day04_tx.dao.Impl;

import com.day04_tx.dao.AccountDao;
import com.day04_tx.domain.Account;
import com.day04_tx.rowmapper.AccountRowMapper;
import org.springframework.jdbc.core.support.JdbcDaoSupport;

/**
 * 继承JdbcDaoSupport 使用这个类中的getJdbcTemplate方法
 * @ Author     :ShaoWei Sun.
 * @ Date       :Created in 9:38 2018/11/15
 */
public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {

    /**
     * 根据id查询用户对象
     * @param id
     * @return
     */
    @Override
    public Account findById(Long id) {
        Account account = this.getJdbcTemplate().queryForObject("select * from account where id = ?", new AccountRowMapper(), id);
        return account;
    }

    /**
     * 更新用户对象
     * @param account
     */
    @Override
    public void update(Account account) {
        this.getJdbcTemplate().update("update account set name = ?, money = ? where id =? ",account.getName(),account.getMoney(),account.getId());
    }
}

AccountService.java接口

package com.day04_tx.service;

import com.day04_tx.domain.Account;

/**
 * @ Author     :ShaoWei Sun.
 * @ Date       :Created in 9:50 2018/11/15
 */
public interface AccountService {
    /**
     * 业务层:转账方法
     * @param fromId 转出账户
     * @param toId  转入账户
     * @param money 转帐金额
     */
    public abstract void transfer(Long fromId, Long toId, Double money);

    /**
     * 根据id查询账户对象
     * @param id 用户id
     * @return 返回Account
     */
    Account findById(Long id);
}

编写AccountServiceImpl实现类 :

package com.day04_tx.service.Imlp;

import com.day04_tx.dao.AccountDao;
import com.day04_tx.domain.Account;
import com.day04_tx.service.AccountService;

/**
 * @ Author     :ShaoWei Sun.
 * @ Date       :Created in 9:53 2018/11/15
 */
public class AccountServiceImpl implements AccountService {

    //依赖注入accountDao到Spring中
    private AccountDao accountDao;
    //通过set方法把AccountDao注入到Spring容器中
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Override
    public void transfer(Long fromId, Long toId, Double money) {
        //查询转出账户
        Account fromAccount = accountDao.findById(fromId);
        //查询转入帐户
        Account toAccount = accountDao.findById(toId);
        //转出帐户捡钱
        fromAccount.setMoney(fromAccount.getMoney()-money);
        //转入帐户加钱
        toAccount.setMoney(toAccount.getMoney()+money);
        //更新转出账户
        accountDao.update(fromAccount);
//        int i = 1/0;
        //更新转入账户
        accountDao.update(toAccount);
    }

    /**
     * 查询功能、现在做了修改操作、但不允许的。
     * @param id 用户id
     * @return
     */
    @Override
    public Account findById(Long id) {
        Account account = accountDao.findById(id);
        account.setMoney(100000d);
        accountDao.update(account);
        return account;
    }
}

3、把类交给Spring容器来管理

创建applicationContext.xml,并引入jdbc.properties,log4j.properties:

log4j.properties

### direct log messages to stdout ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### direct messages to file mylog.log ###
log4j.appender.file=org.apache.log4j.FileAppender
log4j.appender.file.File=D:/mylog.log
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=%d{ABSOLUTE} %5p %c{1}:%L - %m%n

### set log levels - for more verbose logging change 'info' to 'debug' ###

log4j.rootLogger=debug, stdout, file

jdbc.properties

jdbc.driverClass = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/mybatis
jdbc.username = root
jdbc.password = sswqzx

applicationContext.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"
       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">

        <!--导入jdbc-->
        <context:property-placeholder location="classpath:jdbc.properties"/>

        <bean id="accountService" class="com.day04_tx.service.Imlp.AccountServiceImpl">
            <property name="accountDao" ref="accountDao" > </property>
        </bean>

        <bean id="accountDao" class="com.day04_tx.dao.Impl.AccountDaoImpl">
            <property name="dataSource" ref="dataSource"></property>
        </bean>

        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${jdbc.driverClass}"></property>
            <property name="url" value="${jdbc.url}"></property>
            <property name="username" value="${jdbc.username}"></property>
            <property name="password" value="${jdbc.password}"></property>
         </bean>

</beans>

4、编写测试类

src/test/java/com.day04_tx.test/TestTx.java

package com.day04_tx.test;

import com.day04_tx.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * @ Author     :ShaoWei Sun.
 * @ Date       :Created in 10:15 2018/11/15
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Tx_test {
    @Autowired
    private AccountService accountService;
    //用了注解就不要用set方法了。不会生效
//    public void setAccountService(AccountService accountService) {
////        this.accountService = accountService;
////    }

    //事务测试方法
    @Test
    public void test1(){
        accountService.transfer(1L,2L,1000d);
    }

    //查询测试方法
    @Test
    public void test2(){
        accountService.findById(1L);
    }
}

运行Test1

注意:

如果在AccountServiceImpl中加入自定义的异常、张三钱转了。但王五没收到的情况、解决这种情况就要用到事务

5、在Spring中配置事务

上面的案例中、需要据transfer方法套在一个事务里、transfer中所有的操作要么成功、要么全部取消

applicationContext.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"
       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">

        <!--导入jdbc-->
        <context:property-placeholder location="classpath:jdbc.properties"/>

        <bean id="accountService" class="com.day04_tx.service.Imlp.AccountServiceImpl">
            <property name="accountDao" ref="accountDao" > </property>
        </bean>

        <bean id="accountDao" class="com.day04_tx.dao.Impl.AccountDaoImpl">
            <property name="dataSource" ref="dataSource"></property>
        </bean>

        <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
            <property name="driverClassName" value="${jdbc.driverClass}"></property>
            <property name="url" value="${jdbc.url}"></property>
            <property name="username" value="${jdbc.username}"></property>
            <property name="password" value="${jdbc.password}"></property>
         </bean>


        <!--配置事务管理器-->
        <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>
                <!--find开头的方法加只读事务、*表示通配符、匹配任意方法-->
                <tx:method name="find*" read-only="true"/>
                <!--其余方法是加可读写的事务-->
                <tx:method name="*"/>
            </tx:attributes>
        </tx:advice>


        <!--配置事务的切面-->
        <aop:config>
            <!--配置切入点表达式、告诉框架哪些方法要控制事务-->
            <aop:pointcut id="pt" expression="execution(* com.day04_tx.service.Imlp.*.*(..))"/>
            <!--将定义好的事务属性应用到上述切入点-->
            <aop:advisor advice-ref="txAdvice" pointcut-ref="pt"/>
        </aop:config>

</beans>

测试事务是否成功、

再次运行TestTx中的test1方法,控制台依然会报“被零除异常”,但是表中的数据没有发生变化:张三的钱没有转,王五的钱也没有变化。

测试只读事务

在AcountService接口中增加一个findById方法:根据id查询账户对象

package com.day04_tx.service;

import com.day04_tx.domain.Account;

/**
 * @ Author     :ShaoWei Sun.
 * @ Date       :Created in 9:50 2018/11/15
 */
public interface AccountService {
    /**
     * 业务层:转账方法
     * @param fromId 转出账户
     * @param toId  转入账户
     * @param money 转帐金额
     */
    public abstract void transfer(Long fromId, Long toId, Double money);

    /**
     * 根据id查询账户对象
     * @param id 用户id
     * @return 返回Account
     */
    Account findById(Long id);
}

在AccountServiceImpl中实现findById方法 :

package com.day04_tx.service.Imlp;

import com.day04_tx.dao.AccountDao;
import com.day04_tx.domain.Account;
import com.day04_tx.service.AccountService;

/**
 * @ Author     :ShaoWei Sun.
 * @ Date       :Created in 9:53 2018/11/15
 */
public class AccountServiceImpl implements AccountService {

    //依赖注入accountDao到Spring中
    private AccountDao accountDao;
    //通过set方法把AccountDao注入到Spring容器中
    public void setAccountDao(AccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Override
    public void transfer(Long fromId, Long toId, Double money) {
        //查询转出账户
        Account fromAccount = accountDao.findById(fromId);
        //查询转入帐户
        Account toAccount = accountDao.findById(toId);
        //转出帐户捡钱
        fromAccount.setMoney(fromAccount.getMoney()-money);
        //转入帐户加钱
        toAccount.setMoney(toAccount.getMoney()+money);
        //更新转出账户
        accountDao.update(fromAccount);
        int i = 1/0;
        //更新转入账户
        accountDao.update(toAccount);
    }

    /**
     * 查询功能、现在做了修改操作、但不允许的。
     * @param id 用户id
     * @return
     */
    @Override
    public Account findById(Long id) {
        Account account = accountDao.findById(id);
        account.setMoney(100000d);
        accountDao.update(account);
        return account;
    }


}

在单元测试类TestTx中创建第二个单元测试方法test2,测试findById方法 :

package com.day04_tx.test;

import com.day04_tx.service.AccountService;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

/**
 * @ Author     :ShaoWei Sun.
 * @ Date       :Created in 10:15 2018/11/15
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class Tx_test {
    @Autowired
    private AccountService accountService;
    //用了注解就不要用set方法了。不会生效
//    public void setAccountService(AccountService accountService) {
////        this.accountService = accountService;
////    }

    //事务测试方法
    @Test
    public void test1(){
        accountService.transfer(1L,2L,1000d);
    }

    //查询测试方法
    @Test
    public void test2(){
        accountService.findById(1L);
    }
}

运行test2方法,发现控制台报错,异常信息如下:

猜你喜欢

转载自blog.csdn.net/sswqzx/article/details/83989650