程序员的春天-Spring学习04

程序员的春天-Spring学习04

分享自己敲代码&写笔记时候听的歌
这首是 '命运石之门' 的op,这种节奏向的歌很适合码字,前提是手速能跟上,当然错别字什么的,无所谓的啦!
什么啦,才不是心思都在歌上,我有用心学习的,认真脸!

随便把歌曲的网易云的引用代码附上,想要拿去即可。

<iframe frameborder="no" border="0" marginwidth="0" marginheight="0" width=330 height=86 src="//music.163.com/outchain/player?type=2&id=26584163&auto=1&height=66"></iframe>

Spring事务

介绍

首先事务是数据库中的概念,在DAO层。但是要使用事务管理具体的业务,为了方便一般吧事务提高到业务层即service层。

    1. Spring事务管理的API
    • 1.1 事务管理器是PlatformTransactionManager接口对象。主要用于事务的提交,回滚,及获取事务的状态信息。
      PlatformTransactionManager:的两个实现类
      DataSourceTransactionManager:使用JDBC或MyBatis进行持久化数据时使用
      HibernaTransactionManager:使用Hibernate进行持久化数据时使用
    • 1.2 Spring的回滚方式
      Srping事务的默认回滚方式是:发生运行时异常回滚,发生受查异常时提交。
    • 1.3 事务定义接口PlatformTransactionManager定义了事务描述的三大常量:事务隔离级别,事务传播行为,事务牧人超时时限,以及对他们的操作。
      所谓事务传播行为是指,处于不同事物中的方法在相互调用时,执行期间事物的维护情况。如,A事物中的方法doSome()调用B事务中的方法doOther(),在调用时执行期间事务的维护情况,成为事务的传播行为。
    1. 环境搭建
    1. 使用AspectJ的AOP配置管理事务(重)
    1. 使用事务注解管理事务

环境搭建

maven依赖添加

spring核心包,spring-aop包,spring-context包,spring-orm包,aspectj包
上述之前就用过不在赘述
下面是添加的新的依赖
spring-jdbc,spring-tx,c3p0,mysql驱动

    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <!--spring事务-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <!--数据库连接池3cp0-->
    <dependency>
      <groupId>com.mchange</groupId>
      <artifactId>c3p0</artifactId>
      <version>0.9.5.5</version>
    </dependency>
    <!--mysql驱动-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.19</version>
    </dependency>

需求

实现银行和基金会的小业务,向基金会买基金,银行会扣钱,这两个操作应该是一个事务

数据库表设计

account:银行表

create table account
(
  aid     int(5) auto_increment
    primary key,
  aname   varchar(255) null,
  balance double       null
);

fund:基金表

create table fund
(
  fid   int(5) auto_increment
    primary key,
  fname varchar(255) null,
  count int(100)     null
);

DAO层设计

两个接口两个实现类AccountDao和FundDao

AccountDaoImpl

public class AccountDaoImpl extends JdbcDaoSupport implements AccountDao {
    // 开银行账户
    public void inserAccount(String aname, double money) {
        String sql = "insert into account(aname, balance) values(?,?)";
        this.getJdbcTemplate().update(sql, aname, money);
    }
    // 更新银行账户
    public void updateAccount(String aname, double money) {
        String sql = "update account set balance=balance-? where aname=?";
        this.getJdbcTemplate().update(sql, money, aname);
    }
}

FundDaoImpl

public class FundDaoImpl extends JdbcDaoSupport implements FundDao {
    // 开基金账户
    public void insertFound(String fname, int amount) {
        String sql = "insert into fund(fname, count) values(?,?)";
        this.getJdbcTemplate().update(sql, fname, amount);
    }
    // 买基金
    public void updateFound(String fname, int amount) {
        String sql = "update fund set count=count+? where fname=?";
        this.getJdbcTemplate().update(sql, amount, fname);
    }
}

业务层设计

一个接口一个实现类FundService

public class FundServiceImpl implements FundService {

    private AccountDao accountDaoImpl;

    private FundDao fundDaoImpl;

    public AccountDao getAccountDaoImpl() {
        return accountDaoImpl;
    }
    public FundDao getFundDaoImpl() {
        return fundDaoImpl;
    }

    public void setAccountDaoImpl(AccountDao accountDaoImpl) {
        this.accountDaoImpl = accountDaoImpl;
    }

    public void setFundDaoImpl(FundDao fundDaoImpl) {
        this.fundDaoImpl = fundDaoImpl;
    }
    // 开银行账户
    public void openAccount(String aname, double money) {
        accountDaoImpl.inserAccount(aname, money);
    }
    // 开基金账户
    public void openFound(String fname, int amount) {
        fundDaoImpl.insertFound(fname, amount);
    }
    // 买基金
    public void buyFund(String aname, double money, String fname, int amount) throws FundException {
        accountDaoImpl.updateAccount(aname, money);
        fundDaoImpl.updateFound(fname, amount);
    }
}

pojo设计

这个也没啥好设计的,根据表的字段名创建实体类就是了,略!

jdbc.properties文件

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/java?serverTimezone=UTC
jdbc.username=root
jdbc.password=123

异常类设计

public class FundException extends Exception {
    public FundException() {
    }

    public FundException(String message) {
        super(message);
    }
}

AOP通过配置XML实现事务

添加xml约束

因为配置需要使用tx标签,context标签,aop标签,所以需要添加约束

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:context="http://www.springframework.org/schema/context"
       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/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
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd">

1. 加载jdbc属性文件

<context:property-placeholder location="jdbc.properties"/>

2. 注册c3p0数据源

<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    <property name="driverClass" value="${jdbc.driver}"/>
    <property name="jdbcUrl" value="${jdbc.url}"/>
    <property name="user" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

将我们之前加载jdbc的属性文件,使用property注入c3p0

3. 注册Dao层

<bean id="accountDao" class="top.byfree.dao.impl.AccountDaoImpl">
    <property name="dataSource" ref="dataSource"/>
</bean>
<bean id="fundDao" class="top.byfree.dao.impl.FundDaoImpl">
    <property name="dataSource" ref="dataSource"/>
</bean>

将之前注册的c3p0数据源注入Dao层的实现类,因为Dao层的实现类继承了JdbcDaoSupport所以数据源可以直接注入

4. 注册Service层

<bean id="fundService" class="top.byfree.service.impl.FundServiceImpl">
    <property name="accountDaoImpl" ref="accountDao"/>
    <property name="fundDaoImpl" ref="fundDao"/>
</bean>

因为fundService的实现类有Dao层的引用属性和set方法,所以这里可以将Dao层对象直接注入

5. 注册事务管理器

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>

以上5步不论是使用xml配置事务还是使用注解开发事务,都必须有。
也就是说接下来的步骤才是真正开始配置事务

6. 注册事务通知

<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
      <!--连接点方法的事务属性配置-->
      <tx:method name="open*" isolation="DEFAULT" propagation="REQUIRED"/>
      <tx:method name="buy*" isolation="DEFAULT" propagation="REQUIRED" rollback-for="FundException"/>
    </tx:attributes>
</tx:advice>

其他的参数以后有机会细究,这里最重要的参数就是rollback-for设置回滚的异常,意思就是遇到什么异常不进行提交而是回滚,这里写的是我们声明的异常
no-rollback-for这个属性的意思也很好理解,就是遇到什么异常不回滚

7. AOP配置

<aop:config>
    <aop:pointcut id="fundPc" expression="execution(* *..service.*.buyFund(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="fundPc"/>
</aop:config>

就是正常的匹配切入点什么的,正常的aop配置,不过这里配置的是一个顾问而不是一个通知
aop:advisor顾问标签通过advice-ref引用一个通知

编写测试类测试

public class test {
    private FundService service;
    @Before
    public void Before() {
        ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
        service = ac.getBean("fundService", FundService.class);
    }

    // 开账户

    @Test
    public void openAccount () {
        service.openAccount("工商银行", 10000);
    }

    @Test
    public void openFund () {
        service.openFound("byFree基金", 2000);
    }

    @Test
    public void buyFund () throws FundException {
        service.buyFund("工商银行",2000, "byFree基金", 2000);
    }
}

手动抛出异常测试

为了测试效果,手动在buyFund方法中两个dao操作的中间抛出一个异常,看是否回滚

public void buyFund(String aname, double money, String fname, int amount) throws FundException {
        accountDaoImpl.updateAccount(aname, money);
        if (1==1) {
            throw new FundException("购买基金异常!");
        }
        fundDaoImpl.updateFound(fname, amount);
}

使用注解开发事务

注解开发就会简单很多
不需要在进行配置事务通知和aop配置
也就是说之前的配置只需要做到第5步

配置注解驱动

<tx:annotation-driven transaction-manager="transactionManager"/>

将之前的678步换成这一句即可

使用@Transactional注解

@Transactional(rollbackFor = FundException.class)
public void buyFund(String aname, double money, String fname, int amount) throws FundException {
        accountDaoImpl.updateAccount(aname, money);
        if (1==1) {
            throw new FundException("购买基金异常!");
        }
        fundDaoImpl.updateFound(fname, amount);
}

在业务层的buyFund方法上是用@Transactional注解,这个注解的传值,可以是之前tx:method标签中的那些属性,注意的是rollbackFor的值需要是一个Class对象

Copyright: 采用 知识共享署名4.0 国际许可协议进行许可

Links: https://www.byfree.top/archives/spring04