动力节点Spring (16-17)

⼗六、Spring对事务的⽀持

16.1 事务概述

什么是事务
        ○ 在⼀个业务流程当中,通常需要多条DML(insert delete update)语句共同联合才能完成,这多条DML语句必须同时成功,或者同时失败,这样才能保证数据的安全。
        ○ 多条DML要么同时成功,要么同时失败,这叫做事务。
        ○ 事务:Transaction(tx)
事务的四个处理过程:
        ○ 第⼀步:开启事务 (start transaction)
        ○ 第⼆步:执⾏核⼼业务代码
        ○ 第三步:提交事务(如果核⼼业务处理过程中没有出现异常)(commit transaction)
        ○ 第四步:回滚事务(如果核⼼业务处理过程中出现异常)(rollback transaction)
事务的四个特性:
        ○ A 原⼦性:事务是最⼩的⼯作单元,不可再分。
        ○ C ⼀致性:事务要求要么同时成功,要么同时失败。事务前和事务后的总量不变。
        ○ I 隔离性:事务和事务之间因为有隔离性,才可以保证互不⼲扰。
        ○ D 持久性:持久性是事务结束的标志。

16.2 引⼊事务场景

以银⾏账户转账为例学习事务。两个账户act- 001 和act- 002 。act- 001 账户向act- 002 账户转账 10000 ,必须同时成功,或者同时失败。(⼀个减成功,⼀个加成功, 这两条update语句必须同时成功,或同时失败。)
连接数据库的技术采⽤Spring框架的JdbcTemplate。
采⽤三层架构搭建:

 模块名:spring6-013-tx-bank(依赖如下)

    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>6.0.0</version>
        </dependency>
        <!--spring jdbc-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.0</version>
        </dependency>
        <!--mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.31</version>
        </dependency>
        <!--德鲁伊连接池-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.13</version>
        </dependency>
        <!--@Resource注解-->
        <dependency>
            <groupId>jakarta.annotation</groupId>
            <artifactId>jakarta.annotation-api</artifactId>
            <version>2.1.1</version>
        </dependency>
    </dependencies>
第⼀步:准备数据库表

第⼆步:创建包结构  

第三步:准备POJO类 

public class Account {
    private String actno;
    private Double balance;

    public Account(){}

    public Account(String actno, Double balance) {
        this.actno = actno;
        this.balance = balance;
    }
    //get,set,toString方法
}
第四步:编写持久层
AccountDao接口:
/*
专门负责账户信息的CRUD操作
DAO中只执行SQL语句,没有任何业务逻辑
也就是说DAO不和业务挂钩。
*/
public interface AccountDao {
    //根据账号查询信息
    Account selectByActno(String actno);

    //更新账户信息
    int update(Account act);


}
AccountDaoImpl类:
@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
    @Resource(name="jdbcTemplate")
    private JdbcTemplate jdbcTemplate;

    @Override
    public Account selectByActno(String actno) {
        String sql = "select actno,balance from t_act where actno=?";
        Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Account.class), actno);
        return account;
    }

    @Override
    public int update(Account act) {
        String sql = "update t_act set balance=? where actno=?";
        int count = jdbcTemplate.update(sql, act.getBalance(),act.getActno());
        return count;
    }
}

第五步:编写业务层
AccountService接口:
/*
* 业务接口
* 事物就是在这个接口下控制的
* */
public interface AccountService {
    //转账业务方法
    void transfer(String fromActno,String toActno,double money);

}
AccountServiceImpl类:
@Service("accountService")
public class AccountServiceImpl implements AccountService {
    @Resource(name="accountDao")
    private AccountDao accountDao;

    //控制事物,因为在这个方法中要完成所有的转账业务
    @Override
    public void transfer(String fromActno, String toActno, double money) {
        //查询转出账户的余额是否充足
        Account fromAct = accountDao.selectByActno(fromActno);
        if (fromAct.getBalance() <money) {
            throw new RuntimeException("余额不足!");
        }

        //余额充足
        Account toAct = accountDao.selectByActno(toActno);

        //将内存中的两个对象的余额先修改
        fromAct.setBalance(fromAct.getBalance()-money);
        toAct.setBalance((toAct.getBalance()+money));

        //数据库更新
        int count = accountDao.update(fromAct);
        count+= accountDao.update(toAct);

        if(count!=2){
            throw new RuntimeException("转账失败,联系银行!");
        }

    }
}
第六步:编写Spring配置⽂件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       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">


    <!--组件扫描-->
    <context:component-scan base-package="com.powernode.bank"/>


    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
        <property name="username" value="root"/>
        <property name="password" value="r38153"/>
    </bean>
    <!--配置JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>


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

    <!--开启事物注解驱动器,开启事物注解。告诉Spring框架,采用注解的方式去控制事物-->
    <tx:annotation-driven transaction-manager="txManager"/>
</beans>

第七步:编写表示层(测试程序)
public class SpringTxTest {
    @Test
    public void testSpringTx(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
        try{
            accountService.transfer("act-001","act-002",10000);
            System.out.println("转账成功");
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

模拟异常
@Service("accountService")
@Transactional
public class AccountServiceImpl implements AccountService {
    @Resource(name="accountDao")
    private AccountDao accountDao;

    //控制事物,因为在这个方法中要完成所有的转账业务
    @Override
    //@Transactional
    public void transfer(String fromActno, String toActno, double money) {
        //第一步:开启事物

        //第二步:执行核心业务逻辑
        //查询转出账户的余额是否充足
        Account fromAct = accountDao.selectByActno(fromActno);
        if (fromAct.getBalance() <money) {
            throw new RuntimeException("余额不足!");
        }

        //余额充足
        Account toAct = accountDao.selectByActno(toActno);

        //将内存中的两个对象的余额先修改
        fromAct.setBalance(fromAct.getBalance()-money);
        toAct.setBalance((toAct.getBalance()+money));

        //数据库更新
        int count = accountDao.update(fromAct);

        //模拟异常
//        String s=null;
//        s.toString();


        count+= accountDao.update(toAct);

        if(count!=2){
            throw new RuntimeException("转账失败,联系银行!");
        }

        //第三步:如果执行业务过程中没有异常,提交事物
        //第四步:如果执行业务过程中有异常,回滚事物

    }
}

数据库表中数据: 发现少了1W

16.3 Spring对事务的⽀持 

Spring实现事务的两种⽅式
编程式事务
        ○ 通过编写代码的⽅式来实现事务的管理。
声明式事务
        ○ 基于注解⽅式
        ○ 基于XML配置⽅式
Spring事务管理API
Spring对事务的管理底层实现⽅式是基于AOP实现的。采⽤AOP的⽅式进⾏了封装。所以Spring专⻔针对事务开发了⼀套API,API的核⼼接⼝如下:

PlatformTransactionManager接⼝:spring事务管理器的核⼼接⼝。在 Spring 6 中它有两个实现:
        ●DataSourceTransactionManager:⽀持JdbcTemplate、MyBatis、Hibernate等事务管理。
        ●JtaTransactionManager:⽀持分布式事务管理。
如果要在Spring 6 中使⽤JdbcTemplate,就要使⽤DataSourceTransactionManager来管理事务。
(Spring内置写好了,可以直接⽤。)
声明式事务之注解实现⽅式
●第⼀步:在spring配置⽂件中配置事务管理器。
    <!--配置事物管理器-->
    <bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>
●第⼆步:在spring配置⽂件中引⼊tx命名空间。
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                           http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

●第三步:在spring配置⽂件中配置“事务注解驱动器”,开始注解的⽅式控制事务。
    <!--开启事物注解驱动器,开启事物注解。告诉Spring框架,采用注解的方式去控制事物-->
    <tx:annotation-driven transaction-manager="txManager"/>
●第四步:在service类上或⽅法上添加@Transactional注解
 
在类上添加该注解,该类中所有的⽅法都有事务。在某个⽅法上添加该注解,表示只有这个⽅法使⽤事务。
@Service("accountService")
@Transactional
public class AccountServiceImpl implements AccountService {
    @Resource(name="accountDao")
    private AccountDao accountDao;

    //控制事物,因为在这个方法中要完成所有的转账业务
    @Override
    //@Transactional
    public void transfer(String fromActno, String toActno, double money) {
        //第一步:开启事物

        //第二步:执行核心业务逻辑
        //查询转出账户的余额是否充足
        Account fromAct = accountDao.selectByActno(fromActno);
        if (fromAct.getBalance() <money) {
            throw new RuntimeException("余额不足!");
        }

        //余额充足
        Account toAct = accountDao.selectByActno(toActno);

        //将内存中的两个对象的余额先修改
        fromAct.setBalance(fromAct.getBalance()-money);
        toAct.setBalance((toAct.getBalance()+money));

        //数据库更新
        int count = accountDao.update(fromAct);

        //模拟异常
        String s=null;
        s.toString();


        count+= accountDao.update(toAct);

        if(count!=2){
            throw new RuntimeException("转账失败,联系银行!");
        }

        //第三步:如果执行业务过程中没有异常,提交事物
        //第四步:如果执行业务过程中有异常,回滚事物

    }
}
通过测试,发现数据没有变化,事务起作⽤了。

16.3.1 事务属性

事务属性包括哪些
事务中的重点属性:
事务传播⾏为
事务隔离级别
事务超时
只读事务
设置出现哪些异常回滚事务
设置出现哪些异常不回滚事务

16.3.2 事务传播⾏为

什么是事务的传播⾏为?
在service类中有a()⽅法和b()⽅法,a()⽅法上有事务,b()⽅法上也有事务,当a()⽅法执⾏过程中调⽤了 b()⽅法,事务是如何传递的?合并到⼀个事务⾥?还是开启⼀个新的事务?这就是事务传播⾏为。
事务传播⾏为在spring框架中被定义为枚举类型:
⼀共有七种传播⾏为:
REQUIRED:⽀持当前事务,如果不存在就新建⼀个(默认) 【没有就新建,有就加⼊】
SUPPORTS:⽀持当前事务,如果当前没有事务,就以⾮事务⽅式执⾏ 【有就加⼊,没有就不管了】
MANDATORY: 必须运⾏在⼀个事务中,如果当前没有事务正在发⽣,将抛出⼀个异常 【有就
加⼊,没有就抛异常】
REQUIRES_NEW: 开启⼀个新的事务,如果⼀个事务已经存在,则将这个存在的事务挂起
【不管有没有,直接开启⼀个新事务,开启的新事务和之前的事务不存在嵌套关系,之前事务
被挂起】
NOT_SUPPORTED: 以⾮事务⽅式运⾏,如果有事务存在,挂起当前事务 【不⽀持事务,存在
就挂起】
NEVER: 以⾮事务⽅式运⾏,如果有事务存在,抛出异常 【不⽀持事务,存在就抛异常】
NESTED: 如果当前正有⼀个事务在进⾏中,则该⽅法应当运⾏在⼀个嵌套式事务中。被嵌套
的事务可以独⽴于外层事务进⾏提交或回滚。如果外层事务不存在,⾏为就像REQUIRED⼀
样。 【有事务的话,就在这个事务⾥再嵌套⼀个完全独⽴的事务,嵌套的事务可以独⽴的提交
和回滚。没有事务就和 REQUIRED⼀样。
在代码中设置事务的传播⾏为:
@ Transactional ( propagation = Propagation . REQUIRED )

可以编写程序测试⼀下传播⾏为:
AccountServiceImpl:
@Service("accountService")
public class AccountServiceImpl implements AccountService {
    @Resource(name="accountDao")
    private AccountDao accountDao;


    @Resource(name="accountServiceImpl2")
    private AccountService accountService;

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void save(Account act) {
        //这里调用dao的insert方法
        accountDao.insert(act);//保存act-003账户

        //创建账户对象
        Account act2 = new Account("act-004",1000.0);
        accountService.save(act2);//保存act-004账户
    }
}
AccountServiceImpl2:
@Service("accountServiceImpl2")
public class AccountServiceImpl2 implements AccountService {
    @Resource(name="accountDao")
    private AccountDao accountDao;

    @Override
    public void transfer(String fromActno, String toActno, double money) {

    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void save(Account act) {
        accountDao.insert(act);
    }
}
    @Test
    public void testPropagation(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        //获取1号service对象
        AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
        Account act =new Account("act-003",100.0);
        accountService.save(act);
    }
⼀定要集成Log 4 j 2 ⽇志框架,在⽇志信息中可以看到更加详细的信息。

16.3.3 事务隔离级别

事务隔离级别类似于教室A和教室B之间的那道墙,隔离级别越⾼表示墙体越厚。隔⾳效果越好。

数据库中读取数据存在的三⼤问题:(三⼤读问题)
      ●脏读:读取到没有提交到数据库的数据,叫做脏读。
      ●不可重复读:在同⼀个事务当中,第⼀次和第⼆次读取的数据不⼀样。
      ●幻读:读到的数据是假的。
事务隔离级别包括四个级别:
      ●读未提交:READ_UNCOMMITTED
          ○这种隔离级别,存在脏读问题,所谓的脏读(dirty read)表示能够读取到其它事务未提交的 数据。
      ●读提交:READ_COMMITTED
         ○解决了脏读问题,其它事务提交之后才能读到,但存在不可重复读问题。
      ●可重复读:REPEATABLE_READ
         ○解决了不可重复读,可以达到可重复读效果,只要当前事务不结束,读取到的数据⼀直都
是⼀样的。但存在幻读问题。
     ●序列化:SERIALIZABLE
        ○解决了幻读问题,事务排队执⾏。不⽀持并发。
⼤家可以通过⼀个表格来记忆:

在Spring代码中如何设置隔离级别?
隔离级别在spring中以枚举类型存在:

 @Transactional(isolation = Isolation.READ_COMMITTED)

测试事务隔离级别:READ_UNCOMMITTED 和 READ_COMMITTED
怎么测试:⼀个service负责插⼊,⼀个service负责查询。负责插⼊的service要模拟延迟。

 IsolationService1:

@Service("i1")
public class IsolationService1 {
    @Resource(name="accountDao")
    private AccountDao accountDao;

    //负责查询
    //当前事物可以读取到别的事物没有提交的数据
//    @Transactional(isolation = Isolation.READ_UNCOMMITTED)
    //对方事物提交之后的数据我才能读取到
    @Transactional(isolation = Isolation.READ_COMMITTED)
    public void getByActno(String actno){
        Account account =accountDao.selectByActno(actno);
        System.out.println("查询到的账户信息:"+account);
    }
}

IsolationService2:

@Service("i2")
public class IsolationService2 {

    @Resource(name="accountDao")
    private AccountDao accountDao;

    //负责插入
    @Transactional
    public void save(Account act){
        accountDao.insert(act);
        //睡眠一会
        try {
            Thread.sleep(1000*10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
    @Test
    public void testIsolation1(){
        ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring.xml");
        IsolationService1 i1 = applicationContext.getBean("i1", IsolationService1.class);
        i1.getByActno("act-005");
    }

    @Test
    public void testIsolation2(){
        ApplicationContext applicationContext=new ClassPathXmlApplicationContext("spring.xml");
        IsolationService2 i2 = applicationContext.getBean("i2", IsolationService2.class);
        Account act = new Account("act-005", 500.0);
        i2.save(act);
    }
通过执⾏结果可以清晰的看出隔离级别不同,执⾏效果不同。

16.3.4 事务超时

@ Transactional ( timeout = 10 )
以上代码表示设置事务的超时时间为 10 秒。
表示超过 10 秒如果该事务中所有的DML语句还没有执⾏完毕的话,最终结果会选择回滚。
默认值- 1 ,表示没有时间限制。
这⾥有个坑,事务的超时时间指的是哪段时间?
在当前事务当中,最后⼀条DML语句执⾏之前的时间。如果最后⼀条DML语句后⾯很有很多业务逻辑,这些业务代码执⾏的时间不被计⼊超时时间。

 以下代码的超时不会被计⼊超时时间

@Service("i2")
public class IsolationService2 {

    @Resource(name="accountDao")
    private AccountDao accountDao;

    //负责插入
    @Transactional(timeout = 10)//设置事物超时时间为10
    public void save(Account act){
        accountDao.insert(act);*/
        //睡眠一会
        try {
            Thread.sleep(1000*15);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
以下代码超时时间会被计⼊超时时间
@Service("i2")
public class IsolationService2 {

    @Resource(name="accountDao")
    private AccountDao accountDao;

    //负责插入
    @Transactional(timeout = 10)//设置事物超时时间为10
    public void save(Account act){
       //睡眠一会
        try {
            Thread.sleep(1000*15);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        accountDao.insert(act);
    }
}
当然,如果想让整个⽅法的所有代码都计⼊超时时间的话,可以在⽅法最后⼀⾏添加⼀⾏⽆关紧要的 DML语句。

16.3.5 只读事务

@Transactional(readOnly = true)

将当前事务设置为只读事务,在该事务执⾏过程中只允许select语句执⾏,delete insert update均不可执⾏。
该特性的作⽤是: 启动spring的优化策略。提⾼select语句执⾏效率。
如果该事务中确实没有增删改操作,建议设置为只读事务。
设置哪些异常回滚事务

 @Transactional(rollbackFor = RuntimeException.class)

表示只有发⽣RuntimeException异常或该异常的⼦类异常才回滚。

设置哪些异常不回滚事务  

@Transactional(noRollbackFor = NullPointerException.class)

表示发⽣NullPointerException或该异常的⼦类异常不回滚,其他异常则回滚。  

16.3.6 事务的全注解式开发

编写⼀个类来代替配置⽂件,代码如下:
@Configuration//代替spring.xml文件,在这个类当中完成配置
@ComponentScan("com.powernode.bank")//组件扫描
@EnableTransactionManagement//开启事物注解驱动器,开启事物注解。告诉Spring框架,采用注解的方式去控制事物
public class Spring6Config {
    // Spring框架,看到这个@Bean注解后,会调用这个被标注的方法,这个方法的返回值是一个java对象,这个java对象会自动纳入IoC容器管理。
    // 返回的对象就是Spring容器当中的一个Bean了。
    // 并且这个bean的名字是:dataSource
    @Bean(name = "dataSource")
    public DruidDataSource getDataSource(){
        DruidDataSource dataSource = new DruidDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql://localhost:3306/spring6");
        dataSource.setUsername("root");
        dataSource.setPassword("r38153");
        return dataSource;
    }

    @Bean(name="jdbcTemplate")
    public JdbcTemplate getJdbcTemplate(DataSource dataSource){//Spring在调用这个方法的时候会自动给我们传递过来一个dataSource对象。
        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        jdbcTemplate.setDataSource(dataSource);
        return jdbcTemplate;
    }

    @Bean(name="txManager")
    public DataSourceTransactionManager getDataSourceTransactionManager(DataSource dataSource){
        DataSourceTransactionManager txManager = new DataSourceTransactionManager();
        txManager.setDataSource(dataSource);
        return txManager;
    }
}
测试程序如下:
 @Test
    public void testNoXml(){
        ApplicationContext applicationContext =new AnnotationConfigApplicationContext(Spring6Config.class);
        AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
        try{
            accountService.transfer("act-001","act-002",10000);
            System.out.println("转账成功");
        }catch (Exception e){
            e.printStackTrace();
        }
    }

16.3.7 声明式事务之XML实现方式

配置步骤:
●第⼀步:配置事务管理器
●第⼆步:配置通知
●第三步:配置切⾯
记得添加aspectj的依赖:
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>6.0.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.31</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.2.13</version>
        </dependency>
        <dependency>
            <groupId>jakarta.annotation</groupId>
            <artifactId>jakarta.annotation-api</artifactId>
            <version>2.1.1</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aspects</artifactId>
            <version>6.0.11</version>
        </dependency>
    </dependencies>
Spring配置⽂件如下:
记得添加aop的命名空间。
<?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">


    <!--组件扫描-->
    <context:component-scan base-package="com.powernode.bank"/>


    <!--配置数据源-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/spring6"/>
        <property name="username" value="root"/>
        <property name="password" value="r38153"/>
    </bean>
    <!--配置JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"/>
    </bean>


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

    <!--配置通知-->
    <tx:advice id="txAdvice" transaction-manager="txManager">
        <!--配置通知的相关属性-->
        <tx:attributes>
            <!--之前所讲的所有的事务属性都可以在以下标签中配置。-->
            <tx:method name="transfer" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
            <!--使用模糊匹配-->
            <tx:method name="save*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
            <tx:method name="delete*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
            <tx:method name="update*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
            <tx:method name="modify*" propagation="REQUIRED" rollback-for="java.lang.Throwable"/>
            <tx:method name="query*" read-only="true"/>
            <tx:method name="select*" read-only="true"/>
            <tx:method name="find*" read-only="true"/>
            <tx:method name="get*" read-only="true"/>
        </tx:attributes>
    </tx:advice>

    <!--配置切面-->
    <aop:config>
        <!--切点-->
        <aop:pointcut id="txPointcut" expression="execution(* com.powernode.bank.service..*(..))"/>
        <!--切面=通知+切点-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointcut"/>
    </aop:config>
</beans>

Account:

public class Account {
    private String actno;
    private Double balance;

    public Account(){}

    public Account(String actno, Double balance) {
        this.actno = actno;
        this.balance = balance;
    }
//get.set,toString方法
}

AccountDao:

/*
专门负责账户信息的CRUD操作
DAO中只执行SQL语句,没有任何业务逻辑
也就是说DAO不和业务挂钩。
*/
public interface AccountDao {
    //根据账号查询信息
    Account selectByActno(String actno);

    //更新账户信息
    int update(Account act);
}

AccountDaoImpl:

@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {
    @Resource(name="jdbcTemplate")
    private JdbcTemplate jdbcTemplate;

    @Override
    public Account selectByActno(String actno) {
        String sql = "select actno,balance from t_act where actno=?";
        Account account = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Account.class), actno);
        return account;
    }

    @Override
    public int update(Account act) {
        String sql = "update t_act set balance=? where actno=?";
        int count = jdbcTemplate.update(sql, act.getBalance(),act.getActno());
        return count;
    }
}

AccountService:

/*
* 业务接口
* 事物就是在这个接口下控制的
* */
public interface AccountService {
    //转账业务方法
    void transfer(String fromActno,String toActno,double money);
}

AccountServiceImpl:

@Service("accountService")
public class AccountServiceImpl implements AccountService {
    @Resource(name="accountDao")
    private AccountDao accountDao;

    //控制事物,因为在这个方法中要完成所有的转账业务
    @Override
    public void transfer(String fromActno, String toActno, double money) {
        //第一步:开启事物

        //第二步:执行核心业务逻辑
        //查询转出账户的余额是否充足
        Account fromAct = accountDao.selectByActno(fromActno);
        if (fromAct.getBalance() <money) {
            throw new RuntimeException("余额不足!");
        }

        //余额充足
        Account toAct = accountDao.selectByActno(toActno);

        //将内存中的两个对象的余额先修改
        fromAct.setBalance(fromAct.getBalance()-money);
        toAct.setBalance((toAct.getBalance()+money));

        //数据库更新
        int count = accountDao.update(fromAct);

        //模拟异常
//        String s=null;
//        s.toString();


        count+= accountDao.update(toAct);

        if(count!=2){
            throw new RuntimeException("转账失败,联系银行!");
        }

        //第三步:如果执行业务过程中没有异常,提交事物
        //第四步:如果执行业务过程中有异常,回滚事物

    }
}

测试程序:

public class BankTxTest {
    @Test
    public void testNoAnnotation() {
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        AccountService accountService = applicationContext.getBean("accountService", AccountService.class);
        try {
            accountService.transfer("act-001", "act-002", 10000.0);
            System.out.println("转账成功!");
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

⼗七、Spring6整合JUnit5

17.1 Spring对JUnit4的⽀持

准备⼯作:
    <packaging>jar</packaging>

    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>6.0.0</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!--spring对junit支持的依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <!--这个版本spring6,既支持junit4又支持junit5-->
            <version>6.0.0</version>
        </dependency>
    </dependencies>

User:

@Component
public class User {
    @Value("张三")
    private String name;

    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public User(){

    }

    public User(String name) {
        this.name = name;
    }
}

spring.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                           http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">

    <context:component-scan base-package="com.powernode.spring6.bean"/>
</beans>

程序测试:

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:spring.xml")
public class SpringJUnit4Test {
    @Autowired
    private User user;

    @Test
    public void testUser2(){
        System.out.println(user.getName());
    }

    @Test
    public void testUser(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("spring.xml");
        User user = applicationContext.getBean("user", User.class);
        System.out.println(user.getName());
    }
}

17.2 Spring对JUnit5的⽀持

引⼊JUnit5的依赖,Spring对JUnit⽀持的依赖还是:spring-test,如下:

        <!--junit5依赖-->
        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter</artifactId>
            <version>5.9.0</version>
            <scope>test</scope>
        </dependency>

程序测试:

@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:spring.xml")
public class SpringJUnit5Test {
    @Autowired
    private User user;

    @Test
    public void testUser(){
        System.out.println(user.getName());
    }
}
在JUnit 5 当中,可以使⽤Spring提供的以下两个注解,标注到单元测试类上,这样在类当中就可以使⽤
@Autowired注解了。
@ExtendWith(SpringExtension.class)
@ContextConfiguration("classpath:spring.xml")
​​​​​​​
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值