原创

    Spring 入门教程

    Spring 概述

    TIPS: 建议去 Spring 官网 学习!!!

    Spring 是什么

    Spring 是分层的 Java SE/EE 应用 full-stack 轻量级开源框架,以 IoC(Inverse Of Control:反转控制)和 AOP(Aspect Oriented Programming:面向切面编程)为内核, 提供了展现层 SpringMVC 和持久层 Spring JDBC 以及业务层事务管理等众多的企业级应用技术,还能整合开源世界众多著名的第三方框架和类库,逐渐成为使用最多的 JavaEE 企业应用开源框架。

    Spring 的发展历程

    Spring 是从 EJB 时代发展过来的,它剔除了 EJB 的很多诟病。 所以,我们需要了解一下它的发展历程,如下:

    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    
    + 1997年 IBM 提出了 EJB 的思想
    + 1998年,SUN 制定开发标准规范 EJB1.0
    + 1999年,EJB1.1 发布
    + 2001年,EJB2.0 发布
    + 2003年,EJB2.1 发布
    + 2006年,EJB3.0 发布
    + Rod Johnson(Spring 之父)
    + Expert One-to-One J2EE Design and Development(2002) 阐述了 J2EE 使用 EJB 开发设计的优点及解决方案
    + Expert One-to-One J2EE Development without EJB(2004) 阐述了 J2EE 开发不使用 EJB 的解决方式(Spring 雏形)
    + 2017 年 9 月份发布了 Spring 的最新版本 Spring 5.0 通用版(GA)
    
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    

    Spring 的优势

    那么我们学习 Spring 有什么好处呢?那么接下来就介绍一下 Spring 的优势。 其优势如下:

    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    
    +++ 1、方便解耦,简化开发
    + 通过 Spring 提供的 IoC 容器,可以将对象间的依赖关系交由 Spring 进行控制,避免硬编码所造成的过度程序耦合。
    + 用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
    
    +++ 2、AOP 编程的支持
    + 通过 Spring 的 AOP 功能,方便进行面向切面的编程,许多不容易用传统 OOP 实现的功能可以通过 AOP 轻松应付。
    
    +++ 3、声明式事务的支持
    + 可以将我们从单调烦闷的事务管理代码中解脱出来,通过声明式方式灵活的进行事务的管理,提高开发效率和质量。
    
    +++ 4、方便程序的测试
    + 可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。
    
    +++ 5、方便集成各种优秀框架
    + Spring 可以降低各种框架的使用难度,提供了对各种优秀框架(Struts、Hibernate、Hessian、Quartz 等)的直接支持。
    
    +++ 6、降低 JavaEE-API 的使用难度
    + Spring 对 JavaEE-API(如 JDBC、JavaMail、远程调用等)进行了薄薄的封装层,使这些 API 的使用难度大为降低。
    
    +++ 7、Java 源码是经典学习范例
    + Spring 的源代码设计精妙、结构清晰、匠心独用,处处体现着大师对 Java 设计模式灵活运用以及对 Java 技术的高深造诣。
    + 它的源代码无疑是 Java 技术的最佳实践的范例。
    
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    

    Spring 的体系结构

    既然 Spring 如此重要,那就先来看看它的体系结构。 如下图:

    由上图可知,***Spring 是由多个模块组成的。那么它们的具体功能是干什么的呢?*** 详情如下:

    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    
    +++ 1、核心容器(Coin Container)
    + Spring 的核心容器是其他模块建立的基础,有 Spring-core、Spring-beans、Spring-context、Spring-context-support 和 Spring-expression(String 表达式语言)等模块组成。
    + Spring-core 模块:提供了框架的基本组成部分,包括控制反转(Inversion of Control,IOC)和依赖注入(Dependency Injection,DI)功能。
    + Spring-beans 模块:提供了 BeanFactory,是工厂模式的一个经典实现,Spring 将管理对象称为 Bean 。
    + Spring-context 模块:建立在 Core 和 Beans 模块的基础之上,提供一个框架式的对象访问方式,是访问定义和配置的任何对象的媒介。ApplicationContext 接口是 Context 模块的焦点。
    + Spring-context-support 模块:支持整合第三方库到Spring应用程序上下文,特别是用于高速缓存(EhCache、JCache)和任务调度(CommonJ、Quartz)的支持。
    + Spring-expression 模块:提供了强大的表达式语言去支持运行时查询和操作对象图。这是对JSP2.1规范中规定的统一表达式语言(Unified EL)的扩展。
    + 该语言支持设置和获取属性值、属性分配、方法调用、访问数组、集合和索引器的内容、逻辑和算术运算、变量命名以及从Spring的IOC容器中以名称检索对象。
    + 它还支持列表投影、选择以及常用的列表聚合。
    
    +++ 2、AOP 和 Instrumentation
    + Spring-aop 模块:提供了一个符合 AOP 要求的面向切面的编程实现,允许定义方法拦截器和切入点,将代码按照功能进行分离,以便干净地解耦。
    + Spring-aspects 模块:提供了与 AspectJ 的集成功能,AspectJ 是一个功能强大且成熟的 AOP 框架。
    + Spring-instrument 模块:提供了类植入(Instrumentation)支持和类加载器的实现,可以在特定的应用服务器中使用。
    
    +++ 3、消息(Messaging)
    + Spring4.0 以后新增了消息(Spring-messaging)模块,该模块提供了对消息传递体系结构和协议的支持。
    
    +++ 4、数据访问/集成
    + 数据访问/集成层由 JDBC、ORM、OXM、JMS 和事务模块组成。
    + Spring-jdbc 模块:提供了一个 JDBC 的抽象层,消除了烦琐的 JDBC 编码和数据库厂商特有的错误代码解析。
    + Spring-orm 模块:为流行的对象关系映射(Object-Relational Mapping)API 提供集成层,包括 JPA 和 Hibernate。
    + 使用 Spring-orm 模块可以将这些 O/R 映射框架与 Spring 提供的所有其他功能结合使用,例如声明式事务管理功能。
    + Spring-oxm 模块:提供了一个支持 对象/XML 映射的抽象层实现,例如 JAXB、Castor、JiBX 和 XStream。
    + Spring-jms 模块(Java Messaging Service):指 Java 消息传递服务,包含用于生产和使用消息的功能。自 Spring4.1 以后,提供了与 Spring-messaging 模块的集成。
    + Spring-tx模块(事务模块):支持用于实现特殊接口和所有 POJO(普通 Java 对象)类的编程和声明式事务管理。
    
    +++ 5、Web
    + Web 层由 Spring-web、Spring-webmvc、Spring-websocket 和 Portlet 模块组成。
    + Spring-web 模块:提供了基本的 Web 开发集成功能,例如多文件上传功能、使用 Servlet 监听器初始化一个 IOC 容器以及 Web 应用上下文。
    + Spring-webmvc 模块:也称为 Web-Servlet 模块,包含用于web应用程序的Spring MVC和REST Web Services实现。
    + Spring MVC 框架提供了领域模型代码和 Web 表单之间的清晰分离,并与 Spring Framework 的所有其他功能集成。
    + Spring-websocket 模块:Spring4.0 以后新增的模块,它提供了 WebSocket 和 SocketJS 的实现。
    + Portlet 模块:类似于 Servlet 模块的功能,提供了 Portlet 环境下的 MVC 实现。
    
    +++ 6、测试(Test)
    + Spring-test 模块支持使用 JUnit 或 TestNG 对 Spring 组件进行单元测试和集成测试。
    
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    

    工厂模式解耦

    在实际开发中,我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候,让一个类中的方法通过读取配置文件,把这些对象创建出来并存起来。 在接下来使用的时候,直接拿过来用就好了。那么,这个读取配置文件、创建和获取三层对象的类就是工厂,使用这种方式实现解耦的模式也称之为 工厂模式


    上述介绍了 工厂模式实现解耦 ,那么就来操作一下。第一步: 新建 Maven 工程,完善包结构。 如下图:

    第二步: 导入如下依赖到 pom.xml 文件中。 依赖如下:

    <!-- 使用 jar 的方式进行打包 -->
    <packaging>jar</packaging>
    
    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.47</version>
        </dependency>
    </dependencies>
    

    第三步: dao 包下新建 IAccountDao 接口,然后在 dao/impl 包下新建 AccountDaoImpl 实现类。 代码如下:

    package club.guoshizhan.dao;
    
    /**
     * 账户的持久层接口
     */
    public interface IAccountDao {
    
        /**
         * 模拟保存账户
         */
        void saveAccount();
    
    }
    
    package club.guoshizhan.dao.impl;
    
    import club.guoshizhan.dao.IAccountDao;
    
    /**
     * 账户的持久层实现类
     */
    public class AccountDaoImpl implements IAccountDao {
    
        public void saveAccount() {
            System.out.println("保存了账户");
        }
    
    }
    

    第四步: service 包下新建 IAccountService 接口,然后在 service/impl 包下新建 AccountServiceImpl 实现类。 代码如下:

    package club.guoshizhan.service;
    
    /**
     * 账户业务层的接口
     */
    public interface IAccountService {
    
        /**
         * 模拟保存账户
         */
        void saveAccount();
    
    }
    
    package club.guoshizhan.service.impl;
    
    import club.guoshizhan.dao.IAccountDao;
    import club.guoshizhan.factory.BeanFactory;
    import club.guoshizhan.service.IAccountService;
    
    /**
     * 账户的业务层实现类
     */
    public class AccountServiceImpl implements IAccountService {
        // 这里使用了 new 关键字创建对象,耦合度高
    //    private IAccountDao accountDao = new AccountDaoImpl();
        // 这里使用了工厂模式解耦(没有 new 对象),降低了耦合度,即解耦
        private IAccountDao accountDao = (IAccountDao) BeanFactory.getBean("accountDao");
    
    //    private int i = 1;
    
        public void saveAccount() {
            int i = 1;
            accountDao.saveAccount();
            System.out.println(i);
            i++;
        }
    }
    

    第五步: resources 目录下新建 bean.properties 配置文件。 代码如下:

    accountService=club.guoshizhan.service.impl.AccountServiceImpl
    accountDao=club.guoshizhan.dao.impl.AccountDaoImpl
    

    第六步: factory 包下新建 BeanFactory 工厂类。 代码如下:

    package club.guoshizhan.factory;
    
    import java.io.InputStream;
    import java.util.Enumeration;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.Properties;
    
    /**
     * 一个创建 Bean 对象的工厂,它就是创建我们的 service 和 dao 对象的。
     * Bean:在计算机英语中,有可重用组件的含义。
     * JavaBean:用 java 语言编写的可重用组件。javabean > 实体类
     * 第一个:需要一个配置文件来配置我们的 service 和 dao。配置的内容:唯一标识=全限定类名(key=value)
     * 第二个:通过读取配置文件中配置的内容,反射创建对象。配置文件可以是 xml 也可以是 properties
     */
    public class BeanFactory {
    
        // 定义一个 Properties 对象
        private static Properties props;
        // 定义一个 Map ,用于存放我们要创建的对象。我们把它称之为容器
        private static Map<String, Object> beans;
    
        // 使用静态代码块为 Properties 对象赋值
        static {
            try {
                // 实例化对象
                props = new Properties();
                // 获取 properties 文件的流对象
                InputStream in = BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
                props.load(in);
                // 实例化容器
                beans = new HashMap<>();
                // 取出配置文件中所有的 Key
                Enumeration keys = props.keys();
                // 遍历枚举
                while (keys.hasMoreElements()) {
                    // 取出每个 Key
                    String key = keys.nextElement().toString();
                    // 根据 key 获取 value
                    String beanPath = props.getProperty(key);
                    // 反射创建对象
                    Object value = Class.forName(beanPath).newInstance();
                    // 把 key 和 value 存入容器中
                    beans.put(key, value);
                }
            } catch (Exception e) {
                throw new ExceptionInInitializerError("初始化properties失败!");
            }
        }
    
        /**
         * 根据 bean 的名称获取对象
         */
        public static Object getBean(String beanName) {
            return beans.get(beanName);
        }
    
        /**
         * 根据 Bean 的名称获取 bean 对象
         */
        /*public static Object getBean(String beanName) {
            Object bean = null;
            try {
                String beanPath = props.getProperty(beanName);
    //            System.out.println(beanPath);
                bean = Class.forName(beanPath).newInstance();//每次都会调用默认构造方法创建对象
            } catch (Exception e) {
                e.printStackTrace();
            }
            return bean;
        }*/
    
    }
    

    第七步: controller 包下新建 Client 类,用于测试我们是否解耦成功。 代码如下:

    package club.guoshizhan.controller;
    
    import club.guoshizhan.factory.BeanFactory;
    import club.guoshizhan.service.IAccountService;
    
    /**
     * 模拟一个表现层,用于调用业务层
     */
    public class Client {
        public static void main(String[] args) {
            //IAccountService as = new AccountServiceImpl();
            for (int i = 0; i < 5; i++) {
                IAccountService as = (IAccountService) BeanFactory.getBean("accountService");
                System.out.println(as);
                as.saveAccount();
            }
        }
    }
    

    执行上述 main 方法即可查看到结果。 结果如下:

    club.guoshizhan.service.impl.AccountServiceImpl@140e19d
    保存了账户
    1
    club.guoshizhan.service.impl.AccountServiceImpl@140e19d
    保存了账户
    1
    club.guoshizhan.service.impl.AccountServiceImpl@140e19d
    保存了账户
    1
    club.guoshizhan.service.impl.AccountServiceImpl@140e19d
    保存了账户
    1
    club.guoshizhan.service.impl.AccountServiceImpl@140e19d
    保存了账户
    1
    

    由结果可知,***工厂模式 解耦成功(我们没有 new 对象)。*** 那我们再回过头来看如下问题:

    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    
    +++ 1、解决程序耦合的思路
    + 当我们使用 JDBC 的时候,是通过反射来注册驱动的,代码: Class.forName("com.mysql.jdbc.Driver"); // 此处只是一个字符串
    + 反射注册驱动的好处是,我们的类中不再依赖具体的驱动类,此时就算删除 MySQL 的 jar 包,依然可以编译(运行就不要想了,没有驱动不可能运行成功的)。
    - 但是,也产生了一个新的问题,MySQL 驱动的全限定类名字符串是在 java 类中写死的,一旦需要修改,还是要修改源码。
    + 解决这个问题也很简单,使用配置文件配置(xml 或者是 properties)。如我们的 bean.properties 配置文件。
    
    +++ 2、使用工厂模式解耦(原先可能不太理解,经过代码的编写再来阅读这段话,应该理解更深刻)
    + 在实际开发中我们可以把三层的对象都使用配置文件配置起来,当启动服务器应用加载的时候,让一个类中的方法通过读取配置文件,把这些对象同时创建出来并存起来。
    + 在接下来使用的时候,直接可以把这些保存好了的对象拿过来用。那么,这个读取配置文件、创建和获取三层对象的类就是工厂,这种模式就叫工厂模式。
    
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    

    TIPS: Spring 概述 到此结束!!!

    Spring 中的 IoC

    遗留问题

    我们已经知道了 工厂模式,因此在学习 IoC 之前,先来解决工厂模式遗留下下来的问题。 问题如下:

    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    
    +++ 1、工厂中生产的对象存哪去了?
    + 由于工厂中有很多对象,肯定要找个集合来存。这时候有 Map 和 List 供选择。
    + 到底选 Map 集合还是 List 集合,就看我们有没有查找需求。如果有查找需求,选 Map 。
    + 所以我们的答案就是在应用加载时,创建一个 Map 集合,用于存放三层对象。我们把这个 map 称之为容器。
    + 所以,IoC 容器就和我们的 map 是一样的,只不过它比我们的容器更加复杂。
    
    +++ 2、到底什么是工厂?
    + 工厂就是负责给我们从容器中获取指定对象的类。这时候我们获取对象的方式发生了改变,变成了被动获取对象。
    + 而不是我们在程序中去 new 对象(主动获取对象),而是通过工厂去创建对象,并且通过工厂去获取对象。
    + 这种被动接收的方式获取对象的思想就是控制反转(IOC),它是 Spring 框架的核心之一。
    
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    

    主动获取对象和被动获取对象 图示如下:

    IoC 概念和作用

    控制反转(Inversion of Control,缩写为 IoC ),是面向对象编程中的一种设计原则,可以用来减低计算机代码之间的耦合度。 其中最常见的方式叫做 依赖注入(Dependency Injection,简称 DI) ,还有一种方式叫 依赖查找(Dependency Lookup) 。通过控制反转,对象在被创建的时候,由一个调控系统内所有对象的外界实体将其所依赖的对象的引用传递给它。也可以说,依赖被注入到对象中。


    上述介绍来自于 百度百科 ,解释得很官方。以下是通俗一点的解释:

    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    
    +++ IoC 概念的通俗版解释:
    + 传统的 Java 程序设计过程中,如果一个类依赖与另一个类的话,需要通 new 进行创建对象,这个过程是程序主动去创建依赖对象。
    + 而 IoC 是通过一个容器创建这些对象,即有 Ioc 容器来控制对象的创建。即 IoC 容器控制了对象。
    + 其次,正常的方法是主动控制获取依赖对象,这个正转;而反转是由容器来创建以及注入依赖对象,这个就是反转!
    
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    

    IoC 的作用: 削减计算机程序的耦合(解除我们代码中的依赖关系),将对象的创建和调用都交给 Spring 容器去处理。


    IoC 环境搭建

    第一步: 新建 Maven 工程,完善包结构。(和上面的 工厂解耦 一模一样) 然后导入如下依赖:

    <!-- 使用 jar 的方式进行打包 -->
    <packaging>jar</packaging>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>
    </dependencies>
    

    第二步: resources 目录下新建 bean.xml 配置文件,这个就是 Spring 的 IoC 配置文件。 代码如下:

    <?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="club.guoshizhan.service.impl.AccountServiceImpl"></bean>
        <bean id="accountDao" class="club.guoshizhan.dao.impl.AccountDaoImpl"></bean>
    </beans>
    

    第三步: 把以下的每一个类放到对应包结构中。(具体放到哪一个包参考 package 语句) 各个类的代码如下:

    package club.guoshizhan.dao;
    
    /**
     * 账户的持久层接口
     */
    public interface IAccountDao {
    
        /**
         * 模拟保存账户
         */
        void saveAccount();
    
    }
    
    package club.guoshizhan.dao.impl;
    
    import club.guoshizhan.dao.IAccountDao;
    
    /**
     * 账户的持久层实现类
     */
    public class AccountDaoImpl implements IAccountDao {
    
        public void saveAccount() {
            System.out.println("保存了账户");
        }
    
    }
    
    package club.guoshizhan.service;
    
    /**
     * 账户业务层的接口
     */
    public interface IAccountService {
    
        /**
         * 模拟保存账户
         */
        void saveAccount();
    
    }
    
    package club.guoshizhan.service.impl;
    
    import club.guoshizhan.dao.IAccountDao;
    import club.guoshizhan.dao.impl.AccountDaoImpl;
    import club.guoshizhan.service.IAccountService;
    
    /**
     * 账户的业务层实现类
     */
    public class AccountServiceImpl implements IAccountService {
    
        private IAccountDao accountDao = new AccountDaoImpl();
        
        public void saveAccount() {
            accountDao.saveAccount();
        }
        
    }
    
    package club.guoshizhan.controller;
    
    import club.guoshizhan.dao.IAccountDao;
    import club.guoshizhan.service.IAccountService;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    /**
     * 模拟一个表现层,用于调用业务层
     */
    public class Client {
        public static void main(String[] args) {
    
            // 1、获取核心容器对象
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
    //        ApplicationContext ac = new FileSystemXmlApplicationContext("C:\\Users\\zhy\\Desktop\\bean.xml");
            
            // 2、根据 id 获取 Bean 对象(以下采用了两种方式获取 bean 对象)
            IAccountService as = (IAccountService) ac.getBean("accountService");
            IAccountDao dao = ac.getBean("accountDao", IAccountDao.class);
    
            System.out.println(as);
            System.out.println(dao);
            as.saveAccount();
            
        }
    }
    

    最后我们运行 main 方法即可。 结果如下:

    club.guoshizhan.service.impl.AccountServiceImpl@14ba872
    club.guoshizhan.dao.impl.AccountDaoImpl@6ba0ac
    保存了账户
    

    TIPS: 以上的过程就是 通过 Spring 的 IoC 容器来创建对象,这样就降低了程序之间的耦合度。IoC 环境搭建 到此结束!!!

    ApplicationContext 的实现类

    ApplicationContext 有三个实现类, 具体介绍以及使用如下:

    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    
    + 1、ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下。不在的话,加载不了。(更常用)
    + 2、FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须有访问权限)
    + 3、AnnotationConfigApplicationContext:它是用于读取注解创建容器的,后面会说到。
    
    +++ IoC 核心容器的两个接口引发出的问题:
    + 1、ApplicationContext: 它在构建核心容器时,创建对象采取的策略是采用立即加载的方式。
    + 也就是说,只要一读取完配置文件马上就创建配置文件中配置的对象。(适用范围:单例对象适用,一般情况下采用此接口)
    
    + 2、BeanFactory: 它在构建核心容器时,创建对象采取的策略是采用延迟加载的方式。
    + 也就是说,什么时候根据 id 获取对象了,什么时候才真正的创建对象。(适用范围:多例对象使用)
    
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    

    前两个实现类的使用方法如下:

    package club.guoshizhan.controller;
    
    import club.guoshizhan.dao.IAccountDao;
    import club.guoshizhan.service.IAccountService;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    /**
     * 模拟一个表现层,用于调用业务层
     */
    public class Client {
        public static void main(String[] args) {
    
            // 1、获取核心容器对象
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
    //        ApplicationContext ac = new FileSystemXmlApplicationContext("C:\\Users\\zhy\\Desktop\\bean.xml");
            
            // 2、根据 id 获取 Bean 对象(以下采用了两种方式获取 bean 对象)
            IAccountService as = (IAccountService) ac.getBean("accountService");
            IAccountDao dao = ac.getBean("accountDao", IAccountDao.class);
    
            System.out.println(as);
            System.out.println(dao);
            as.saveAccount();
            
            // ---------------- BeanFactory 接口创建对象的策略 ----------------
    //        Resource resource = new ClassPathResource("bean.xml");
    //        BeanFactory factory = new XmlBeanFactory(resource);
    //        IAccountService as  = (IAccountService)factory.getBean("accountService");
    //        System.out.println(as);
        }
    }
    

    Spring 中 bean 的细节

    三种创建 bean 的方式

    第一步: 先创建一个 SimulationFactory 模拟工厂类,方便后续介绍。 代码如下:

    package club.guoshizhan.factory;
    
    import club.guoshizhan.dao.IAccountDao;
    import club.guoshizhan.dao.impl.AccountDaoImpl;
    import club.guoshizhan.service.IAccountService;
    import club.guoshizhan.service.impl.AccountServiceImpl;
    
    /**
     * 模拟一个工厂类:该类可能是存在于 jar 包中的,我们无法通过修改源码的方式来提供默认构造方法
     * 所以,如果我们需要在这种情况下创建对象,就需要用到第二种和第三种创建方式
     */
    public class SimulationFactory {
    
        public IAccountDao getAccountDao() {
            return new AccountDaoImpl();
        }
    
        public static IAccountService getAccountService() {
            return new AccountServiceImpl();
        }
    
    }
    

    第二步: bean.xml 配置文件中使用三种创建 bean 的方式。 详情如下:

    <?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 和 class 属性之后,且没有其他属性和标签时,
            这种方式采用的就是默认构造方法创建 bean 对象,此时如果类中没有默认构造方法,则对象无法创建。实例如下:
        -->
        <bean id="accountService" class="club.guoshizhan.service.impl.AccountServiceImpl"></bean>
    
        <!-- 第二种方式: 使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入 spring 容器) -->
        <bean id="simulationFactory" class="club.guoshizhan.factory.SimulationFactory"></bean>
        <bean id="accountDaoFactory" factory-bean="simulationFactory" factory-method="getAccountDao"></bean>
    
        <!-- 第三种方式:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入 spring 容器) -->
        <bean id="accountServiceFactory" class="club.guoshizhan.factory.SimulationFactory" factory-method="getAccountService"></bean>
    
    </beans>
    

    作用范围

    在 Spring 中,那些组成应用程序的主体及由 Spring IoC 容器所管理的对象,被称之为 bean 其作用范围的详细介绍如下:

    <?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 的作用范围:
            bean 标签的 scope 属性:
                1、作用:用于指定 bean 的作用范围
                2、取值: 常用的就是单例的和多例的
                      singleton:单例的(默认值)
                      prototype:多例的
                      request:作用于 web 应用的请求范围
                      session:作用于 web 应用的会话范围
                      global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,它就是 session
        -->
        <bean id="accountService" class="club.guoshizhan.service.impl.AccountServiceImpl" scope="prototype"></bean>
    
    </beans>
    

    生命周期

    Spring Bean 的完整生命周期从创建 Spring 容器开始,直到最终 Spring 容器销毁 Bean ,这其中包含了一系列关键点。 如下图:

    Spring Bean 的生命周期的相关介绍以及配置如下:

    <?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 对象的生命周期
            1、单例对象
                出生:当容器创建时对象出生
                活着:只要容器还在,对象一直活着
                死亡:容器销毁,对象消亡
                总结:单例对象的生命周期和容器相同
            2、多例对象
                出生:当我们使用对象时 spring 框架为我们创建
                活着:对象只要是在使用过程中就一直活着。
                死亡:当对象长时间不用,且没有别的对象引用时,由 Java 的垃圾回收器回收
        -->
        <!-- init 方法和 destroy 方法是自己定义在 AccountServiceImpl 类里面的创建和销毁方法(定义方法,随便写的东西就行) -->
        <bean id="accountService" class="club.guoshizhan.service.impl.AccountServiceImpl" 
              scope="prototype" init-method="init" destroy-method="destroy"></bean>
    
    </beans>
    

    Spring 中的依赖注入

    Spring 中的依赖注入详情介绍如下: 如需更多详情,请参考: 关于Spring IOC (DI-依赖注入)你需要知道的一切

    +++ spring 中的依赖注入
    + 依赖注入:Dependency Injection
    + IOC 的作用:降低程序间的耦合(依赖关系)
    + 依赖关系的管理:以后都交给 spring 来维护。在当前类需要用到其他类的对象,由 spring 为我们提供,我们只需要在配置文件中说明即可。
    + 这种依赖关系的维护就称之为依赖注入。
    
    +++ 在依赖注入中,能注入的数据有三类,如下:
    + 1、基本类型和 String
    + 2、其他 bean 类型(在配置文件中或者注解配置过的 bean)
    + 3、复杂类型/集合类型
    
    +++ 注入的方式:有三种
    + 1、第一种:使用构造方法提供
    + 2、第二种:使用 set 方法提供
    + 3、第三种:使用注解提供
    
    +++ It's over!!!
    

    构造方法注入

    第一步: 先创建一个 User 类,用于方便后续的注入操作。 代码如下:

    package club.guoshizhan.domain;
    
    import java.util.Date;
    
    public class User {
    
        // 如果经常变化的数据,并不适用于构造方法注入,此处只是为了演示
        // 此处需要关注的是变量的数据类型
        private String name;
        private Integer age;
        private Date birthday;
    
        public User(String name, Integer age, Date birthday) {
            this.name = name;
            this.age = age;
            this.birthday = birthday;
        }
        
    }
    

    第二步: 编写 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">
    
        <!-- 构造方法注入:
            1、使用的标签:constructor-arg
            2、标签出现的位置:bean 标签的内部
            3、标签中的属性:
                type:用于指定要注入的数据的数据类型,该数据类型也是构造方法中某个或某些参数的类型
                index:用于指定要注入的数据给构造方法中指定索引位置的参数赋值。索引的位置是从0开始
                name:用于指定给构造方法中指定名称的参数赋值(这个是常用的属性)
                ============= 以上三个用于指定给构造方法中哪个参数赋值 =============
                value:用于提供基本类型和 String 类型的数据
                ref:用于指定其他的 bean 类型数据。它指的就是在 spring 的 IoC 核心容器中出现过的 bean 对象
            4、构造方法注入的优势:在获取 bean 对象时,注入数据是必须的操作,否则对象无法创建成功。
            5、构造方法注入的弊端:改变了 bean 对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。
        -->
        <bean id="user" class="club.guoshizhan.domain.User">
            <constructor-arg name="name" value="James Gosling"></constructor-arg>
            <constructor-arg name="age" value="58"></constructor-arg>
            <constructor-arg name="birthday" ref="now"></constructor-arg>
        </bean>
    
        <!-- 配置一个日期对象 -->
        <bean id="now" class="java.util.Date"></bean>
    
    </beans>
    

    第三步: 编写 Client 类进行测试。 代码如下:

    package club.guoshizhan.controller;
    
    import club.guoshizhan.domain.User;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    /**
     * 模拟一个表现层,用于调用业务层
     */
    public class Client {
        public static void main(String[] args) {
    
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
            User user = (User) ac.getBean("user");
            user.show();
    
        }
    }
    

    第四步: 运行 main 方法,测试结果如下:

    name = James Gosling
    age = 58
    birthday = Mon Sep 07 18:50:18 CST 2020
    

    TIPS: 由于构造方法的弊端,导致它无法成为常用的注入方式。

    set 方法注入

    第一步: 修改 User 类的代码,把原来的构造方法删掉,然后新加 setXXX 方法。 代码如下:

    package club.guoshizhan.domain;
    
    import java.util.Date;
    
    public class User {
    
        private String name;
        private Integer age;
        private Date birthday;
    
        public void setName(String name) {
            this.name = name;
        }
    
        public void setAge(Integer age) {
            this.age = age;
        }
    
        public void setBirthday(Date birthday) {
            this.birthday = birthday;
        }
    
        public void show() {
            System.out.println("name = " + name);
            System.out.println("age = " + age);
            System.out.println("birthday = " + birthday);
        }
    
    }
    

    第二步: 编写 bean.xml 配置文件(set 注入的详细介绍写于注释之中)。 代码如下:

    <?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">
    
        <!-- set 方法注入(更常用的方式)
            1、涉及的标签:property
            2、出现的位置:bean 标签的内部
            3、标签的属性:
                name:用于指定注入时所调用的 set 方法名称。例如:如果 方法名是 SetName,那么 name 的属性值就是 name,
                      如果是 SetAAA,那么 name 的属性值就是 AAA(即使类中没有 AAA 变量)
                value:用于提供基本类型和 String 类型的数据
                ref:用于指定其他的 bean 类型数据。它指的就是在 spring 的 IoC 核心容器中出现过的 bean 对象
            4、set 方法注入的优势:创建对象时没有明确的限制,可以直接使用默认构造方法
            5、set 方法注入的弊端:如果有某个成员必须有值,那么 set 注入可能不能保证它一定有值。(可能这个成员变量没有 set 方法,也有可能 set 方法没有执行等等)
        -->
        <bean id="user" class="club.guoshizhan.domain.User">
            <property name="name" value="Java" ></property>
            <property name="age" value="25"></property>
            <property name="birthday" ref="now"></property>
        </bean>
    
        <!-- 配置一个日期对象 -->
        <bean id="now" class="java.util.Date"></bean>
    
    </beans>
    

    第三步: 运行 Client 类中的 main 方法,测试结果如下:

    name = Java
    age = 25
    birthday = Mon Sep 07 19:05:51 CST 2020
    

    TIPS: 由于 set 注入的灵活性,因此它是常用的注入方式。

    集合类型注入

    第一步: 修改 User 类的代码,加入复杂的属性,并提供 setXXX 方法。 代码如下:

    package club.guoshizhan.domain;
    
    import java.util.*;
    
    public class User {
    
       private String[] arrStr;
       private List<String> list;
       private Set<String> set;
       private Map<String,String > map;
       private Properties properties;
    
        public void setArrStr(String[] arrStr) {
            this.arrStr = arrStr;
        }
    
        public void setList(List<String> list) {
            this.list = list;
        }
    
        public void setSet(Set<String> set) {
            this.set = set;
        }
    
        public void setMap(Map<String, String> map) {
            this.map = map;
        }
    
        public void setProperties(Properties properties) {
            this.properties = properties;
        }
    
        public void show() {
            System.out.println("arrStr = " + Arrays.toString(arrStr));
            System.out.println("list = " + list);
            System.out.println("set = " + set);
            System.out.println("map = " + map);
            System.out.println("properties = " + properties);
        }
    
    }
    

    第二步: 编写 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">
    
        <!-- 复杂类型的注入 / 集合类型的注入
            1、用于给 List 结构的集合注入的标签:list array set ,这三个标签可以互换,用哪一个都行。例如下面的 list 属性用了 array 标签
            2、用于个 Map 结构集合注入的标签: map  props ,这两个标签也可互换
            3、结构相同,标签可以互换
        -->
        <bean id="user" class="club.guoshizhan.domain.User">
            <property name="arrStr">
                <set>
                    <value>AAA</value>
                    <value>BBB</value>
                    <value>CCC</value>
                </set>
            </property>
    
            <property name="list">
                <array>
                    <value>Lisa</value>
                    <value>Jack</value>
                    <value>Lucy</value>
                </array>
            </property>
    
            <property name="set">
                <list>
                    <value>SET1</value>
                    <value>SET2</value>
                    <value>SET3</value>
                </list>
            </property>
    
            <property name="map">
                <props>
                    <prop key="MAP1">MAP1</prop>
                    <prop key="MAP2">MAP2</prop>
                </props>
            </property>
    
            <property name="properties">
                <map>
                    <entry key="PRO1" value="PRO1"></entry>
                    <entry key="PRO2">
                        <value>PRO2</value>
                    </entry>
                </map>
            </property>
        </bean>
    
    </beans>
    

    第三步: 运行 Client 类中的 main 方法,测试结果如下:

    arrStr = [AAA, BBB, CCC]
    list = [Lisa, Jack, Lucy]
    set = [SET1, SET2, SET3]
    map = {MAP2=MAP2, MAP1=MAP1}
    properties = {PRO2=PRO2, PRO1=PRO1}
    

    TIPS: 集合类型注入 到此结束!!!

    基于注解的 IoC


    写在前面: 学习基于注解的 IoC 配置,大家脑海里首先得有一个认知,即注解配置和 xml 配置要实现的功能都是一样的,都是要降低程序间的耦合,只是配置的形式不一样。 关于实际的开发中到底使用 xml 还是注解,我们不得而知,所以这两种配置方式我们都需要掌握。


    注解 IoC 的环境搭建

    第一步: 新建 Maven 工程,完善包结构。 然后导入如下依赖:

    <!-- 使用 jar 的方式进行打包 -->
    <packaging>jar</packaging>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>
    </dependencies>
    

    第二步: resources 目录下新建 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:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    
        <!-- 告知 spring 在创建容器时要扫描的包,配置所需的标签不是在 <beans> 的约束中,而是一个名称为 <context> 名称空间和约束中 -->
        <context:component-scan base-package="club.guoshizhan"></context:component-scan>
        
    </beans>
    

    第三步: 把以下的每一个类放到对应包结构中。(具体放到哪一个包参考 package 语句) 各个类的代码如下:

    package club.guoshizhan.dao;
    
    /**
     * 账户的持久层接口
     */
    public interface IAccountDao {
    
        /**
         * 模拟保存账户
         */
        void saveAccount();
    
    }
    
    package club.guoshizhan.dao.impl;
    
    import club.guoshizhan.dao.IAccountDao;
    import org.springframework.stereotype.Repository;
    
    /**
     * 账户的持久层实现类
     */
    @Repository("accountDao")
    public class AccountDaoImpl implements IAccountDao {
    
        public void saveAccount() {
            System.out.println("保存了账户");
        }
    
    }
    
    package club.guoshizhan.service;
    
    /**
     * 账户业务层的接口
     */
    public interface IAccountService {
    
        /**
         * 模拟保存账户
         */
        void saveAccount();
    
    }
    
    package club.guoshizhan.service.impl;
    
    import club.guoshizhan.dao.IAccountDao;
    import club.guoshizhan.dao.impl.AccountDaoImpl;
    import club.guoshizhan.service.IAccountService;
    import org.springframework.stereotype.Component;
    
    /**
     * 账户的业务层实现类
     * 曾经 XML 的配置:
     *  <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl" scope=""  init-method="" destroy-method="">
     *      <property name=""  value="" | ref=""></property>
     *  </bean>
     * 如今只需要使用 @Component 一个注解即可搞定。这个注解的详细介绍下一小结立马讲解
     */
    @Component
    public class AccountServiceImpl implements IAccountService {
    
        private IAccountDao accountDao = new AccountDaoImpl();
    
        public void saveAccount() {
            accountDao.saveAccount();
        }
    
        public void init() {
            System.out.println("init...");
        }
    
        public void destroy() {
            System.out.println("destroy...");
        }
    
    }
    
    package club.guoshizhan.controller;
    
    import club.guoshizhan.dao.IAccountDao;
    import club.guoshizhan.service.impl.AccountServiceImpl;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    /**
     * 模拟一个表现层,用于调用业务层
     */
    public class Client {
        public static void main(String[] args) {
    
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
            AccountServiceImpl accountServiceImpl = (AccountServiceImpl) ac.getBean("accountServiceImpl");
            IAccountDao accountDao = ac.getBean("accountDao", IAccountDao.class);
            System.out.println(accountServiceImpl);
            System.out.println(accountDao);
            accountServiceImpl.saveAccount();
            accountDao.saveAccount();
    
        }
    }
    

    最后我们运行 main 方法即可。 结果如下:

    club.guoshizhan.service.impl.AccountServiceImpl@57643e
    club.guoshizhan.dao.impl.AccountDaoImpl@13e16fd
    保存了账户
    保存了账户
    

    TIPS: 以上的过程就是 注解 IoC 的环境搭建 ,那么环境搭建到此结束!!!

    常用注解详解

    用于创建对象的注解

    接下来介绍一下 Spring 中的 常用注解 ,具体详情如下:

    +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    
    +++ 用于创建对象的注解:他们的作用就和在 XML 配置文件中编写一个 <bean> 标签实现的功能是一样的
    + @Component 注解:
    + 作用:用于把当前类对象存入 spring 容器中
    + 属性:value:用于指定 bean 的 id 。当我们不写时,它的默认值就是当前类名,且首字母改小写。
    
    +++ 以下三个注解的作用和属性与 Component 是一模一样。
    +++ 他们是 spring 框架提供明确的三层使用的注解,使我们的 MVC 三层对象更加清晰
    + @Controller 注解:一般用在表现层
    + @Service 注解:一般用在业务层
    + @Repository 注解:一般用在持久层
    
    +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    

    用于注入数据的注解

    +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    
    +++ 用于注入数据的注解:他们的作用就和在 xml 配置文件中的 bean 标签中写一个 <property> 标签的作用是一样的
    + @Autowired 注解:
    + 作用:自动按照类型注入。只要容器中有唯一的一个 bean 对象类型和要注入的变量类型匹配,就可以注入成功
    +       如果 IoC 容器中没有任何 bean 的类型和要注入的变量类型匹配,则报错。
    +       如果 IoC 容器中有多个类型匹配时:那么 Spring 会先去匹配数据类型,然后再根据变量名去确定哪一个 bean
    + 出现位置:可以是变量上,也可以是方法上
    + 细节:在使用注解注入时,set 方法就不是必须的了。
    
    + @Qualifier 注解:
    + 作用:在按照类注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用。但是在给方法参数注入时可以
    + 属性:value 属性用于指定注入 bean 的 id 。
    
    + @Resource 注解:
    + 作用:直接按照 bean 的 id 注入,它可以独立使用
    + 属性:name 属性用于指定 bean 的 id 。
    
    +++ 注意:以上三个注入都只能注入其他 bean 类型的数据,而基本类型和 String 类型无法使用上述注解实现。另外,集合类型的注入只能通过 XML 来实现。
    
    + @Value 注解:
    + 作用:用于注入基本类型和 String 类型的数据
    + 属性:value 属性用于指定数据的值。它可以使用 spring 中 SpEL(也就是 spring 的 el 表达式)。SpEL 的写法:${表达式}
    
    +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    

    用于改变作用范围的注解

    +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    
    +++ 3、用于改变作用范围的注解:他们的作用就和在 bean 标签中使用 scope 属性实现的功能是一样的
    + @Scope 注解:
    + 作用:用于指定 bean 的作用范围。可以写在类的上方
    + 属性:value 属性指定范围的取值。常用取值:singleton / prototype
    
    +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    

    生命周期相关的注解

    +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    
    +++ 4、和生命周期相关的注解(了解即可):他们的作用就和在 bean 标签中使用 init-method 和 destroy-methode 的作用是一样的
    + @PreDestroy 注解:作用就是用于指定 bean 的销毁方法。此注解写在销毁的方法上
    + @PostConstruct 注解:作用就是用于指定 bean 的初始化方法。此注解写在初始化的方法上
    
    +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    

    基于 XML 的 IoC 案例

    第一步: 创建数据库和数据表。 代码如下:

    -- 创建 spring 数据库
    CREATE DATABASE spring;
    
    -- 创建 account 数据表
    CREATE TABLE account(
        id INT PRIMARY KEY AUTO_INCREMENT,
        NAME VARCHAR(40),
        money FLOAT
    )CHARACTER SET utf8 COLLATE utf8_general_ci;
    
    INSERT INTO account(NAME,money) VALUES('Jack',1000);
    INSERT INTO account(NAME,money) VALUES('Lucy',1000);
    INSERT INTO account(NAME,money) VALUES('Lisa',1000);
    

    第二步: 新建一个 Maven 工程。 导入如下依赖:

    <packaging>jar</packaging>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.4</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.5</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    

    第三步: 创建如下各个接口和类,根据 package 语句将类或接口放到对应的包中。 接口和类的代码如下:

    package club.guoshizhan.domain;
    
    import java.io.Serializable;
    
    /**
     * 账户实体类
     */
    public class Account implements Serializable {
    
        private Integer id;
        private String name;
        private Float money;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Float getMoney() {
            return money;
        }
    
        public void setMoney(Float money) {
            this.money = money;
        }
    
        @Override
        public String toString() {
            return "Account{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", money=" + money +
                    '}';
        }
    
    }
    
    package club.guoshizhan.dao;
    
    import club.guoshizhan.domain.Account;
    
    import java.util.List;
    
    /**
     * 账户的持久层接口
     */
    public interface IAccountDao {
    
        // 查询所有
        List<Account> findAll();
    
        // 查询一个账户
        Account findById(Integer id);
    
        // 保存账户
        void save(Account account);
    
        // 更新账户
        void update(Account account);
    
        // 删除账户
        void delete(Integer id);
    
    }
    
    package club.guoshizhan.dao.impl;
    
    import club.guoshizhan.dao.IAccountDao;
    import club.guoshizhan.domain.Account;
    import org.apache.commons.dbutils.QueryRunner;
    import org.apache.commons.dbutils.handlers.BeanHandler;
    import org.apache.commons.dbutils.handlers.BeanListHandler;
    
    import java.util.List;
    
    /**
     * 账户持久层实现类
     */
    public class AccountDaoImpl implements IAccountDao {
    
        private QueryRunner runner;
    
        public void setRunner(QueryRunner runner) {
            this.runner = runner;
        }
    
        public List<Account> findAll() {
            try {
                return runner.query("select * from account", new BeanListHandler<Account>(Account.class));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        public Account findById(Integer id) {
            try {
                return runner.query("select * from account where id = ?", new BeanHandler<Account>(Account.class), id);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        public void save(Account account) {
            try {
                runner.update("insert into account(name,money) values (?,?)", account.getName(), account.getMoney());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        public void update(Account account) {
            try {
                runner.update("update account set name=?,money=? where id=?", account.getName(), account.getMoney(), account.getId());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        public void delete(Integer id) {
            try {
                runner.update("delete from account where id=?", id);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
    }
    
    package club.guoshizhan.service;
    
    import club.guoshizhan.domain.Account;
    
    import java.util.List;
    
    /**
     * 账号的业务层接口
     */
    public interface IAccountService {
    
        // 查询所有
        List<Account> findAll();
    
        // 查询一个账户
        Account findById(Integer id);
    
        // 保存账户
        void save(Account account);
    
        // 更新账户
        void update(Account account);
    
        // 删除账户
        void delete(Integer id);
    
    }
    
    package club.guoshizhan.service.impl;
    
    import club.guoshizhan.dao.IAccountDao;
    import club.guoshizhan.domain.Account;
    import club.guoshizhan.service.IAccountService;
    
    import java.util.List;
    
    /**
     * 账号的业务层实现类
     */
    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 findById(Integer id) {
            return accountDao.findById(id);
        }
    
        public void save(Account account) {
            accountDao.save(account);
        }
    
        public void update(Account account) {
            accountDao.update(account);
        }
    
        public void delete(Integer id) {
            accountDao.delete(id);
        }
    
    }
    

    第四步: resources 目录下新建 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">
    
        <!-- 配置 Service -->
        <bean id="accountService" class="club.guoshizhan.service.impl.AccountServiceImpl">
            <!-- 注入 dao -->
            <property name="accountDao" ref="accountDao"></property>
        </bean>
    
        <!-- 配置 Dao 对象 -->
        <bean id="accountDao" class="club.guoshizhan.dao.impl.AccountDaoImpl">
            <!-- 注入 QueryRunner -->
            <property name="runner" ref="runner"></property>
        </bean>
    
        <!--配置 QueryRunner -->
        <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
            <!-- 注入数据源 -->
            <constructor-arg name="ds" ref="dataSource"></constructor-arg>
        </bean>
    
        <!-- 配置数据源 -->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <!-- 连接数据库的必备信息 -->
            <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"></property>
            <property name="user" value="root"></property>
            <property name="password" value="root"></property>
        </bean>
    
    </beans>
    

    第五步: test 目录下新建测试类,包名根据 package 语句自行创建。 代码如下:

    package club.guoshizhan.test;
    
    import club.guoshizhan.domain.Account;
    import club.guoshizhan.service.IAccountService;
    import org.junit.Before;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    import java.util.List;
    
    /**
     * 使用 Junit 单元测试:测试我们的配置
     */
    public class AccountServiceTest {
    
        private ApplicationContext ac;
        private IAccountService accountService;
    
        @Before
        public void init() {
            ac = new ClassPathXmlApplicationContext("bean.xml");
            accountService = ac.getBean("accountService", IAccountService.class);
        }
    
        @Test
        public void testFindAll() {
            List<Account> accounts = accountService.findAll();
            for(Account account : accounts){
                System.out.println(account);
            }
        }
    
        @Test
        public void testFindOne() {
            Account account = accountService.findById(1);
            System.out.println(account);
        }
    
        @Test
        public void testSave() {
            Account account = new Account();
            account.setName("test");
            account.setMoney(12345f);
            accountService.save(account);
    
        }
    
        @Test
        public void testUpdate() {
            Account account = accountService.findById(4);
            account.setMoney(23456f);
            accountService.update(account);
        }
    
        @Test
        public void testDelete() {
            accountService.delete(4);
        }
    
    }
    

    TIPS: 依次执行每一个测试方法即可,结果完全正确!!!

    基于注解的 IoC 案例


    这部分的操作是在 XML 案例 的基础上进行的操作。就是只需要修改部分代码即可。那就操作一下吧!!!


    第一步: 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:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    
        <!-- 告知 spring 在创建容器时要扫描的包 -->
        <context:component-scan base-package="club.guoshizhan"></context:component-scan>
    
        <!--配置 QueryRunner -->
        <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
            <!-- 注入数据源 -->
            <constructor-arg name="ds" ref="dataSource"></constructor-arg>
        </bean>
    
        <!-- 配置数据源 -->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <!-- 连接数据库的必备信息 -->
            <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"></property>
            <property name="user" value="root"></property>
            <property name="password" value="root"></property>
        </bean>
    
    </beans>
    

    第二步: 修改 AccountDaoImpl 类,+ 代表增加代码,- 代表删除代码。 修改如下:

    /**
     * 账户持久层实现类
     */
    + @Repository("accountDao")
    public class AccountDaoImpl implements IAccountDao {
    
    +   @Autowired
        private QueryRunner runner;
    
    -   public void setRunner(QueryRunner runner) {
    -       this.runner = runner;
    -   }
    
    +++ 剩下的代码不变
    

    第三步: 修改 AccountServiceImpl 类,+ 代表增加代码,- 代表删除代码。 修改如下:

    /**
     * 账号的业务层实现类
     */
    + @Service("accountService")
    public class AccountServiceImpl implements IAccountService {
    
    +   @Autowired
        private IAccountDao accountDao;
    
    -   public void setAccountDao(IAccountDao accountDao) {
    -       this.accountDao = accountDao;
    -   }
    
    +++ 剩下的代码不变
    

    第四步: 去测试类进行测试(我测试的是 findAll 方法,只要能测试成功就表示你的配置没有问题)。 结果如下:

    九月 08, 2020 11:09:08 上午 com.mchange.v2.log.MLog 
    信息: MLog clients using java 1.4+ standard logging.
    九月 08, 2020 11:09:09 上午 com.mchange.v2.c3p0.C3P0Registry 
    信息: Initializing c3p0-0.9.5.5 [built 11-December-2019 22:18:33 -0800; debug? true; trace: 10]
    九月 08, 2020 11:09:09 上午 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource 
    信息: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> 1hgeby9actvo5zi1fbj4vu|114c583, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceSynchronousCheckins -> false, forceUseNamedDriverClass -> false, identityToken -> 1hgeby9actvo5zi1fbj4vu|114c583, idleConnectionTestPeriod -> 0, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/spring, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 15, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 3, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ]
    Account{id=1, name='Jack', money=1000.0}
    Account{id=2, name='Lucy', money=1000.0}
    Account{id=3, name='Lisa', money=1000.0}
    
    Process finished with exit code 0
    

    上面的注解操作虽然简化了一点点,但是还是需要 bean.xml 这个配置文件。那么问题来了,能不能删除这个配置文件呢?安排!!!


    第一步: 删除 bean.xml 配置文件,新建 SpringConfiguration 配置类(注解介绍地很详细)。 代码如下:

    package club.guoshizhan.config;
    
    import com.mchange.v2.c3p0.ComboPooledDataSource;
    import org.apache.commons.dbutils.QueryRunner;
    import org.springframework.context.annotation.*;
    
    import javax.sql.DataSource;
    
    /**
     * 该类是一个配置类,它的作用和 bean.xml 是一样的
     */
    
    /**
     * @Configuration 注解:用于指定当前类是一个配置类
     * 注意事项:当配置类作为 AnnotationConfigApplicationContext 对象创建的参数时,该注解可以不写。
     */
    @Configuration
    
    /**
     * @ComponentScan 注解:用于指定 spring 在创建容器时要扫描的包
     * value 属性:它和 basePackages 属性的作用是一样的,都是用于指定创建容器时要扫描的包。
     * 我们使用此注解就等同于在 xml 中配置了: <context:component-scan base-package="club.guoshizhan"></context:component-scan>
     */
    @ComponentScan("club.guoshizhan")
    public class SpringConfiguration {
    
        /**
         * @Bean 注解:用于把当前方法的返回值作为 bean 对象存入 spring 的 IoC 容器中
         * name 属性:用于指定 bean 的 id 。当不写时,默认值是当前方法的名称
         * 注意事项:当我们使用注解配置方法时,如果方法有参数,spring 框架会去容器中查找有没有可用的 bean 对象。查找的方式和 @Autowired 注解的作用是一样的
         */
        @Bean
        @Scope("prototype")
        public QueryRunner queryRunner(DataSource dataSource) {
            return new QueryRunner(dataSource);
        }
    
        @Bean
        public DataSource createDataSource() {
            ComboPooledDataSource dataSource = new ComboPooledDataSource();
            try {
                dataSource.setDriverClass("com.mysql.jdbc.Driver");
                dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/spring");
                dataSource.setUser("root");
                dataSource.setPassword("root");
                return dataSource;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
    

    第二步: 把测试类 AccountServiceTest 修改成下面的样子(只要一个 testFindAll 方法即可)。 代码如下:

    package club.guoshizhan.test;
    
    import club.guoshizhan.config.SpringConfiguration;
    import club.guoshizhan.domain.Account;
    import club.guoshizhan.service.IAccountService;
    import org.junit.Before;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.annotation.AnnotationConfigApplicationContext;
    
    import java.util.List;
    
    /**
     * 使用 Junit 单元测试:测试我们的配置
     */
    public class AccountServiceTest {
    
        private ApplicationContext ac;
        private IAccountService accountService;
    
        @Before
        public void init() {
            // 此处使用了 AnnotationConfigApplicationContext 类(注解的方式)来创建 ApplicationContext 对象
            ac = new AnnotationConfigApplicationContext(SpringConfiguration.class);
            accountService = ac.getBean("accountService", IAccountService.class);
        }
    
        @Test
        public void testFindAll() {
            List<Account> accounts = accountService.findAll();
            for(Account account : accounts){
                System.out.println(account);
            }
        }
    
    }
    

    第三步: 执行测试类中的 testFindAll 方法。 结果如下:

    九月 08, 2020 12:49:39 下午 com.mchange.v2.log.MLog 
    信息: MLog clients using java 1.4+ standard logging.
    九月 08, 2020 12:49:39 下午 com.mchange.v2.c3p0.C3P0Registry 
    信息: Initializing c3p0-0.9.5.5 [built 11-December-2019 22:18:33 -0800; debug? true; trace: 10]
    九月 08, 2020 12:49:39 下午 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource 
    信息: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> 1hgeby9actz9f0b1jp9fll|514af7, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceSynchronousCheckins -> false, forceUseNamedDriverClass -> false, identityToken -> 1hgeby9actz9f0b1jp9fll|514af7, idleConnectionTestPeriod -> 0, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/spring, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 15, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 3, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ]
    Account{id=1, name='Jack', money=1000.0}
    Account{id=2, name='Lucy', money=1000.0}
    Account{id=3, name='Lisa', money=1000.0}
    
    Process finished with exit code 0
    

    假设有很多个配置类,而 SpringConfiguration 作为主配置类,那么它该如何去加载其他的配置类呢?


    第一步: 新建 JdbcConfig 配置类,用于数据库相关的配置。 代码如下:

    package club.guoshizhan.config;
    
    import com.mchange.v2.c3p0.ComboPooledDataSource;
    import org.apache.commons.dbutils.QueryRunner;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Scope;
    
    import javax.sql.DataSource;
    
    /**
     * spring 连接数据库相关的配置类
     */
    public class JdbcConfig {
    
        /**
         * @Bean 注解:用于把当前方法的返回值作为 bean 对象存入 spring 的 IoC 容器中
         * name 属性:用于指定 bean 的 id 。当不写时,默认值是当前方法的名称
         * 注意事项:当我们使用注解配置方法时,如果方法有参数,spring 框架会去容器中查找有没有可用的 bean 对象。查找的方式和 @Autowired 注解的作用是一样的
         */
        @Bean
        @Scope("prototype")
        public QueryRunner queryRunner(DataSource dataSource1) {
            return new QueryRunner(dataSource1);
        }
    
        /**
         * 创建数据源对象
         */
        @Bean
        public DataSource createDataSource() {
            ComboPooledDataSource dataSource = new ComboPooledDataSource();
            try {
                dataSource.setDriverClass("com.mysql.jdbc.Driver");
                dataSource.setJdbcUrl("jdbc:mysql://localhost:3306/spring");
                dataSource.setUser("root");
                dataSource.setPassword("root");
                return dataSource;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
    }
    

    第二步: 把主配置类修改为如下的样子(里面什么都没有,但是使用 @Import 注解加载了其他的配置类)。 代码如下:

    package club.guoshizhan.config;
    
    import org.springframework.context.annotation.*;
    
    /**
     * 该类是一个配置类,它的作用和 bean.xml 是一样的
     */
    
    /**
     * @Import 注解:用于导入其他的配置类
     * value 属性:用于指定其他配置类的字节码。
     * 当我们使用 @Import 的注解之后,有 @Import 注解的类就父配置类,而导入的都是子配置类
     */
    
    /**
     * @PropertySource 注解:用于指定 properties 文件的位置
     * value 属性:指定文件的名称和路径。
     * 关键字:classpath 表示类路径下
     */
    
    @ComponentScan("club.guoshizhan")
    @Import(JdbcConfig.class)
    public class SpringConfiguration {
    
    }
    

    TIPS: 然后执行测试类的测试方法也可实现相关的操作。


    那么问题又来了,数据库的相关信息的配置都写在代码里面了,这就造成了硬编码,最终导致无法灵活的实现配置。 因此,我们需要把它写到配置文件里面,然后从配置文件中读取相关的信息。那么,接下来就在 resources 目录下创建 jdbcConfig.properties 配置文件。 代码如下:


    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/spring
    jdbc.username=root
    jdbc.password=root
    

    接下来把 JdbcConfig 配置类修改成如下样子。 代码如下:

    package club.guoshizhan.config;
    
    import com.mchange.v2.c3p0.ComboPooledDataSource;
    import org.apache.commons.dbutils.QueryRunner;
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.context.annotation.Scope;
    
    import javax.sql.DataSource;
    
    /**
     * spring 连接数据库相关的配置类
     */
    public class JdbcConfig {
    
        @Value("${jdbc.driver}")
        private String driver;
    
        @Value("${jdbc.url}")
        private String url;
    
        @Value("${jdbc.username}")
        private String username;
    
        @Value("${jdbc.password}")
        private String password;
        
        @Bean
        @Scope("prototype")
        public QueryRunner queryRunner(DataSource dataSource1) {
            return new QueryRunner(dataSource1);
        }
        
        @Bean
        public DataSource createDataSource() {
            ComboPooledDataSource dataSource = new ComboPooledDataSource();
            try {
                dataSource.setDriverClass(driver);
                dataSource.setJdbcUrl(url);
                dataSource.setUser(username);
                dataSource.setPassword(password);
                return dataSource;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
    }
    

    然后去 SpringConfiguration 主配置文件添加如下代码:

    @ComponentScan("club.guoshizhan")
    @Import(JdbcConfig.class)
    + @PropertySource("classpath:jdbcConfig.properties")
    public class SpringConfiguration {
    
    }
    

    TIPS: 最后去测试类测试,仍然可以执行成功。到此为止就完全实现了注解的配置,而不再需要 XML 文件了。

    Spring 和 Junit 的整合

    Spring 和 Junit 的整合 的步骤如下:

    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    
    + Spring 使用 Junit 单元测试:测试我们的配置。步骤如下:
    + 1、导入 Spring 整合 junit 的 jar 包(坐标)
    + 2、使用 Junit 提供的 @RunWith 注解把原有的 main 方法替换了,替换成 Spring 提供的
    + 3、使用 @ContextConfiguration 注解告知 Spring 的运行器 ==> Spring 和 ioc 创建是基于 xml 还是注解的,并且说明位置
    +    locations:指定 xml 文件的位置,加上 classpath 关键字,表示在类路径下
    +    classes:指定注解类所在地位置
    + 4、注意事项:当我们使用 Spring 5.x 版本的时候,要求 junit 的 jar 包版本必须是 4.12 及以上
    
    ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
    

    第一步: 导入相关依赖。 代码如下:

    <!-- 版本需要在 4.12 及以上,否则报错 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.13</version>
        <scope>test</scope>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-test</artifactId>
        <version>5.2.2.RELEASE</version>
    </dependency>
    

    第二步: 编写测试类(有空可以自己去对比一下之前的测试类,看一下不同点)。 代码如下:

    package club.guoshizhan.test;
    
    import club.guoshizhan.config.SpringConfiguration;
    import club.guoshizhan.domain.Account;
    import club.guoshizhan.service.IAccountService;
    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;
    
    import java.util.List;
    
    /**
     * Spring 使用 Junit 单元测试:测试我们的配置。步骤如下:
     * 1、导入 Spring 整合 junit 的 jar 包(坐标)
     * 2、使用 Junit 提供的 @RunWith 注解把原有的 main 方法替换了,替换成 Spring 提供的
     * 3、使用 @ContextConfiguration 注解告知 Spring 的运行器 ==> Spring 和 ioc 创建是基于 xml 还是注解的,并且说明位置
     *     locations:指定 xml 文件的位置,加上 classpath 关键字,表示在类路径下
     *     classes:指定注解类所在地位置
     *  4、注意事项:当我们使用 Spring 5.x 版本的时候,要求 junit 的 jar 包版本必须是 4.12 及以上
     */
    
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = SpringConfiguration.class)
    /*// XML 方式的配置
    @ContextConfiguration(locations = "classpath:bean.xml")*/
    public class AccountServiceTest {
    
        @Autowired
        private IAccountService accountService;
    
        @Test
        public void testFindAll() {
            List<Account> accounts = accountService.findAll();
            for(Account account : accounts){
                System.out.println(account);
            }
        }
    
    }
    

    TIPS: 运行测试类的方法,即可看到对应的结果。Spring 整合 Junit 到此结束!!!

    Spring 中的 AOP

    转账小案例

    第一步: 新建一个 Maven 工程,导入如下依赖。 代码如下:

    <packaging>jar</packaging>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>commons-dbutils</groupId>
            <artifactId>commons-dbutils</artifactId>
            <version>1.4</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</version>
        </dependency>
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.5.5</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.2.2.RELEASE</version>
        </dependency>
    </dependencies>
    

    第二步: 创建如下各个接口和类,根据 package 语句将类或接口放到对应的包中。 接口和类的代码如下:

    package club.guoshizhan.domain;
    
    import java.io.Serializable;
    
    /**
     * 账户实体类
     */
    public class Account implements Serializable {
    
        private Integer id;
        private String name;
        private Float money;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Float getMoney() {
            return money;
        }
    
        public void setMoney(Float money) {
            this.money = money;
        }
    
        @Override
        public String toString() {
            return "Account{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", money=" + money +
                    '}';
        }
    
    }
    
    package club.guoshizhan.dao;
    
    import club.guoshizhan.domain.Account;
    
    import java.util.List;
    
    /**
     * 账户的持久层接口
     */
    public interface IAccountDao {
    
        // 查询所有
        List<Account> findAll();
    
        // 查询一个账户
        Account findById(Integer id);
    
        // 保存账户
        void save(Account account);
    
        // 更新账户
        void update(Account account);
    
        // 删除账户
        void delete(Integer id);
    
        // 根据名称查询账户
        Account findByName(String name);
    
    }
    
    package club.guoshizhan.dao.impl;
    
    import club.guoshizhan.dao.IAccountDao;
    import club.guoshizhan.domain.Account;
    import org.apache.commons.dbutils.QueryRunner;
    import org.apache.commons.dbutils.handlers.BeanHandler;
    import org.apache.commons.dbutils.handlers.BeanListHandler;
    
    import java.util.List;
    
    /**
     * 账户持久层实现类
     */
    public class AccountDaoImpl implements IAccountDao {
    
        private QueryRunner runner;
    
        public void setRunner(QueryRunner runner) {
            this.runner = runner;
        }
    
        @Override
        public List<Account> findAll() {
            try {
                return runner.query("select * from account", new BeanListHandler<Account>(Account.class));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public Account findById(Integer id) {
            try {
                return runner.query("select * from account where id = ?", new BeanHandler<Account>(Account.class), id);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public void save(Account account) {
            try {
                runner.update("insert into account(name,money) values (?,?)", account.getName(), account.getMoney());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public void update(Account account) {
            try {
                runner.update("update account set name=?,money=? where id=?", account.getName(), account.getMoney(), account.getId());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public void delete(Integer id) {
            try {
                runner.update("delete from account where id=?", id);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public Account findByName(String name) {
            try {
                List<Account> accounts = runner.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 (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
    }
    
    package club.guoshizhan.service;
    
    import club.guoshizhan.domain.Account;
    
    import java.util.List;
    
    /**
     * 账号的业务层接口
     */
    public interface IAccountService {
    
        // 查询所有
        List<Account> findAll();
    
        // 查询一个账户
        Account findById(Integer id);
    
        // 保存账户
        void save(Account account);
    
        // 更新账户
        void update(Account account);
    
        // 删除账户
        void delete(Integer id);
    
        // 转账操作
        void transfer(String sourceName, String targetName, Float money);
    
    }
    
    package club.guoshizhan.service.impl;
    
    import club.guoshizhan.dao.IAccountDao;
    import club.guoshizhan.domain.Account;
    import club.guoshizhan.service.IAccountService;
    
    import java.util.List;
    
    /**
     * 账号的业务层实现类
     */
    public class AccountServiceImpl implements IAccountService {
    
        private IAccountDao accountDao;
    
        public void setAccountDao(IAccountDao accountDao) {
            this.accountDao = accountDao;
        }
    
        @Override
        public List<Account> findAll() {
            return accountDao.findAll();
        }
    
        @Override
        public Account findById(Integer id) {
            return accountDao.findById(id);
        }
    
        @Override
        public void save(Account account) {
            accountDao.save(account);
        }
    
        @Override
        public void update(Account account) {
            accountDao.update(account);
        }
    
        @Override
        public void delete(Integer id) {
            accountDao.delete(id);
        }
    
        @Override
        public void transfer(String sourceName, String targetName, Float money) {
    
            Account source = accountDao.findByName(sourceName);    // 根据名称查询转出账户
            Account target = accountDao.findByName(targetName);    // 根据名称查询转入账户
    
            source.setMoney(source.getMoney() - money);            // 转出账户减钱
            target.setMoney(target.getMoney() + money);            // 转入账户加钱
    
            accountDao.update(source);                             // 更新转出账户
            accountDao.update(target);                             // 更新转入账户
    
        }
    
    }
    

    第三步: resources 目录下新建 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">
    
        <!-- 配置 Service -->
        <bean id="accountService" class="club.guoshizhan.service.impl.AccountServiceImpl">
            <!-- 注入 dao -->
            <property name="accountDao" ref="accountDao"></property>
        </bean>
    
        <!-- 配置 Dao 对象 -->
        <bean id="accountDao" class="club.guoshizhan.dao.impl.AccountDaoImpl">
            <!-- 注入 QueryRunner -->
            <property name="runner" ref="runner"></property>
        </bean>
    
        <!--配置 QueryRunner -->
        <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
            <!-- 注入数据源 -->
            <constructor-arg name="ds" ref="dataSource"></constructor-arg>
        </bean>
    
        <!-- 配置数据源 -->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <!-- 连接数据库的必备信息 -->
            <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"></property>
            <property name="user" value="root"></property>
            <property name="password" value="root"></property>
        </bean>
    
    </beans>
    

    第四步: test 目录下新建 AccountServiceTest 测试类,包名根据 package 语句自行创建。 代码如下:

    package club.guoshizhan.test;
    
    import club.guoshizhan.domain.Account;
    import club.guoshizhan.service.IAccountService;
    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;
    
    import java.util.List;
    
    /**
     * 使用 Junit 单元测试:测试我们的配置
     */
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = "classpath:bean.xml")
    public class AccountServiceTest {
    
        @Autowired
        private IAccountService accountService;
    
        @Test
        public void transferTest() {
            accountService.transfer("Jack", "Lucy", 100F);
            List<Account> accounts = accountService.findAll();
            for (Account account : accounts) {
                System.out.println(account);
            }
        }
    
    }
    

    第五步: 执行 transferTest 方法,然后查看结果。 结果如下:

    九月 08, 2020 10:42:04 下午 org.springframework.test.context.support.AbstractTestContextBootstrapper getDefaultTestExecutionListenerClassNames
    信息: Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener]
    九月 08, 2020 10:42:04 下午 org.springframework.test.context.support.AbstractTestContextBootstrapper getTestExecutionListeners
    信息: Using TestExecutionListeners: [org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@d25987, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@101b7af, org.springframework.test.context.support.DirtiesContextTestExecutionListener@3884b2, org.springframework.test.context.event.EventPublishingTestExecutionListener@1275dab]
    九月 08, 2020 10:42:05 下午 com.mchange.v2.log.MLog 
    信息: MLog clients using java 1.4+ standard logging.
    九月 08, 2020 10:42:05 下午 com.mchange.v2.c3p0.C3P0Registry 
    信息: Initializing c3p0-0.9.5.5 [built 11-December-2019 22:18:33 -0800; debug? true; trace: 10]
    九月 08, 2020 10:42:06 下午 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource 
    信息: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> 1hgeby9acukfaofbdzec9|90b489, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceSynchronousCheckins -> false, forceUseNamedDriverClass -> false, identityToken -> 1hgeby9acukfaofbdzec9|90b489, idleConnectionTestPeriod -> 0, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/spring, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 15, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 3, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ]
    Account{id=1, name='Jack', money=900.0}
    Account{id=2, name='Lucy', money=1100.0}
    Account{id=3, name='Lisa', money=1000.0}
    
    Process finished with exit code 0
    

    由上述结果可知:Jack 用户少了 100 元,所以他的余额为 900 元;而 Lucy 用户多了 100 元,所以她的余额为 1100 元。 这结果没毛病,那是因为程序没有发生异常的情况。如果程序有异常,那结果就不一样了。往下看!!!


    我们给 AccountServiceImpl 类中的 transfer 方法加一行代码。 详情如下:

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

    再次执行 transferTest 方法,然后查看结果发现,程序报错了。 结果如下:

    信息: MLog clients using java 1.4+ standard logging.
    九月 08, 2020 11:22:38 下午 com.mchange.v2.c3p0.C3P0Registry 
    信息: Initializing c3p0-0.9.5.5 [built 11-December-2019 22:18:33 -0800; debug? true; trace: 10]
    九月 08, 2020 11:22:38 下午 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource 
    信息: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> 1hgeby9aculvfii1d1svyq|90b489, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceSynchronousCheckins -> false, forceUseNamedDriverClass -> false, identityToken -> 1hgeby9aculvfii1d1svyq|90b489, idleConnectionTestPeriod -> 0, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/spring, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 15, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 3, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ]
    
    java.lang.ArithmeticException: / by zero
    
        at club.guoshizhan.service.impl.AccountServiceImpl.transfer(AccountServiceImpl.java:54)
        at club.guoshizhan.test.AccountServiceTest.transfer(AccountServiceTest.java:25)
        ...
    

    程序报错了没关系,调嘛!但是呢,你报错就报错嘛,可是 Jack 用户少了 100 元,而 Lucy 却没有收到 Jack 的 100 元,这就难受了。 那么这是什么原因造成的呢?有人可能会说没有事务,如果没有事务的话,增删改的方法就不起作用。显然不是这个原因。 那么接下来就去找出原因,并解决这个问题。


    我们通过代码发现:每执行一次操作,就会有一个新的连接对象。 这个就是原因。详情如下图:

    那我们如何解决呢?第一步: 新建两个工具类。 代码如下:

    package club.guoshizhan.utils;
    
    import javax.sql.DataSource;
    import java.sql.Connection;
    
    /**
     * 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定
     */
    public class ConnectionUtils {
    
        private ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
        private DataSource dataSource;
    
        public void setDataSource(DataSource dataSource) {
            this.dataSource = dataSource;
        }
    
        /**
         * 获取当前线程上的连接
         */
        public Connection getThreadConnection() {
            try {
                // 1、先从 ThreadLocal 上获取
                Connection conn = threadLocal.get();
                // 2、判断当前线程上是否有连接
                if (conn == null) {
                    // 3、从数据源中获取一个连接,并且存入 ThreadLocal 中
                    conn = dataSource.getConnection();
                    threadLocal.set(conn);
                }
                // 4、返回当前线程上的连接
                return conn;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        /**
         * 把连接和线程解绑
         */
        public void removeConnection() {
            threadLocal.remove();
        }
    }
    
    package club.guoshizhan.utils;
    
    /**
     * 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
     */
    public class TransactionManager {
    
        private ConnectionUtils connectionUtils;
    
        public void setConnectionUtils(ConnectionUtils connectionUtils) {
            this.connectionUtils = connectionUtils;
        }
    
        /**
         * 开启事务
         */
        public void beginTransaction() {
            try {
                connectionUtils.getThreadConnection().setAutoCommit(false);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 提交事务
         */
        public void commit() {
            try {
                connectionUtils.getThreadConnection().commit();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 回滚事务
         */
        public void rollback() {
            try {
                connectionUtils.getThreadConnection().rollback();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    
        /**
         * 释放连接
         */
        public void release() {
            try {
                connectionUtils.getThreadConnection().close();    // 还回连接池中
                connectionUtils.removeConnection();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    }
    

    第二步: 修改 AccountServiceImpl 类。 代码如下:

    package club.guoshizhan.service.impl;
    
    import club.guoshizhan.dao.IAccountDao;
    import club.guoshizhan.domain.Account;
    import club.guoshizhan.service.IAccountService;
    import club.guoshizhan.utils.TransactionManager;
    
    import java.util.List;
    
    /**
     * 账号的业务层实现类
     */
    public class AccountServiceImpl implements IAccountService {
    
        private IAccountDao accountDao;
        private TransactionManager txManager;
    
        public void setTxManager(TransactionManager txManager) {
            this.txManager = txManager;
        }
    
        public void setAccountDao(IAccountDao accountDao) {
            this.accountDao = accountDao;
        }
    
        @Override
        public List<Account> findAll() {
            try {
                txManager.beginTransaction();    // 开启事务
                List<Account> accounts = accountDao.findAll();
                txManager.commit();     // 提交事务
                return accounts;        // 返回结果
            }catch (Exception e){
                txManager.rollback();   // 回滚操作
                throw new RuntimeException(e);
            }finally {
                txManager.release();    // 释放连接
            }
        }
    
        @Override
        public Account findById(Integer id) {
            try {
                txManager.beginTransaction();    // 开启事务
                Account accounts = accountDao.findById(id);
                txManager.commit();     // 提交事务
                return accounts;        // 返回结果
            }catch (Exception e){
                txManager.rollback();   // 回滚操作
                throw new RuntimeException(e);
            }finally {
                txManager.release();    // 释放连接
            }
        }
    
        @Override
        public void save(Account account) {
            try {
                txManager.beginTransaction();    // 开启事务
                accountDao.save(account);
                txManager.commit();     // 提交事务
            }catch (Exception e){
                txManager.rollback();   // 回滚操作
                throw new RuntimeException(e);
            }finally {
                txManager.release();    // 释放连接
            }
        }
    
        @Override
        public void update(Account account) {
            try {
                txManager.beginTransaction();    // 开启事务
                accountDao.update(account);
                txManager.commit();     // 提交事务
            }catch (Exception e){
                txManager.rollback();   // 回滚操作
                throw new RuntimeException(e);
            }finally {
                txManager.release();    // 释放连接
            }
        }
    
        @Override
        public void delete(Integer id) {
            try {
                txManager.beginTransaction();    // 开启事务
                accountDao.delete(id);
                txManager.commit();     // 提交事务
            }catch (Exception e){
                txManager.rollback();   // 回滚操作
                throw new RuntimeException(e);
            }finally {
                txManager.release();    // 释放连接
            }
        }
    
        @Override
        public void transfer(String sourceName, String targetName, Float money) {
    
            try {
                txManager.beginTransaction();    // 1、开启事务,即把事务的自动提交改为 false
    
                // 2、执行如下的一系列操作,如果出现异常,则事务回滚,就不会发生原来的问题了
                Account source = accountDao.findByName(sourceName);    // 根据名称查询转出账户
                Account target = accountDao.findByName(targetName);    // 根据名称查询转入账户
    
                source.setMoney(source.getMoney() - money);            // 转出账户减钱
                target.setMoney(target.getMoney() + money);            // 转入账户加钱
    
                accountDao.update(source);                             // 更新转出账户
                int i = 10 / 0;
                accountDao.update(target);                             // 更新转入账户
    
                txManager.commit();     // 3、提交事务
            }catch (Exception e){
                txManager.rollback();   // 4、回滚操作
                throw new RuntimeException(e);
            }finally {
                txManager.release();    // 5、释放连接
            }
    
        }
    
    }
    

    第三步: 修改 AccountDaoImpl 类。 代码如下:

    package club.guoshizhan.dao.impl;
    
    import club.guoshizhan.dao.IAccountDao;
    import club.guoshizhan.domain.Account;
    import club.guoshizhan.utils.ConnectionUtils;
    import org.apache.commons.dbutils.QueryRunner;
    import org.apache.commons.dbutils.handlers.BeanHandler;
    import org.apache.commons.dbutils.handlers.BeanListHandler;
    
    import java.util.List;
    
    /**
     * 账户持久层实现类
     */
    public class AccountDaoImpl implements IAccountDao {
    
        private QueryRunner runner;
        private ConnectionUtils connectionUtils;
    
        public void setConnectionUtils(ConnectionUtils connectionUtils) {
            this.connectionUtils = connectionUtils;
        }
    
        public void setRunner(QueryRunner runner) {
            this.runner = runner;
        }
    
        @Override
        public List<Account> findAll() {
            try {
                return runner.query(connectionUtils.getThreadConnection(), "select * from account", new BeanListHandler<Account>(Account.class));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public Account findById(Integer id) {
            try {
                return runner.query(connectionUtils.getThreadConnection(), "select * from account where id = ?", new BeanHandler<Account>(Account.class), id);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public void save(Account account) {
            try {
                runner.update(connectionUtils.getThreadConnection(), "insert into account(name,money) values (?,?)", account.getName(), account.getMoney());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public void update(Account account) {
            try {
                runner.update(connectionUtils.getThreadConnection(), "update account set name=?,money=? where id=?", account.getName(), account.getMoney(), account.getId());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public void delete(Integer id) {
            try {
                runner.update(connectionUtils.getThreadConnection(), "delete from account where id=?", id);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public Account findByName(String name) {
            try {
                List<Account> accounts = runner.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 (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
    }
    

    第四步: 修改 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">
    
        <!-- 配置 Service -->
        <bean id="accountService" class="club.guoshizhan.service.impl.AccountServiceImpl">
            <!-- 注入 dao -->
            <property name="accountDao" ref="accountDao"></property>
            <!-- 注入事务管理器 -->
            <property name="txManager" ref="txManager"></property>
        </bean>
    
        <!-- 配置 Dao 对象 -->
        <bean id="accountDao" class="club.guoshizhan.dao.impl.AccountDaoImpl">
            <!-- 注入 QueryRunner -->
            <property name="runner" ref="runner"></property>
            <!-- 注入 ConnectionUtils -->
            <property name="connectionUtils" ref="connectionUtils"></property>
        </bean>
    
        <!--配置 QueryRunner -->
        <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
    <!--        &lt;!&ndash; 注入数据源 &ndash;&gt;
            <constructor-arg name="ds" ref="dataSource"></constructor-arg>-->
        </bean>
    
        <!-- 配置数据源 -->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <!-- 连接数据库的必备信息 -->
            <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"></property>
            <property name="user" value="root"></property>
            <property name="password" value="root"></property>
        </bean>
    
        <!-- 配置数据库连接的工具类 ConnectionUtils -->
        <bean id="connectionUtils" class="club.guoshizhan.utils.ConnectionUtils">
            <!--  注入数据源 -->
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    
        <!-- 配置事务管理器 -->
        <bean id="txManager" class="club.guoshizhan.utils.TransactionManager">
            <!-- 注入 ConnectionUtils -->
            <property name="connectionUtils" ref="connectionUtils"></property>
        </bean>
    
    </beans>
    

    第五步: 执行测试类中的 transferTest 方法。 结果如下:

    九月 09, 2020 2:14:15 下午 org.springframework.test.context.support.AbstractTestContextBootstrapper getDefaultTestExecutionListenerClassNames
    信息: Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener, org.springframework.test.context.event.EventPublishingTestExecutionListener]
    九月 09, 2020 2:14:15 下午 org.springframework.test.context.support.AbstractTestContextBootstrapper getTestExecutionListeners
    信息: Using TestExecutionListeners: [org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@d25987, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@101b7af, org.springframework.test.context.support.DirtiesContextTestExecutionListener@3884b2, org.springframework.test.context.event.EventPublishingTestExecutionListener@1275dab]
    九月 09, 2020 2:14:16 下午 com.mchange.v2.log.MLog 
    信息: MLog clients using java 1.4+ standard logging.
    九月 09, 2020 2:14:17 下午 com.mchange.v2.c3p0.C3P0Registry 
    信息: Initializing c3p0-0.9.5.5 [built 11-December-2019 22:18:33 -0800; debug? true; trace: 10]
    九月 09, 2020 2:14:17 下午 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource 
    信息: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, contextClassLoaderSource -> caller, dataSourceName -> 1hgeby9acvhq3mgqa4tsv|15d6a01, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, extensions -> {}, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, forceSynchronousCheckins -> false, forceUseNamedDriverClass -> false, identityToken -> 1hgeby9acvhq3mgqa4tsv|15d6a01, idleConnectionTestPeriod -> 0, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/spring, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 15, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 3, numHelperThreads -> 3, preferredTestQuery -> null, privilegeSpawnedThreads -> false, properties -> {user=******, password=******}, propertyCycle -> 0, statementCacheNumDeferredCloseThreads -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, userOverrides -> {}, usesTraditionalReflectiveProxies -> false ]
    
    java.lang.RuntimeException: java.lang.ArithmeticException: / by zero
    
        at club.guoshizhan.service.impl.AccountServiceImpl.transfer(AccountServiceImpl.java:118)
    

    由上述结果可知:程序报错了。但是,我们去数据库查一下,发现 Jack 用户没有少钱,Lucy 也没有多钱,这说明事务控制住了。那么转账小案例的操作到此就结束了!!! 接下来介绍一下 动态代理 相关的知识,用于解决上面还存在的一些问题(别以为事务解决了就没有问题了)。


    动态代理实现事务控制

    基于接口的动态代理

    新建如下三个类,然后执行 Client 中的 main 方法即可。 代码如下:

    package club.guoshizhan.proxy;
    
    /**
     * 对生产厂家要求的接口
     */
    public interface IProducer {
    
        // 销售产品
        public void saleProduct(float money);
    
        // 售后服务
        public void afterService(float money);
    
    }
    
    package club.guoshizhan.proxy;
    
    /**
     * 一个生产者
     */
    public class Producer implements IProducer {
    
        // 销售产品
        public void saleProduct(float money) {
            System.out.println("销售产品,并拿到钱:" + money);
        }
    
        // 售后服务
        public void afterService(float money) {
            System.out.println("提供售后服务,并拿到钱:" + money);
        }
    
    }
    
    package club.guoshizhan.proxy;
    
    import java.lang.reflect.InvocationHandler;
    import java.lang.reflect.Method;
    import java.lang.reflect.Proxy;
    
    /**
     * 模拟一个消费者
     */
    public class Client {
        /**
         * 动态代理特点:字节码随用随创建,随用随加载。  动态代理作用:不修改源码的基础上对方法增强
         * 动态代理分类:1、基于接口的动态代理;     2、基于子类的动态代理
         * 基于接口的动态代理:涉及的类:Proxy  提供者:JD K官方
         * 如何创建代理对象:使用 Proxy 类中的 newProxyInstance 方法
         * 创建代理对象的要求:被代理类最少实现一个接口,如果没有则不能使用
         *  newProxyInstance 方法的参数:
         *      ClassLoader:类加载器,它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
         *      Class[]:字节码数组,它是用于让代理对象和被代理对象有相同方法。固定写法。
         *      InvocationHandler:用于提供增强的代码,它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。此接口的实现类都是谁用谁写。
         */
        public static void main(String[] args) {
            final Producer producer = new Producer();
            IProducer proxyProducer = (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;
                            // 获取方法执行的参数
                            Float money = (Float) args[0];
                            // 判断当前方法是不是销售
                            if ("saleProduct".equals(method.getName())) {
                                returnValue = method.invoke(producer, money * 0.8f);
                            }
                            return returnValue;
                        }
                    });
            proxyProducer.saleProduct(10000f);
        }
    }
    

    TIPS: 基于接口的动态代理 到此结束!!!

    基于子类的动态代理

    新建如下两个类,然后执行 Client 中的 main 方法即可。(注意:这些类和上面的类不在同一个包) 代码如下:

    package club.guoshizhan.cglib;
    
    /**
     * 一个生产者
     */
    public class Producer {
    
        // 销售产品
        public void saleProduct(float money) {
            System.out.println("销售产品,并拿到钱:" + money);
        }
    
        // 售后服务
        public void afterService(float money) {
            System.out.println("提供售后服务,并拿到钱:" + money);
        }
    
    }
    
    package club.guoshizhan.cglib;
    
    import org.springframework.cglib.proxy.Enhancer;
    import org.springframework.cglib.proxy.MethodInterceptor;
    import org.springframework.cglib.proxy.MethodProxy;
    
    import java.lang.reflect.Method;
    
    /**
     * 模拟一个消费者
     */
    public class Client {
        /**
         *  基于子类的动态代理:涉及的类:Enhancer    提供者:第三方 cglib 库
         *  如何创建代理对象:使用 Enhancer 类中的 create 方法
         *  创建代理对象的要求:被代理类不能是最终类,否则没有办法创建子类
         *  create 方法的参数:
         *      Class:字节码它是用于指定被代理对象的字节码。
         *      Callback:用于提供增强的代码,它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
         *      此接口的实现类都是谁用谁写。我们一般写的都是该接口的子接口实现类:MethodInterceptor
         */
        public static void main(String[] args) {
            final Producer producer = new Producer();
            Producer cglibProducer = (Producer) Enhancer.create(producer.getClass(), new MethodInterceptor() {
                // 执行被代理对象的任何方法都会经过该方法,方法里的三个参数和基于接口的动态代理中 invoke 方法的参数是一样的
                @Override
                public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                    // 提供增强的代码
                    Object returnValue = null;
                    // 获取方法执行的参数
                    Float money = (Float) args[0];
                    // 判断当前方法是不是销售
                    if ("saleProduct".equals(method.getName())) {
                        returnValue = method.invoke(producer, money * 0.8f);
                    }
                    return returnValue;
                }
            });
            cglibProducer.saleProduct(12000f);
        }
    }
    

    TIPS: 基于子类的动态代理 到此结束!!!

    AOP 的概念及使用

    在软件业,AOP 为 Aspect Oriented Programming 的缩写,意为:面向切面编程 ,通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP 是 OOP 的延续,是软件开发中的一个热点,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范型。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。


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


    接下来介绍一下 AOP 相关的术语及细节,方便以后查看文档时能够更好的理解。 相关术语如下:

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

    基于 XML 的 AOP 配置

    第一步: 新建 Maven 工程,完善包结构。 然后导入如下依赖:

    <packaging>jar</packaging>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>
        <!-- aspectjweaver 这个依赖用于解析切入点表达式 -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
    </dependencies>
    

    第二步: 创建如下各个接口和类,根据 package 语句将类或接口放到对应的包中。 接口和类的代码如下:

    package club.guoshizhan.service;
    
    /**
     * 账号的业务层接口
     */
    public interface IAccountService {
    
        // 模拟保存账户
        void save();
    
        // 模拟更新账户
        void update(int id);
    
        // 模拟删除账户
        int delete();
    
    }
    
    package club.guoshizhan.service.impl;
    
    import club.guoshizhan.service.IAccountService;
    
    /**
     * 账号的业务层实现类
     */
    public class AccountServiceImpl implements IAccountService {
    
        @Override
        public void save() {
            System.out.println("执行了保存操作……");
        }
    
        @Override
        public void update(int id) {
            System.out.println("执行了更新操作……");
        }
    
        @Override
        public int delete() {
            System.out.println("执行了删除操作……");
            return 0;
        }
    
    }
    
    package club.guoshizhan.utils;
    
    /**
     * 用于记录日志的工具类,它里面提供了公共的代码
     */
    public class Logger {
    
        // 用于打印日志:计划让其在切入点方法执行之前执行(切入点方法就是业务层方法)
        public void printLog() {
            System.out.println("Logger 类中的 printLog 方法开始记录日志了……");
        }
    
    }
    

    第三步: resources 目录下新建 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"
           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">
    
        <!-- spring 中基于 XML 的 AOP 配置步骤:
            1、把通知 Bean 也交给 spring 来管理
            2、使用 <aop:config> 标签表明开始 AOP 的配置
            3、使用 <aop:aspect> 标签表明配置切面
                    id  属性:是给切面提供一个唯一标识
                    ref 属性:是指定通知类 bean 的 Id 。
            4、在 <aop:aspect> 标签的内部使用对应标签来配置通知的类型
               我们现在示例是让 printLog 方法在切入点方法执行之前执行:所以是前置通知
               <aop:before> 表示配置前置通知,其属性如下:
                    method   属性:用于指定 Logger 类中哪个方法是前置通知
                    pointcut 属性:用于指定切入点表达式,该表达式的含义指的是对业务层中哪些方法增强
        -->
        
        <!-- 切入点表达式的写法:
             关键字:execution(表达式)
             表达式:访问修饰符  返回值  包名.包名.包名...类名.方法名(参数列表)
             标准的表达式写法:public void club.guoshizhan.service.impl.AccountServiceImpl.save()
             访问修饰符可以省略:void club.guoshizhan.service.impl.AccountServiceImpl.save()
             返回值可以使用通配符,表示任意返回值: * club.guoshizhan.service.impl.AccountServiceImpl.save()
             包名可以使用通配符,表示任意包。但是有几级包,就需要写几个 *.     例如: * *.*.*.*.AccountServiceImpl.save())
             包名可以使用 .. 表示当前包及其子包:   例如: * *..AccountServiceImpl.save()
             类名和方法名都可以使用 * 来实现通配:  例如: * *..*.*()
    
             参数列表:
             可以直接写数据类型:
                 基本类型直接写名称           int
                 引用类型写包名.类名的方式   java.lang.String
                 可以使用通配符表示任意类型,但是必须有参数
                 可以使用 .. 表示有无参数均可,有参数可以是任意类型
             全通配写法: * *..*.*(..)
             实际开发中切入点表达式的通常写法:切到业务层实现类下的所有方法: * club.guoshizhan.service.impl.*.*(..)
        -->
    
        <!-- 配置 spring 的 IoC ,把 service 对象配置进来 -->
        <bean id="accountService" class="club.guoshizhan.service.impl.AccountServiceImpl"></bean>
    
        <!-- 配置 Logger 类 -->
        <bean id="logger" class="club.guoshizhan.utils.Logger"></bean>
    
        <!-- 配置 AOP -->
        <aop:config>
            <!-- 配置切面 -->
            <aop:aspect id="logAdvice" ref="logger">
                <!-- 配置通知的类型,并且建立通知方法和切入点方法的关联 -->
                <aop:before method="printLog" pointcut="execution(* club.guoshizhan.service.impl.*.*(..))"></aop:before>
            </aop:aspect>
        </aop:config>
    
    </beans>
    

    第四步: test 目录下新建测试类,包名根据 package 语句自行创建。 代码如下:

    package club.guoshizhan.test;
    
    import club.guoshizhan.service.IAccountService;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    /**
     * 测试 AOP 的配置
     */
    public class AOPTest {
        public static void main(String[] args) {
            // 1、获取容器
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
            // 2、获取对象
            IAccountService as = (IAccountService)ac.getBean("accountService");
            // 3、执行方法
            as.save();
            as.update(1);
            as.delete();
        }
    }
    

    执行 AOPTest 测试类,结果如下:

    Logger 类中的 printLog 方法开始记录日志了……
    执行了保存操作……
    Logger 类中的 printLog 方法开始记录日志了……
    执行了更新操作……
    Logger 类中的 printLog 方法开始记录日志了……
    执行了删除操作……
    
    Process finished with exit code 0
    

    由上述结果可知:我们已经成功实现了 AOP 的配置 。这部分核心内容就是如何去配置,以及 切入点表达式 的各种写法。 这个部分内容不多,重点关注 如何配置 以及 切入点表达式 即可。接下来介绍 四种常用的通知类型 。接着往下看!!!


    四种常用的通知类型

    第一步: 新建 Maven 工程,完善包结构。 然后导入如下依赖:

    <packaging>jar</packaging>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>
        <!-- aspectjweaver 这个依赖用于解析切入点表达式 -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
    </dependencies>
    

    第二步: 创建如下各个接口和类,根据 package 语句将类或接口放到对应的包中。 接口和类的代码如下:

    package club.guoshizhan.service;
    
    /**
     * 账号的业务层接口
     */
    public interface IAccountService {
    
        // 模拟保存账户
        void save();
    
    }
    
    package club.guoshizhan.service.impl;
    
    import club.guoshizhan.service.IAccountService;
    
    /**
     * 账号的业务层实现类
     */
    public class AccountServiceImpl implements IAccountService {
    
        @Override
        public void save() {
            System.out.println("执行了保存操作……");
        }
    
    }
    
    package club.guoshizhan.utils;
    
    /**
     * 用于记录日志的工具类,它里面提供了公共的代码
     */
    public class Logger {
    
        // 前置通知
        public void beforePrintLog() {
            System.out.println("前置通知:Logger 类中的 beforePrintLog 方法开始记录日志了……");
        }
    
        // 后置通知
        public void afterPrintLog() {
            System.out.println("后置通知:Logger 类中的 afterPrintLog 方法开始记录日志了……");
        }
    
        // 异常通知
        public void exceptionPrintLog() {
            System.out.println("异常通知:Logger 类中的 exceptionPrintLog 方法开始记录日志了……");
        }
    
        // 最终通知
        public void finalPrintLog() {
            System.out.println("最终通知:Logger 类中的 finalPrintLog 方法开始记录日志了……");
        }
    
    }
    

    第三步: resources 目录下新建 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"
           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">
    
    
        <!-- 配置 spring 的 IoC ,把 service 对象配置进来 -->
        <bean id="accountService" class="club.guoshizhan.service.impl.AccountServiceImpl"></bean>
    
        <!-- 配置 Logger 类 -->
        <bean id="logger" class="club.guoshizhan.utils.Logger"></bean>
    
        <!-- 配置 AOP -->
        <aop:config>
            <!-- 配置切面 -->
            <aop:aspect id="logAdvice" ref="logger">
    
                <!-- 配置前置通知:在切入点方法执行之前执行 -->
                <aop:before method="beforePrintLog" pointcut="execution(* club.guoshizhan.service.impl.*.*(..))"></aop:before>
    
                <!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个 -->
                <aop:after-returning method="afterPrintLog" pointcut="execution(* club.guoshizhan.service.impl.*.*(..))"></aop:after-returning>
    
                <!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个 -->
                <aop:after-throwing method="exceptionPrintLog" pointcut="execution(* club.guoshizhan.service.impl.*.*(..))"></aop:after-throwing>
    
                <!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行 pointcut-ref="pt1" -->
                <aop:after method="finalPrintLog" pointcut="execution(* club.guoshizhan.service.impl.*.*(..))"></aop:after>
    
            </aop:aspect>
        </aop:config>
    
    </beans>
    

    第四步: test 目录下新建测试类,包名根据 package 语句自行创建。 代码如下:

    package club.guoshizhan.test;
    
    import club.guoshizhan.service.IAccountService;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    /**
     * 测试 AOP 的配置
     */
    public class AOPTest {
        public static void main(String[] args) {
            // 1、获取容器
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
            // 2、获取对象
            IAccountService as = (IAccountService)ac.getBean("accountService");
            // 3、执行方法
            as.save();
        }
    }
    

    执行 AOPTest 测试类,结果如下:

    前置通知:Logger 类中的 beforePrintLog 方法开始记录日志了……
    执行了保存操作……
    后置通知:Logger 类中的 afterPrintLog 方法开始记录日志了……
    最终通知:Logger 类中的 finalPrintLog 方法开始记录日志了……
    
    Process finished with exit code 0
    

    由上述结果可知:我们已经成功实现了 四种常用的通知类型的配置 但是还有点小问题:在配置文件中配置各种通知时,里面有这么一个属性: apointcut="execution(* club.guoshizhan.service.impl.*.*(..))"我觉得这太繁琐了,能不能优化一下呢?安排!!!(把配置文件按照如下修改,再运行测试方法即可)


    <!-- 配置 AOP -->
    <aop:config>
        <!-- 配置切面 -->
        <aop:aspect id="logAdvice" ref="logger">
    
            <!-- 配置前置通知:在切入点方法执行之前执行 -->
            <aop:before method="beforePrintLog" pointcut-ref="springAdvice"></aop:before>
    
            <!-- 配置后置通知:在切入点方法正常执行之后值。它和异常通知永远只能执行一个 -->
            <aop:after-returning method="afterPrintLog" pointcut-ref="springAdvice"></aop:after-returning>
    
            <!-- 配置异常通知:在切入点方法执行产生异常之后执行。它和后置通知永远只能执行一个 -->
            <aop:after-throwing method="exceptionPrintLog" pointcut-ref="springAdvice"></aop:after-throwing>
    
            <!-- 配置最终通知:无论切入点方法是否正常执行它都会在其后面执行 -->
            <aop:after method="finalPrintLog" pointcut-ref="springAdvice"></aop:after>
    
            <!-- 配置切入点表达式:id 属性用于指定表达式的唯一标识,expression 属性用于指定表达式内容
                 此标签写在 <aop:aspect> 标签内部,表示它只能在当前的切面使用。
                 它还可以写在 <aop:aspect> 标签外面,此时就变成了所有切面可用。
                 当放在 <aop:aspect> 标签外面外面时,它必须位于所有 <aop:aspect> 标签的上方,否则会报错,这个导入的约束造成的问题 -->
            <aop:pointcut id="springAdvice" expression="execution(* club.guoshizhan.service.impl.*.*(..))"></aop:pointcut>
    
        </aop:aspect>
    </aop:config>
    

    TIPS: 修改完配置之后,运行测试类即可。到此为止,四种常用的通知类型 到此结束!!!(接下来介绍 环绕通知

    Spring 中的环绕通知

    第一步: Logger 类中加入如下方法。 代码如下:

    public void aroundPrintLog() {
        System.out.println("Logger 类中的 aroundPrintLog 方法开始记录日志了……");
    }
    

    第二步: bean.xml 配置文件中,把配置切面部分改为如下配置。 代码如下:

    <!-- 配置切面 -->
    <aop:aspect id="logAdvice" ref="logger">
        <aop:pointcut id="springAdvice" expression="execution(* club.guoshizhan.service.impl.*.*(..))"></aop:pointcut>
        <!-- 配置环绕通知:详细的注释请查阅 Logger 类 -->
        <aop:around method="aroundPrintLog" pointcut-ref="springAdvice"></aop:around>
    </aop:aspect>
    

    第三步: 执行测试类。 结果如下:

    Logger 类中的 aroundPrintLog 方法开始记录日志了……
    
    Process finished with exit code 0
    

    由上面结果发现:save 方法中的语句并没有被执行,这是为什么呢? 接下来就解决这个问题。


    把上面的 aroundPrintLog 方法修改为下面代码(问题原因和解决方式写于注释之中):

     /**
     * 环绕通知相关介绍:
     * 问题:当我们配置了环绕通知之后,切入点方法没有执行,而环绕通知方法执行了。
     * 分析:通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而当前的代码中没有。
     * 解决:Spring 框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法 proceed(),此方法就相当于明确调用切入点方法。
     *     该接口可以作为环绕通知的方法参数,在程序执行时,Spring 框架会为我们提供该接口的实现类供我们使用。
     * Spring 中的环绕通知:它是 Spring 框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
     */
    public Object aroundPrintLog(ProceedingJoinPoint pjp) {
        Object rtValue = null;
        try {
            Object[] args = pjp.getArgs();  // 得到方法执行所需的参数
            System.out.println("Logger 类中的 aroundPrintLog 方法开始记录日志了……前置通知");
            rtValue = pjp.proceed(args);    // 明确调用业务层方法(切入点方法)
            System.out.println("Logger 类中的 aroundPrintLog 方法开始记录日志了……后置通知");
            return rtValue;
        } catch (Throwable t) {             // 必须写 Throwable ,Exception 拦不住它
            System.out.println("Logger 类中的 aroundPrintLog 方法开始记录日志了……异常通知");
            throw new RuntimeException(t);
        } finally {
            System.out.println("Logger 类中的 aroundPrintLog 方法开始记录日志了……最终通知");
        }
    }
    

    修改完成之后,我们去测试类进行测试。 结果如下:

    Logger 类中的 aroundPrintLog 方法开始记录日志了……前置通知
    执行了保存操作……
    Logger 类中的 aroundPrintLog 方法开始记录日志了……后置通知
    Logger 类中的 aroundPrintLog 方法开始记录日志了……最终通知
    
    Process finished with exit code 0
    

    TIPS: 运行结果非常 OK ,完全没有问题。 那么 环绕通知 就到此结束了!!!接下来介绍 基于注解的 AOP 配置。

    基于注解的 AOP 配置

    第一步: 新建 Maven 工程,完善包结构。 然后导入如下依赖:

    <packaging>jar</packaging>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>
        <!-- aspectjweaver 这个依赖用于解析切入点表达式 -->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
    </dependencies>
    

    第二步: 创建如下各个接口和类(注解 AOP 就配置在下面的类中)。 接口和类的代码如下:

    package club.guoshizhan.service;
    
    /**
     * 账号的业务层接口
     */
    public interface IAccountService {
    
        // 模拟保存账户
        void save();
    
    }
    
    package club.guoshizhan.service.impl;
    
    import club.guoshizhan.service.IAccountService;
    import org.springframework.stereotype.Service;
    
    /**
     * 账号的业务层实现类
     */
    @Service("accountService")
    public class AccountServiceImpl implements IAccountService {
    
        @Override
        public void save() {
            System.out.println("执行了保存操作……");
        }
    
    }
    
    package club.guoshizhan.utils;
    
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    /**
     * 用于记录日志的工具类,它里面提供了公共的代码
     */
    @Component("logger")
    @Aspect    // 表示当前类是一个切面
    public class Logger {
    
        @Pointcut("execution(* club.guoshizhan.service.impl.*.*(..))")
        private void springAdvice() {}
    
        // 前置通知
        @Before("springAdvice()")    // 注意:springAdvice() 方法的括号一定要加上,否则报错
        public void beforePrintLog() {
            System.out.println("前置通知:Logger 类中的 beforePrintLog 方法开始记录日志了……");
        }
    
        // 后置通知
        @AfterReturning("springAdvice()")
        public void afterPrintLog() {
            System.out.println("后置通知:Logger 类中的 afterPrintLog 方法开始记录日志了……");
        }
    
        // 异常通知
        @AfterThrowing("springAdvice()")
        public void exceptionPrintLog() {
            System.out.println("异常通知:Logger 类中的 exceptionPrintLog 方法开始记录日志了……");
        }
    
        // 最终通知
        @After("springAdvice()")
        public void finalPrintLog() {
            System.out.println("最终通知:Logger 类中的 finalPrintLog 方法开始记录日志了……");
        }
    
    }
    

    第三步: resources 目录下新建 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 创建容器时要扫描的包 -->
        <context:component-scan base-package="club.guoshizhan"></context:component-scan>
    
        <!-- 配置 Spring 开启注解 AOP 的支持,写上了才支持注解配置,没写的话,即使使用了注解配置也不生效 -->
        <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    
    </beans>
    

    第四步: test 目录下新建测试类,包名根据 package 语句自行创建。 代码如下:

    package club.guoshizhan.test;
    
    import club.guoshizhan.service.IAccountService;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    /**
     * 测试 AOP 的配置
     */
    public class AOPTest {
        public static void main(String[] args) {
            // 1、获取容器
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
            // 2、获取对象
            IAccountService as = (IAccountService)ac.getBean("accountService");
            // 3、执行方法
            as.save();
        }
    }
    

    执行 AOPTest 测试类,结果如下:

    前置通知:Logger 类中的 beforePrintLog 方法开始记录日志了……
    执行了保存操作……
    最终通知:Logger 类中的 finalPrintLog 方法开始记录日志了……
    后置通知:Logger 类中的 afterPrintLog 方法开始记录日志了……
    
    Process finished with exit code 0
    

    由上述结果可知:我们已经成功实现了 基于注解的 AOP 配置 但是还有点小问题:在结果中,最终通知在后置通知的前面,不知道你有没有发现。 这个小问题是存在的,没办法解决,官方的 bug (调用顺序错误)。所以在选择使用注解配置还是 XML 配置时,这个小问题需要考虑一下。 如果使用 环绕通知 ,会出现这个小 bug 吗?接着往下看!!!


    Logger 类修改为如下代码(使用环绕通知):

    package club.guoshizhan.utils;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.stereotype.Component;
    
    /**
     * 用于记录日志的工具类,它里面提供了公共的代码
     */
    @Component("logger")
    @Aspect    // 表示当前类是一个切面
    public class Logger {
    
        @Pointcut("execution(* club.guoshizhan.service.impl.*.*(..))")
        private void springAdvice() {}
    
        /**
         * 环绕通知相关介绍:
         * 问题:当我们配置了环绕通知之后,切入点方法没有执行,而环绕通知方法执行了。
         * 分析:通过对比动态代理中的环绕通知代码,发现动态代理的环绕通知有明确的切入点方法调用,而当前的代码中没有。
         * 解决:Spring 框架为我们提供了一个接口:ProceedingJoinPoint。该接口有一个方法 proceed(),此方法就相当于明确调用切入点方法。
         *     该接口可以作为环绕通知的方法参数,在程序执行时,Spring 框架会为我们提供该接口的实现类供我们使用。
         * Spring 中的环绕通知:它是 Spring 框架为我们提供的一种可以在代码中手动控制增强方法何时执行的方式。
         */
        @Around("springAdvice()")    // 注意:springAdvice() 方法的括号一定要加上,否则报错
        public Object aroundPrintLog(ProceedingJoinPoint pjp) {
            Object rtValue = null;
            try {
                Object[] args = pjp.getArgs();  // 得到方法执行所需的参数
                System.out.println("Logger 类中的 aroundPrintLog 方法开始记录日志了……前置通知");
                rtValue = pjp.proceed(args);    // 明确调用业务层方法(切入点方法)
                System.out.println("Logger 类中的 aroundPrintLog 方法开始记录日志了……后置通知");
                return rtValue;
            } catch (Throwable t) {             // 必须写 Throwable ,Exception 拦不住它
                System.out.println("Logger 类中的 aroundPrintLog 方法开始记录日志了……异常通知");
                throw new RuntimeException(t);
            } finally {
                System.out.println("Logger 类中的 aroundPrintLog 方法开始记录日志了……最终通知");
            }
        }
    
    }
    

    然后执行 AOPTest 测试类,结果如下:

    Logger 类中的 aroundPrintLog 方法开始记录日志了……前置通知
    执行了保存操作……
    Logger 类中的 aroundPrintLog 方法开始记录日志了……后置通知
    Logger 类中的 aroundPrintLog 方法开始记录日志了……最终通知
    
    Process finished with exit code 0
    

    由结果可知: 使用环绕通知不会出现上述的问题(毕竟是我们自己写的代码,自己定义的) 。到此为止,基于注解的 AOP 配置 到此结束!!!


    TIPS: 能实现纯注解配置吗?当然可以。配置如下:

    @Configuration
    @ComponentScan(basePackages="club.guoshizhan")
    @EnableAspectJAutoProxy
    public class SpringConfiguration {}
    

    Spring 中的 JdbcTemplate

    JdbcTemplate 环境搭建

    第一步: 新建 Maven 工程,完善包结构。 然后导入如下依赖:

    <packaging>jar</packaging>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.2.1.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
    </dependencies>
    

    第二步: 创建如下 Account 实体类和 JdbcTemplateTest 测试类。 接口和类的代码如下:

    package club.guoshizhan.domain;
    
    import java.io.Serializable;
    
    /**
     * 账户的实体类
     */
    public class Account implements Serializable {
    
        private Integer id;
        private String name;
        private Float money;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Float getMoney() {
            return money;
        }
    
        public void setMoney(Float money) {
            this.money = money;
        }
    
        @Override
        public String toString() {
            return "Account{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", money=" + money +
                    '}';
        }
    
    }
    
    package club.guoshizhan.jdbctemplate;
    
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.datasource.DriverManagerDataSource;
    
    /**
     * JdbcTemplate 的最基本用法
     */
    public class JdbcTemplateTest {
        public static void main(String[] args) {
    
            // 1、准备数据源:Spring 的内置数据源
            DriverManagerDataSource ds = new DriverManagerDataSource();
            ds.setDriverClassName("com.mysql.jdbc.Driver");
            ds.setUrl("jdbc:mysql://localhost:3306/spring");
            ds.setUsername("root");
            ds.setPassword("root");
    
            // 2、创建 JdbcTemplate 对象
            JdbcTemplate jt = new JdbcTemplate();
    
            // 3、给 jt 设置数据源
            jt.setDataSource(ds);
    
            // 4、执行操作
            jt.execute("insert into account(name, money) values ('Mary', 1000)");
    
        }
    }
    

    TIPS: 执行测试类中的 main 方法,然后去数据库查看一下,发现新加了一条数据。 即上述代码完成了添加操作。那么上述代码有没有问题呢?能否改呢? 上述代码的数据源写死了,应该写在配置文件中。 那就来操作一下吧!!!

    第三步: 新建 dao 接口和实现类。 接口和类的代码如下:

    package club.guoshizhan.dao;
    
    import club.guoshizhan.domain.Account;
    
    /**
     * 账户的持久层接口
     */
    public interface IAccountDao {
    
        // 根据 Id 查询账户
        Account findById(Integer accountId);
    
        // 根据名称查询账户
        Account findByName(String accountName);
    
        // 更新账户
        void update(Account account);
    
    }
    
    package club.guoshizhan.dao.impl;
    
    import club.guoshizhan.dao.IAccountDao;
    import club.guoshizhan.domain.Account;
    import org.springframework.jdbc.core.BeanPropertyRowMapper;
    import org.springframework.jdbc.core.JdbcTemplate;
    
    import java.util.List;
    
    /**
     * 账户的持久层实现类
     */
    public class AccountDaoImpl implements IAccountDao {
    
        private JdbcTemplate jdbcTemplate;
    
        public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
            this.jdbcTemplate = jdbcTemplate;
        }
    
        @Override
        public Account findById(Integer accountId) {
            List<Account> accounts = jdbcTemplate.query("select * from account where id = ?", new BeanPropertyRowMapper<>(Account.class), accountId);
            return accounts.isEmpty() ? null : accounts.get(0);
        }
    
        @Override
        public Account findByName(String accountName) {
            List<Account> accounts = jdbcTemplate.query("select * from account where name = ?", new BeanPropertyRowMapper<>(Account.class), accountName);
            if (accounts.isEmpty()) {
                return null;
            }
            if (accounts.size() > 1) {
                throw new RuntimeException("结果集不唯一!");
            }
            return accounts.get(0);
        }
    
        @Override
        public void update(Account account) {
            jdbcTemplate.update("update account set name=?,money=? where id=?", account.getName(), account.getMoney(), account.getId());
        }
    
    }
    

    第四步: resources 目录下新建 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="accountDao" class="club.guoshizhan.dao.impl.AccountDaoImpl">
            <property name="jdbcTemplate" ref="jdbcTemplate"></property>
    <!--        <property name="dataSource" ref="dataSource"></property>-->
        </bean>
    
        <!-- 配置 JdbcTemplate -->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    
        <!-- 配置数据源 -->
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
            <property name="url" value="jdbc:mysql://localhost:3306/spring"></property>
            <property name="username" value="root"></property>
            <property name="password" value="root"></property>
        </bean>
    </beans>
    

    第五步: 修改 JdbcTemplateTest 测试类。 代码如下:

    package club.guoshizhan.jdbctemplate;
    
    import club.guoshizhan.dao.IAccountDao;
    import club.guoshizhan.domain.Account;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    /**
     * JdbcTemplate 的最基本用法
     */
    public class JdbcTemplateTest {
        public static void main(String[] args) {
    
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
            IAccountDao accountDao = ac.getBean("accountDao", IAccountDao.class);
    
            Account account = accountDao.findById(1);
            System.out.println(account);
    
            account.setMoney(2000f);
            accountDao.update(account);
    
            Account jack = accountDao.findByName("Mary");
            System.out.println(jack);
    
        }
    }
    

    执行测试类中的 main 方法,结果如下(数据库也是发生了改变的):。

    Account{id=1, name='Jack', money=2000.0}
    Account{id=4, name='Mary', money=1000.0}
    
    Process finished with exit code 0
    

    TIPS: 到此为止,JdbcTemplate 环境搭建 就告一段落了!!!接下来介绍 JdbcTemplate 实现 CRUD 的操作。继续往下看!!!

    JdbcTemplate 实现 CRUD

    我们使用上述工程的代码,修改 JdbcTemplateTest 测试类即可。 代码如下:

    package club.guoshizhan.jdbctemplate;
    
    import club.guoshizhan.dao.IAccountDao;
    import club.guoshizhan.domain.Account;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.core.RowMapper;
    
    import java.sql.ResultSet;
    import java.sql.SQLException;
    
    /**
     * JdbcTemplate 的 CRUD 操作
     */
    public class JdbcTemplateTest {
        public static void main(String[] args) {
    
            ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
            JdbcTemplate jt = ac.getBean("jdbcTemplate", JdbcTemplate.class);
    
            // 保存操作
    //        jt.update("insert into account(name, money) values (?, ?)", "eee", 3333f);
    
            // 更新操作
    //        jt.update("update account set name=?, money=? where id=?", "test", 4567, 7);
    
            // 删除操作
    //        jt.update("delete from account where id=?", 8);
    
            // 查询所有操作
    //        List<Account> accounts = jt.query("select * from account where money > ?", new AccountRowMapper(), 1000f);
            /*List<Account> accounts = jt.query("select * from account where money > ?", new BeanPropertyRowMapper<>(Account.class), 900f);
            for (Account account : accounts) {
                System.out.println(account);
            }*/
    
            // 查询一个操作
            /*List<Account> accounts = jt.query("select * from account where id = ?", new BeanPropertyRowMapper<Account>(Account.class), 1);
            System.out.println(accounts.isEmpty() ? "没有内容" : accounts.get(0));*/
    
            // 查询返回一行一列(使用聚合函数,但不加 group by 子句)
            Long count = jt.queryForObject("select count(*) from account where money > ?", Long.class, 900f);
            System.out.println(count);
    
        }
    }
    
    /**
     * 定义 Account 的封装策略
     */
    class AccountRowMapper implements RowMapper<Account> {
    
        // 把结果集中的数据封装到 Account 中,然后由 spring 把每个 Account 加到集合中
        @Override
        public Account mapRow(ResultSet rs, int rowNum) throws SQLException {
            Account account = new Account();
            account.setId(rs.getInt("id"));
            account.setName(rs.getString("name"));
            account.setMoney(rs.getFloat("money"));
            return account;
        }
    
    }
    

    TIPS: 需要执行哪一个操作,就放开哪一个操作。 这就实现了 JdbcTemplate 的 CRUD 操作。

    基于 XML 的 AOP 事务控制

    第一步: 新建 Maven 工程,完善包结构。 然后导入如下依赖:

    <packaging>jar</packaging>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-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>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</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>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
    </dependencies>
    

    第二步: 创建如下各个接口和类,根据 package 语句将类或接口放到对应的包中。 接口和类的代码如下:

    package club.guoshizhan.domain;
    
    import java.io.Serializable;
    
    /**
     * 账户的实体类
     */
    public class Account implements Serializable {
    
        private Integer id;
        private String name;
        private Float money;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Float getMoney() {
            return money;
        }
    
        public void setMoney(Float money) {
            this.money = money;
        }
    
        @Override
        public String toString() {
            return "Account{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", money=" + money +
                    '}';
        }
    
    }
    
    package club.guoshizhan.utils;
    
    import javax.sql.DataSource;
    import java.sql.Connection;
    
    /**
     * 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定
     */
    public class ConnectionUtils {
    
        private ThreadLocal<Connection> threadLocal = new ThreadLocal<Connection>();
        private DataSource dataSource;
    
        public void setDataSource(DataSource dataSource) {
            this.dataSource = dataSource;
        }
    
        /**
         * 获取当前线程上的连接
         */
        public Connection getThreadConnection() {
            try {
                // 1、先从 ThreadLocal 上获取
                Connection conn = threadLocal.get();
                // 2、判断当前线程上是否有连接
                if (conn == null) {
                    // 3、从数据源中获取一个连接,并且存入 ThreadLocal 中
                    conn = dataSource.getConnection();
                    threadLocal.set(conn);
                }
                // 4、返回当前线程上的连接
                return conn;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        /**
         * 把连接和线程解绑
         */
        public void removeConnection() {
            threadLocal.remove();
        }
    
    }
    
    package club.guoshizhan.utils;
    
    /**
     * 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
     */
    public class TransactionManager {
    
        private ConnectionUtils connectionUtils;
    
        public void setConnectionUtils(ConnectionUtils connectionUtils) {
            this.connectionUtils = connectionUtils;
        }
    
        /**
         * 开启事务
         */
        public void beginTransaction() {
            try {
                connectionUtils.getThreadConnection().setAutoCommit(false);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 提交事务
         */
        public void commit() {
            try {
                connectionUtils.getThreadConnection().commit();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 回滚事务
         */
        public void rollback() {
            try {
                connectionUtils.getThreadConnection().rollback();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    
        /**
         * 释放连接
         */
        public void release() {
            try {
                connectionUtils.getThreadConnection().close();    // 还回连接池中
                connectionUtils.removeConnection();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    }
    
    package club.guoshizhan.dao;
    
    import club.guoshizhan.domain.Account;
    import java.util.List;
    
    /**
     * 账户的持久层接口
     */
    public interface IAccountDao {
    
        /**
         * 查询所有
         */
        List<Account> findAll();
    
        /**
         * 查询一个
         */
        Account findById(Integer accountId);
    
        /**
         * 保存
         */
        void save(Account account);
    
        /**
         * 更新
         */
        void update(Account account);
    
        /**
         * 删除
         */
        void delete(Integer acccountId);
    
        /**
         * 根据名称查询账户
         * @return 如果有唯一的一个结果就返回,如果没有结果就返回 null,如果结果集超过一个就抛异常
         */
        Account findByName(String accountName);
    
    }
    
    package club.guoshizhan.dao.impl;
    
    import club.guoshizhan.dao.IAccountDao;
    import club.guoshizhan.domain.Account;
    import club.guoshizhan.utils.ConnectionUtils;
    import org.apache.commons.dbutils.QueryRunner;
    import org.apache.commons.dbutils.handlers.BeanHandler;
    import org.apache.commons.dbutils.handlers.BeanListHandler;
    
    import java.util.List;
    
    /**
     * 账户的持久层实现类
     */
    public class AccountDaoImpl implements IAccountDao {
    
        private QueryRunner runner;
        private ConnectionUtils connectionUtils;
    
        public void setRunner(QueryRunner runner) {
            this.runner = runner;
        }
    
        public void setConnectionUtils(ConnectionUtils connectionUtils) {
            this.connectionUtils = connectionUtils;
        }
    
        @Override
        public List<Account> findAll() {
            try {
                return runner.query(connectionUtils.getThreadConnection(), "select * from account", new BeanListHandler<Account>(Account.class));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public Account findById(Integer accountId) {
            try {
                return runner.query(connectionUtils.getThreadConnection(), "select * from account where id = ? ", new BeanHandler<Account>(Account.class), accountId);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public void save(Account account) {
            try {
                runner.update(connectionUtils.getThreadConnection(), "insert into account(name,money)values(?,?)", account.getName(), account.getMoney());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public void update(Account account) {
            try {
                runner.update(connectionUtils.getThreadConnection(), "update account set name=?,money=? where id=?", account.getName(), account.getMoney(), account.getId());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public void delete(Integer accountId) {
            try {
                runner.update(connectionUtils.getThreadConnection(), "delete from account where id=?", accountId);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public Account findByName(String accountName) {
            try {
                List<Account> accounts = runner.query(connectionUtils.getThreadConnection(), "select * from account where name = ? ", new BeanListHandler<>(Account.class), accountName);
                if (accounts == null || accounts.size() == 0) {
                    return null;
                }
                if (accounts.size() > 1) {
                    throw new RuntimeException("结果集不唯一,数据有问题");
                }
                return accounts.get(0);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    }
    
    package club.guoshizhan.service;
    
    import club.guoshizhan.domain.Account;
    import java.util.List;
    
    /**
     * 账户的业务层接口
     */
    public interface IAccountService {
    
        // 查询所有
        List<Account> findAllAccount();
    
        // 查询一个
        Account findAccountById(Integer accountId);
    
        // 保存
        void saveAccount(Account account);
    
        // 更新
        void updateAccount(Account account);
    
        // 删除
        void deleteAccount(Integer acccountId);
    
        // 转账
        void transfer(String sourceName, String targetName, Float money);
    
        void test();    // 它只是连接点,但不是切入点,因为没有被增强
        
    }
    
    package club.guoshizhan.service.impl;
    
    import club.guoshizhan.dao.IAccountDao;
    import club.guoshizhan.domain.Account;
    import club.guoshizhan.service.IAccountService;
    
    import java.util.List;
    
    /**
     * 账号的业务层实现类
     */
    public class AccountServiceImpl implements IAccountService {
    
        private IAccountDao accountDao;
    
        public void setAccountDao(IAccountDao accountDao) {
            this.accountDao = accountDao;
        }
    
        @Override
        public List<Account> findAllAccount() {
            return accountDao.findAll();
        }
    
        @Override
        public Account findAccountById(Integer id) {
            return accountDao.findById(id);
        }
    
        @Override
        public void saveAccount(Account account) {
            accountDao.save(account);
        }
    
        @Override
        public void updateAccount(Account account) {
            accountDao.update(account);
        }
    
        @Override
        public void deleteAccount(Integer id) {
            accountDao.delete(id);
        }
    
        @Override
        public void transfer(String sourceName, String targetName, Float money) {
    
            Account source = accountDao.findByName(sourceName);    // 根据名称查询转出账户
            Account target = accountDao.findByName(targetName);    // 根据名称查询转入账户
    
            source.setMoney(source.getMoney() - money);            // 转出账户减钱
            target.setMoney(target.getMoney() + money);            // 转入账户加钱
    
            accountDao.update(source);                             // 更新转出账户
            accountDao.update(target);                             // 更新转入账户
    
        }
    
        @Override
        public void test() {
            System.out.println("test……");
        }
    
    }
    

    第三步: resources 目录下新建 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"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/aop
            http://www.springframework.org/schema/aop/spring-aop.xsd">
    
        <!-- 配置 Service -->
        <bean id="accountService" class="club.guoshizhan.service.impl.AccountServiceImpl">
            <!-- 注入 dao -->
            <property name="accountDao" ref="accountDao"></property>
        </bean>
    
        <!-- 配置 Dao 对象 -->
        <bean id="accountDao" class="club.guoshizhan.dao.impl.AccountDaoImpl">
            <!-- 注入 QueryRunner -->
            <property name="runner" ref="runner"></property>
            <!-- 注入 ConnectionUtils -->
            <property name="connectionUtils" ref="connectionUtils"></property>
        </bean>
    
        <!-- 配置QueryRunner -->
        <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>
    
        <!-- 配置数据源 -->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <!-- 连接数据库的必备信息 -->
            <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"></property>
            <property name="user" value="root"></property>
            <property name="password" value="root"></property>
        </bean>
    
        <!-- 配置 Connection 的工具类 ConnectionUtils -->
        <bean id="connectionUtils" class="club.guoshizhan.utils.ConnectionUtils">
            <!-- 注入数据源 -->
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    
        <!-- 配置事务管理器 -->
        <bean id="txManager" class="club.guoshizhan.utils.TransactionManager">
            <!-- 注入 ConnectionUtils -->
            <property name="connectionUtils" ref="connectionUtils"></property>
        </bean>
    
        <!-- 配置 aop -->
        <aop:config>
            <!-- 配置通用切入点表达式 -->
            <aop:pointcut id="pt1" expression="execution(* club.guoshizhan.service.impl.*.*(..))"></aop:pointcut>
            <aop:aspect id="txAdvice" ref="txManager">
                <!-- 配置前置通知:开启事务 -->
                <aop:before method="beginTransaction" pointcut-ref="pt1"></aop:before>
                <!-- 配置后置通知:提交事务 -->
                <aop:after-returning method="commit" pointcut-ref="pt1"></aop:after-returning>
                <!-- 配置异常通知:回滚事务 -->
                <aop:after-throwing method="rollback" pointcut-ref="pt1"></aop:after-throwing>
                <!-- 配置最终通知:释放连接 -->
                <aop:after method="release" pointcut-ref="pt1"></aop:after>
            </aop:aspect>
        </aop:config>
    </beans>
    

    第四步: test 目录下新建测试类,包名根据 package 语句自行创建。 代码如下:

    package club.guoshizhan.test;
    
    import club.guoshizhan.service.IAccountService;
    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;
    
    /**
     * 使用 Junit 单元测试:测试我们的配置
     */
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = "classpath:bean.xml")
    public class AOPTest {
    
        @Autowired
        private IAccountService accountService;
    
        @Test
        public void testTransfer() {
            accountService.transfer("Jack", "Mary", 500f);
        }
    
    }
    

    执行 AOPTest 测试类中的 testTransfer 方法,结果如下:

    九月 11, 2020 10:47:04 上午 org.springframework.test.context.support.AbstractTestContextBootstrapper getDefaultTestExecutionListenerClassNames
    信息: Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]
    九月 11, 2020 10:47:04 上午 org.springframework.test.context.support.AbstractTestContextBootstrapper instantiateListeners
    信息: Could not instantiate TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [javax/servlet/ServletContext]
    九月 11, 2020 10:47:04 上午 org.springframework.test.context.support.AbstractTestContextBootstrapper getTestExecutionListeners
    信息: Using TestExecutionListeners: [org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@190c9c3, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@e6e9c3, org.springframework.test.context.support.DirtiesContextTestExecutionListener@56dfcb, org.springframework.test.context.transaction.TransactionalTestExecutionListener@174b225, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@69b199]
    九月 11, 2020 10:47:04 上午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
    信息: Loading XML bean definitions from class path resource [bean.xml]
    九月 11, 2020 10:47:05 上午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
    信息: Refreshing org.springframework.context.support.GenericApplicationContext@d62f43: startup date [Fri Sep 11 10:47:05 CST 2020]; root of context hierarchy
    九月 11, 2020 10:47:05 上午 com.mchange.v2.log.MLog <clinit>
    信息: MLog clients using java 1.4+ standard logging.
    九月 11, 2020 10:47:06 上午 com.mchange.v2.c3p0.C3P0Registry banner
    信息: Initializing c3p0-0.9.1.2 [built 21-May-2007 15:04:56; debug? true; trace: 10]
    九月 11, 2020 10:47:06 上午 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource getPoolManager
    信息: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, dataSourceName -> 1hgeby9acy57d3k19bnfa0|167eb2a, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> 1hgeby9acy57d3k19bnfa0|167eb2a, idleConnectionTestPeriod -> 0, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/spring, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 15, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 3, numHelperThreads -> 3, numThreadsAwaitingCheckoutDefaultUser -> 0, preferredTestQuery -> null, properties -> {user=******, password=******}, propertyCycle -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, usesTraditionalReflectiveProxies -> false ]
    
    Process finished with exit code 0
    

    TIPS: 然后去数据库查询一下,发现转账成功。 基于 XML 的 AOP 事务控制 到此结束!!!

    基于注解的 AOP 事务控制

    第一步: 新建 Maven 工程,完善包结构。 然后导入如下依赖:

    <packaging>jar</packaging>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-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>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</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>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
    </dependencies>
    

    第二步: 创建如下各个接口和类,根据 package 语句将类或接口放到对应的包中。 接口和类的代码如下:

    package club.guoshizhan.domain;
    
    import java.io.Serializable;
    
    /**
     * 账户的实体类
     */
    public class Account implements Serializable {
    
        private Integer id;
        private String name;
        private Float money;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Float getMoney() {
            return money;
        }
    
        public void setMoney(Float money) {
            this.money = money;
        }
    
        @Override
        public String toString() {
            return "Account{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", money=" + money +
                    '}';
        }
    
    }
    
    package club.guoshizhan.utils;
    
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    import javax.sql.DataSource;
    import java.sql.Connection;
    
    /**
     * 连接的工具类,它用于从数据源中获取一个连接,并且实现和线程的绑定
     */
    @Component("connectionUtils")
    public class ConnectionUtils {
    
        private ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
    
        @Autowired
        private DataSource dataSource;
    
        /**
         * 获取当前线程上的连接
         */
        public Connection getThreadConnection() {
            try {
                // 1、先从 ThreadLocal 上获取
                Connection conn = threadLocal.get();
                // 2、判断当前线程上是否有连接
                if (conn == null) {
                    // 3、从数据源中获取一个连接,并且存入 ThreadLocal 中
                    conn = dataSource.getConnection();
                    threadLocal.set(conn);
                }
                // 4、返回当前线程上的连接
                return conn;
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        /**
         * 把连接和线程解绑
         */
        public void removeConnection() {
            threadLocal.remove();
        }
    
    }
    
    package club.guoshizhan.utils;
    
    import org.aspectj.lang.ProceedingJoinPoint;
    import org.aspectj.lang.annotation.*;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Component;
    
    /**
     * 和事务管理相关的工具类,它包含了,开启事务,提交事务,回滚事务和释放连接
     */
    @Component("txManager")
    @Aspect
    public class TransactionManager {
    
        @Autowired
        private ConnectionUtils connectionUtils;
    
        @Pointcut("execution(* club.guoshizhan.service.impl.*.*(..))")
        private void springAdvice() {
        }
    
        /**
         * 开启事务
         */
        public void beginTransaction() {
            try {
                connectionUtils.getThreadConnection().setAutoCommit(false);
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 提交事务
         */
        public void commit() {
            try {
                connectionUtils.getThreadConnection().commit();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        /**
         * 回滚事务
         */
        public void rollback() {
            try {
                connectionUtils.getThreadConnection().rollback();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
    
        /**
         * 释放连接
         */
        public void release() {
            try {
                connectionUtils.getThreadConnection().close();    // 还回连接池中
                connectionUtils.removeConnection();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    
        @Around("springAdvice()")
        public Object aroundPrintLog(ProceedingJoinPoint pjp) {
            Object rtValue = null;
            try {
                Object[] args = pjp.getArgs();  // 得到方法执行所需的参数
                this.beginTransaction();
                rtValue = pjp.proceed(args);    // 明确调用业务层方法(切入点方法)
                this.commit();
                return rtValue;
            } catch (Throwable t) {             // 必须写 Throwable ,Exception 拦不住它
                this.rollback();
                throw new RuntimeException(t);
            } finally {
                this.release();
            }
        }
    
    }
    
    package club.guoshizhan.dao.impl;
    
    import club.guoshizhan.dao.IAccountDao;
    import club.guoshizhan.domain.Account;
    import club.guoshizhan.utils.ConnectionUtils;
    import org.apache.commons.dbutils.QueryRunner;
    import org.apache.commons.dbutils.handlers.BeanHandler;
    import org.apache.commons.dbutils.handlers.BeanListHandler;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Repository;
    
    import java.util.List;
    
    /**
     * 账户的持久层实现类
     */
    @Repository("accountDao")
    public class AccountDaoImpl implements IAccountDao {
    
        @Autowired
        private QueryRunner runner;
    
        @Autowired
        private ConnectionUtils connectionUtils;
    
        @Override
        public List<Account> findAll() {
            try {
                return runner.query(connectionUtils.getThreadConnection(), "select * from account", new BeanListHandler<>(Account.class));
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public Account findById(Integer accountId) {
            try {
                return runner.query(connectionUtils.getThreadConnection(), "select * from account where id = ? ", new BeanHandler<>(Account.class), accountId);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public void save(Account account) {
            try {
                runner.update(connectionUtils.getThreadConnection(), "insert into account(name,money)values(?,?)", account.getName(), account.getMoney());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public void update(Account account) {
            try {
                runner.update(connectionUtils.getThreadConnection(), "update account set name=?,money=? where id=?", account.getName(), account.getMoney(), account.getId());
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public void delete(Integer accountId) {
            try {
                runner.update(connectionUtils.getThreadConnection(), "delete from account where id=?", accountId);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
        @Override
        public Account findByName(String accountName) {
            try {
                List<Account> accounts = runner.query(connectionUtils.getThreadConnection(), "select * from account where name = ? ", new BeanListHandler<>(Account.class), accountName);
                if (accounts == null || accounts.size() == 0) {
                    return null;
                }
                if (accounts.size() > 1) {
                    throw new RuntimeException("结果集不唯一,数据有问题");
                }
                return accounts.get(0);
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
    }
    
    package club.guoshizhan.service;
    
    import club.guoshizhan.domain.Account;
    import java.util.List;
    
    /**
     * 账户的业务层接口
     */
    public interface IAccountService {
    
        // 查询所有
        List<Account> findAllAccount();
    
        // 查询一个
        Account findAccountById(Integer accountId);
    
        // 保存
        void saveAccount(Account account);
    
        // 更新
        void updateAccount(Account account);
    
        // 删除
        void deleteAccount(Integer acccountId);
    
        // 转账
        void transfer(String sourceName, String targetName, Float money);
    
        void test();    // 它只是连接点,但不是切入点,因为没有被增强
        
    }
    
    package club.guoshizhan.service.impl;
    
    import club.guoshizhan.dao.IAccountDao;
    import club.guoshizhan.domain.Account;
    import club.guoshizhan.service.IAccountService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    /**
     * 账号的业务层实现类
     */
    @Service("accountService")
    public class AccountServiceImpl implements IAccountService {
    
        @Autowired
        private IAccountDao accountDao;
    
        @Override
        public List<Account> findAllAccount() {
            return accountDao.findAll();
        }
    
        @Override
        public Account findAccountById(Integer id) {
            return accountDao.findById(id);
        }
    
        @Override
        public void saveAccount(Account account) {
            accountDao.save(account);
        }
    
        @Override
        public void updateAccount(Account account) {
            accountDao.update(account);
        }
    
        @Override
        public void deleteAccount(Integer id) {
            accountDao.delete(id);
        }
    
        @Override
        public void transfer(String sourceName, String targetName, Float money) {
    
            Account source = accountDao.findByName(sourceName);    // 根据名称查询转出账户
            Account target = accountDao.findByName(targetName);    // 根据名称查询转入账户
    
            source.setMoney(source.getMoney() - money);            // 转出账户减钱
            target.setMoney(target.getMoney() + money);            // 转入账户加钱
    
            accountDao.update(source);                             // 更新转出账户
            accountDao.update(target);                             // 更新转入账户
    
        }
    
        @Override
        public void test() {
            System.out.println("test……");
        }
    
    }
    

    第三步: resources 目录下新建 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 创建容器时要扫描的包 -->
        <context:component-scan base-package="club.guoshizhan"></context:component-scan>
    
        <!-- 配置 QueryRunner -->
        <bean id="runner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype"></bean>
    
        <!-- 配置数据源 -->
        <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
            <!-- 连接数据库的必备信息 -->
            <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/spring"></property>
            <property name="user" value="root"></property>
            <property name="password" value="root"></property>
        </bean>
    
        <!-- 开启 spring 对注解 AOP 的支持 -->
        <aop:aspectj-autoproxy></aop:aspectj-autoproxy>
    
    </beans>
    

    第四步: test 目录下新建测试类,包名根据 package 语句自行创建。 代码如下:

    package club.guoshizhan.test;
    
    import club.guoshizhan.service.IAccountService;
    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;
    
    /**
     * 使用 Junit 单元测试:测试我们的配置
     */
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = "classpath:bean.xml")
    public class AOPTest {
    
        @Autowired
        private IAccountService accountService;
    
        @Test
        public void testTransfer() {
            accountService.transfer("Lucy", "Lisa", 100f);
        }
    
    }
    
    

    执行 AOPTest 测试类中的 testTransfer 方法,结果如下:

    九月 11, 2020 2:04:32 下午 org.springframework.test.context.support.AbstractTestContextBootstrapper getDefaultTestExecutionListenerClassNames
    信息: Loaded default TestExecutionListener class names from location [META-INF/spring.factories]: [org.springframework.test.context.web.ServletTestExecutionListener, org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener, org.springframework.test.context.support.DependencyInjectionTestExecutionListener, org.springframework.test.context.support.DirtiesContextTestExecutionListener, org.springframework.test.context.transaction.TransactionalTestExecutionListener, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener]
    九月 11, 2020 2:04:32 下午 org.springframework.test.context.support.AbstractTestContextBootstrapper instantiateListeners
    信息: Could not instantiate TestExecutionListener [org.springframework.test.context.web.ServletTestExecutionListener]. Specify custom listener classes or make the default listener classes (and their required dependencies) available. Offending class: [javax/servlet/ServletContext]
    九月 11, 2020 2:04:32 下午 org.springframework.test.context.support.AbstractTestContextBootstrapper getTestExecutionListeners
    信息: Using TestExecutionListeners: [org.springframework.test.context.support.DirtiesContextBeforeModesTestExecutionListener@190c9c3, org.springframework.test.context.support.DependencyInjectionTestExecutionListener@e6e9c3, org.springframework.test.context.support.DirtiesContextTestExecutionListener@56dfcb, org.springframework.test.context.transaction.TransactionalTestExecutionListener@174b225, org.springframework.test.context.jdbc.SqlScriptsTestExecutionListener@69b199]
    九月 11, 2020 2:04:32 下午 org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
    信息: Loading XML bean definitions from class path resource [bean.xml]
    九月 11, 2020 2:04:33 下午 org.springframework.context.support.AbstractApplicationContext prepareRefresh
    信息: Refreshing org.springframework.context.support.GenericApplicationContext@d62f43: startup date [Fri Sep 11 14:04:32 CST 2020]; root of context hierarchy
    九月 11, 2020 2:04:33 下午 com.mchange.v2.log.MLog <clinit>
    信息: MLog clients using java 1.4+ standard logging.
    九月 11, 2020 2:04:34 下午 com.mchange.v2.c3p0.C3P0Registry banner
    信息: Initializing c3p0-0.9.1.2 [built 21-May-2007 15:04:56; debug? true; trace: 10]
    九月 11, 2020 2:04:34 下午 com.mchange.v2.c3p0.impl.AbstractPoolBackedDataSource getPoolManager
    信息: Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, dataSourceName -> 1hgeby9acyc9b0k1ixj7bx|3bfa16, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> 1hgeby9acyc9b0k1ixj7bx|3bfa16, idleConnectionTestPeriod -> 0, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql://localhost:3306/spring, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 15, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 3, numHelperThreads -> 3, numThreadsAwaitingCheckoutDefaultUser -> 0, preferredTestQuery -> null, properties -> {user=******, password=******}, propertyCycle -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, usesTraditionalReflectiveProxies -> false ]
    
    Process finished with exit code 0
    

    TIPS: 然后去数据库查询一下,发现转账成功。(重点是需要关注环绕通知,其他的四种通知有顺序问题,从而会导致程序执行失败。) 基于注解的 AOP 事务控制 到此结束!!!

    Spring 中的声明式事务

    环境搭建

    第一步: 新建 Maven 工程,完善包结构。 然后导入如下依赖:

    <packaging>jar</packaging>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    

    第二步: 创建如下各个接口和类,根据 package 语句将类或接口放到对应的包中。 接口和类的代码如下:

    package club.guoshizhan.domain;
    
    import java.io.Serializable;
    
    /**
     * 账户的实体类
     */
    public class Account implements Serializable {
    
        private Integer id;
        private String name;
        private Float money;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Float getMoney() {
            return money;
        }
    
        public void setMoney(Float money) {
            this.money = money;
        }
    
        @Override
        public String toString() {
            return "Account{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", money=" + money +
                    '}';
        }
    
    }
    
    package club.guoshizhan.dao;
    
    import club.guoshizhan.domain.Account;
    
    /**
     * 账户的持久层接口
     */
    public interface IAccountDao {
    
        /**
         * 根据 Id 查询账户
         */
        Account findAccountById(Integer accountId);
    
        /**
         * 根据名称查询账户
         */
        Account findAccountByName(String accountName);
    
        /**
         * 更新账户
         */
        void updateAccount(Account account);
    
    }
    
    package club.guoshizhan.dao.impl;
    
    import club.guoshizhan.dao.IAccountDao;
    import club.guoshizhan.domain.Account;
    import org.springframework.jdbc.core.BeanPropertyRowMapper;
    import org.springframework.jdbc.core.support.JdbcDaoSupport;
    
    import java.util.List;
    
    /**
     * 账户的持久层实现类
     */
    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());
        }
        
    }
    
    package club.guoshizhan.service;
    
    import club.guoshizhan.domain.Account;
    
    /**
     * 账户的业务层接口
     */
    public interface IAccountService {
    
        /**
         * 根据 id 查询账户信息
         */
        Account findAccountById(Integer accountId);
    
        /**
         * 转账操作
         */
        void transfer(String sourceName, String targetName, Float money);
    
    }
    
    package club.guoshizhan.service.impl;
    
    import club.guoshizhan.dao.IAccountDao;
    import club.guoshizhan.domain.Account;
    import club.guoshizhan.service.IAccountService;
    
    /**
     * 账户的业务层实现类
     */
    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);                             // 更新转入账户
    
        }
        
    }
    

    第三步: resources 目录下新建 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="club.guoshizhan.service.impl.AccountServiceImpl">
            <property name="accountDao" ref="accountDao"></property>
        </bean>
    
        <!-- 配置账户的持久层 -->
        <bean id="accountDao" class="club.guoshizhan.dao.impl.AccountDaoImpl">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    
        <!-- 配置数据源 -->
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <!-- 连接数据库的必备信息 -->
            <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
            <property name="url" value="jdbc:mysql://localhost:3306/spring"></property>
            <property name="username" value="root"></property>
            <property name="password" value="root"></property>
        </bean>
    
    </beans>
    

    第四步: test 目录下新建测试类,包名根据 package 语句自行创建。 代码如下:

    package club.guoshizhan.test;
    
    import club.guoshizhan.service.IAccountService;
    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;
    
    /**
     * 使用 Junit 单元测试:测试我们的配置
     */
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = "classpath:bean.xml")
    public class TransactionTest {
    
        @Autowired
        private IAccountService accountService;
    
        @Test
        public void testTransfer() {
            accountService.transfer("Lisa", "Lucy", 100f);
        }
    
    }
    

    TIPS: 执行 TransactionTest 测试类中的 testTransfer 方法, 然后去数据库查询结果即可。 注意: 现在还不能控制事务。

    基于 XML 的声明式事务

    第一步: 新建 Maven 工程,完善包结构。 然后导入如下依赖:

    <packaging>jar</packaging>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    

    第二步: 创建如下各个接口和类,根据 package 语句将类或接口放到对应的包中。 接口和类的代码如下:

    package club.guoshizhan.domain;
    
    import java.io.Serializable;
    
    /**
     * 账户的实体类
     */
    public class Account implements Serializable {
    
        private Integer id;
        private String name;
        private Float money;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Float getMoney() {
            return money;
        }
    
        public void setMoney(Float money) {
            this.money = money;
        }
    
        @Override
        public String toString() {
            return "Account{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", money=" + money +
                    '}';
        }
    
    }
    
    package club.guoshizhan.dao;
    
    import club.guoshizhan.domain.Account;
    
    /**
     * 账户的持久层接口
     */
    public interface IAccountDao {
    
        /**
         * 根据 Id 查询账户
         */
        Account findAccountById(Integer accountId);
    
        /**
         * 根据名称查询账户
         */
        Account findAccountByName(String accountName);
    
        /**
         * 更新账户
         */
        void updateAccount(Account account);
    
    }
    
    package club.guoshizhan.dao.impl;
    
    import club.guoshizhan.dao.IAccountDao;
    import club.guoshizhan.domain.Account;
    import org.springframework.jdbc.core.BeanPropertyRowMapper;
    import org.springframework.jdbc.core.support.JdbcDaoSupport;
    
    import java.util.List;
    
    /**
     * 账户的持久层实现类
     */
    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());
        }
        
    }
    
    package club.guoshizhan.service;
    
    import club.guoshizhan.domain.Account;
    
    /**
     * 账户的业务层接口
     */
    public interface IAccountService {
    
        /**
         * 根据 id 查询账户信息
         */
        Account findAccountById(Integer accountId);
    
        /**
         * 转账操作
         */
        void transfer(String sourceName, String targetName, Float money);
    
    }
    
    package club.guoshizhan.service.impl;
    
    import club.guoshizhan.dao.IAccountDao;
    import club.guoshizhan.domain.Account;
    import club.guoshizhan.service.IAccountService;
    
    /**
     * 账户的业务层实现类
     */
    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);                             // 更新转入账户
    
        }
        
    }
    

    第三步: resources 目录下新建 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: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="club.guoshizhan.service.impl.AccountServiceImpl">
            <property name="accountDao" ref="accountDao"></property>
        </bean>
    
        <!-- 配置账户的持久层 -->
        <bean id="accountDao" class="club.guoshizhan.dao.impl.AccountDaoImpl">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    
        <!-- 配置数据源 -->
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <!-- 连接数据库的必备信息 -->
            <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
            <property name="url" value="jdbc:mysql://localhost:3306/spring"></property>
            <property name="username" value="root"></property>
            <property name="password" value="root"></property>
        </bean>
    
        <!-- Spring 中基于 XML 的声明式事务控制配置步骤如下:
        1、配置事务管理器
        2、配置事务的通知。此时我们需要导入事务的约束【tx 和 aop 的名称空间和约束】
                使用tx:advice标签配置事务通知
                    相关属性 ==> id 属性:给事务通知起一个唯一标识。 transaction-manager 属性:给事务通知提供一个事务管理器引用
        3、配置 AOP 中的通用切入点表达式
        4、建立事务通知和切入点表达式的对应关系
        5、配置事务的属性:是在事务的通知 <tx:advice> 标签的内部 -->
    
        <!-- 配置事务管理器 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    
        <!-- 配置事务的通知 -->
        <tx:advice id="txAdvice" transaction-manager="transactionManager">
            <!-- 配置事务的属性
                    isolation:用于指定事务的隔离级别。默认值是 DEFAULT,表示使用数据库的默认隔离级别。
                    propagation:用于指定事务的传播行为。默认值是 REQUIRED ,表示一定会有事务,增删改的选择。查询方法可以选择 SUPPORTS。
                    read-only:用于指定事务是否只读。只有查询方法才能设置为 true 。默认值是 false,表示读写。
                    timeout:用于指定事务的超时时间,默认值是 -1,表示永不超时。如果指定了数值,以秒为单位。
                    rollback-for:用于指定一个异常,当产生该异常时,事务回滚,产生其他异常时,事务不回滚。没有默认值。表示任何异常都回滚。
                    no-rollback-for:用于指定一个异常,当产生该异常时,事务不回滚,产生其他异常时事务回滚。没有默认值。表示任何异常都回滚。
            -->
            <tx:attributes>
                <tx:method name="*" propagation="REQUIRED" read-only="false"/>
                <tx:method name="find*" propagation="SUPPORTS" read-only="true"></tx:method>
            </tx:attributes>
        </tx:advice>
    
        <!-- 配置 aop -->
        <aop:config>
            <!-- 配置切入点表达式 -->
            <aop:pointcut id="springAdvice" expression="execution(* club.guoshizhan.service.impl.*.*(..))"></aop:pointcut>
            <!-- 建立切入点表达式和事务通知的对应关系 -->
            <aop:advisor advice-ref="txAdvice" pointcut-ref="springAdvice"></aop:advisor>
        </aop:config>
    
    </beans>
    

    第四步: test 目录下新建测试类,包名根据 package 语句自行创建。 代码如下:

    package club.guoshizhan.test;
    
    import club.guoshizhan.service.IAccountService;
    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;
    
    /**
     * 使用 Junit 单元测试:测试我们的配置
     */
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = "classpath:bean.xml")
    public class TransactionTest {
    
        @Autowired
        private IAccountService accountService;
    
        @Test
        public void testTransfer() {
            accountService.transfer("Lisa", "Lucy", 100f);
        }
    
    }
    

    TIPS: 执行 TransactionTest 测试类中的 testTransfer 方法, 然后去数据库查询结果即可(此时已经配置好了事务)。

    基于注解的声明式事务

    第一步: 新建 Maven 工程,完善包结构。 然后导入如下依赖:

    <packaging>jar</packaging>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    

    第二步: 创建如下各个接口和类,根据 package 语句将类或接口放到对应的包中。 接口和类的代码如下:

    package club.guoshizhan.domain;
    
    import java.io.Serializable;
    
    /**
     * 账户的实体类
     */
    public class Account implements Serializable {
    
        private Integer id;
        private String name;
        private Float money;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Float getMoney() {
            return money;
        }
    
        public void setMoney(Float money) {
            this.money = money;
        }
    
        @Override
        public String toString() {
            return "Account{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", money=" + money +
                    '}';
        }
    
    }
    
    package club.guoshizhan.dao;
    
    import club.guoshizhan.domain.Account;
    
    /**
     * 账户的持久层接口
     */
    public interface IAccountDao {
    
        /**
         * 根据 Id 查询账户
         */
        Account findAccountById(Integer accountId);
    
        /**
         * 根据名称查询账户
         */
        Account findAccountByName(String accountName);
    
        /**
         * 更新账户
         */
        void updateAccount(Account account);
    
    }
    
    package club.guoshizhan.dao.impl;
    
    import club.guoshizhan.dao.IAccountDao;
    import club.guoshizhan.domain.Account;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.BeanPropertyRowMapper;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.stereotype.Repository;
    
    import java.util.List;
    
    /**
     * 账户的持久层实现类
     */
    @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());
        }
    
    }
    
    package club.guoshizhan.service;
    
    import club.guoshizhan.domain.Account;
    
    /**
     * 账户的业务层接口
     */
    public interface IAccountService {
    
        /**
         * 根据 id 查询账户信息
         */
        Account findAccountById(Integer accountId);
    
        /**
         * 转账操作
         */
        void transfer(String sourceName, String targetName, Float money);
    
    }
    
    package club.guoshizhan.service.impl;
    
    import club.guoshizhan.dao.IAccountDao;
    import club.guoshizhan.domain.Account;
    import club.guoshizhan.service.IAccountService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    /**
     * 账户的业务层实现类
     */
    @Service("accountService")
    @Transactional
    public class AccountServiceImpl implements IAccountService {
    
        @Autowired
        private IAccountDao 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);                             // 更新转出账户
            //int i = 10/0;
            accountDao.updateAccount(target);                             // 更新转入账户
    
        }
    
    }
    

    第三步: resources 目录下新建 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:tx="http://www.springframework.org/schema/tx"
           xmlns:context="http://www.springframework.org/schema/context"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/tx
            http://www.springframework.org/schema/tx/spring-tx.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    
        <!-- 配置 Spring 创建容器时要扫描的包 -->
        <context:component-scan base-package="club.guoshizhan"></context:component-scan>
    
        <!-- 配置 JdbcTemplate -->
        <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    
        <!-- 配置数据源 -->
        <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
            <property name="driverClassName" value="com.mysql.jdbc.Driver"></property>
            <property name="url" value="jdbc:mysql://localhost:3306/spring"></property>
            <property name="username" value="root"></property>
            <property name="password" value="root"></property>
        </bean>
    
        <!-- Spring 中基于注解的声明式事务控制配置步骤:
            1、配置事务管理器
            2、开启 Spring 对注解事务的支持
            3、在需要事务支持的地方使用 @Transactional 注解 -->
    
        <!-- 配置事务管理器 -->
        <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
            <property name="dataSource" ref="dataSource"></property>
        </bean>
    
        <!-- 开启 Spring 对注解事务的支持 -->
        <tx:annotation-driven transaction-manager="transactionManager"></tx:annotation-driven>
    
    </beans>
    

    第四步: test 目录下新建测试类,包名根据 package 语句自行创建。 代码如下:

    package club.guoshizhan.test;
    
    import club.guoshizhan.service.IAccountService;
    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;
    
    /**
     * 使用 Junit 单元测试:测试我们的配置
     */
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(locations = "classpath:bean.xml")
    public class TransactionTest {
    
        @Autowired
        private IAccountService accountService;
    
        @Test
        public void testTransfer() {
            accountService.transfer("Lisa", "Lucy", 100f);
        }
    
    }
    

    TIPS: 执行 TransactionTest 测试类中的 testTransfer 方法, 然后去数据库查询结果即可(此时已经配置好了事务)。

    基于纯注解的声明式事务

    第一步: 新建 Maven 工程,完善包结构。 然后导入如下依赖:

    <packaging>jar</packaging>
    
    <dependencies>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-tx</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.46</version>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.8.7</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-test</artifactId>
            <version>5.0.2.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13</version>
            <scope>test</scope>
        </dependency>
    </dependencies>
    

    第二步: 创建如下各个接口和类,根据 package 语句将类或接口放到对应的包中。 接口和类的代码如下:

    package club.guoshizhan.domain;
    
    import java.io.Serializable;
    
    /**
     * 账户的实体类
     */
    public class Account implements Serializable {
    
        private Integer id;
        private String name;
        private Float money;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Float getMoney() {
            return money;
        }
    
        public void setMoney(Float money) {
            this.money = money;
        }
    
        @Override
        public String toString() {
            return "Account{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", money=" + money +
                    '}';
        }
    
    }
    
    package club.guoshizhan.dao;
    
    import club.guoshizhan.domain.Account;
    
    /**
     * 账户的持久层接口
     */
    public interface IAccountDao {
    
        /**
         * 根据 Id 查询账户
         */
        Account findAccountById(Integer accountId);
    
        /**
         * 根据名称查询账户
         */
        Account findAccountByName(String accountName);
    
        /**
         * 更新账户
         */
        void updateAccount(Account account);
    
    }
    
    package club.guoshizhan.dao.impl;
    
    import club.guoshizhan.dao.IAccountDao;
    import club.guoshizhan.domain.Account;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.jdbc.core.BeanPropertyRowMapper;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.stereotype.Repository;
    
    import java.util.List;
    
    /**
     * 账户的持久层实现类
     */
    @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());
        }
    
    }
    
    package club.guoshizhan.service;
    
    import club.guoshizhan.domain.Account;
    
    /**
     * 账户的业务层接口
     */
    public interface IAccountService {
    
        /**
         * 根据 id 查询账户信息
         */
        Account findAccountById(Integer accountId);
    
        /**
         * 转账操作
         */
        void transfer(String sourceName, String targetName, Float money);
    
    }
    
    package club.guoshizhan.service.impl;
    
    import club.guoshizhan.dao.IAccountDao;
    import club.guoshizhan.domain.Account;
    import club.guoshizhan.service.IAccountService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    import org.springframework.transaction.annotation.Transactional;
    
    /**
     * 账户的业务层实现类
     */
    @Service("accountService")
    @Transactional
    public class AccountServiceImpl implements IAccountService {
    
        @Autowired
        private IAccountDao 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);                             // 更新转出账户
            //int i = 10/0;
            accountDao.updateAccount(target);                             // 更新转入账户
    
        }
    
    }
    

    第三步: resources 目录下新建 jdbcConfig.properties 数据库配置文件。 代码如下:

    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3306/spring
    jdbc.username=root
    jdbc.password=root
    

    第四步: 在 club 包下新建 config 包(用于存放 Spring 的各种配置类)。然后编写各种配置类。 代码如下:

    package club.config;
    
    import org.springframework.beans.factory.annotation.Value;
    import org.springframework.context.annotation.Bean;
    import org.springframework.jdbc.core.JdbcTemplate;
    import org.springframework.jdbc.datasource.DriverManagerDataSource;
    
    import javax.sql.DataSource;
    
    /**
     * 和连接数据库相关的配置类
     */
    public class JdbcConfig {
    
        @Value("${jdbc.driver}")
        private String driver;
    
        @Value("${jdbc.url}")
        private String url;
    
        @Value("${jdbc.username}")
        private String username;
    
        @Value("${jdbc.password}")
        private String password;
    
        /**
         * 创建 JdbcTemplate
         */
        @Bean(name = "jdbcTemplate")
        public JdbcTemplate createJdbcTemplate(DataSource dataSource) {
            return new JdbcTemplate(dataSource);
        }
    
        /**
         * 创建数据源对象
         */
        @Bean(name = "dataSource")
        public DataSource createDataSource() {
            DriverManagerDataSource ds = new DriverManagerDataSource();
            ds.setDriverClassName(driver);
            ds.setUrl(url);
            ds.setUsername(username);
            ds.setPassword(password);
            return ds;
        }
    
    }
    
    package club.config;
    
    import org.springframework.context.annotation.Bean;
    import org.springframework.jdbc.datasource.DataSourceTransactionManager;
    import org.springframework.transaction.PlatformTransactionManager;
    
    import javax.sql.DataSource;
    
    /**
     * 和事务相关的配置类
     */
    public class TransactionConfig {
    
        /**
         * 用于创建事务管理器对象
         */
        @Bean(name = "transactionManager")
        public PlatformTransactionManager createTransactionManager(DataSource dataSource) {
            return new DataSourceTransactionManager(dataSource);
        }
    
    }
    
    package club.config;
    
    import org.springframework.context.annotation.ComponentScan;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.Import;
    import org.springframework.context.annotation.PropertySource;
    import org.springframework.transaction.annotation.EnableTransactionManagement;
    
    /**
     * Spring 的配置类,相当于 bean.xml
     */
    @Configuration
    @ComponentScan("club.guoshizhan")
    @Import({JdbcConfig.class, TransactionConfig.class})
    @PropertySource("jdbcConfig.properties")
    @EnableTransactionManagement
    public class SpringConfiguration {
    
    }
    

    第五步: test 目录下新建测试类,包名根据 package 语句自行创建。 代码如下:

    package club.guoshizhan.test;
    
    import club.config.SpringConfiguration;
    import club.guoshizhan.service.IAccountService;
    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;
    
    /**
     * 使用 Junit 单元测试:测试我们的配置
     */
    @RunWith(SpringJUnit4ClassRunner.class)
    @ContextConfiguration(classes = SpringConfiguration.class)
    public class TransactionTest {
    
        @Autowired
        private IAccountService accountService;
    
        @Test
        public void testTransfer() {
            accountService.transfer("Lisa", "Lucy", 100f);
        }
    
    }
    

    TIPS: 执行 TransactionTest 测试类中的 testTransfer 方法, 然后去数据库查询结果即可(此时已经配置好了事务)。

    编程式事务控制

    + 此部分作为了解内容,以后有时间再写,以此带来的不便,请谅解!!!
    + 此部分作为了解内容,以后有时间再写,以此带来的不便,请谅解!!!
    + 此部分作为了解内容,以后有时间再写,以此带来的不便,请谅解!!!
    + 此部分作为了解内容,以后有时间再写,以此带来的不便,请谅解!!!
    + 此部分作为了解内容,以后有时间再写,以此带来的不便,请谅解!!!
    

    Spring5 新特性

    此部分使用搜索引擎搜索一下就可以了。以后有时间再写。

    + 此部分内容以后有时间再写,以此带来的不便,请谅解!!!
    + 此部分内容以后有时间再写,以此带来的不便,请谅解!!!
    + 此部分内容以后有时间再写,以此带来的不便,请谅解!!!
    + 此部分内容以后有时间再写,以此带来的不便,请谅解!!!
    + 此部分内容以后有时间再写,以此带来的不便,请谅解!!!
    

    SSM 框架整合

    搭建开发环境

    第一步: 创建数据库和表结构。 代码如下:

    -- 创建数据库和数据表
    create database ssm;
    use ssm;
    create table account(
        id int primary key auto_increment,
        name varchar(20),
        money double
    );
    
    -- 向表中插入数据
    insert  into `account`(`id`,`name`,`money`) values (1,'Jack',1500),(2,'Lucy',1100),(3,'Lisa',900),(4,'Mary',1500);
    

    第二步: 创建 Maven 项目(记得勾选 maven-archetype-webapp ),完善相应包结构。 然后导入如下依赖:

    <?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>club.guoshizhan</groupId>
      <artifactId>ssm</artifactId>
      <version>1.0-SNAPSHOT</version>
      <packaging>war</packaging>
    
      <name>ssm Maven Webapp</name>
      <!-- FIXME change it to the project's website -->
      <url>http://www.example.com</url>
    
      <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
        <spring.version>5.0.2.RELEASE</spring.version>
        <slf4j.version>1.6.6</slf4j.version>
        <log4j.version>1.2.12</log4j.version>
        <mysql.version>5.1.6</mysql.version>
        <mybatis.version>3.4.5</mybatis.version>
      </properties>
    
      <dependencies>
        <!-- spring -->
        <dependency>
          <groupId>org.aspectj</groupId>
          <artifactId>aspectjweaver</artifactId>
          <version>1.6.8</version>
        </dependency>
    
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-aop</artifactId>
          <version>${spring.version}</version>
        </dependency>
    
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-context</artifactId>
          <version>${spring.version}</version>
        </dependency>
    
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-web</artifactId>
          <version>${spring.version}</version>
        </dependency>
    
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-webmvc</artifactId>
          <version>${spring.version}</version>
        </dependency>
    
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-test</artifactId>
          <version>${spring.version}</version>
        </dependency>
    
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-tx</artifactId>
          <version>${spring.version}</version>
        </dependency>
    
        <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-jdbc</artifactId>
          <version>${spring.version}</version>
        </dependency>
    
        <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.12</version>
          <scope>compile</scope>
        </dependency>
    
        <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>${mysql.version}</version>
        </dependency>
    
        <dependency>
          <groupId>javax.servlet</groupId>
          <artifactId>servlet-api</artifactId>
          <version>2.5</version>
          <scope>provided</scope>
        </dependency>
    
        <dependency>
          <groupId>javax.servlet.jsp</groupId>
          <artifactId>jsp-api</artifactId>
          <version>2.0</version>
          <scope>provided</scope>
        </dependency>
    
        <dependency>
          <groupId>jstl</groupId>
          <artifactId>jstl</artifactId>
          <version>1.2</version>
        </dependency>
    
        <!-- log start -->
        <dependency>
          <groupId>log4j</groupId>
          <artifactId>log4j</artifactId>
          <version>${log4j.version}</version>
        </dependency>
    
        <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-api</artifactId>
          <version>${slf4j.version}</version>
        </dependency>
    
        <dependency>
          <groupId>org.slf4j</groupId>
          <artifactId>slf4j-log4j12</artifactId>
          <version>${slf4j.version}</version>
        </dependency>
    
        <!-- log end -->
        <dependency>
          <groupId>org.mybatis</groupId>
          <artifactId>mybatis</artifactId>
          <version>${mybatis.version}</version>
        </dependency>
    
        <dependency>
          <groupId>org.mybatis</groupId>
          <artifactId>mybatis-spring</artifactId>
          <version>1.3.0</version>
        </dependency>
    
        <dependency>
          <groupId>c3p0</groupId>
          <artifactId>c3p0</artifactId>
          <version>0.9.1.2</version>
          <type>jar</type>
          <scope>compile</scope>
        </dependency>
      </dependencies>
    
      <build>
        <finalName>ssm</finalName>
        <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
          <plugins>
            <plugin>
              <artifactId>maven-clean-plugin</artifactId>
              <version>3.1.0</version>
            </plugin>
            <!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
            <plugin>
              <artifactId>maven-resources-plugin</artifactId>
              <version>3.0.2</version>
            </plugin>
            <plugin>
              <artifactId>maven-compiler-plugin</artifactId>
              <version>3.8.0</version>
            </plugin>
            <plugin>
              <artifactId>maven-surefire-plugin</artifactId>
              <version>2.22.1</version>
            </plugin>
            <plugin>
              <artifactId>maven-war-plugin</artifactId>
              <version>3.2.2</version>
            </plugin>
            <plugin>
              <artifactId>maven-install-plugin</artifactId>
              <version>2.5.2</version>
            </plugin>
            <plugin>
              <artifactId>maven-deploy-plugin</artifactId>
              <version>2.8.2</version>
            </plugin>
          </plugins>
        </pluginManagement>
      </build>
    </project>
    

    第三步: 创建如下各个接口和类,根据 package 语句将类或接口放到对应的包中。 接口和类的代码如下:

    package club.guoshizhan.domain;
    
    import java.io.Serializable;
    
    /**
     * 帐户实体类
     */
    public class Account implements Serializable {
    
        private Integer id;
        private String name;
        private Double money;
    
        public Integer getId() {
            return id;
        }
    
        public void setId(Integer id) {
            this.id = id;
        }
    
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        public Double getMoney() {
            return money;
        }
    
        public void setMoney(Double money) {
            this.money = money;
        }
    
        @Override
        public String toString() {
            return "Account{" +
                    "id=" + id +
                    ", name='" + name + '\'' +
                    ", money=" + money +
                    '}';
        }
    
    }
    
    package club.guoshizhan.dao;
    
    import club.guoshizhan.domain.Account;
    
    import java.util.List;
    
    /**
     * 帐户 dao 接口
     */
    public interface AccountDao {
    
        // 查询所有账户
        public List<Account> findAll();
    
        // 保存帐户信息
        public void saveAccount(Account account);
    
    }
    
    package club.guoshizhan.service;
    
    import club.guoshizhan.domain.Account;
    
    import java.util.List;
    
    public interface AccountService {
    
        // 查询所有账户
        public List<Account> findAll();
    
        // 保存帐户信息
        public void saveAccount(Account account);
    
    }
    
    package club.guoshizhan.service.impl;
    
    import club.guoshizhan.domain.Account;
    import club.guoshizhan.service.AccountService;
    
    import java.util.List;
    
    public class AccountServiceImpl implements AccountService {
    
        public List<Account> findAll() {
            System.out.println("业务层:查询所有账户...");
            return null;
        }
    
        public void saveAccount(Account account) {
            System.out.println("业务层:保存帐户...");
        }
    
    }
    
    package club.guoshizhan.controller;
    
    /**
     * 帐户的 web 控制层
     */
    public class AccountController {
    
    }
    

    TIPS: ssm 环境搭建 到此结束!!!

    整合 Spring 框架

    注意一下: 在 ssm 的整合过程中,它们是有顺序的。必须先整合 Spring 框架,然后再去整合 Spring MVC 和 Mybatis 框架。 如果先整合 Spring MVC 或 Mybatis 框架的话,会导致整合不成功,所以我们 来整合 Spring 框架。


    第一步: resources 目录下新建 applicationContext.xml 配置文件(这个是 Spring 的配置文件)。 代码如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context"
           xmlns: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/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">
    
        <!-- 开启注解的扫描,希望处理 service 和 dao 层,controller 层不需要 Spring 框架去处理 -->
        <context:component-scan base-package="club.guoshizhan" >
            <!-- 配置哪些注解不被扫描,这里配置了 @Controller 注解不扫描 -->
            <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
        </context:component-scan>
    
    </beans>
    

    第二步: resources 目录下新建 log4j.properties 日志文件(可要可不要,加上是方便查看日志信息)。 代码如下:

    # Set root category priority to INFO and its only appender to CONSOLE.
    #log4j.rootCategory=INFO, CONSOLE            debug   info   warn error fatal
    log4j.rootCategory=info, CONSOLE, LOGFILE
    
    # Set the enterprise logger category to FATAL and its only appender to CONSOLE.
    log4j.logger.org.apache.axis.enterprise=FATAL, CONSOLE
    
    # CONSOLE is set to be a ConsoleAppender using a PatternLayout.
    log4j.appender.CONSOLE=org.apache.log4j.ConsoleAppender
    log4j.appender.CONSOLE.layout=org.apache.log4j.PatternLayout
    log4j.appender.CONSOLE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
    
    # LOGFILE is set to be a File appender using a PatternLayout.
    log4j.appender.LOGFILE=org.apache.log4j.FileAppender
    log4j.appender.LOGFILE.File=d:\axis.log
    log4j.appender.LOGFILE.Append=true
    log4j.appender.LOGFILE.layout=org.apache.log4j.PatternLayout
    log4j.appender.LOGFILE.layout.ConversionPattern=%d{ISO8601} %-6r [%15.15t] %-5p %30.30c %x - %m\n
    

    第三步: AccountServiceImpl 类加入到 Spring 容器中(使用 @Service 注解)。 代码如下:

    package club.guoshizhan.service.impl;
    
    import club.guoshizhan.domain.Account;
    import club.guoshizhan.service.AccountService;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    @Service("accountService")
    public class AccountServiceImpl implements AccountService {
    
    
        public List<Account> findAll() {
            System.out.println("业务层:查询所有账户...");
            return null;
        }
    
        public void saveAccount(Account account) {
            System.out.println("业务层:保存帐户...");
        }
    
    }
    

    第四步: 编写 SSMTest 测试类,测试 整合 Spring 框架 是否成功。 代码如下:

    package club.guoshizhan.test;
    
    import club.guoshizhan.service.AccountService;
    import org.junit.Test;
    import org.springframework.context.ApplicationContext;
    import org.springframework.context.support.ClassPathXmlApplicationContext;
    
    public class SSMTest {
    
        @Test
        public void springTest() {
    
            // 加载配置文件
            ApplicationContext ac = new ClassPathXmlApplicationContext("classpath:applicationContext.xml");
    
            // 获取 accountService 对象
            AccountService accountService = (AccountService) ac.getBean("accountService");
    
            System.out.println(accountService);
            accountService.findAll();
    
        }
    
    }
    

    执行 springTest 方法,然后查看结果。(下列结果是加入了日志信息的) 结果如下:

    2020-09-11 21:43:21,894 0      [           main] INFO  ClassPathXmlApplicationContext  - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@19b4d0f: startup date [Fri Sep 11 21:43:21 CST 2020]; root of context hierarchy
    2020-09-11 21:43:21,961 67     [           main] INFO  ry.xml.XmlBeanDefinitionReader  - Loading XML bean definitions from class path resource [applicationContext.xml]
    2020-09-11 21:43:22,459 565    [           main] WARN  .mapper.ClassPathMapperScanner  - No MyBatis mapper was found in '[cn.itcast.dao]' package. Please check your configuration.
    2020-09-11 21:43:22,818 924    [           main] INFO         com.mchange.v2.log.MLog  - MLog clients using log4j logging.
    2020-09-11 21:43:23,289 1395   [           main] INFO  m.mchange.v2.c3p0.C3P0Registry  - Initializing c3p0-0.9.1.2 [built 21-May-2007 15:04:56; debug? true; trace: 10]
    club.guoshizhan.service.impl.AccountServiceImpl@a923bb
    2020-09-11 21:43:23,576 1682   [           main] INFO  l.AbstractPoolBackedDataSource  - Initializing c3p0 pool... com.mchange.v2.c3p0.ComboPooledDataSource [ acquireIncrement -> 3, acquireRetryAttempts -> 30, acquireRetryDelay -> 1000, autoCommitOnClose -> false, automaticTestTable -> null, breakAfterAcquireFailure -> false, checkoutTimeout -> 0, connectionCustomizerClassName -> null, connectionTesterClassName -> com.mchange.v2.c3p0.impl.DefaultConnectionTester, dataSourceName -> 1hgeby9acysnco9r43se4|12b5695, debugUnreturnedConnectionStackTraces -> false, description -> null, driverClass -> com.mysql.jdbc.Driver, factoryClassLocation -> null, forceIgnoreUnresolvedTransactions -> false, identityToken -> 1hgeby9acysnco9r43se4|12b5695, idleConnectionTestPeriod -> 0, initialPoolSize -> 3, jdbcUrl -> jdbc:mysql:///ssm, maxAdministrativeTaskTime -> 0, maxConnectionAge -> 0, maxIdleTime -> 0, maxIdleTimeExcessConnections -> 0, maxPoolSize -> 15, maxStatements -> 0, maxStatementsPerConnection -> 0, minPoolSize -> 3, numHelperThreads -> 3, numThreadsAwaitingCheckoutDefaultUser -> 0, preferredTestQuery -> null, properties -> {user=******, password=******}, propertyCycle -> 0, testConnectionOnCheckin -> false, testConnectionOnCheckout -> false, unreturnedConnectionTimeout -> 0, usesTraditionalReflectiveProxies -> false ]
    业务层:查询所有账户...
    
    Process finished with exit code 0
    

    TIPS: 如果执行测试方法有如上输出,那么表示 整合 Spring 框架 成功了。

    整合 Spring MVC 框架

    第一步: resources 目录下新建 springMvc.xml 配置文件(这是 Spring MVC 的配置文件)。 代码如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:mvc="http://www.springframework.org/schema/mvc"
           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/mvc
            http://www.springframework.org/schema/mvc/spring-mvc.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd">
    
        <!-- 1、开启注解扫描,只扫描 @Controller 注解 -->
        <context:component-scan base-package="club.guoshizhan">
            <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller" />
        </context:component-scan>
    
        <!-- 2、配置的视图解析器对象(往哪里跳转) -->
        <bean id="internalResourceViewResolver" class="org.springframework.web.servlet.view.InternalResourceViewResolver">
            <property name="prefix" value="/WEB-INF/pages/"/>
            <property name="suffix" value=".jsp"/>
        </bean>
    
        <!-- 3、过滤静态资源 -->
        <mvc:resources location="/css/" mapping="/css/**" />
        <mvc:resources location="/images/" mapping="/images/**" />
        <mvc:resources location="/js/" mapping="/js/**" />
    
        <!-- 4、开启 SpringMVC 注解的支持 -->
        <mvc:annotation-driven/>
    
    </beans>
    

    第二步: 编写 WEB-INF 目录下的 web.xml 配置文件(配置前端控制器和过滤器)。 代码如下:

    <!DOCTYPE web-app PUBLIC
     "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
     "http://java.sun.com/dtd/web-app_2_3.dtd" >
    
    <web-app>
      <display-name>Archetype Created Web Application</display-name>
      <!-- 配置前端控制器 -->
      <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!-- 加载 springMvc.xml 配置文件 -->
        <init-param>
          <param-name>contextConfigLocation</param-name>
          <param-value>classpath:springMvc.xml</param-value>
        </init-param>
        <!-- 启动服务器,创建该 servlet -->
        <load-on-startup>1</load-on-startup>
      </servlet>
      <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
      </servlet-mapping>
    
      <!-- 解决中文乱码的过滤器 -->
      <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <init-param>
          <param-name>encoding</param-name>
          <param-value>UTF-8</param-value>
        </init-param>
      </filter>
      <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
      </filter-mapping>
    </web-app>
    

    第三步: 修改 webapp 目录下的 index.jsp 页面。 代码如下:

    <%--
      Created by IntelliJ IDEA.
      User: guoshizhan
      Date: 2020/9/11
      Time: 22:18
      To change this template use File | Settings | File Templates.
    --%>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
    <title>Title</title>
    </head>
    <body>
    <a href="account/findAll">测试查询</a>
    </body>
    </html>
    

    第四步: 在 WEB-INF 目录下新建 pages 目录,然后在 pages 目录下新建 list.jsp 页面。 代码如下:

    <%--
      Created by IntelliJ IDEA.
      User: guoshizhan
      Date: 2020/9/11
      Time: 22:24
      To change this template use File | Settings | File Templates.
    --%>
    <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    <html>
    <head>
    <title>Title</title>
    </head>
    <body>
    <h3>查询所有的帐户</h3>
    </body>
    </html>
    

    第五步: 编写 AccountController 类,实现页面跳转。 代码如下:

    package club.guoshizhan.controller;
    
    import org.springframework.stereotype.Controller;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    /**
     * 帐户的 web 控制层
     */
    @Controller
    @RequestMapping("/account")
    public class AccountController {
    
        @RequestMapping("/findAll")
        public String findAll() {
            return "list";
        }
    
    }
    

    第六步: 配置 tomcat ,然后运行 tomcat 进行测试。 结果如下:

    TIPS: 如果看到上述页面且点击有效果,则说明 Spring MVC 能起作用了。 那么接下来才是真正的 Spring 整合 Spring MVC 框架


    那么问题来了,Spring 如何才算成功整合了 Spring MVC 框架呢?如果 controller 能够成功调用到 service 的方法,那么就算整合成功了。那么要怎么样才能实现 controller 调用 service 呢?由于在 applicationContext.xml 配置文件中已经明确说明了不扫描 Controller , 所以即使在 controller 里面自动注入 service 也是没有用的。因此,我们需要借助 tomcat 把 controller 注入到 Spring 中。 过程如下图:


    第一步: web.xml 配置文件中新增如下代码。 新增代码如下:

    <!DOCTYPE web-app PUBLIC
            "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
            "http://java.sun.com/dtd/web-app_2_3.dtd" >
    
    <web-app>
        <display-name>Archetype Created Web Application</display-name>
    
    +   <!-- 配置 Spring 的监听器,默认只加载 WEB-INF 目录下的 applicationContext.xml 配置文件 -->
    +   <listener>
    +       <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
    +   </listener>
    +   <!-- 设置配置文件的路径 -->
    +   <context-param>
    +       <param-name>contextConfigLocation</param-name>
    +       <param-value>classpath:applicationContext.xml</param-value>
    +   </context-param>
    
        <!-- 配置前端控制器 -->
        <servlet>
            <servlet-name>dispatcherServlet</servlet-name>
            <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
            <!-- 加载 springMvc.xml 配置文件 -->
            <init-param>
                <param-name>contextConfigLocation</param-name>
                <param-value>classpath:springMvc.xml</param-value>
            </init-param>
            <!-- 启动服务器,创建该 servlet -->
            <load-on-startup>1</load-on-startup>
        </servlet>
        <servlet-mapping>
            <servlet-name>dispatcherServlet</servlet-name>
            <url-pattern>/</url-pattern>
        </servlet-mapping>
    
        <!-- 解决中文乱码的过滤器 -->
        <filter>
            <filter-name>characterEncodingFilter</filter-name>
            <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
            <init-param>
                <param-name>encoding</param-name>
                <param-value>UTF-8</param-value>
            </init-param>
        </filter>
        <filter-mapping>
            <filter-name>characterEncodingFilter</filter-name>
            <url-pattern>/*</url-pattern>
        </filter-mapping>
    </web-app>
    

    第二步: 编写 AccountController 类。 新增代码如下:

    package club.guoshizhan.controller;
    
    import club.guoshizhan.domain.Account;
    import club.guoshizhan.service.AccountService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import java.util.List;
    
    /**
     * 帐户的 web 控制层
     */
    @Controller
    @RequestMapping("/account")
    public class AccountController {
    
        @Autowired
        private AccountService accountService;
    
        // 查询所有操作
        @RequestMapping("/findAll")
        public String findAll(Model model) {
            System.out.println("表现层:查询所有账户...");
            // 调用 service 的方法
            List<Account> list = accountService.findAll();
            model.addAttribute("list", list);
            return "list";
        }
    
    }
    

    第三步: 启动 tomcat ,然后进入到浏览器,点击测试查询,然后回到控制台查看结果。 结果如下:

    表现层:查询所有账户...
    业务层:查询所有账户...
    

    TIPS: 如果能在控制台输出如上结果,那么证明 Spring 整合 Spring MVC 框架 成功了。

    整合 Mybatis 框架

    第一步: 编写 AccountDao 接口,使用注解进行相应的操作。 代码如下:

    package club.guoshizhan.dao;
    
    import club.guoshizhan.domain.Account;
    import org.apache.ibatis.annotations.Insert;
    import org.apache.ibatis.annotations.Select;
    
    import java.util.List;
    
    /**
     * 帐户 dao 接口
     */
    public interface AccountDao {
    
        // 查询所有账户
        @Select("select * from account")
        public List<Account> findAll();
    
        // 保存帐户信息
        @Insert("insert into account (name, money) values (#{name}, #{money})")
        public void saveAccount(Account account);
    
    }
    

    第二步: 编写 SqlMapConfig.xml 配置文件,进行数据库的相关配置。 代码如下:

    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE configuration
            PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-config.dtd">
    
    <configuration>
        <environments default="mysql">
            <environment id="mysql">
                <transactionManager type="JDBC"></transactionManager>
                <dataSource type="POOLED">
                    <property name="driver" value="com.mysql.jdbc.Driver"/>
                    <property name="url" value="jdbc:mysql://localhost:3306/ssm"/>
                    <property name="username" value="root"/>
                    <property name="password" value="root"/>
                </dataSource>
            </environment>
        </environments>
    
        <mappers>
            <mapper class="club.guoshizhan.dao.AccountDao"/>
        </mappers>
    
    </configuration>
    

    第三步: 编写 SSMTest 测试类进行测试,看一下 mybatis 框架能否正常使用。 代码如下:

    package club.guoshizhan.test;
    
    import club.guoshizhan.dao.AccountDao;
    import club.guoshizhan.domain.Account;
    import org.apache.ibatis.io.Resources;
    import org.apache.ibatis.session.SqlSession;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.session.SqlSessionFactoryBuilder;
    import org.junit.Test;
    import java.io.InputStream;
    import java.util.List;
    
    public class SSMTest {
    
        @Test
        public void mybatisTest() throws Exception {
    
            InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
            SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(in);
            SqlSession session = factory.openSession();
            AccountDao dao = session.getMapper(AccountDao.class);
    
            List<Account> list = dao.findAll();
            for(Account account : list){
                System.out.println(account);
            }
    
            session.close();
            in.close();
    
        }
    
    }
    

    执行测试类中的 mybatisTest 方法,查看返回的结果。 结果如下:

    Account{id=1, name='Jack', money=1500.0}
    Account{id=2, name='Lucy', money=1100.0}
    Account{id=3, name='Lisa', money=900.0}
    Account{id=4, name='Mary', money=1500.0}
    
    Process finished with exit code 0
    

    TIPS: 如果看到上述结果,则说明 mybatis 起作用了。 那么接下来才是真正的 Spring 整合 mybatis 框架


    那么要如何去整合 mybatis 呢?如何才算整合成功呢?只要把 dao 对象存到 Spring 容器中,然后 service 能够成功调用 dao 的方法,这样便算整合成功。 那么接下来就按照步骤一步一步地来实现整合的过程。往下看!!!


    第一步: 删除 SqlMapConfig.xml 配置文件,然后在 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"
           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/beans
            http://www.springframework.org/schema/beans/spring-beans.xsd
            http://www.springframework.org/schema/context
            http://www.springframework.org/schema/context/spring-context.xsd
            http://www.springframework.org/schema/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">
    
        <!-- 开启注解的扫描,希望处理 service 和 dao 层,controller 层不需要 Spring 框架去处理 -->
        <context:component-scan base-package="club.guoshizhan" >
            <!-- 配置哪些注解不扫描,这里配置了 @Controller 注解不扫描 -->
            <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller" />
        </context:component-scan>
    
    +   <!-- Spring 整合 MyBatis 框架 -->
    +   <!-- 配置 c3p0 连接池 -->
    +   <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
    +       <property name="driverClass" value="com.mysql.jdbc.Driver"/>
    +       <property name="jdbcUrl" value="jdbc:mysql:///ssm"/>
    +       <property name="user" value="root"/>
    +       <property name="password" value="root"/>
    +   </bean>
    +
    +   <!-- 配置 SqlSessionFactory 工厂 -->
    +   <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    +       <property name="dataSource" ref="dataSource" />
    +   </bean>
    +
    +   <!-- 配置 AccountDao 接口所在包 -->
    +   <bean id="mapperScanner" class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    +       <property name="basePackage" value="club.guoshizhan.dao"/>
    +   </bean>
    
    </beans>
    

    第二步: 把 dao 交给 Spring 容器管理,即给 AccountDao 接口加上 @Repository 注解。 新加代码如下:

    package club.guoshizhan.dao;
    
    import club.guoshizhan.domain.Account;
    import org.apache.ibatis.annotations.Insert;
    import org.apache.ibatis.annotations.Select;
    + import org.springframework.stereotype.Repository;
    
    import java.util.List;
    
    /**
     * 帐户 dao 接口
     */
    + @Repository
    public interface AccountDao {
        ……
    

    第三步: AccountServiceImpl 类中注入 dao ,从而实现 mybatis 的整合操作。 新加代码如下:

    package club.guoshizhan.service.impl;
    
    + import club.guoshizhan.dao.AccountDao;
    import club.guoshizhan.domain.Account;
    import club.guoshizhan.service.AccountService;
    + import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    @Service("accountService")
    public class AccountServiceImpl implements AccountService {
    
    +   @Autowired
    +   private AccountDao accountDao;
    
        public List<Account> findAll() {
            System.out.println("业务层:查询所有账户...");
    +       return accountDao.findAll();
        }
    
        public void saveAccount(Account account) {
            System.out.println("业务层:保存帐户...");
    +       accountDao.saveAccount(account);
        }
    
    }
    

    第四步: 编写 AccountController 类,从而可以在页面中展示数据。 代码如下:

    package club.guoshizhan.controller;
    
    import club.guoshizhan.domain.Account;
    import club.guoshizhan.service.AccountService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    import java.util.List;
    
    /**
     * 帐户的 web 控制层
     */
    @Controller
    @RequestMapping("/account")
    public class AccountController {
    
        @Autowired
        private AccountService accountService;
    
        // 查询所有操作
        @RequestMapping("/findAll")
        public String findAll(Model model) {
            System.out.println("表现层:查询所有账户...");
            // 调用 service 的方法
            List<Account> list = accountService.findAll();
            model.addAttribute("list", list);
            return "list";
        }
    
    }
    

    第五步: 编写 list.jsp 页面,把从数据库查到的数据展示出来。 代码如下:

    <%--
      Created by IntelliJ IDEA.
      User: guoshizhan
      Date: 2020/9/11
      Time: 22:24
      To change this template use File | Settings | File Templates.
    --%>
    <%@ page contentType="text/html;charset=UTF-8" language="java" isELIgnored="false" %>
    <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
    <h3>查询所有的帐户</h3>
    <c:forEach items="${list}" var="account">
        ${account.name} ==> ${account.money} <br/>
    </c:forEach>
    </body>
    </html>
    

    运行 tomcat ,然后去浏览器访问,点击测试查询。 结果如下:


    如果能够看到和上图类似的结果,那么恭喜你,你已经成功整合了 mybatis 。 但是呢,Spring 和 mybatis 的整合还没完。上述只是实现了从数据库查询,然后在页面展示出来。这个操作是不需要事务支持的。然而,我们的增删改操作是需要事务来支持的。所以,我们还需要实现事务的配置。 那么就接着往下看吧!!!


    第一步: 编写 applicationContext.xml 配置文件,新加声明式事务的配置。 新加代码如下:

    + <!-- 配置 Spring 框架声明式事务管理 -->
    + <!-- 配置事务管理器 -->
    + <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    +     <property name="dataSource" ref="dataSource" />
    + </bean>
    + 
    + <!-- 配置事务通知 -->
    + <tx:advice id="txAdvice" transaction-manager="transactionManager">
    +     <tx:attributes>
    +         <tx:method name="find*" read-only="true"/>
    +         <tx:method name="*" isolation="DEFAULT"/>
    +     </tx:attributes>
    + </tx:advice>
    + 
    + <!-- 配置 AOP 增强 -->
    + <aop:config>
    +     <aop:advisor advice-ref="txAdvice" pointcut="execution(* club.guoshizhan.service.impl.*ServiceImpl.*(..))"/>
    + </aop:config>
    

    第二步: AccountController 类中新加保存操作代码。 新加代码如下:

    + // 保存操作
    + @RequestMapping("/save")
    + public void save(Account account, HttpServletRequest request, HttpServletResponse response) throws IOException {
    +     accountService.saveAccount(account);
    +     response.sendRedirect(request.getContextPath() + "/account/findAll");
    + }
    

    第三步: 编写 index.jsp 页面(加入了表单提交相关的代码)。 代码如下:

    <%--
      Created by IntelliJ IDEA.
      User: guoshizhan
      Date: 2020/9/11
      Time: 22:18
      To change this template use File | Settings | File Templates.
    --%>
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>Title</title>
    </head>
    <body>
    <a href="account/findAll">测试查询</a>
    <h3>测试保存</h3>
    <form action="account/save" method="post">
        姓名:<input type="text" name="name"/><br/>
        金额:<input type="text" name="money"/><br/>
        <input type="submit" value="保存"/><br/>
    </form>
    </body>
    </html>
    

    运行 tomcat ,然后去浏览器访问,输入相关数据,点击测试保存。 结果如下:

    TIPS: SSM 框架的整合到此结束!!!感谢阅读完此文,有时间可以看看别的文章。

    Spring
    • 文章作者:GuoShiZhan
    • 创建时间:2021-08-16 11:53:45
    • 更新时间:2021-08-16 11:54:52
    • 版权声明:本文为博主原创文章,未经博主允许不得转载!
    请 在 评 论 区 留 言 哦 ~~~
    1024