Spring(四)JdbcTemplate及Spring事务控制

Spring系列笔记
Spring(一)Spring 入门&IOC介绍.
Spring(二)基于注解的IOC配置及实现crud.
Spring(三)AOP及基于XML和注解的AOP配置.
Spring(四)JdbcTemplate及Spring事务控制.

1.Spring 中的 JdbcTemplate

1.1.JdbcTemplate 概述

  • 是什么?
    • 它是 spring 框架中提供的一个对象,是对原始 Jdbc API 对象的简单封装
  • 需要两个jar包:
    • spring-jdbc-5.0.2.RELEASE.jar(JdbcTemplate在此内)
    • spring-tx-5.0.2.RELEASE.jar(与事务相关)
  • 作用(为什么学):
    • 它是用于和数据库交互的,实现对表的crud

1.2.JdbcTemplate 对象的创建

  • JdbcTemplatede最基本用法
/**
 * @author liuyongjun
 * @date 2020-06-02-15:53
 * JdbcTemplatede最基本用法
 */
public class JdbcTemplateDemo1 {
    public static void main(String[] args) {
        //准备数据源:spring的内置数据源(除了cp30和dbcp)
        //配置数据库连接四要素
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/spring");
        ds.setUsername("root");
        ds.setPassword("123456");
        //1.创建JdbcTemplate对象
        JdbcTemplate jt = new JdbcTemplate();
        //给对象jt设置数据源
        jt.setDataSource(ds);
        //2.执行操作
        jt.execute("insert into account(name,money)values('ccc',1000)");
    }
}

通过上面JdbcTemplate的简单用法,通过创建对象,不管是设置数据源还是准备数据源都用到了有set方法,依据我们之前学过的依赖注入,我们可以在配置文件中配置这些对象。因此引出IOC

1.3.JdbcTemplate在spring的IOC中使用

  • 首先在resources目录下创建xml文件,并进行配置导包
<packaging>jar</packaging>
    <dependencies>
        <!-- 与IOC相关-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <!-- 与JdbcTemplate相关-->
        <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.6</version>
        </dependency>
    </dependencies>
  • bean.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--配置JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 配置数据源  除了cp30和dbcp以外,使用spring的内置数据源DriverManager-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!-- 配置属性-->
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>
</beans>
  • 然后进行基本用法测试:
public class JdbcTemplateDemo2 {
    public static void main(String[] args) {

        //1.获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.获取对象
        JdbcTemplate jt = ac.getBean("jdbcTemplate",JdbcTemplate.class);
        //3.执行操作
        jt.execute("insert into account(name,money)values('ddd',2222)");
    }
}

结果:
在这里插入图片描述

1.4.JdbcTemplate 的CRUD操作

  • 查询所需的方法:
    在这里插入图片描述
public class JdbcTemplateDemo3 {
    public static void main(String[] args) {

        //1.获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.获取对象
        JdbcTemplate jt = ac.getBean("jdbcTemplate",JdbcTemplate.class);
        //3.执行操作
        //保存
//        jt.update("insert into account(name,money)values(?,?)","eee",3333f);
        //更新
//        jt.update("update account set name=?,money=? where id=?","test",4567,5);
        //删除
//        jt.update("delete from account where id=?",5);
        //查询所有
        //此查询方式需要自己写实现类,下面我们定义了Account的封装策略,这样就能查询了
//        List<Account> accounts = jt.query("select * from account where money > ?",new AccountRowMapper(),1000f);
        //此查询方式无需自己写实现类,用BeanPropertyRowMapper这样spring会给提供
//        List<Account> accounts = jt.query("select * from account where money > ?",new BeanPropertyRowMapper<Account>(Account.class),1000f);
//        for(Account account : accounts){
//            System.out.println(account);
//        }
        //查询一个
//        List<Account> accounts = jt.query("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),1);
//        System.out.println(accounts.isEmpty()?"没有内容":accounts.get(0));

        //查询返回一行一列(使用聚合函数,但不加group by子句)
        Long count = jt.queryForObject("select count(*) from account where money > ?",Long.class,1000f);
        System.out.println(count);

    }
}
/**
 * 定义Account的封装策略
 * 上面查询操作用到了RowMapper
 */
class AccountRowMapper implements RowMapper<Account>{
    /**
     * 把结果集中的数据封装到Account中,然后由spring把每个Account加到集合中
     * @param rs
     * @param rowNum
     * @return Account类型
     * @throws SQLException
     */
    @Override
    public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
        Account account = new Account();
        account.setId(rs.getInt("id"));
        account.setName(rs.getString("name"));
        account.setMoney(rs.getFloat("money"));
        return account;
    }
}

这样我们就使用main函数测试了JdbcTemplate的crud,但是在实际开发中,JdbcTemplate不是这样用的,那是怎样用呢?我们通常都是在Dao中使用,请看下面介绍

1.5.JdbcTemplate 在Dao 中使用

1.5.1.准备实体类

package com.xuexi.domain;

import java.io.Serializable;

/**
 * @author liuyongjun
 * @date 2020-06-02-15:38
 * 账户的实体类
 */
public class Account implements Serializable {
    private Integer id;
    private String name;
    private Float 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 Float getMoney() {
        return money;
    }

    public void setMoney(Float money) {
        this.money = money;
    }

    @Override
    public String toString() {
        return "Account{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", money=" + money +
                '}';
    }
}

1.5.2. 第一种方式:在 dao 中定义 JdbcTemplate

  • 定义账户持久层接口
/**
 * @author liuyongjun
 * @date 2020-06-02-21:34
 * 账户的持久层接口
 */
public interface IAccountDao {
    /**
     * 根据Id查询账户
     * @param accountId
     * @return
     */
    Account findAccountById(Integer accountId);

    /**
     * 根据名称查询账户
     * @param accountName
     * @return
     */
    Account findAccountByName(String accountName);

    /**
     * 更新账户
     * @param account
     */
    void updateAccount(Account account);
}
  • 账户的持久层实现类
/**
 * 账户的持久层实现类
 * 需要给 dao 注入 JdbcTemplate
 */
@Repository
public class AccountDaoImpl2 implements IAccountDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;


    @Override
    public Account findAccountById(Integer accountId) {
        List<Account> accounts = jdbcTemplate.query("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),accountId);
        return accounts.isEmpty()?null:accounts.get(0);
    }

    @Override
    public Account findAccountByName(String accountName) {
        List<Account> accounts = jdbcTemplate.query("select * from account where name = ?",new BeanPropertyRowMapper<Account>(Account.class),accountName);
        if(accounts.isEmpty()){
            return null;
        }
        if(accounts.size()>1){
            throw new RuntimeException("结果集不唯一");
        }
        return accounts.get(0);
    }

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

  • 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 配置账户的持久层-->
    <bean id="accountDao" class="com.xuexi.dao.impl.AccountDaoImpl2">
        <!--注入JdbcTemplate-->
        <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    </bean>
    <!--配置JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!-- 配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!-- 配置属性-->
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>
</beans>
  • 测试类
public class JdbcTemplateDemo4 {

    public static void main(String[] args) {
        //1.获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.获取对象
        IAccountDao accountDao = ac.getBean("accountDao",IAccountDao.class);
		//3.执行操作
        Account account = accountDao.findAccountById(1);
        System.out.println(account);

        account.setMoney(30000f);
        accountDao.updateAccount(account);
    }

此种方式有一个小问题:
就是我们的 dao 有很多时,每个 dao 都有一些重复性的代码。下面就是重复代码:

private JdbcTemplate jdbcTemplate;
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

能不能把它抽取出来呢? 请看下一小节。

1.5.3. 第二种方式:让 dao 继承 JdbcDaoSupport

  • JdbcDaoSupport 是 spring 框架为我们提供的一个类,该类中定义了一个 JdbcTemplate 对象,我们可以直接获取使用,但是要想创建该对象,需要为其提供一个数据源:具体源码如下:
/**
 * 此类用于抽取dao中的重复代码
 */
public class JdbcDaoSupport {
    //定义对象
    private JdbcTemplate jdbcTemplate;


    //set 方法注入数据源,判断是否注入了,注入了就创建 JdbcTemplate
    //在配置文件中,配置账户的持久层中可选择注入dataSource,从而间接注入jdbcTemplate,而不必直接注入jdbcTemplate
    public void setDataSource(DataSource dataSource) {
        if(jdbcTemplate == null){
            jdbcTemplate = createJdbcTemplate(dataSource);
        }
    }
    //使用数据源创建 JdcbTemplate
    private JdbcTemplate createJdbcTemplate(DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }
    //当然,我们也可以通过注入 JdbcTemplate 对象
    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }
    //使用 getJdbcTmeplate 方法获取操作模板对象
    public JdbcTemplate getJdbcTemplate() {
        return jdbcTemplate;
    }
}

当然,我们想到了父类,spring自然也想到了,因此spring早就为我们写好了父类,我们不需要自己写,只需要导包即可:
import org.springframework.jdbc.core.support.JdbcDaoSupport;

  • 定义持久层接口和实现类
/**
 * @author liuyongjun
 * @date 2020-06-02-21:34
 * 账户的持久层接口
 */
public interface IAccountDao {
    /**
     * 根据Id查询账户
     * @param accountId
     * @return
     */
    Account findAccountById(Integer accountId);

    /**
     * 根据名称查询账户
     * @param accountName
     * @return
     */
    Account findAccountByName(String accountName);

    /**
     * 更新账户
     * @param account
     */
    void updateAccount(Account account);
}
/**
 * 账户的持久层实现类
 */
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {

    @Override
    public Account findAccountById(Integer accountId) {
        List<Account> accounts = super.getJdbcTemplate().query("select * from account where id = ?",new BeanPropertyRowMapper<Account>(Account.class),accountId);
        return accounts.isEmpty()?null:accounts.get(0);
    }

    @Override
    public Account findAccountByName(String accountName) {
        List<Account> accounts = super.getJdbcTemplate().query("select * from account where name = ?",new BeanPropertyRowMapper<Account>(Account.class),accountName);
        if(accounts.isEmpty()){
            return null;
        }
        if(accounts.size()>1){
            throw new RuntimeException("结果集不唯一");
        }
        return accounts.get(0);
    }

    @Override
    public void updateAccount(Account account) {
        super.getJdbcTemplate().update("update account set name=?,money=? where id=?",account.getName(),account.getMoney(),account.getId());
    }
}

  • 配置文件
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
    <!-- 配置账户的持久层-->
    <bean id="accountDao" class="com.xuexi.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!-- 配置属性-->
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>
</beans>
  • 测试类:
public class JdbcTemplateDemo4 {

    public static void main(String[] args) {
        //1.获取容器
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.获取对象
        IAccountDao accountDao = ac.getBean("accountDao",IAccountDao.class);

        Account account = accountDao.findAccountById(1);
        System.out.println(account);

        account.setMoney(30000f);
        accountDao.updateAccount(account);
    }
}
  • 这两种方式的区别:
    • 第一种在 Dao 类中定义 JdbcTemplate 的方式,适用于所有配置方式(xml 和注解都可以)。
    • 第二种让 Dao 继承 JdbcDaoSupport 的方式,只能用于基于 XML 的方式,注解用不了。

2.上一个笔记基于XML和注解的AOP实现事务控制

  • 上一个笔记中通知类用了一个日志类,没有用事务控制实现,这次当作业补充,分别基于xml和注解进行AOP实现事务控制。

2.1.基于xml的AOP实现事务控制

  • 开始pom.xml文件配置,定义坐标,导包
<packaging>jar</packaging>

    <dependencies>
        <!-- 与IOC相关-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!-- spring中对Junit框架的整合-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!--开源 JDBC工具类库-->
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.4</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <!--配置 C3P0 数据源使用-->
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>

        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>

        <!-- aspectj框架支持的包 进行aop操作使用-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>

        <!-- 与事务相关-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
    </dependencies>
  • 配置xml文件
  • 此配置前一部分是xml、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: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">

<!-- 配置Service -->
    <bean id="accountService" class="com.xuexi.service.impl.AccountServiceImpl">
        <!-- 注入dao -->
        <property name="accountDao" ref="accountDao"></property>

    </bean>

    <!--配置Dao对象-->
    <bean id="accountDao" class="com.xuexi.dao.impl.AccountDaoImpl">
        <!-- 注入QueryRunner -->
        <property name="runner" ref="runner"></property>
        <!-- 注入ConnectionUtils -->
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>

    <!--配置QueryRunner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>

    <!-- 配置数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--连接数据库的必备信息-->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>
    <!-- 配置Connection的工具类 ConnectionUtils -->
    <bean id="connectionUtils" class="com.xuexi.utils.ConnectionUtils">
        <!-- 注入数据源-->
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 配置事务管理器-->
    <bean id="txManager" class="com.xuexi.utils.TransactionManager">
        <!-- 注入ConnectionUtils -->
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>
    <!-- 配置AOP-->
    <aop:config>
        <!-- 配置通用的切入点表达式-->
        <aop:pointcut id="pt1" expression="execution(* com.xuexi.service.impl.*.*(..))"></aop:pointcut>
        <!-- 配置切面-->
        <aop:aspect id="txAdvice" ref="txManager">
            <!-- 配置前置通知:开启事务-->
            <aop:before method="beginTransaction" pointcut-ref="pt1"></aop:before>
            <!-- 配置后置通知:提交事务-->
            <aop:after-returning method="commit" pointcut-ref="pt1"></aop:after-returning>
            <!-- 配置异常通知:回滚事务-->
            <aop:after-throwing method="rollback" pointcut-ref="pt1"></aop:after-throwing>
            <!-- 配置最终通知:释放连接-->
            <aop:after method="release" pointcut-ref="pt1"></aop:after>
        </aop:aspect>
    </aop:config>
</beans>
  • 上面的配置是去掉了代理类工厂配置,加入了AOP配置,如图
    在这里插入图片描述
  • 将事务管理器类 TransactionManager 作为通知类用于增强方法
/**
 * @author liuyongjun
 * @date 2020-06-01-14:12
 * 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
 */
public class TransactionManager {
    private ConnectionUtils connectionUtils;

    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        this.connectionUtils = connectionUtils;
    }

    /**
     * 开启事务
     */
    public  void beginTransaction(){
        try {
            connectionUtils.getThreadConnection().setAutoCommit(false);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 提交事务
     */
    public  void commit(){
        try {
            connectionUtils.getThreadConnection().commit();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 回滚事务
     */
    public  void rollback(){
        try {
            connectionUtils.getThreadConnection().rollback();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    /**
     * 释放连接
     */
    public  void release(){
        try {
            //将连接还回连接池中
            connectionUtils.getThreadConnection().close();
            //将连接与线程解绑,防止下次使用报错。因为连接被关闭后不能再调用
            connectionUtils.removeConnection();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    /**
     * 环绕通知,一般单独使用
     */
    public Object aroundTransaction(ProceedingJoinPoint proceedingJoinPoint) {
        // 用于接收返回值
        Object returnValue = null;
        try {
            // 获取切入点方法参数
            Object[] args = proceedingJoinPoint.getArgs();
            // 前置通知:开始事务
            this.beginTransaction();
            // 调用切入点方法
            returnValue = proceedingJoinPoint.proceed(args);
            // 后置通知:提交事务
            this.commit();
        } catch (Throwable throwable) {
            // 异常通知:回滚事务
            this.rollback();
            throwable.printStackTrace();
        } finally {
            // 最终通知:释放连接
            this.release();
        }

        return returnValue;
    }

}
  • service实现类
public class AccountServiceImpl implements IAccountService {
    private IAccountDao accountDao;

    public void setAccountDao(IAccountDao accountDao) {
        this.accountDao = accountDao;
    }

    @Override
    public List<Account> findAllAccount() {

        return accountDao.findAllAccount();
    }

    @Override
    public Account findAccountById(Integer accountId) {
        return accountDao.findAccountById(accountId);

    }

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

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

    @Override
    public void deleteAccount(Integer acccountId) {
        accountDao.deleteAccount(acccountId);
    }

    @Override
    public void transfer(String sourceName, String targetName, Float money) {
        System.out.println("transfer....");
        //2.1根据名称查询转出账户
        Account source = accountDao.findAccountByName(sourceName);
        //2.2根据名称查询转入账户
        Account target = accountDao.findAccountByName(targetName);
        //2.3转出账户减钱
        source.setMoney(source.getMoney()-money);
        //2.4转入账户加钱
        target.setMoney(target.getMoney()+money);
        //2.5更新转出账户
        accountDao.updateAccount(source);

//            int i=1/0;

        //2.6更新转入账户
        accountDao.updateAccount(target);
    }
}

  • 测试
 /*
  使用Junit单元测试:测试我们的配置
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {

    @Autowired
    private  IAccountService as;
    @Test
    public  void testTransfer(){
        as.transfer("aaa","bbb",100f);
    }
}
  • 结果
    在这里插入图片描述
  • 同样,若出现异常时,也会进行事务管理,回滚事务,不会进行转账。
  • 环绕通知
    • 首先在配置文件中使用 aop:around 来配置环绕通知
<!-- 配置aop -->
<aop:config>
    <aop:pointcut id="pt1" expression="execution(* com.xuexi.service.impl.*.*(..))"/>
    <aop:aspect id="txAdvice" ref="txManager">
        <!-- 配置环绕通知 -->
        <aop:around method="aroundTransaction" pointcut-ref="pt1"></aop:around>
    </aop:aspect>
</aop:config>

  • 在通知类中编写环绕通知的具体实现
/**
     * 环绕通知,一般单独使用
     */
    public Object aroundTransaction(ProceedingJoinPoint proceedingJoinPoint) {
        // 用于接收返回值
        Object returnValue = null;
        try {
            // 获取切入点方法参数
            Object[] args = proceedingJoinPoint.getArgs();
            // 前置通知:开始事务
            this.beginTransaction();
            // 调用切入点方法
            returnValue = proceedingJoinPoint.proceed(args);
            // 后置通知:提交事务
            this.commit();
        } catch (Throwable throwable) {
            // 异常通知:回滚事务
            this.rollback();
            throwable.printStackTrace();
        } finally {
            // 最终通知:释放连接
            this.release();
        }

        return returnValue;
    }

2.2.基于注解的AOP实现事务控制

2.2.1.半注解(XML + 注解)方式

  • 首先配置pom.xml文件,确定依赖关系,这和基于xml一样
  • 然后配置xml文件,在基于xml配置中添加context命名和约束
<?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: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/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">
</beans>
  • 我们在基于xml的基础上进行修改(针对四大通知,环绕通知后面介绍)
    • 开始前配置spring创建容器时要扫描的包
      <context:component-scan base-package=“com.xuexi”></context:component-scan>
    • 首先配置service对象在业务层实现类中用@Service(“accountService”)取代,其内的注入dao配置用自动类型注入@Autowired取代,这样就不需要set方法了。
    • 接着,配置dao对象在持久层实现类中用@Repository(“accountDao”)取代,其内的注入QueryRunner和ConnectionUtils都用属性上加自动类型注入@Autowired取代,这样也就不需要set方法了。
    • 然后,配置Connection的工具类 ConnectionUtils在ConnectionUtils类上用@Component(“connectionUtils”)取代,其内的注入数据源配置用自动类型注入@Autowired取代,这样就不需要set方法了。
    • 然后,配置事务管理器在TransactionManager类上用@Component(“txManager”)取代,其内的注入ConnectionUtils配置用自动类型注入@Autowired取代,这样就不需要set方法了。
    • 最后,也是在TransactionManager类中,我们针对于AOP配置:
      • 第一,将< aop:config></aop:config>开启spring对注解AOP的支持换成了< aop:aspectj-autoproxy></aop:aspectj-autoproxy>配置
      • 第二,配置切面用@Aspect表明当前类是个切面类。
      • 第三,对于切入点表达式用
        @Pointcut(“execution(* com.xuexi.service.impl..(…))”)
        private void pt1(){}表示。
      • 第四,配置四个通知:
        • 前置通知用@Before(“pt1()”)取代,
        • 后置通知用@AfterReturning(“pt1()”)取代
        • 异常通知用@AfterThrowing(“pt1()”)取代
        • 最终通知用@After(“pt1()”)取代
  • 完整配置
<?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: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/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">
    <!--配置spring创建容器时要扫描的包-->
    <context:component-scan base-package="com.xuexi"></context:component-scan>


    <!--配置QueryRunner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>

    <!-- 配置数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--连接数据库的必备信息-->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>
    <!--开启spring对注解AOP的支持-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>
  • 业务层实现类
@Service("accountService")
public class AccountServiceImpl implements IAccountService {
    @Autowired
    private IAccountDao accountDao;
    //方法和之前的相似
}
  • 持久层实现类
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
    @Autowired
    private QueryRunner runner;
    @Autowired
    private ConnectionUtils connectionUtils;
    //方法相似就先忽略
}
  • 工具类 ConnectionUtils
@Component("connectionUtils")
public class ConnectionUtils {
    private ThreadLocal<Connection> tl = new ThreadLocal<Connection>();
    @Autowired
    private DataSource dataSource;
    //方法相似就先忽略
}
  • TransactionManager类
 /* 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
 */
@Component("txManager")
//切面
@Aspect
public class TransactionManager {
    @Autowired
    private ConnectionUtils connectionUtils;
    //切入点表达式
    @Pointcut("execution(* com.xuexi.service.impl.*.*(..))")
    private void pt1(){}


    /**
     * 开启事务
     */
    @Before("pt1()")
    public  void beginTransaction(){
        try {
            connectionUtils.getThreadConnection().setAutoCommit(false);
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 提交事务
     */
    @AfterReturning("pt1()")
    public  void commit(){
        try {
            connectionUtils.getThreadConnection().commit();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

    /**
     * 回滚事务
     */
    @AfterThrowing("pt1()")
    public  void rollback(){
        try {
            connectionUtils.getThreadConnection().rollback();
        }catch (Exception e){
            e.printStackTrace();
        }
    }
    /**
     * 释放连接
     */
    @After("pt1()")
    public  void release(){
        try {
            //将连接还回连接池中
            connectionUtils.getThreadConnection().close();
            //将连接与线程解绑,防止下次使用报错。因为连接被关闭后不能再调用
            connectionUtils.removeConnection();
        }catch (Exception e){
            e.printStackTrace();
        }
    }

}
  • 测试:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountServiceTest {

    @Autowired
    private  IAccountService as;
    @Test
    public  void testTransfer(){
        as.transfer("aaa","bbb",100f);
    }
}

-结果
在这里插入图片描述

  • 分析原因:原本正常的调用顺序应该为:前置通知 --> 后置通知/异常通知 --> 最终通知,而此时的调用顺序变成了 前置通知 --> 最终通知 --> 后置通知/异常通知,如果是在事务管理中,一旦先执行了最终通知,连接被释放,那么再执行事务的提交或者回滚就行不通了
  • 图解
    在这里插入图片描述

我们上次笔记也提到了,上面的代码有一点小问题,当使用注解来配置前置通知、后置通知、异常通知以及环绕通知时,由于 Spring 的 bug,导致了通知被调用的顺序有误,想解决这个问题,那么只需要使用环绕通知即可,因为在环绕通知中每种通知的调用顺序是由我们自己决定的,不会受 Spring 的影响。所以,使用注解推荐使用环绕通知!!!

环绕通知(基于注解)

  • 将四大通知的注解注释,打开环绕注解注释
  • TransactionManager类更改通知注解
 /**
     * 环绕通知,一般单独使用
     */
@Around("pt1()")
    public Object aroundTransaction(ProceedingJoinPoint proceedingJoinPoint) {
        // 用于接收返回值
        Object returnValue = null;
        try {
            // 获取切入点方法参数
            Object[] args = proceedingJoinPoint.getArgs();
            // 前置通知:开始事务
            this.beginTransaction();
            // 调用切入点方法
            returnValue = proceedingJoinPoint.proceed(args);
            // 后置通知:提交事务
            this.commit();
        } catch (Throwable throwable) {
            // 异常通知:回滚事务
            this.rollback();
            throwable.printStackTrace();
        } finally {
            // 最终通知:释放连接
            this.release();
        }

        return returnValue;
    }

2.2.3.纯注解

  • 如果要使用纯注解的方式,那么只需要在配置上用 @EnableAspectJAutoProxy 注解开启对 AOP 的支持即可
@Configuration
@ComponentScan("com.xuexi")
@EnableAspectJAutoProxy// 开启aop支持
public class SpringConfiguration {
}

  • 具体操作:
  • 我们在半注解的基础上进行操作,配置文件中还有如下内容:
<?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: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/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">
    <!--配置spring创建容器时要扫描的包-->
    <context:component-scan base-package="com.xuexi"></context:component-scan>


    <!--配置QueryRunner-->
    <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>

    <!-- 配置数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!--连接数据库的必备信息-->
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="user" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>
    <!--开启spring对注解AOP的支持-->
    <aop:aspectj-autoproxy></aop:aspectj-autoproxy>

</beans>
  • 因此我们需要用注解的方式将配置文件的内容替换掉:
    • 首先,定义一个注解类,用@Configuration注解,用于指定当前类是一个 Spring 配置类SpringConfiguration,当创建容器时会从该类上加载注解
    • 接着,@ComponentScan,用于指定 Spring 在初始化容器时要扫描的包。相当于<context:component-scan base-package=“com.xuexi”/>
    • 然后新建一个和spring连接数据库相关的配置类JdbcConfig,用@Bean表明使用此方法创建一个对象,并且放入 Spring 容器,可指明QueryRunner配置的id为“runner”,在该类中创建一个QueryRunner对象
    • 在JdbcConfig类中创建一个数据源,并用@Bean(“dataSource”)注解,同时书写配置文件(内含四要素),该配置文件在SpringConfiguration中用@PropertySource(“classpath:jdbcConfig.properties”)注解,表示调用该文件
    • 在SpringConfiguration中使用@Import(JdbcConfig.class),使两个配置类建立他们的关系
    • 在SpringConfiguration中使用@EnableAspectJAutoProxy// 开启aop支持
    • 最后在测试类中设@ContextConfiguration(classes = SpringConfiguration.class)
  • SpringConfiguration配置类
@Configuration
@ComponentScan(basePackages = "com.xuexi")
@Import(JdbcConfig.class)
@PropertySource("classpath:jdbcConfig.properties")
@EnableAspectJAutoProxy// 开启aop支持
public class SpringConfiguration {
}
  • JdbcConfig配置类
/**
 * 和spring连接数据库相关的配置类
 */
public class JdbcConfig {

    @Value("${jdbc.driver}")
    private String driver;

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    /**
     * 用于创建一个QueryRunner对象
     * @param dataSource
     * @return
     */
    @Bean(name="runner")
    @Scope("prototype")
    public QueryRunner createQueryRunner(DataSource dataSource){
        return new QueryRunner(dataSource);
    }

    /**
     * 创建数据源对象
     * @return
     */
    @Bean("dataSource")
    public DataSource createDataSource(){
        try {
            ComboPooledDataSource ds = new ComboPooledDataSource();
            ds.setDriverClass(driver);
            ds.setJdbcUrl(url);
            ds.setUser(username);
            ds.setPassword(password);
            return ds;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }
}
  • 配置文件JdbcConfig.properties
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring
jdbc.username=root
jdbc.password=123456
  • 测试类
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceTest {

    @Autowired
    private  IAccountService as;
    @Test
    public  void testTransfer(){
        as.transfer("aaa","bbb",100f);
    }
}

3. Spring 中的事务控制

3.1.基本介绍

  • 在上一篇笔记中,为了实现业务层的事务管理,我们自己创建了连接工具类 ConnectionUtils 和事务管理器 TransactionManager。但是其实 Spring 已经给我们提供了事务管理相关的接口,我们只需要在配置文件中声明使用即可。
  • avaEE 体系进行分层开发,事务处理位于业务层,Spring 提供了分层设计业务层的事务处理解决方案。
  • Spring 框架为我们提供了一组事务控制的接口,这组接口是在 spring-tx-5.0.2.RELEASE.jar 中,因此我们需要导入相关依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>5.0.2.RELEASE</version>
</dependency>

  • Spring 的事务控制都是基于 AOP 的,它既可以使用编程的方式实现,也可以使用配置的方式实现。由于编程式事务比较繁琐并且耦合度高,所以这里不作介绍,只介绍声明式事务。

3.2.Spring 中事务控制的 API 介绍

3.2.1 PlatformTransactionManager 接口

  • PlatformTransactionManager 接口是 Spring 的事务管理器,它里面提供了我们常用的操作事务的方法:
    • TransactionStatus getTransaction(@Nullable TransactionDefinition definition):获取事务状态信息
    • void commit(TransactionStatus status):提交事务
    • void rollback(TransactionStatus status):回滚事务
  • 在开发我们一般使用以下实现类:
    • org.springframework.jdbc.datasource.DataSourceTransactionManager:使用 Spring
      JDBC 或 Mybatis 进行持久化数据时使用
    • org.springframework.orm.hibernate5.HibernateTransactionManag:使用 HIbernate 进行持久化数据时使用

3.2.2 TransactionDefinition 接口

  • TransactionDefinition 接口是事务的定义信息对象,提供以下方法:
    • String getName(): 获取事务的名称
    • int getIsolationLevel(): 获取事务的隔离级别
    • int getPropagationBehavior: 获取事务的传播行为
    • int getTimeout() : 获取事务的超时时间
    • boolean isReadOnly() : 获取事务是否只读
  • 事务的隔离级别反映了事务提交并发访问时的处理态度,在 TransactionDefinition 中定义了以下几种级别:
    • ISOLATION_DEFAULT: 使用数据库默认的隔离级别,为以下四种的其中之一
    • ISOLATION_READ_UNCOMMITTED: 可以读取未提交数据
    • ISOLATION_READ_COMMITTED : 只能读取已提交数据,解决脏读问题 (Oracle 默认级别)
    • ISOLATION_REPEATABLE_READ : 只有当前事务提交后才可以看到其他事务提交的修改 (MySQL 默认级别)
    • ISOLATION_SERIALIZABLE : 事务串行执行,一个时刻只能有一个事务执行
  • 事务的传播行为反映了事务的控制范围,在 TransactionDefinition 中定义了以下几种级别:
    • REQUIRED: 默认的 Spring 事务传播行为,能满足大部分应用场景。如果当前没有事务,就新建一个事务;如果已经存在一个事务,那么加入到这个事务中。
    • SUPPORTS:支持当前事务,如果没有事务,就以非事务方式执行
    • MANDATORY:使用当前的事务,如果没有事务就抛出异常
    • REQUERS_NEW:新建事务,如果当前在事务中,把当前事务挂起。
    • NOT_SUPPORTED:以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
    • NEVER:以非事务方式运行,如果当前存在事务,抛出异常
    • NESTED:如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作。
  • 事务的超时时间
    • 默认值是 -1 ,没有超时限制。设置的时候以秒为单位进行设置
  • 事务是否为只读事务
    • 一般把查询操作设置为只读事务

3.2.3 TransactionStatus 接口

  • TransactionStatus 接口描述了某个时间点上事务对象具体的运行状态,提供以下方法:
    • void flush():刷新事务
    • boolean hasSavepoint():获取是否存在存储点

存储点:一旦设置了存储点表明事务是按步提交

    • boolean isCompleted() :获取事务是否完成
    • boolean isNewTransaction() :获取事务是否为新的事务
    • boolean isRollbackOnly(): 获取事务是否回滚
    • void setRollbackOnly(): 设置回滚

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

  • 我们还是用账户案例:
  • 配置pom.xml文件,导入相关依赖
<packaging>jar</packaging>
    <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.6</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
    </dependencies>
  • 声明式事务的配置步骤
    • 1.使用 < bean > 标签配置一个事务管理器 DataSourceTransactionManager,注入dataSource
    • 2.使用 < tx:advice > 标签配置事务的通知并且引用事务管理器,此时我们需要导入事务的约束 tx名称空间和约束,同时也需要aop的。
      • 属性id:给事务通知起一个唯一标识
      • 属性transaction-manager:给事务通知提供一个事务管理器引用
    • 3.在 < tx:advice> 标签内部使用 < tx:attributes> 标签和 < tx:method> 标签配置事务的属性
  • < tx:method> 标签用于配置事务的属性,有以下属性:
    • name 属性:指定要控制的方法,可以使用通配符 *
    • isolation 属性:用于指定事务的隔离级别。默认值是DEFAULT,表示使用数据库的默认隔离级别。
    • propagation 属性:用于指定事务的传播行为。默认值是REQUIRED,表示一定会有事务,增删改的选择。查询方法可以选择SUPPORTS。
    • read-only 属性:用于指定事务是否只读。只有查询方法才能设置为true。默认值是false,表示读写。
    • timeout 属性:用于指定事务的超时时间,默认值是-1,表示永不超时。如果指定了数值,以秒为单位。
    • **rollback-for **属性:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
    • **no-rollback-for **属性:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。
    • 4.使用 < aop:config> 标签和 < aop:pointcut> 标签配置 AOP 切入点表达式
    • 5.在 < aop:config> 标签内部使用 < aop:advisor> 标签配置切入点表达式和事务通知的关系
      • advice-ref 属性用于指定通知的引用
      • pointcut-ref 属性用于指定切入点表达式的引用
  • 编写配置文件,注意要导入 aop 和 tx 的名称空间
<?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">

    <!-- 配置业务层-->
    <bean id="accountService" class="com.xuexi.service.impl.AccountServiceImpl">
        <!-- 注入dao-->
        <property name="accountDao" ref="accountDao"></property>
    </bean>
    <!-- 配置账户的持久层-->
    <bean id="accountDao" class="com.xuexi.dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!-- 配置属性-->
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></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>
            <tx:method name="*" propagation="REQUIRED" read-only="false"/>
            <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.xuexi.service.impl.*.*(..))"></aop:pointcut>
        <!--建立切入点表达式和事务通知的对应关系 -->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="pt1"></aop:advisor>
    </aop:config>

</beans>
  • 基本的业务层接口和实现类,持久层接口和实现类,账户的实体类以及测试类很简单就不写了

3.4.基于半注解(XML + 注解) 的声明式事务控制

  • 首先pom.xml文件依赖关系和上面一样
  • 其次配置xml文件
    • 第一,注解需要加入context的命名空间和约束
    • 第二,编写配置文件,注意在配置文件中一样要配置事务管理器,同时还要开启对注解事务的支持
    • 第三,因为是注解了,所以dao不能继承jdbctemplate了,而是要在其中定义,同时别忘了在配置文件中配置jdbctemplate
<?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">

    <!-- 配置jdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 配置spring创建容器时要扫描的包-->
    <context:component-scan base-package="com.xuexi"></context:component-scan>

    <!-- 配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!-- 配置属性-->
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/spring"></property>
        <property name="username" value="root"></property>
        <property name="password" value="123456"></property>
    </bean>
    <!-- spring中基于注解 的声明式事务控制配置步骤
        1、配置事务管理器
        2、开启spring对注解事务的支持
        3、在需要事务支持的地方使用@Transactional注解
     -->
    <!-- 配置事务管理器 -->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
    <!-- 开启注解事务的支持 -->
    <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
</beans>
  • 修改业务层和持久层代码,并且在需要事务支持的地方使用@Transactional注解
/**
 * 账户的业务层实现类
 *
 * 事务控制应该都是在业务层
 */
@Service("accountService")
@Transactional(propagation= Propagation.SUPPORTS,readOnly=true)//只读型事务的配置
public class AccountServiceImpl implements IAccountService{
    @Autowired
    private IAccountDao accountDao;


    @Override
    public Account findAccountById(Integer accountId) {
        return accountDao.findAccountById(accountId);

    }
    //需要的是读写型事务配置
    @Transactional(propagation= Propagation.REQUIRED,readOnly=false)
    @Override
    public void transfer(String sourceName, String targetName, Float money) {
        System.out.println("transfer....");
            //2.1根据名称查询转出账户
            Account source = accountDao.findAccountByName(sourceName);
            //2.2根据名称查询转入账户
            Account target = accountDao.findAccountByName(targetName);
            //2.3转出账户减钱
            source.setMoney(source.getMoney()-money);
            //2.4转入账户加钱
            target.setMoney(target.getMoney()+money);
            //2.5更新转出账户
            accountDao.updateAccount(source);

           // int i=1/0;

            //2.6更新转入账户
            accountDao.updateAccount(target);
    }
}
/**
 * 账户的持久层实现类
 */
@Repository("accountDao")
public class AccountDaoImpl implements IAccountDao {
    @Autowired
    private JdbcTemplate jdbcTemplate;
    //....
}
  • 关于 @Transactional 注解:
  • 该注解的属性和 XML 中的属性含义一致。该注解可以出现在接口上,类上和方法上。
    • 出现接口上,表示该接口的所有实现类都有事务支持。
    • 出现在类上,表示类中所有方法有事务支持
    • 出现在方法上,表示方法有事务支持。
    • 以上三个位置的优先级:方法>类>接口
  • 使用注解不需要配置 AOP 的原因是在业务层配置了 @Transactional 注解的地方就相当于是切入点

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

  • 要想使用纯注解,需要创建两个配置类
  • 注解类
/**
 * spring的配置类,相当于bean.xml
 */
@Configuration
@ComponentScan("com.xuexi")
@Import({JdbcConfig.class,TransactionConfig.class})
@PropertySource("jdbcConfig.properties")
@EnableTransactionManagement//开启注解事务的支持
public class SpringConfiguration {
}
  • jdbc配置类
/**
 * 和连接数据库相关的配置类
 */
public class JdbcConfig {

    @Value("${jdbc.driver}")
    private String driver;

    @Value("${jdbc.url}")
    private String url;

    @Value("${jdbc.username}")
    private String username;

    @Value("${jdbc.password}")
    private String password;

    /**
     * 创建JdbcTemplate
     * @param dataSource
     * @return
     */
    @Bean(name="jdbcTemplate")
    public JdbcTemplate createJdbcTemplate(DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }

    /**
     * 创建数据源对象
     * @return
     */
    @Bean(name="dataSource")
    public DataSource createDataSource(){
        DriverManagerDataSource ds = new DriverManagerDataSource();
        ds.setDriverClassName(driver);
        ds.setUrl(url);
        ds.setUsername(username);
        ds.setPassword(password);
        return ds;
    }
}

  • 数据对应的配置文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring
jdbc.username=root
jdbc.password=123456
  • 同时,为了替换xml文件中的事务管理器,创建事务配置类
/**
 * 和事务相关的配置类
 */
public class TransactionConfig {

    /**
     * 用于创建事务管理器对象
     * @param dataSource
     * @return
     */
    @Bean(name="transactionManager")
    public PlatformTransactionManager createTransactionManager(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }
}

  • 测试类
/**
 * 使用Junit单元测试:测试我们的配置
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfiguration.class)
public class AccountServiceTest {

    @Autowired
    private IAccountService as;

    @Test
    public  void testTransfer(){
        as.transfer("aaa","bbb",100f);

    }

}

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值