简单易懂的Spring基础入门学习总结笔记(下)

本文源地址:https://blog.csdn.net/qq_41112238/article/details/103865555
计算机网络:https://www.nowcoder.com/discuss/342320
MySQL:https://www.nowcoder.com/discuss/353707
Java并发上:https://www.nowcoder.com/discuss/355081
Java并发下:https://www.nowcoder.com/discuss/355876
JDBC:https://www.nowcoder.com/discuss/356804
Linux:https://www.nowcoder.com/discuss/357410
JavaWeb上:https://www.nowcoder.com/discuss/358423
JavaWeb中:https://www.nowcoder.com/discuss/358741
JavaWeb下:https://www.nowcoder.com/discuss/359327
Redis:https://www.nowcoder.com/discuss/359700
Mybatis上:https://www.nowcoder.com/discuss/360108
Mybatis下:https://www.nowcoder.com/discuss/360108
Spring上:https://www.nowcoder.com/discuss/360470


存在问题的转账案例

环境的搭建和测试

pom.xml中的依赖jar包坐标

<packaging>jar</packaging>

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

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>


        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.4</version>
        </dependency>

        <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>
            <scope>compile</scope>
        </dependency>
    </dependencies>

账户持久层的接口和实现类

//账户持久层接口
public interface IAccountDao {

    //查询所有
    List<Account> findAll();

    //查询一个
    Account findAccountById(Integer id);

    //保存
    void saveAccount(Account account);

    //更新
    void updateAccount(Account account);

    //删除
    void deleteAccount(Integer id);

    //根据名称查找唯一一个账户
    Account findAccountByName(String name);
}

//账户持久层实现类
public class AccountDaoImpl implements IAccountDao {

    private QueryRunner queryRunner;

    public void setQueryRunner(QueryRunner queryRunner) {
        this.queryRunner = queryRunner;
    }

    public List<Account> findAll() {
        try {
            return queryRunner.query("select * from account",new BeanListHandler<Account>(Account.class));
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public Account findAccountById(Integer id) {
        try {
            return queryRunner.query("select * from account where id = ?",new BeanHandler<Account>(Account.class),id);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public void saveAccount(Account account) {
        try {
            queryRunner.update("insert into account(name,money) values (?,?)",account.getName(),account.getMoney());
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public void updateAccount(Account account) {
        try {
            queryRunner.update("update account set name=?,money=? where id =?",account.getName(),account.getMoney(),account.getId());
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public void deleteAccount(Integer id) {
        try {
            queryRunner.update("delete from account where id =?",id);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Account findAccountByName(String name) {
        try {
            List<Account> accounts = queryRunner.query("select * from account where name = ?", new BeanListHandler<>(Account.class), name);
            //查找结果为空
            if(accounts==null||accounts.size()==0)
                return null;
            //查找结果不唯一
            if(accounts.size()>1)
                throw new RuntimeException("结果集不唯一");
            return accounts.get(0);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

账户业务层的接口和实现类

//业务层接口
public interface IAccountService {

    //查询所有
    List<Account> findAll();

    //查询一个
    Account findAccountById(Integer id);

    //保存
    void saveAccount(Account account);

    //更新
    void updateAccount(Account account);

    //删除
    void deleteAccount(Integer id);

    //交易
    void transfer(String sourceName,String targetName,Float money);
}

//账户的业务层实现类
public class AccountServiceImpl implements IAccountService {

    private IAccountDao accountDao;

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

    public List<Account> findAll() {
        return accountDao.findAll();
    }

    public Account findAccountById(Integer id) {
        return accountDao.findAccountById(id);
    }

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

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

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

    @Override
    public void transfer(String sourceName, String targetName, Float money) {
        //根据名称查出转出账户
        Account source = accountDao.findAccountByName(sourceName);
        //根据名称查出转入账户
        Account target = accountDao.findAccountByName(targetName);
        //转出账户减钱
        source.setMoney(source.getMoney()-money);
        //转入账户加钱
        target.setMoney(target.getMoney()+money);
        //转出账户更新
        accountDao.updateAccount(source);
        //转入账户更新
        accountDao.updateAccount(target);
    }


}

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

    <!--把对象的创建交给spring -->
    <bean id="accountService" class="service.impl.AccountServiceImpl">
        <!-- 注入dao对象 -->
       <property name="accountDao" ref="accountDao"/>
    </bean>

    <!-- 配置dao -->
    <bean id="accountDao" class="dao.impl.AccountDaoImpl">
        <!-- 注入queryrunner -->
        <property name="queryRunner" ref="query"/>
    </bean>

    <!-- 配置query runner 使用多例-->
    <bean id="query" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
        <!-- 注入数据源 -->
        <constructor-arg name="ds" ref="dataSource"/>
    </bean>

    <!-- 配置数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!-- 连接数据库的信息 -->
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql:///test?serverTimezone=UTC"/>
        <property name="user" value="root"/>
        <property name="password" value="sjh2019"/>
    </bean>

</beans>

测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountTest {

    @Autowired
    IAccountService accountService;

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

此时运行测试,结果正常(初始aaa和bbb账户均为1000元,建表sql语句见上篇)
在这里**图片描述


但如果业务层实现类存在错误,转账会存在问题
在这里**图片描述
此时运行的结果:
在这里**图片描述
我们看到发生错误后,转出成功了,但转入失败了,这显然不是我们想要的结果。


问题原因分析

在业务层实现类的方法中,调用了多次和数据库交互的方法,每次都使用的是新的数据库连接,因此当出现错误时,前面的事务已经提交了,而最后一次并不能执行到。
因此我们需要将所有连接绑定为同一个,方法是使用thread local,将数据库连接和当前线程绑定,从而使一个线程中只有一个能控制事务的对象。
在这里**图片描述


解决问题

首先编写一个数据库连接的工具类ConnectionUtils

//连接的工具类,用于从数据源获取一个连接,并实现和线程的绑定
public class ConnectionUtils {

    private ThreadLocal<Connection> threadLocal=new ThreadLocal<>();

    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
           //提供set方法等待spring的注入
        this.dataSource = dataSource;
    }

    //获取当前线程上的连接
    public Connection getThreadConnection(){
        //1.从threadlocal获取
        Connection connection = threadLocal.get();
        try {
            //2.判断当前线程上是否有连接
            if(connection==null){
                //3.从数据源获取一个连接并和线程绑定
                connection=dataSource.getConnection();
                threadLocal.set(connection);
            }
            //4.返回当前线程上的连接
            return connection;
        }catch (Exception e){
            throw new RuntimeException();
        }
    }

    //解绑连接和线程
    public void remove(){
        threadLocal.remove();
    }
}

再编写一个进行事务管理的工具类TransactionManager

//和事务管理相关的工具类,包含了事务的开启,提交,回滚,释放连接
public class TransactionManager {
    private ConnectionUtils connectionUtils;

    public void setConnectionUtils(ConnectionUtils connectionUtils) {
        //提供set方法等待spring的注入
        this.connectionUtils = connectionUtils;
    }

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

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

    //回滚事务
    public void rollback(){
        try {
            connectionUtils.getThreadConnection().rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    //释放连接
    public void release(){
        try {
            //把连接关闭,返回池中
            connectionUtils.getThreadConnection().close();
            //如果不解绑连接,下次获取时仍有连接,但已经不可用了
            connectionUtils.remove();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

}

在业务层的实现类中通过事务类实现事务的控制

//账户的业务层实现类
public class AccountServiceImpl implements IAccountService {

    private IAccountDao accountDao;

    private TransactionManager transactionManager;

    public void setTransactionManager(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

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

    public List<Account> findAll() {
        try{
            transactionManager.beginTran();
            List<Account> accounts = accountDao.findAll();
            transactionManager.commit();
            return accounts;
        }catch (Exception e){
            transactionManager.rollback();
            throw new RuntimeException(e);
        }finally {
            transactionManager.release();
        }
    }

    public Account findAccountById(Integer id) {
        try{
            transactionManager.beginTran();
            Account accountById = accountDao.findAccountById(id);
            transactionManager.commit();
            return accountById;
        }catch (Exception e){
            transactionManager.rollback();
            throw new RuntimeException(e);
        }finally {
            transactionManager.release();
        }
    }

    public void saveAccount(Account account) {
        try{
            transactionManager.beginTran();
            accountDao.saveAccount(account);
            transactionManager.commit();
        }catch (Exception e){
            transactionManager.rollback();
        }finally {
            transactionManager.release();
        }

    }

    public void updateAccount(Account account) {
        try{
            transactionManager.beginTran();
            accountDao.updateAccount(account);
            transactionManager.commit();
        }catch (Exception e){
            transactionManager.rollback();
        }finally {
            transactionManager.release();
        }

    }

    public void deleteAccount(Integer id) {
        try{
            transactionManager.beginTran();
            accountDao.deleteAccount(id);
            transactionManager.commit();
        }catch (Exception e){
            transactionManager.rollback();
        }finally {
            transactionManager.release();
        }

    }

    @Override
    public void transfer(String sourceName, String targetName, Float money) {
        try{
            transactionManager.beginTran();
            //根据名称查出转出账户
            Account source = accountDao.findAccountByName(sourceName);
            //根据名称查出转入账户
            Account target = accountDao.findAccountByName(targetName);
            //转出账户减钱
            source.setMoney(source.getMoney()-money);
            //转入账户加钱
            target.setMoney(target.getMoney()+money);
            //转出账户更新
            accountDao.updateAccount(source);
            //模拟错误
            int i=1/0;
            //转入账户更新
            accountDao.updateAccount(target);
            transactionManager.commit();
        }catch (Exception e){
            transactionManager.rollback();
            e.printStackTrace();
        }finally {
            transactionManager.release();
        }

    }


}

由于我们希望获取的连接是同一个,因此还要修改持久层的实现类,将连接加入查询方法的第一个参数,使连接为同一个

//账户持久层实现类
public class AccountDaoImpl implements IAccountDao {

    private QueryRunner queryRunner;

    private ConnectionUtils connectionUtils;

    public void setQueryRunner(QueryRunner queryRunner) {
        this.queryRunner = queryRunner;
    }

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

    public List<Account> findAll() {
        try {
            return queryRunner.query(connectionUtils.getThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class));
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public Account findAccountById(Integer id) {
        try {
            return queryRunner.query(connectionUtils.getThreadConnection(),"select * from account where id = ?",new BeanHandler<Account>(Account.class),id);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public void saveAccount(Account account) {
        try {
            queryRunner.update(connectionUtils.getThreadConnection(),"insert into account(name,money) values (?,?)",account.getName(),account.getMoney());
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public void updateAccount(Account account) {
        try {
            queryRunner.update(connectionUtils.getThreadConnection(),"update account set name=?,money=? where id =?",account.getName(),account.getMoney(),account.getId());
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    public void deleteAccount(Integer id) {
        try {
            queryRunner.update(connectionUtils.getThreadConnection(),"delete from account where id =?",id);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Account findAccountByName(String name) {
        try {
            List<Account> accounts = queryRunner.query(connectionUtils.getThreadConnection(),"select * from account where name = ?", new BeanListHandler<>(Account.class), name);
            //查找结果为空
            if(accounts==null||accounts.size()==0)
                return null;
            //查找结果不唯一
            if(accounts.size()>1)
                throw new RuntimeException("结果集不唯一");
            return accounts.get(0);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
}

最后,由于要使连接为同一个,我们要取消对queryRunner的数据源注入,将数据源注入到连接的工具类中,这样确保使用的连接每次都是同一个,并配置事务工具类

<?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">

    <!--把对象的创建交给spring -->
    <bean id="accountService" class="service.impl.AccountServiceImpl">
        <!-- 注入dao对象 -->
       <property name="accountDao" ref="accountDao"/>
        <!-- 注入事务管理 -->
        <property name="transactionManager" ref="transactionManager"/>
    </bean>

    <!-- 配置dao -->
    <bean id="accountDao" class="dao.impl.AccountDaoImpl">
        <!-- 注入queryrunner -->
        <property name="queryRunner" ref="query"/>
        <!-- 注入工具类 -->
        <property name="connectionUtils" ref="connectionUtils"/>
    </bean>

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

    <!--  配置connection工具类 -->
    <bean id="connectionUtils" class="utils.ConnectionUtils">
        <!-- 注入数据源 -->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 配置数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!-- 连接数据库的信息 -->
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql:///test?serverTimezone=UTC"/>
        <property name="user" value="root"/>
        <property name="password" value="sjh2019"/>
    </bean>

    <!-- 配置事务管理器 -->
    <bean id="transactionManager" class="utils.TransactionManager">
        <!-- 注入工具类 -->
        <property name="connectionUtils" ref="connectionUtils"/>
    </bean>
</beans>

此时我们再对交易方法进行测试,测试是成功的,打印出了错误信息(除数为0),并且事务成功回滚,没有出现支出账户钱减少的现象。
在这里**图片描述

在这里**图片描述
但此时我们的代码、配置十分复杂和臃肿,接下来将解决此问题。


动态***的引入

基于接口的动态***回顾

动态***:

  • 特点:字节码随用随创建,随用随加载
  • 作用:不修改源码的基础上对方法增强
  • 分类:
    基于接口的动态***,基于子类的动态***
          基于接口的动态***:设计的类:Proxy 提供者:jdk官方
  • 如何创建:使用proxy类的newProxyInstance方法
  • 创建要求:被***类最少实现一个接口,如果没有则不能使用
  • newProxyInstance参数:
    class loader:类加载器,加载***对象字节码,和被***对象使用相同的类加载器,固定写法
    class[]:字节码数组 用于让***对象和被***对象有相同方法 固定写法
    InvocationHandler:用于增强代码的方法,一般都是写一个该接口的实现类,通常是匿名内部类

一个销售接口和实现类

//对生产厂家要求的接口
public interface IProducer {

    //销售
    public void sale(float money);

}

//生产者
public class Producer implements IProducer{

    //销售
    public void sale(float money){
        System.out.println("销售产品,拿到钱: "+money);
    }

}


测试类

//模拟消费者
public class Customer {

    public static void main(String[] args) {
        Producer producer = new Producer();
        IProducer proxyProduer= (IProducer) Proxy.newProxyInstance(producer.getClass().getClassLoader(), producer.getClass().getInterfaces(),
                new InvocationHandler() {
                    /**执行被***对象的任何方法都会经过该方法
                     * 方法参数的含义
                     * @param proxy ***对象的引用
                     * @param method 当前执行的方法
                     * @param args 当前执行方法所需参数
                     * @return 和被***对象方法有相同返回值
                     * @throws Throwable
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        //提供增强逻辑
                        Object returnValue=null;
                        //1.获取方法执行参数
                        Float money = (Float) args[0];
                        //2.判断方法是不是销售
                        if("sale".equals(method.getName())){
                            returnValue = method.invoke(producer, money*0.8f);
                        }
                        return returnValue;
                    }
                });
        //不使用***
        producer.sale(10000);
        //使用***
        proxyProduer.sale(10000);
    }
}

运行结果
在这里**图片描述
但使用接口的动态***存在一定的问题:如果一个类不实现接口的话,就不能被***,所以接下来学习基于子类的动态***。


基于子类的动态***

首页需要添加一个新的依赖

        <dependency>
            <groupId>cglib</groupId>
            <artifactId>cglib</artifactId>
            <version>2.1.3</version>
        </dependency>

基于子类的动态***

  • 提供者:第三方的 CGLib,如果报 asmxxxx 异常,需要导入 asm.jar。
  • 要求:被***类不能用 final 修饰的类(最终类)。
  • 用到的类: Enhancer
  • 用到的方法: create(Class, Callback)
  • 方法的参数:
    • Class:被***对象的字节码
    • Callback:如何***

去掉生产者实现类的接口,使用基于子类的动态***

public class Customer {

    public static void main(String[] args) {
        Producer producer = new Producer();

        Producer cglibProducer= (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
            /**
             * 执行被***对象的任何方法,都会经过该方法。在此方法内部就可以对被***对象的任何
             方法进行增强。
             *
             * 参数:
             * 前三个和基于接口的动态***是一样的。
             * MethodProxy:当前执行方法的***对象。
             * 返回值:
             * 当前执行方法的返回值
             */
            @Override
            public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
                //提供增强逻辑f
                Object returnValue=null;
                //1.获取方法执行参数
                Float money = (Float) objects[0];
                //2.判断方法是不是销售
                if("sale".equals(method.getName())){
                    returnValue = method.invoke(producer, money*0.8f);
                }
                return returnValue;
            }
        });
        //不使用***
        producer.sale(10000);//10000
        //使用***
        cglibProducer.sale(10000);//8000

    }
}

结果和之前一样。


使用动态***实现事务控制

简化之前臃肿的业务层实现类

//账户的业务层实现类
public class AccountServiceImpl implements IAccountService {

    private IAccountDao accountDao;

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

    public List<Account> findAll() {
        return accountDao.findAll();
    }

    public Account findAccountById(Integer id) {
        return accountDao.findAccountById(id);
    }

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

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

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

    @Override
    public void transfer(String sourceName, String targetName, Float money) {
            //根据名称查出转出账户
            Account source = accountDao.findAccountByName(sourceName);
            //根据名称查出转入账户
            Account target = accountDao.findAccountByName(targetName);
            //转出账户减钱
            source.setMoney(source.getMoney()-money);
            //转入账户加钱
            target.setMoney(target.getMoney()+money);
            //转出账户更新
            accountDao.updateAccount(source);
            //模拟错误
            int i=1/0;
            //转入账户更新
            accountDao.updateAccount(target);
    }


}

创建一个动态***类来对业务层实现类提供事务支持

//用于创建service***对象的工厂
public class BeanFactory {

    private IAccountService accountService;

    public void setAccountService(IAccountService accountService) {
        this.accountService = accountService;
    }

    private TransactionManager transactionManager;

    public void setTransactionManager(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

    //获取service的***对象
    public IAccountService getAccountService() {
        IAccountService proxyService= (IAccountService) Proxy.newProxyInstance(accountService.getClass().getClassLoader(), accountService.getClass().getInterfaces(),
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                        Object returnValue;
                        try{
                            transactionManager.beginTran();
                            returnValue = method.invoke(accountService, args);
                            transactionManager.commit();
                            return returnValue;
                        }catch (Exception e){
                            transactionManager.rollback();
                            throw new RuntimeException(e);
                        }finally {
                            transactionManager.release();
                        }
                    }
                });
        return proxyService;
    }
}

修改bean.xml配置

    <!-- 配置service工厂 -->
    <bean id="beanFactory" class="factory.BeanFactory">
        <!--注入业务层实现类 -->
        <property name="accountService" ref="accountService"/>
        <!--注入事务控制类 -->
        <property name="transactionManager" ref="transactionManager"/>
    </bean>

    <!-- 配置***类service-->
    <bean id="proxyService" factory-bean="beanFactory" factory-method="getAccountService"/>

测试类
使用@Qualifier("proxyService")指明要使用***的service

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountTest {

    @Autowired@Qualifier("proxyService")
    IAccountService accountService;

    @Test
    public void findAll(){
        List<Account> accounts = accountService.findAll();
        accounts.forEach(System.out::println);
    }

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

初始时将aaa和bbb账户都置为1000,使用错误的转账方法,报错,事务回滚成功,双方金额并未改变。

在这里**图片描述


在这里**图片描述
将错误语句注释掉,可以成功运行。
在这里**图片描述


在这里**图片描述


Aop

AOP 的相关概念

AOP
全称是 Aspect Oriented Programming 即:面向切面编程。
简单的说它就是把我们程序重复的代码抽取出来,在需要执行的时候,使用动态***的技术,在不修改源码的基础上,对我们的已有方法进行增强。
作用:
在程序运行期间,不修改源码对已有方法进行增强。
优势:
减少重复代码
提高开发效率
维护方便
实现方式
使用动态***技术


Spring中的AOP

***方式
spring中会判断类是否实现了接口决定采用哪种动态***方式。
相关术语

  • Joinpoint(连接点):
    所谓连接点是指那些被拦截到的点。在 spring 中,这些点指的是方法,因为 spring 只支持方法类型的连接点。
    例如业务层实现类中的方法都是连接点。
  • Pointcut(切入点):
    所谓切入点是指我们要对哪些 Joinpoint 进行拦截的定义。
    例如业务层实现类中被增强的方法都是切入点,切入点一定是连接点,但连接点不一定是切入点。
  • Advice(通知/增强):
    所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。
    通知的类型:前置通知,后置通知,异常通知,最终通知,环绕通知。
  • Introduction(引介):
    引介是一种特殊的通知在不修改类代码的前提下, Introduction 可以在运行期为类动态地添加一些方法或 Field。
  • Target(目标对象):
    ***的目标对象。
    例如前面被***的accountService。
  • Weaving(织入):
    是指把增强应用到目标对象来创建新的***对象的过程。
    spring 采用动态***织入,而 AspectJ 采用编译期织入和类装载期织入。
  • Proxy(***):
    一个类被 AOP 织入增强后,就产生一个结果***类。
    例如前例的***对象proxyService。
  • Aspect(切面):
    是切入点和通知(引介)的结合。

基于XML的AOP实现

导入依赖

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>

业务层接口和实现类

//业务层接口
public interface IAccountService {

    //模拟保存
    void saveAccount();

    //模拟更新
    void updateAccount(int i);

    //模拟删除
    int deleteAccount();

}

//账户的业务层实现类
public class AccountServiceImpl implements IAccountService {

    @Override
    public void saveAccount() {
        System.out.println("save");
    }

    @Override
    public void updateAccount(int i) {
        System.out.println("update");
    }

    @Override
    public int deleteAccount() {
        System.out.println("delete");
        return 0;
    }
}

通知类

//用于记录日志的工具类,提供公共代码
public class Logger {


    public void beforePrintLog(){
        System.out.println("前置通知");
    }


    public void afterPrintLog(){
        System.out.println("后置通知");
    }


    public void errorPrintLog(){
        System.out.println("异常通知");
    }

    public void finalPrintLog(){
        System.out.println("最终通知");
    }
}

bean.xml配置

  • aop:config:
    作用:用于声明开始 aop 的配置
  • aop:aspect:
    作用: 用于配置切面。
    属性:id:给切面提供一个唯一标识。ref:引用配置好的通知类 bean 的 id。
  • aop:pointcut
    作用: 用于配置切入点表达式。就是指定对哪些类的哪些方法进行增强。
    属性: expression:用于定义切入点表达式。id:用于给切入点表达式提供一个唯一标识
- `aop:before`     

作用: 用于配置前置通知。指定增强的方法在切入点方法之前执行
执行时间点: 切入点方法执行之前执行
- aop:after-returning
作用: 用于配置后置通知
执行时间点: 切入点方法正常执行之后。它和异常通知只能有一个执行
- aop:after-throwing
作用: 用于配置异常通知
执行时间点: 切入点方法执行产生异常后执行。它和后置通知只能执行一个
- aop:after
作用: 用于配置最终通知
执行时间点: 无论切入点方法执行时是否有异常,它都会在其后面执行。

切入点表达式说明

execution:匹配方法的执行(常用)
execution(表达式)
表达式语法:execution([修饰符] 返回值类型 包名.类名.方法名(参数))
写法说明:
全匹配方式:
public void service.impl.AccountServiceImpl.saveAccount(domain.Account)
访问修饰符可以省略:
void service.impl.AccountServiceImpl.saveAccount(domain.Account)
返回值可以使用*号,表示任意返回值
* service.impl.AccountServiceImpl.saveAccount(domain.Account)
包名可以使用*号,表示任意包,但是有几级包,需要写几个*
* *.*.AccountServiceImpl.saveAccount(domain.Account)
使用..来表示当前包,及其子包
* *..AccountServiceImpl.saveAccount(domain.Account)
类名可以使用*号,表示任意类
* *..*.saveAccount(.domain.Account)
方法名可以使用 *号,表示任意方法
* *..*.*( domain.Account)
参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数
* *..*.*(*)
参数列表可以使用..表示有无参数均可,有参数可以是任意类型
* *..*.*(..)
全通配方式:
* *..*.*(..)

<?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">

    <bean id="accountService" class="service.impl.AccountServiceImpl"/>

    <bean id="logger" class="utils.Logger"/>

    <!-- 配置aop -->
    <aop:config>
        <!-- 配置切面 -->
        <aop:aspect id="logAdvice" ref="logger">
            <!-- 配置前置通知 -->
            <aop:before method="beforePrintLog" pointcut="execution(* *..AccountServiceImpl.*(..))"/>
            <!-- 配置后置通知 -->
            <aop:after-returning method="afterPrintLog" pointcut="execution(* *..AccountServiceImpl.*(..))"/>
            <!-- 配置异常通知 -->
            <aop:after-throwing method="errorPrintLog" pointcut="execution(* *..AccountServiceImpl.*(..))"/>
            <!-- 配置最终通知 -->
            <aop:after method="finalPrintLog" pointcut="execution(* *..AccountServiceImpl.*(..))"/>
        </aop:aspect>
    </aop:config>

</beans>

测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountTest {

    @Autowired
    IAccountService accountService;


    @Test
    public void test(){
        accountService.saveAccount();
    }
}

结果

前置通知
save
后置通知
最终通知


优化切入点表达式的配置

 <!-- 配置aop -->
    <aop:config>
        <!-- 配置切面 -->
        <aop:aspect id="logAdvice" ref="logger">
            <!-- 配置前置通知 -->
            <aop:before method="beforePrintLog" pointcut-ref="pt"/>
            <!-- 配置后置通知 -->
            <aop:after-returning method="afterPrintLog" pointcut-ref="pt"/>
            <!-- 配置异常通知 -->
            <aop:after-throwing method="errorPrintLog" pointcut-ref="pt"/>
            <!-- 配置最终通知 -->
            <aop:after method="finalPrintLog" pointcut-ref="pt"/>
            <!-- 配置切入点表达式 写在aspect标签内 只能在当前切面使用 -->
            <aop:pointcut id="pt" expression="execution(* *..AccountServiceImpl.*(..))"/>
        </aop:aspect>
    </aop:config>
<!-- 配置aop -->
    <aop:config>
        <!-- 配置切入点表达式 写在aspect标签外 可在所有切面使用-->
        <aop:pointcut id="pt" expression="execution(* *..AccountServiceImpl.*(..))"/>
        <!-- 配置切面 -->
        <aop:aspect id="logAdvice" ref="logger">
            <!-- 配置前置通知 -->
            <aop:before method="beforePrintLog" pointcut-ref="pt"/>
            <!-- 配置后置通知 -->
            <aop:after-returning method="afterPrintLog" pointcut-ref="pt"/>
            <!-- 配置异常通知 -->
            <aop:after-throwing method="errorPrintLog" pointcut-ref="pt"/>
            <!-- 配置最终通知 -->
            <aop:after method="finalPrintLog" pointcut-ref="pt"/>
        </aop:aspect>
    </aop:config>

环绕通知的配置

  • aop:around:
    作用:
    用于配置环绕通知
  • 属性:
    method:指定通知中方法的名称。
    pointct:定义切入点表达式
    pointcut-ref:指定切入点表达式的引用
  • 说明:
    它是 spring 框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。
    注意:通常情况下,环绕通知都是独立使用的

在Logger类中添加环绕通知方法,相当于之前的动态***

/**
* 环绕通知
* @param point
* spring 框架为我们提供了一个接口:ProceedingJoinPoint,它可以作为环绕通知的方法参数。
* 在环绕通知执行时,spring 框架会为我们提供该接口的实现类对象,我们直接使用就行。
* @return
*/
public Object aroundLog(ProceedingJoinPoint point){
        Object val;
        try {
            Object[] args = point.getArgs();
            System.out.println("前置通知");
            val=point.proceed(args);
            System.out.println("后置通知");
            return val;
        }catch (Throwable e){
            System.out.println("异常通知");
            throw new RuntimeException(e);
        }finally {
            System.out.println("最终通知");
        }
    }

修改bean.xml的aop配置

 <!-- 配置aop -->
    <aop:config>
        <!-- 配置切入点表达式 -->
        <aop:pointcut id="pt" expression="execution(* *..AccountServiceImpl.*(..))"/>
        <!-- 配置切面 -->
        <aop:aspect id="logAdvice" ref="logger">
            <!-- 配置环绕置通知 -->
            <aop:around method="aroundLog" pointcut-ref="pt"/>
        </aop:aspect>
    </aop:config>

测试运行

在这里**图片描述


使用注解的aop配置

修改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"
       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">

  要扫描的包:
      (给业务层实现类加上@Sevice注解,此处省略)
    <context:component-scan base-package="service"/>
    <context:component-scan base-package="utils"/>
  开启注解aop支持:
    <aop:aspectj-autoproxy/>

</beans>

修改后的Logger类

//用于记录日志的工具类,提供公共代码
@Component("logger")
@Aspect//指明是切面类
public class Logger {
    //配置切入点表达式
    @Pointcut("execution(* *..*.AccountServiceImpl.*(..))")
    private void pt(){};

    @Before("pt()")
    public void beforePrintLog(){
        System.out.println("前置通知");
    }

    @AfterReturning("pt()")
    public void afterPrintLog(){
        System.out.println("后置通知");
    }

    @AfterThrowing("pt()")
    public void errorPrintLog(){
        System.out.println("异常通知");
    }
    @After("pt()")
    public void finalPrintLog(){
        System.out.println("最终通知");
    }

    @Around("pt()")
    public Object aroundLog(ProceedingJoinPoint point){
        Object val;
        try {
            Object[] args = point.getArgs();
            System.out.println("前置通知");
            val=point.proceed(args);
            System.out.println("后置通知");
            return val;
        }catch (Throwable e){
            System.out.println("异常通知");
            throw new RuntimeException(e);
        }finally {
            System.out.println("最终通知");
        }
    }
}

测试运行
在这里**图片描述
我们发现最终通知出现在后置通知前,这是注解配置aop的问题,一般我们使用环绕通知解决。我们给除了环绕通知的其他注解都注释掉,发现可以正常执行。


纯注解配置
去掉bean.xml,创建一个配置类

@Configuration//配置类
@ComponentScan(basePackages={"service","utils"})//要扫描的包
@EnableAspectJAutoProxy//开启aop支持
public class SpringConfig {
}

测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = {SpringConfig.class})//引入配置类
public class AccountTest {

    @Autowired
    IAccountService accountService;


    @Test
    public void test(){
        accountService.saveAccount();
    }
}

JDBCemplate

概述

它是 spring 框架中提供的一个对象,是对原始 JDBC API 对象的简单封装。
spring 框架为我们提供了很多的操作模板类。

  • 操作关系型数据的:
    JdbcTemplate
    HibernateTemplate
  • 操作 nosql 数据库的:
    RedisTemplate
  • 操作消息队列的:
    JmsTemplate

环境搭建

在pom.xml中导入相关依赖

<!-- 导入mysql的jar包 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.18</version>
        </dependency>
        <!-- 导入context的jar包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>
        <!-- 导入测试单元的jar包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!-- 导入JDBC的jar包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!-- 导入事务控制的jar包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

基本使用

仍然使用前2篇文章的account表和Account类
测试类:

public class JDBCdemo1 {

    public static void main(String[] args) {
        //准备数据源:spring的内置数据源
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName("com.mysql.cj.jdbc.Driver");
        dataSource.setUrl("jdbc:mysql:///test?serverTimezone=UTC");
        dataSource.setUsername("root");
        dataSource.setPassword("sjh2019");

        JdbcTemplate jdbcTemplate = new JdbcTemplate();
        //设置数据源
        jdbcTemplate.setDataSource(dataSource);
        jdbcTemplate.execute("insert into account(name,money) values('eee',3000)");
    }
}

运行后,查询数据库,数据被成功**:

在这里**图片描述


使用IOC简化操作

bean.xml中的配置:

<!-- 配置数据源 -->
    <bean id="dataSource1" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!-- 连接数据库的信息 -->
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql:///test?serverTimezone=UTC"/>
        <property name="username" value="root"/>
        <property name="password" value="sjh2019"/>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource1"/>
    </bean>

测试类

public class JDBCdemo1 {

    public  static void main(String[] args) {
        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        JdbcTemplate jdbcTemplate = (JdbcTemplate) ac.getBean("jdbcTemplate");
        jdbcTemplate.execute("insert into account(name,money) values('eee',3000)");
    }
}

使用Jdbctemplate实现单表CRUD

增删改一般用update方法,查询用query,查询一行一列用queryForObject

public  static void main(String[] args) {
        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        JdbcTemplate jdbcTemplate = (JdbcTemplate) ac.getBean("jdbcTemplate");
        //保存
        jdbcTemplate.update("insert into account(name,money) values(?,?)","eee",3000);
        //更新
        jdbcTemplate.update("update account set name=?,money=? where id=?","test",4000,5);
        //删除
        jdbcTemplate.update("delete from account where id=?",5);
        //查询所有
        List<Account> acounts = jdbcTemplate.query("select * from acount", new BeanPropertyRowMapper<>(Account.class));
        //查询一个
        List<Account> accounts = jdbcTemplate.query("select * from acount where id =?", new BeanPropertyRowMapper<>(Account.class), 1);
        System.out.println(accounts.get(0));
        //查询一行一列,不使用groupby
        Integer count = jdbcTemplate.queryForObject("select count(*) from account where money > ?", Integer.class, 2000);
        System.out.println(count);
    }

JDBCTemplate在dao中的使用

持久层接口

//账户持久层接口
public interface IAccountDao {


    //查询一个
    Account findAccountById(Integer id);

    //更新
    void updateAccount(Account account);


    //根据名称查找唯一一个账户
    Account findAccountByName(String name);
}

持久层实现类

//账户持久层实现类
public class AccountDaoImpl implements IAccountDao {

    private JdbcTemplate jdbcTemplate;

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

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



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



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

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: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">


    <!-- 配置账户持久层 -->
    <bean id="accountDao" class="dao.impl.AccountDaoImpl">
        <property name="jdbcTemplate" ref="jdbcTemplate"/>
    </bean>

    <!-- 配置数据源 -->
    <bean id="dataSource1" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!-- 连接数据库的信息 -->
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql:///test?serverTimezone=UTC"/>
        <property name="username" value="root"/>
        <property name="password" value="sjh2019"/>
    </bean>

    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource1"/>
    </bean>
</beans>

测试类

public class DaoTest {

    public static void main(String[] args) {
        ApplicationContext ac=new ClassPathXmlApplicationContext("bean.xml");
        IAccountDao accountDao = ac.getBean("accountDao",IAccountDao.class);

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

        accountById.setMoney(2000f);
        accountDao.updateAccount(accountById);
        System.out.println(accountDao.findAccountById(1));

        Account accountByName = accountDao.findAccountByName("范闲");
        System.out.println(accountByName);

    }
}

结果:

Account{id=1, name='aaa', money=1000.0}
Account{id=1, name='aaa', money=2000.0}
Account{id=4, name='范闲', money=5000.0}


另一种编写dao的方式
JdbcDaoSupport 是 spring 框架为我们提供的一个类,该类中定义了一个 JdbcTemplate 对象,我们可以直接获取使用,但是要想创建该对象,需要为其提供一个数据源。
让之前的实现类继承JdbcDaoSupport类,使用继承的方法获取jdbctemplate对象。

//账户持久层实现类
public class AccountDaoImpl extends JdbcDaoSupport implements IAccountDao {


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



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



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

xml的对应配置
只需配置并注入数据源即可。

 <!-- 配置账户持久层 -->
    <bean id="accountDao" class="dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource1"/>
    </bean>

    <!-- 配置数据源 -->
    <bean id="dataSource1" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!-- 连接数据库的信息 -->
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql:///test?serverTimezone=UTC"/>
        <property name="username" value="root"/>
        <property name="password" value="sjh2019"/>
    </bean>

基于XML实现事务控制

在上一篇中,我们通过动态***实现了事务控制和aop,现在通过xml配置aop的方式实现事务控制。

在事务控制类中加入一个环绕通知的方法,删除之前的工厂类和***类

public Object around(ProceedingJoinPoint point){
        Object val;
        try{
            beginTran();
            System.out.println("开始事务");
            val=point.proceed(point.getArgs());
            commit();
            System.out.println("提交事务");
            return val;
        }catch (Throwable e){
            rollback();
            throw new RuntimeException(e);
        }finally {
            release();
            System.out.println("释放连接");
        }
    }

在bean.xml中配置

<aop:config>
        <aop:aspect id="aop" ref="transactionManager">
            <aop:around method="around" pointcut="execution(* *..AccountServiceImpl.*(..))"/>
        </aop:aspect>
    </aop:config>

删除之前工厂类和***类的配置,完整的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"
       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 -->
    <bean id="accountService" class="service.impl.AccountServiceImpl">
        <!-- 注入dao对象 -->
       <property name="accountDao" ref="accountDao"/>
    </bean>

    <!-- 配置dao -->
    <bean id="accountDao" class="dao.impl.AccountDaoImpl">
        <!-- 注入queryrunner -->
        <property name="queryRunner" ref="query"/>
        <!-- 注入工具类 -->
        <property name="connectionUtils" ref="connectionUtils"/>
    </bean>

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

    <!--  配置connection工具类 -->
    <bean id="connectionUtils" class="utils.ConnectionUtils">
        <!-- 注入数据源 -->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 配置数据源 -->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <!-- 连接数据库的信息 -->
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"/>
        <property name="jdbcUrl" value="jdbc:mysql:///test?serverTimezone=UTC"/>
        <property name="user" value="root"/>
        <property name="password" value="sjh2019"/>
    </bean>

    <!-- 配置事务管理器 -->
    <bean id="transactionManager" class="utils.TransactionManager">
        <!-- 注入工具类 -->
        <property name="connectionUtils" ref="connectionUtils"/>
    </bean>

    <aop:config>
        <aop:aspect id="aop" ref="transactionManager">
            <aop:around method="around" pointcut="execution(* *..AccountServiceImpl.*(..))"/>
        </aop:aspect>
    </aop:config>

</beans>

测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class AccountTest {

    @Autowired
    IAccountService accountService;


    @Test
    public void findAll(){
        List<Account> accounts = accountService.findAll();
        accounts.forEach(System.out::println);
    }


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

执行测试方法后,出现异常,但事务回滚成功,并未出现问题。


基于XML的声明式事务

环境准备和测试

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.sjh</groupId>
    <artifactId>spring01</artifactId>
    <version>1.0-SNAPSHOT</version>
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <configuration>
                    <source>8</source>
                    <target>8</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    <packaging>jar</packaging>

    <dependencies>
        <!-- 导入mysql的jar包 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.18</version>
        </dependency>
        <!-- 导入context的jar包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>
        <!-- 导入测试单元的jar包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <!-- 导入JDBC的jar包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

        <!-- 导入事务控制的jar包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>

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

        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>

    </dependencies>

</project>

持久层接口和实现类

/**
 * 账户的持久层接口
 */
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());
    }
}

业务层接口和实现类

public interface IAccountService {

    //根据Id查询账户
    Account findAccountById(Integer accountId);

    //转账
    void transfer(String sourceName,String targetName,Float money);
}
public class AccountServiceImpl implements IAccountService {
    private IAccountDao accountDao;

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

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

    @Override
    public void transfer(String sourceName, String targetName, Float money) {
        //查询出转出和转入账户
        Account source = accountDao.findAccountByName(sourceName);
        Account target = accountDao.findAccountByName(targetName);
        //更改账户金额
        source.setMoney(source.getMoney()-money);
        target.setMoney(target.getMoney()+money);
        //更新账户
        accountDao.updateAccount(source);
        accountDao.updateAccount(target);
    }
}

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

    <!-- 配置账户的业务层-->
    <bean id="accountService" class="service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"/>
    </bean>

    <!-- 配置账户的持久层-->
    <bean id="accountDao" class="dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/test?serverTimezone=UTC"/>
        <property name="username" value="root"/>
        <property name="password" value="1234"/>
    </bean>
</beans>

测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = "classpath:bean.xml")
public class JDBCTest {
    @Autowired
    private IAccountService accountService;
    @Test
    public void test(){
        accountService.transfer("aaa","bbb",100f);
    }
}

进行测试
初始状态:
在这里**图片描述


执行测试方法后,结果正常
在这里**图片描述


但此时由于没有事务控制,如果转账代码中加入异常代码,会出现问题
在这里**图片描述


此时再次执行,转出账户金额减少,但转入账户金额并未增加
在这里**图片描述


进行事务XML配置

第一步:配置事务管理器

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

第二步:配置事务的通知引用事务管理器

<!-- 事务的配置 --> 
<tx:advice id="txAdvice" transaction-manager="transactionManager">
</tx:advice>

第三步:配置事务的属性

  • read-only:是否是只读事务。默认 false,不只读。只有查询方法才能设置true。
  • isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。
  • propagation:指定事务的传播行为,默认required,表示一定有事务,增删改方法的选择。查询方法可用supports。
  • timeout:指定超时时间。默认值为:-1。永不超时。
  • rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。没有默认值,任何异常都回滚。
  • no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回滚。没有默认值,任何异常都回滚。
<!--在 tx:advice 标签内部 配置事务的属性 --> 
<tx:attributes>
<tx:method name="*" read-only="false" propagation="REQUIRED"/>
<tx:method name="find*" read-only="true" propagation="SUPPORTS"/>
</tx:attributes>

第四步:配置 AOP 切入点表达式

<!-- 配置 aop -->
 <aop:config>
<!-- 配置切入点表达式 --> 
<aop:pointcut expression="execution(* *..impl.*.*(..))" id="pt1"/>
</aop:config>

第五步:配置切入点表达式和事务通知的对应关系

<!-- 在 aop:config 标签内部:建立事务的通知和切入点表达式的关系 --> 
<aop:advisor advice-ref="txAdvice" pointcut-ref="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: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="service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"/>
    </bean>

    <!-- 配置账户的持久层-->
    <bean id="accountDao" class="dao.impl.AccountDaoImpl">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!-- 配置数据源-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/test?serverTimezone=UTC"/>
        <property name="username" value="root"/>
        <property name="password" value="sjh2019"/>
    </bean>

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

    <!-- 配置事务通知 -->
    <tx:advice id="transactionInterceptor" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="*" read-only="false" propagation="REQUIRED"/>
            <!-- 下面的优先级更高 -->
            <tx:method name="find*" read-only="true" propagation="SUPPORTS"/>
        </tx:attributes>
    </tx:advice>

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


</beans>

此时再次运行测试类
在这里**图片描述
事务控制是成功的
在这里**图片描述


基于注解的声明式事务
项目整体结构
在这里**图片描述


持久层配置

@Repository("accountDao")
public class AccountDaoImpl 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());
    }
}

业务层配置

@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);
    }

    @Override
    @Transactional(propagation = Propagation.REQUIRED)
    public void transfer(String sourceName, String targetName, Float money) {
        //查询出转出和转入账户
        Account source = accountDao.findAccountByName(sourceName);
        Account target = accountDao.findAccountByName(targetName);
        //更改账户金额
        source.setMoney(source.getMoney()-money);
        target.setMoney(target.getMoney()+money);
        //更新账户
        accountDao.updateAccount(source);
        //模拟错误
        int i=1/0;
        accountDao.updateAccount(target);
    }
}

jdbc配置
propertis文件

driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql:///test?serverTimezone=UTC
user=root
password=sjh2019
public class JdbcConfig {
    @Value("${driver}")
    private String driver;
    @Value("${url}")
    private String url;
    @Value("${user}")
    private String user;
    @Value("${password}")
    private String password;

    @Bean("jdbcTemplate")
    public JdbcTemplate createJdbcTemplate(DataSource dataSource){
        return new JdbcTemplate(dataSource);
    }

    @Bean("dataSource")
    public DataSource createDataSource (){
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(user);
        dataSource.setPassword(password);
        return dataSource;
    }




}

事务控制

//事务配置相关类
public class TransactionConfig {

    @Bean("transactionManager")
    public PlatformTransactionManager getTransactionManager(DataSource dataSource){
        return new DataSourceTransactionManager(dataSource);
    }
}

主配置

@Configuration//配置类,相当于bean.xml
@ComponentScan(basePackages={"service","dao"})//要扫描的包
@Import({JdbcConfig.class,TransactionConfig.class})//读取的配置类
@PropertySource("jdbc.properties")//读取的配置资源
@EnableTransactionManagement//开启spring事务注解支持
public class SpringConfig {
}

测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(classes = SpringConfig.class)//引入主配值文件
public class JDBCTest {
    @Autowired
    private IAccountService accountService;

    @Test
    public void test(){
        accountService.transfer("aaa","bbb",100f);
    }
}

运行结果:可成功控制事务


#Java##Spring##笔记#
全部评论
期待MVC
1 回复
分享
发布于 2020-01-11 11:08

相关推荐

点赞 评论 收藏
转发
2 17 评论
分享
牛客网
牛客企业服务