2022-05-25  2022-05-25    11177 字   23 分钟

复习回顾:

DI: 依赖注入
依赖注入的方式: 
构造器注入: 
setter注入u:
P名称空间注入:
SpEL表达式注入:

IOC的注解版本: 
@Component  所属的类没有明确的层级划分, 此时就可以使用该注解。
@Repository dao层
@Service service层:
@Controller web层:

DI的相关注解: 
@Value("") 能够获得配置文件当中的内容: 
@Autowire 根据类型自动装配。  如果容器当中有多个对象, 此时就会根据名称进行装配。 
@Qualifier 根据对象的名称进行装配。 
@Resource java提供的, @Autowire+@Qualifier 

JdbcTemplate模板对象: 
query() | queryForObject()
update()

spring整合Junit: 
(1)引入依赖包: 
(2)引入注解:

0、实现转账业务

一: 实现转账业务

1 底层数据库表: Account

2 实现类: Account

public class Account implements Serializable {
    private Integer id;
    private String name;
    private Double money;
    
    getter  setter ... 
}

3 AccountDao 层接口 & AccountDaoImpl

package com.offcn.dao;

import com.offcn.pojo.Account;

public interface AccountDao {// data access object


    /**
     *  根据id进行唯一性查询
     * @param id
     * @return
     */
     public Account findById(Integer id);


    /**
     * 更新账户的方法:
     * @param account
     */
    public void update(Account account);
}
package com.offcn.dao.impl;

import com.offcn.dao.AccountDao;
import com.offcn.pojo.Account;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;

import java.sql.SQLException;

public class AccountDaoImpl implements AccountDao {

    //private JdbcTemplate jdbcTemplate;

    private QueryRunner queryRunner ;

    public QueryRunner getQueryRunner() {
        return queryRunner;
    }

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

    @Override
    public Account findById(Integer id) {
        try {
            String sql ="select * from account where id=?";
            Account account = queryRunner.query(sql, new BeanHandler<Account>(Account.class), id);
            return account;
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return null;
    }

    @Override
    public void update(Account account) {

        try {
            String sql ="update account set money=? where id=?";
            queryRunner.update(sql,account.getMoney(),account.getId());
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
}

4 AccountService层接口 & AccountServiceImpl

package com.offcn.service;

public interface AccountService {

    /**
     * 定义转账方法:
     * @param sourceId 来源账户
     * @param targetId 目标账户
     * @param money    转账金额
     */
    public void transfer(Integer sourceId, Integer targetId, Double money);
    
}
package com.offcn.service.impl;

import com.offcn.dao.AccountDao;
import com.offcn.pojo.Account;
import com.offcn.service.AccountService;

public class AccountServiceImpl implements AccountService {


    private AccountDao accountDao;

    @Override
    public void transfer(Integer sourceId, Integer targetId, Double money) {
        //1: 查询来源账户,查询目标账户
        Account sAcc = accountDao.findById(sourceId);
        Account tAcc = accountDao.findById(targetId);

        //2: 内存当中修改金额
        sAcc.setMoney(sAcc.getMoney() - money);
        tAcc.setMoney(tAcc.getMoney() + money);


        //3: 更新到数据库当中:
        accountDao.update(sAcc);
        accountDao.update(tAcc);

    }


    public AccountDao getAccountDao() {
        return accountDao;
    }

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

5: applicationContext.xml 管理对象

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">

  
    <!--dataSource-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
    
    <!--构建QueryRunner 对象
       QueryRunner qr = new QueryRunner(DataSource dataSource);
    -->
    <bean id ="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>
    
    
    <!--accountDao-->
    <bean id="accountDao" class="com.offcn.dao.impl.AccountDaoImpl">
        <property name="queryRunner" ref="queryRunner"></property>
                  
    </bean>
    
    
    <!--accountService-->
    <bean id="accountService" class="com.offcn.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
    </bean>
    
</beans>

6 转账测试

package com.offcn;

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

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestTransfer {

    @Autowired
    private AccountService accountService;
    @Test
    public void test1(){
        accountService.transfer(1001,1002,100D);
    }

}

二、事务相关内容复习

1、事务的定义

2、事务的特性

3、事务的隔离级别

4、不同事务隔离级别,产生不同的错误数据。

5、事务相关的代码: Connection ~

三、事务控制-ThreadLocal

1.定义了一个连接的工具类

/**
 * 管理连接的工具类:  
 */
public class ConnectionUtil {
    
    private DataSource dataSource;//准备连接对象: 

    /**
     * ThreadLocal: map
     * map.put(线程对象,value);
     * map.get(线程对象);
     */
    private ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
    
    
    public Connection getConnection(){
        Connection connection = threadLocal.get();
        if (connection == null){
            try {
                connection = dataSource.getConnection();
                //绑定在ThreadLocal当中: 
                threadLocal.set(connection);
                return connection;
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
        }
        return  connection;
        
    }
     
}

2、改造了AccountDaoImpl

package com.offcn.dao.impl;

import com.offcn.dao.AccountDao;
import com.offcn.pojo.Account;
import com.offcn.utils.ConnectionUtil;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;

import java.sql.SQLException;

public class AccountDaoImpl implements AccountDao {

    //private JdbcTemplate jdbcTemplate;

    private QueryRunner queryRunner ;
    private ConnectionUtil connectionUtil;

    public ConnectionUtil getConnectionUtil() {
        return connectionUtil;
    }

    public void setConnectionUtil(ConnectionUtil connectionUtil) {
        this.connectionUtil = connectionUtil;
    }

    public QueryRunner getQueryRunner() {
        return queryRunner;
    }

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

    @Override
    public Account findById(Integer id) {
        try {
            String sql ="select * from account where id=?";
            Account account = queryRunner.query(connectionUtil.getConnection(),sql, new BeanHandler<Account>(Account.class), id);
            return account;
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
        return null;
    }

    @Override
    public void update(Account account) {

        try {
            String sql ="update account set money=? where id=?";
            queryRunner.update(connectionUtil.getConnection() ,sql,account.getMoney(),account.getId());
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
}

3、定义了事务管理的工具类

package com.offcn.utils;

import java.sql.Connection;
import java.sql.SQLException;

/**
 * 定义和事务相关的代码:
 * 事务的开启 提交  回滚
 *
 */
public class TransactionManager {

    private ConnectionUtil connectionUtil;


    //开启事务:
    public  void beginTransaction(){
        try {
            Connection connection = connectionUtil.getConnection();
            connection.setAutoCommit(false);
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    //事务提交
    public  void commit(){
        try {
            Connection connection = connectionUtil.getConnection();
            connection.commit();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    //事务提交
    public  void rollback(){
        try {
            Connection connection = connectionUtil.getConnection();
            connection.rollback();
        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }

    //定义释放资源:
    public void release() {
        try {
            Connection connection = connectionUtil.getConnection();
            connection.close();

        } catch (SQLException throwables) {
            throwables.printStackTrace();
        }
    }
}

4、改造AccountServiceImpl

package com.offcn.service.impl;

import com.offcn.dao.AccountDao;
import com.offcn.pojo.Account;
import com.offcn.service.AccountService;
import com.offcn.utils.TransactionManager;

public class AccountServiceImpl implements AccountService {


    private AccountDao accountDao;
    private TransactionManager transactionManager;

    @Override
    public void transfer(Integer sourceId, Integer targetId, Double money) {


        try {
            //开启事务:
            transactionManager.beginTransaction();
            //1: 查询来源账户,查询目标账户
            Account sAcc = accountDao.findById(sourceId);
            Account tAcc = accountDao.findById(targetId);

            //2: 内存当中修改金额
            sAcc.setMoney(sAcc.getMoney() - money);
            tAcc.setMoney(tAcc.getMoney() + money);


            //3: 更新到数据库当中:
            accountDao.update(sAcc);

            int i= 1/0;

            accountDao.update(tAcc);

            //事务的提交:
            transactionManager.commit();
        } catch (Exception e) {
            e.printStackTrace();
            transactionManager.rollback();
        } finally {
            //资源释放:
            transactionManager.release();
        }

    }


    public AccountDao getAccountDao() {
        return accountDao;
    }

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

5、配置文件改造

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


    <!--dataSource-->
    <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
        <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
        <property name="url" value="jdbc:mysql://localhost:3306/test"></property>
        <property name="username" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

    <!--构建QueryRunner 对象
       QueryRunner qr = new QueryRunner(DataSource dataSource);
    -->
    <bean id ="queryRunner" class="org.apache.commons.dbutils.QueryRunner">
        <!--为了保证在同一个事务使用同一个连接,不能在构建qr的时候,执行指定DataSource-->
      <!--  <constructor-arg name="ds" ref="dataSource"></constructor-arg>-->
    </bean>

    <!--connectionUtil-->

    <bean id="connectionUtil" class="com.offcn.utils.ConnectionUtil">
        <property name="dataSource" ref="dataSource"></property>
    </bean>

    <!--transactionManager-->
    <bean id="transactionManager" class="com.offcn.utils.TransactionManager">
        <property name="connectionUtil" ref="connectionUtil"></property>
    </bean>


    <!--accountDao-->
    <bean id="accountDao" class="com.offcn.dao.impl.AccountDaoImpl">
        <property name="queryRunner" ref="queryRunner"></property>
        <property name="connectionUtil" ref="connectionUtil"></property>
    </bean>


    <!--accountService-->
    <bean id="accountService" class="com.offcn.service.impl.AccountServiceImpl">
        <property name="accountDao" ref="accountDao"></property>
        <property name="transactionManager" ref="transactionManager"></property>
    </bean>

</beans>

6、测试转账-考虑事务

package com.offcn;

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

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestTransfer {

    @Autowired
    private AccountService accountService;
    @Test
    public void test1(){
        accountService.transfer(1001,1002,100D);
    }

}

四、 事务控制-动态代理

1、动态代理模式【23】

你 【 中介 】 买房 买房~

代理的角色:

​ 代理类: 代理对象【中介】

​ 被代理类: 被代理对象【买房人】

​ 目标方法: 核心业务

作用: 对某个类的某个方法进行功能增强。

动态代理分类:

​ (1)基于接口的动态代理(JDK的动态代理): 被代理的类必须实现接口。 重要【JDK提供的】

​ (2)CGLIB代理: 地方提供的代理,需要引入第三方包。

2、动态代理的入门案例

需求: 公司part , person 完成晚会, sing dance

package com.offcn.proxy;

public interface CompanyParty {

    public void sing(Double money);
    public void dance(Double money);

}

创建接口的实现类对象:

package com.offcn.proxy;

public class Person implements CompanyParty {
    @Override
    public void sing(Double money) {
        System.out.println("给你"+money+"sing");
    }

    @Override
    public void dance(Double money) {
        System.out.println("给你"+money+"dance");
    }
}

使用JDK的动态代理,获得person类的代理类对象,完成业务方法

package com.offcn;

import com.offcn.proxy.CompanyParty;
import com.offcn.proxy.Person;
import org.junit.Test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

public class TestProxy {

    @Test
    public void test1(){
        //被代理:person
        Person person = new Person();
        //person.dance(1001D);
        //person.sing(1008D) ;


        /**
         *  JDK的动态代理
         *  Proxy: 反射包当中提供的类:
         *  newProxyInstance(p1,p2,p3): 获得代理类的方法:返回值是Object类型。
         *     p1: ClassLoader 类的加载器。
         *        目标对象(被代理对象)的类加载器。
         *
         *     p2: 获得了接口当中所有的方法。
         *       目的: 代理类和被代理类对象有着相同的行为:
         *
         *     p3: InvocationHandler 定义代理对象和被代理对象之间的代理策略。
         *
         *
         */
        CompanyParty proxy =(CompanyParty) Proxy.newProxyInstance(
                person.getClass().getClassLoader(),
                person.getClass().getInterfaces(),
                new InvocationHandler() {

                    /**
                     *
                     * @param proxy  一般情况不适用。
                     * @param method  封装了执行的目标方法。
                     * @param args    封装了执行目标方法的参数。
                     * @return object  执行了目标方法,目标方法的返回值。
                     * @throws Throwable 执行目标方法,遇到异常信息
                     *
                     */
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                        String name = method.getName();//获得执行方法的名称。;
                        Double money = (Double) args[0];//获得目标方法的参数信息:
                        Object obj=null;
                        if ("sing".equals(name) && money >=1000){
                            //真正执行目标方法:  p1:目标对象 p2: 目标方法需要的参数信息。
                             obj = method.invoke(person, args);
                        }
                        if ("dance".equals(name)&& money>=2000){
                             obj = method.invoke(person, args);
                        }

                        return obj;
                    }
                }
        );

        proxy.sing(1005D);
        proxy.dance(2005D);

    }
}

3 使用动态代理控制事务

创建了工厂类 生成代理类对象 

package com.offcn.factory;

import com.offcn.service.AccountService;
import com.offcn.utils.TransactionManager;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * 工厂对象用来生成代理类对象;
 */
public class BeanFactory {
    
    //目标对象:
    public AccountService accountService;
    private TransactionManager transactionManager;
    
    
    public AccountService getProxy(){
       return  (AccountService)Proxy.newProxyInstance(
               accountService.getClass().getClassLoader(),
               accountService.getClass().getInterfaces(),
               new InvocationHandler() {//达成具体的代理策略
                   @Override
                   public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                       Object invoke= null;
                       try {
                           //开启事务: 
                           transactionManager.beginTransaction();
                           invoke = method.invoke(accountService, args);
                           //提交事务: 
                           transactionManager.commit();
                           return invoke;
                       } catch (Exception e) {
                           e.printStackTrace();
                           //执行业务方法, 遇到了异常信息: 
                           transactionManager.rollback();
                       } finally {
                           transactionManager.release();
                       }
                       return invoke;
                   }
               }
       );
    }

    public AccountService getAccountService() {
        return accountService;
    }

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

    public TransactionManager getTransactionManager() {
        return transactionManager;
    }

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

业务层改造:

 @Override
    public void transfer(Integer sourceId, Integer targetId, Double money) {
            //1: 查询来源账户,查询目标账户
            Account sAcc = accountDao.findById(sourceId);
            Account tAcc = accountDao.findById(targetId);

            //2: 内存当中修改金额
            sAcc.setMoney(sAcc.getMoney() - money);
            tAcc.setMoney(tAcc.getMoney() + money);

            //3: 更新到数据库当中:
            accountDao.update(sAcc);
            int i= 1/0;
            accountDao.update(tAcc);

          
    }

BeanFactory配置在applicationContext.xml当中:

  <!--BeanFactory: 
       配置了一个实例工厂: factory-bean 指定的工厂的实例对象
                        factory-method:工厂方法,方法的返回值存在IOC容器当中
     -->
    <bean id="factory" class="com.offcn.factory.BeanFactory">
        <property name="transactionManager" ref="transactionManager"></property>
        <property name="accountService" ref="accountService"></property>
    </bean>
    <bean factory-bean="factory" factory-method="getProxy"></bean>

测试:

package com.offcn;

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

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class TestTransfer {

    @Autowired
    @Qualifier("proxy")//获得对象是代理对象
    private AccountService proxy;
    @Test
    public void test1(){
        //accountService.transfer(1001,1002,100D);
        proxy.transfer(1001,1002,100D);
    }

}

一、 SpringAOP机制详解

(一)AOP 概述

1 什么是 AOP

话术:
AOP 为 Aspect Oriented Programming 的缩写,意思为面向切面编程,是通过预编译方式和运行期动态代理实现程序功能的统一维护的一种技术。

aop:面向切面编程
底层: 动态代理 
      反射: java语言, 是一个静态语言, 但是可以通过反射技术, 体现出来动态性。 

2 AOP编程思想

话术:
AOP 面向切面编程是一种编程思想,是 OOP(面向对象) 的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。

应用场景: aop 事务控制, 权限校验, 日志记录, 性能统计

3 Spring中的常用术语

话术:
Joinpoint(连接点):所谓连接点是指那些被拦截到的点。在spring中,这些点指的是方法,因为spring只支持方法类型的连接点。 
所有有机会被增强的方法就就似乎链接点。

Pointcut(切点):真正被增强的方法, 就是切点。  案例: transfer 方法。

Advice(通知/ 增强):所谓通知是指拦截到 Joinpoint 之后所要做的事情就是通知。
在transfer方法上, 进行了事务代码的增强。 事务代码就是通知~ 
通知的类型:前置通知,正常返回通知,异常返回通知,最终通知,环绕通知。【面试问题】

Target(目标对象):代理的目标对象 [被代理对象]
Proxy (代理):一个类被 AOP 织入增强后,就产生一个结果代理类,代理类产生代理对象
Weaving(织入):是指把增强应用到目标对象来创建新的代理对象的过程
Aspect(切面):是切入点和通知(引介)的结合  
              切面描述了: 具体的通知应用在具体的哪个切点上。

4 AOP 的作用及优势

话术:
作用:在程序运行期间,在不修改源码的情况下对方法进行功能增强
优势:减少重复代码,提高开发效率,并且便于维护

(二)Spring基于XML的AOP配置

讲解思路: 
 1、搭建maven工程,沿用上一章节转账的业务场景
 2、准备好通知类TransactionManager
 3、讲解基于XML形式的AOP配置,详细介绍每个标签,每个标签当中属性的作用
 4、测试使用AOP进行事务配置
 5、常用通知类型的总结
 6、详解切入点表达式的语法,常用案例的列举

1 环境搭建

1.1 构建maven工程添加依赖

<properties>
        <spring.version>5.2.5.RELEASE</spring.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>

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

1.2 沿用转账业务的代码

搭建了转账环境~

1.3 创建 Spring 的配置文件并导入约束

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns: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">
        
</beans>

1.4 配置 Spring 的 IOC

<!-- 配置数据源 -->
<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/test"></property>
    <property name="user" value="root"></property>
    <property name="password" value="root"></property>
</bean>

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

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

<!--配置accountDao-->
<bean id="accountDao" class="com.offcn.dao.impl.AccountDaoImpl">
    <property name="queryRunner" ref="queryRunner"></property>
    <property name="connectionUtils" ref="connectionUtils"></property>
</bean>

<!--配置accountService-->
<bean id="accountService" class="com.offcn.service.impl.AccountServiceImpl">
    <property name="accountDao" ref="accountDao"></property>
</bean>

1.5 抽取公共代码制作成通知(增强代码)

/**
 * 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
 */
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.removeConnection();
            connectionUtils.getThreadConnection().close();//还回连接池中
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

2 AOP 配置步骤

2.1 把通知类用 bean 标签配置起来

<!--配置通知:txManager-->
<bean id="txManager" class="com.offcn.utils.TransactionManager">
    <property name="connectionUtils" ref="connectionUtils"></property>
</bean>

2.2 使用 aop:config 声明 AOP 配置

aop:config: 
   作用: 开始声明aop配置
<aop:config>
      <!-- 配置的代码都写在此处 -->
</aop:config>

2.3 使用 aop:aspect 配置切面

aop:aspect
   作用: 用于配置切面
   属性: 
         id :给切面提供一个唯一标识。
         ref:引用配置好的通知类 bean 的 id。
<aop:aspect id="tdAdvice" ref="txManager">
     <!--配置通知的类型要写在此处-->  
</aop:aspect>

2.4 使用 aop:pointcut 配置切入点表达式

aop:pointcut
   作用: 用于配置切入点表达式。就是指定对哪些类的哪些方法进行增强。
   属性: expression:用于定义切入点表达式。
         id:用于给切入点表达式提供一个唯一标识
<aop:pointcut id="point1"
               expression="execution( public void  com.offcn.service.impl.AccountServiceImpl.transfer(java.lang.String,java.lang.String,java.lang.Double))"/>
        

2.5 使用 aop:xxx 配置对应的通知类型

aop:before
    作用:用于配置前置通知。指定增强的方法在切入点方法之前执行
    属性:
         method:用于指定通知类中的增强方法名称
		 ponitcut-ref:用于指定切入点的表达式的引用
		 poinitcut:用于指定切入点表达式
    执行时间点:
         切入点方法执行之前执行   
<aop:before method="beginTransaction" pointcut-ref="point1"></aop:before>
aop:after-returning
	作用: 
    	用于配置后置通知
	属性:
   		method:指定通知中方法的名称。
  		pointct:定义切入点表达式
   		pointcut-ref:指定切入点表达式的引用
   	执行时间点:
   	    切入点方法正常执行之后。它和异常通知只能有一个执行
   	    
 <aop:after-returning method="commit" pointcut-ref="point1"/> 	    
aop:after-throwing
	作用:
		用于配置异常通知
	属性:
		method:指定通知中方法的名称。
		pointct:定义切入点表达式
		pointcut-ref:指定切入点表达式的引用
	执行时间点:
		切入点方法执行产生异常后执行。它和后置通知只能执行一个
 <aop:after-throwing method="rollback" pointcut-ref="point1"/>	
aop:after
	作用:
		用于配置最终通知
	属性:
        method:指定通知中方法的名称。
        pointct:定义切入点表达式
        pointcut-ref:指定切入点表达式的引用
	执行时间点:
		无论切入点方法执行时是否有异常,它都会在其后面执行。
 
 <aop:after method="release" pointcut-ref="point1"/>

3 切入点表达式说明

3.1 切点表达式的语法

execution([修饰符] 返回值类型 包名.类名.方法名(参数))
  • 访问修饰符可以省略
  • 返回值类型、包名、类名、方法名可以使用星号* 代表任意
  • 包名与类名之间一个点 . 代表当前包下的类,两个点 .. 表示当前包及其子包下的类
  • 参数列表可以使用两个点 .. 表示任意个数,任意类型的参数列表

例如:

全匹配方式

public void 
com.ujiuye.service.impl.AccountServiceImpl.saveAccount(com.ujiuye.domain.Account)

访问修饰符可以省略

void com.ujiuye.service.impl.AccountServiceImpl.saveAccount(com.ujiuye.domain.Account)

返回值可以使用*号,表示任意返回值

* com.ujiuye.service.impl.AccountServiceImpl.saveAccount(com.ujiuye.domain.Account)

包名可以使用 * 号,表示任意包,但是有几级包,需要写几个 *

* *.*.*.*.AccountServiceImpl.saveAccount(com.ujiuye.domain.Account)

使用..来表示当前包,及其子包

* com..AccountServiceImpl.saveAccount(com.ujiuye.domain.Account)

类名可以使用*号,表示任意类

* com..*.saveAccount(com.ujiuye.domain.Account)

方法名可以使用*号,表示任意方法

* com..*.*( com.ujiuye.domain.Account)

参数列表可以使用*,表示参数可以是任意数据类型,但是必须有参数

* com..*.*(*)

参数列表可以使用..表示有无参数均可,有参数可以是任意类型

* com..*.*(..)

全通配方式:

* *..*.*(..)

注意: 通常情况下,我们都是对业务层的方法进行增强,所以切入点表达式都是切到业务层实现类。

execution(* com.ujiuye.service.impl.*.*(..))

4 环绕通知配置事务管理

在TransactionManager类当中添加方法

/**
     * 环绕通知:
     * spring 框架为我们提供了一个接口:ProceedingJoinPoint,它可以作为环绕通知的方法参数。
     * 在环绕通知执行时,spring 框架会为我们提供该接口的实现类对象,我们直接使用就行。
     * @param pjp
     * @return
     */
public Object transactionAround(ProceedingJoinPoint pjp) {
    //定义返回值
    Object returnValue = null;
    try {
        //获取方法执行所需的参数
        Object[] args = pjp.getArgs();
        //前置通知:开启事务
        beginTransaction();
        //执行方法
        returnValue = pjp.proceed(args);
        //后置通知:提交事务
        commit();
    }catch(Throwable e) {
        //异常通知:回滚事务
        rollback();
        e.printStackTrace();
    }finally {
        //最终通知:释放资源
        release();
    }
    return returnValue;
}
 aop:around:
	作用:
		用于配置环绕通知
	属性:
        method:指定通知中方法的名称。
        pointct:定义切入点表达式
        pointcut-ref:指定切入点表达式的引用
	说明:
        它是 spring 框架为我们提供的一种可以在代码中手动控制增强代码什么时候执行的方式。
        注意:通常情况下,环绕通知都是独立使用的

<aop:config>
        <aop:aspect id="tdAdvice" ref="txManager">
            <aop:pointcut id="point1" expression="execution(* com.offcn.service.impl.*.*(..))"/>
            <!-- 配置环绕通知 -->
            <aop:around method="transactionAround" pointcut-ref="point1"/>
        </aop:aspect>
    </aop:config>

(三)Spring基于注解的AOP配置

话术: 
 AOP注解方式和XML方式完成的功能是一样的,只是采用了两种开发方式而已。将原有的XML方式使用注解逐一替代。

1 环境搭建

1.1 构建maven工程添加AOP注解的相关依赖

<properties>
        <spring.version>5.2.5.RELEASE</spring.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>

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

1.2 沿用上一章节资源

copy Account AccountDao AccountDaoImpl AccountService AccountServiceImpl

1.3 在配置文件中导入 context 的名称空间且配置

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:context="http://www.springframework.org/schema/context"
       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
        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>

1.4 资源使用注解配置

<!-- 配置数据源 -->
<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/test"></property>
    <property name="user" value="root"></property>
    <property name="password" value="root"></property>
</bean>

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

1.5 在配置文件中指定 spring 要扫描的包

 <!-- 告知 spring,在创建容器时要扫描的包 -->
 <context:component-scan base-package="com.offcn"></context:component-scan>

2 配置步骤

1.1 通知类使用注解配置

/**
 * 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
 */
@Component("txManager")
public class TransactionManager {
    @Autowired
    private ConnectionUtils connectionUtils;
    
}

1.2 在通知类上使用@Aspect 注解声明为切面

/**
 * 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
 */
@Component("txManager")
@Aspect //表明当前类是一个切面类
public class TransactionManager {
    @Autowired
    private ConnectionUtils connectionUtils;
    
}

1.3 在增强的方法上使用注解配置通知

@Before
	作用
		把当前方法看成是前置通知
	属性
		value用于指定切入点表达式还可以指定切入点表达式的引用
		
//开启事务
@Before("execution(* com.offcn.service.impl.*.*(..)))")
public  void beginTransaction(){
    try {
        System.out.println("before..........................");
        connectionUtils.getThreadConnection().setAutoCommit(false);
    }catch (Exception e){
        e.printStackTrace();
    }
}
 @AfterReturning
	作用 
		把当前方法看成是后置通知
	属性 
		value用于指定切入点表达式还可以指定切入点表达式的引用
		
// 提交事务
@AfterReturning("execution(* com.offcn.service.impl.*.*(..)))")
public  void commit(){
    try {
        connectionUtils.getThreadConnection().commit();
    }catch (Exception e){
        e.printStackTrace();
    }
}
@AfterThrowing
	作用 
		把当前方法看成是异常通知
	属性 
		value用于指定切入点表达式还可以指定切入点表达式的引用
		
//回滚事务
@AfterThrowing("execution(* com.offcn.service.impl.*.*(..)))")
public  void rollback(){
    try {
        connectionUtils.getThreadConnection().rollback();
    }catch (Exception e){
        e.printStackTrace();
    }
}
@After
	作用 
		把当前方法看成是最终通知
	属性 
		value用于指定切入点表达式还可以指定切入点表达式的引用

//释放连接
@After("execution(* com.offcn.service.impl.*.*(..)))")
public  void release(){
    try {
        connectionUtils.removeConnection();
        connectionUtils.getThreadConnection().close();//还回连接池中
    }catch (Exception e){
        e.printStackTrace();
    }
}

1.4 在 spring 配置文件中开启 spring 对注解 AOP 的支持

<!-- 开启 spring 对注解 AOP 的支持 -->
<aop:aspectj-autoproxy/>

1.5 环绕通知注解配置

@Around
	作用 
		把当前方法看成是环绕通知
	属性 
		value用于指定切入点表达式还可以指定切入点表达式的引用
// 环绕通知:
@Around("execution(* com.offcn.service.impl.*.*(..)))")
public Object transactionAround(ProceedingJoinPoint pjp) {
    //定义返回值
    Object returnValue = null;
    try {
        //获取方法执行所需的参数
        Object[] args = pjp.getArgs();
        //前置通知:开启事务
        beginTransaction();
        //执行方法
        returnValue = pjp.proceed(args);
        //后置通知:提交事务
        commit();
    }catch(Throwable e) {
        //异常通知:回滚事务
        rollback();
        e.printStackTrace();
    }finally {
        //最终通知:释放资源
        release();
    }
    return returnValue;
}

1.6 切入点表达式注解

@Pointcut("execution(* com.offcn.service.impl.*.*(..))")
private void point1() {}

// 环绕通知:
@Around("point1()")///注意:千万别忘了写括号
public Object transactionAround(ProceedingJoinPoint pjp) {
    //定义返回值
    Object returnValue = null;
    try {
        //获取方法执行所需的参数
        Object[] args = pjp.getArgs();
        //前置通知:开启事务
        beginTransaction();
        //执行方法
        returnValue = pjp.proceed(args);
        //后置通知:提交事务
        commit();
    }catch(Throwable e) {
        //异常通知:回滚事务
        rollback();
        e.printStackTrace();
    }finally {
        //最终通知:释放资源
        release();
    }
    return returnValue;
}

作业: aop 控制事务, 纯注解开发~

二、 Spring事务详解

讲解思路: 
 1、介绍Spring当中进行事务控制的常用对象以及作用
 2、事务的隔离级别,不同隔离级别产生的错误数据
 3、扩展事务的传播行为,事务传播行为的对应几种情况,面试问题
 4、完成基于XML形式事务配置

(一)Spring中事务的API详解

1 PlatformTransactionManager作用【掌握】

PlatformTransactionManager 接口是 Spring 的事务管理器,它里面提供了我们常用的操作事务的方法。

注意:

PlatformTransactionManager 是接口类型,不同的 Dao 层技术则有不同的实现类,例如:Dao 层技术是jdbc 或 mybatis 时,JdbcTemplate 对象:org.springframework.jdbc.datasource.DataSourceTransactionManager

Dao 层技术是hibernate时:org.springframework.orm.hibernate5.HibernateTransactionManager

2 TransactionDefinition作用

TransactionDefinition 是事务的定义信息对象,里面有如下方法

2.1 事务隔离级别

设置隔离级别,可以解决事务并发产生的问题,如脏读、不可重复读和虚读。

事务隔离级别 说明
ISOLATION_DEFAULT 默认级别,归属下列某一种
ISOLATION_READ_UNCOMMITTED 未提交读,可以读取未提交数据
ISOLATION_READ_COMMITTED 已提交读,只能读取已提交数据,解决脏读问题(Oracle默认级别)
ISOLATION_REPEATABLE_READ 可重复读,解决不可重复度问题(MySQL默认级别)
ISOLATION_SERIALIZABLE 串行化,节约幻读(虚读)问题

2.2 事务传播行为

事务传播行为 说明
REQUIRED 如果当前没有事务,就新建一个事务,如果已经存在一个事务中,加入到这个事务中。一般的选择(默认值)
SUPPORTS 支持当前事务,如果当前没有事务,就以非事务方式执行(没有事务)
MANDATORY 使用当前的事务,如果当前没有事务,就抛出异常
REQUERS_NEW 新建事务,如果当前在事务中,把当前事务挂起。
NOT_SUPPORTED 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起
NEVER 以非事务方式运行,如果当前存在事务,抛出异常
NESTED 如果当前存在事务,则在嵌套事务内执行。如果当前没有事务,则执行 REQUIRED 类似的操作

2.3 事务超时时间

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

2.4 是否是只读事务

建议查询时设置为只读。

3 TransactionStatus作用

TransactionStatus 接口提供的是事务具体的运行状态,方法介绍如下。

4 上述三个对象关系

PlatformTransactionManager 根据TransactionDefinition 定义的事务属性,进行事务的管理, 在事务管理的过程当中产生的状态信息保存在TransactionStatus。

(二)Spring基于XML的事务配置

讲解思路: 
 1、准备转账的业务场景,演示没有事务控制时,数据一致性受损
 2、在XML配置文件当中,使用Spring提供的声明式事务进行控制,详解事务控制的每个步骤,每个步骤当中涉及的每个标签,讲解的过程当中复习巩固AOP当中的相关概念,加深学员对概念的理解
 3、测试基于XML声明式事务是否生效

1 环境搭建

1.1 构建maven工程,添加相关技术依赖

<properties>
    <spring.version>5.2.5.RELEASE</spring.version>
</properties>
<dependencies>
    <!--导入junit单元测试-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!--导入spring的context坐标-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <!--导入Jdbc模块依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <!--导入Mysql 驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <!--导入C3P0连接池-->
    <dependency>
        <groupId>com.mchange</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.5.2</version>
    </dependency>
    <!--aop-->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.7</version>
    </dependency>
</dependencies>

1.2 创建Spring 的配置文件并导入约束

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

     <!-- 告知 Spring,在创建容器时要扫描的包 -->
    <context:component-scan base-package="com.offcn"></context:component-scan>

    <!-- 配置数据源 -->
    <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/test"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
    
    <!--JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
</beans>

1.3 沿用转账业务的代码

copy Account AccountDao AccountDaoImpl AccontService AccountServiceImpl 代码:

注意: AccountDaoImpl 具体使用使用Spring提供的JdbcTemplate模板对象实现

@Repository("accountDao")
public class AccountDaoImpl implements AccountDao {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public Account findByName(String name) {
        String sql ="select * from account where name =? ";
        Account account =  this.jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(Account.class), name);
        return  account;
    }

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

2 事务管理配置步骤

2.1 配置事务管理器

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

2.2 配置事务的通知引用事务管理器

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

</tx:advice>

2.3 配置事务的属性

<!-- 
    指定方法名称:是业务核心方法
     read-only:是否是只读事务。默认 false,不只读。
     isolation:指定事务的隔离级别。默认值是使用数据库的默认隔离级别。
     propagation:指定事务的传播行为。
     timeout:指定超时时间。默认值为:-1。永不超时。
     rollback-for:用于指定一个异常,当执行产生该异常时,事务回滚。产生其他异常,事务不回滚。
     没有默认值,任何异常都回滚。
     no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时,事务回滚。没有默认值,任何异常都回滚。
 -->
<tx:attributes>
    <tx:method name="*"/>
</tx:attributes>

2.4 配置 AOP 切入点表达式

<!--事务的aop增强-->
<aop:config>
    <aop:pointcut id="myPointcut" expression="execution(* com.offcn.service.impl.*.*(..))"/>
</aop:config>

2.5 配置切入点表达式和事务通知的对应关系

<!--在aop:config标签内部:建立事务的通知和切入点表达式的关系-->
<aop:advisor advice-ref="txAdvice" pointcut-ref="myPointcut"></aop:advisor>

三、 Spring事务详解-注解

(一) Spring基于注解的事务配置

1 环境搭建

1.1 构建maven工程,添加相关技术依赖

<properties>
    <spring.version>5.2.5.RELEASE</spring.version>
</properties>
<dependencies>
    <!--导入junit单元测试-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!--导入spring的context坐标-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <!--导入Jdbc模块依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <!--导入Mysql 驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <!--导入C3P0连接池-->
    <dependency>
        <groupId>com.mchange</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.5.2</version>
    </dependency>
    <!--aop-->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.7</version>
    </dependency>

1.2 创建 Spring 的配置文件并导入约束

<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.xsd
        http://www.springframework.org/schema/tx
        http://www.springframework.org/schema/tx/spring-tx.xsd
        http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 告知 spring,在创建容器时要扫描的包 -->
    <context:component-scan base-package="com.offcn"></context:component-scan>

    <!-- 配置数据源 -->
    <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/test"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>
    <!--JdbcTemplate-->
    <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
        <property name="dataSource" ref="dataSource"></property>
    </bean>
</beans>

1.3 沿用转账业务的代码:dao实现类和service实现类采用注解的形式,添加到容器中管理

copy Account AccountDao AccountImpl AccountService AccountServiceImpl 到工程当中复用

2 事务管理配置步骤(重点)

2.1 配置事务管理器并注入数据源

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

2.2 在业务层使用@Transactional 注解

/**
 该注解的属性和 xml 中的属性含义一致。该注解可以出现在接口上,类上和方法上。
 出现接口上,表示该接口的所有实现类都有事务支持。
 出现在类上,表示类中所有方法有事务支持
 出现在方法上,表示方法有事务支持。
 以上三个位置的优先级:方法>类>接口 
 */
@Service("accountService")
@Transactional(readOnly = true,propagation = Propagation.SUPPORTS)
public class AccountServiceImpl implements AccountService {
    //依赖dao层
    @Autowired
    private AccountDao accountDao ;

    @Transactional(readOnly = false,propagation = Propagation.REQUIRED)
    @Override
    public void transfer(String sourceAccountName, String targetAccountName, Double money) {
            Account sAccount = accountDao.findByName(sourceAccountName);
            Account tAccount = accountDao.findByName(targetAccountName);

            //来源账户减钱,目标账户加钱
            sAccount.setMoney(sAccount.getMoney()-money);
            tAccount.setMoney(tAccount.getMoney()+money);

            //持久化到数据库
            accountDao.update(sAccount);
            //模拟异常发生
            //int i=1/0;
            accountDao.update(tAccount);
    }
}

2.3 在配置文件中开启 Spring 对注解事务的支持

<!-- 开启 spring 对注解事务的支持 --> 
<tx:annotation-driven transaction-manager="transactionManager"/>

作业: 全注解开发~

四、 Spring整合Mybatis实现用户的CRUD

(一)整合思路分析

Mybatis框架是一个持久层ORM框架,而Spring则是一个综合性一站式框架。所以整合是Mybatis往Spring上整合。就是让Spring框架接管Mybatis的组件。

Mybatis单独运行时,数据源的管理,事务的管理, SqlSessionFactory 以及接口的实现类都是Mybatis管理的,整合后以上组件交给Spring管理。

(二)构建maven工程,添加技术依赖

<properties>
    <spring.version>5.2.5.RELEASE</spring.version>
</properties>
<dependencies>
    <!--导入junit单元测试-->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    <!--导入spring的context坐标-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <!--导入Jdbc模块依赖-->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <!--导入Mysql 驱动-->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.47</version>
    </dependency>
    <!--导入C3P0连接池-->
    <dependency>
        <groupId>com.mchange</groupId>
        <artifactId>c3p0</artifactId>
        <version>0.9.5.2</version>
    </dependency>
    <!--aop-->
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.7</version>
    </dependency>

    <!--mybatis-Spring适配包 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.0</version>
    </dependency>
    <!-- mybatis orm框架 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.4.6</version>
    </dependency>
</dependencies>

(三)构建数据库表并创建实体User

create table User(
 id int primary key auto_increment,
 name varchar(32) not null,
 address varchar(32) not null,
 birthday date 
);
public class User implements Serializable {
    private Integer id;
    private String name;
    private String address;
    private Date birthday;

    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 String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

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

(四)编写dao层的接口UserMapper

public interface UserMapper {
    int insert(User user);
    int update(User user);
    int delete(Integer id);
    User findById(Integer id);
    List<User> findAll();
}

(五)构建mapper接口对应的sql配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.offcn.mapper.UserMapper">
    <resultMap id="BaseResultMap" type="com.offcn.pojo.User">
        <id column="id" jdbcType="INTEGER" property="id" />
        <result column="name" jdbcType="VARCHAR" property="name" />
        <result column="address" jdbcType="VARCHAR" property="address" />
        <result column="birthday" jdbcType="DATE" property="birthday" />
    </resultMap>

    <insert id="insert" parameterType="com.offcn.pojo.User">
        insert into user (name, birthday, address)
        values (#{name}, #{birthday},#{address})
    </insert>

    <update id="update">
        update user
        set
        name= #{name},
        birthday=#{birthday},
        address = #{address}
        where id=#{id}
    </update>

    <delete id="delete">
        delete from user
        where id =#{id}
    </delete>

    <select id="findById" resultMap="BaseResultMap">
        select * from user where id=#{id}
    </select>

    <select id="findAll" resultMap="BaseResultMap">
        select * from user
    </select>
</mapper>

(六)构建服务层接口UserService

public interface UserService {
    int insert(User user);
    int update(User user);
    int delete(Integer id);
    User findById(Integer id);
    List<User> findAll();
}

(七)构建服务层实现类UserServiceImpl

@Service
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;
    @Override
    public int insert(User user) {
        int num = userMapper.insert(user);
        return  num;
    }

    @Override
    public int update(User user) {
        int num = userMapper.update(user);
        return num;
    }

    @Override
    public int delete(Integer id) {
        int num = userMapper.delete(id);
        return num;
    }

    @Override
    public User findById(Integer id) {
        User user = userMapper.findById(id);
        return user;
    }

    @Override
    public List<User> findAll() {
        List<User> userList = userMapper.findAll();
        return userList;
    }
}

(八)构建Spring框架的配置文件applicationContext.xml,配置IOC管理的对象【重点】

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

    <!--开启包扫描-->
    <context:component-scan base-package="com.offcn"> </context:component-scan>

    <!-- 配置数据源 -->
    <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/test"></property>
        <property name="user" value="root"></property>
        <property name="password" value="root"></property>
    </bean>

    <!--Mybatis 核心对象: 工厂对象-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--工厂创建必须注入一个数据源-->
        <property name="dataSource" ref="dataSource"/>
        <!--指定mapper文件位置-->
        <property name="mapperLocations" value="classpath:com/offcn/mapper/*Mapper.xml"></property>
        <!--引入Mybatis的核心配置文件:
           如果Mybaits的核心配置要保留,需要再此处配置:
        -->
        <property name="configLocation" value="classpath:SqlMapConfig.xml"/>

        <!--别名配置-->
        <property name="typeAliasesPackage" value="com/offcn/pojo"/>
        <!--进行分页插件的配置-->
        <property name="plugins">
			<array>
				<bean class="com.github.pagehelper.PageInterceptor">
					<property name="properties">
						<value>
							helperDialect=MySQL
							reasonable=true
							supportMethodsArguments=true
							params=count=countSql
							autoRuntimeDialect=true
						</value>
					</property>
				</bean>
			</array>
		</property>   
    </bean>

    <!--配置接口的扫描-->
    <bean id="mapperScannerConfigurer" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定了包: 能够将包下的接口生成实现类: -->
        <property name="basePackage" value="com.offcn.mapper"></property>
    </bean>

    <!--配置平台管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--注入数据源-->
        <property name="dataSource" ref="dataSource"/>
    </bean>
    <tx:annotation-driven transaction-manager="transactionManager"/>

</beans>

(九)测试代码

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

    @Autowired
    private UserService userService;

    //save
    @Test
    public void testInsert(){
        User user = new User();
        user.setName("admin");
        user.setAddress("china");
        user.setBirthday(new Date());
        int num = userService.insert(user);
        System.out.println("num:"+num);
    }
    //update
    @Test
    public void testUpdate(){
        User user = new User();
        user.setName("marry");
        user.setAddress("America");
        user.setBirthday(new Date());
        user.setId(1);
        int num = userService.update(user);
        System.out.println("num:"+num);
    }

    //delete:
    @Test
    public void testDelete(){
        int num = userService.delete(1);
        System.out.println("num:"+num);
    }

    //findById
    @Test
    public void testFindById(){
        User user = userService.findById(2);
        System.out.println("user:"+user);
    }
    //findAll
    @Test
    public void testFindByAll(){
        List<User> userList = userService.findAll();
        System.out.println("userList:"+userList);
    }
}

avatar
青山
悟已往之不谏 知来者之可追
一言
今日诗词
站点信息
本站访客数 :
本站总访问量 :