SSM框架之Spring框架超详解(part one)

Spirng框架

这是spring的第一部分,第二部分见链接: spring第二部分

写在开始之前
spring框架共4天:
第一天:spring框架的概述以及spring 中基于xml的IOC配置
第二天:spring中基于注解的IOC和ioc的案例
第三天:spring中的aop和基于XML以及注解的AOP配置
第四天:spring中的JdbcTemplate以及Spring事务控制**(非常重要)**

================================

一、spring第一天

1.spring的概述

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

spring的两大核心?
IOC(inverse of control)反转控制
AOP(aspect oriented programing)面向切面编程
spring的发展历程和优势?
发展历程:
1997年IBM提出了EJB的思想
1998年,SUN制定开发标准规范EJB1.01999年,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)

优势:
方便解耦,简化开发
I通过Spring提供的IoC容器,可以将对象间的依赖关系交由 Spring进行控制,避免硬编码所造成的过度程序耦合。用户也不必再为单例模式类、属性文件解析等这些很底层的需求编写代码,可以更专注于上层的应用。
AOP编程的支持
通过spring的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松应付。
声明式事务的支持
通过配置的方式实现事务控制.
方便程序的测试
可以用非容器依赖的编程方式进行几乎所有的测试工作,测试不再是昂贵的操作,而是随手可做的事情。
方便集成各种优秀的框架
降低JavaEE API的使用难度
Java源码时经典学习范例

spring体系结构?
SSM框架之Spring框架超详解(part one)_第1张图片

2.程序的耦合及解耦

2.1 程序的耦合及解耦概念

耦合: 程序间的依赖关系。包括:类之间的依赖和方法间的依赖。我们不可能完全消除依赖。
解耦: 降低程序间的依赖关系。
实际开发中,应该做到:编译期不依赖,运行时才依赖。

解耦的思路:
①使用反射创建对象,而避免使用new 关键字。
②通过读取配置文件的方式来获取要创建的对象的全限定类名。

2.2 程序的耦合及解耦分析

曾经案例中的问题
SSM框架之Spring框架超详解(part one)_第2张图片
SSM框架之Spring框架超详解(part one)_第3张图片
如图所示,这两种情况具有很高的耦合性,我们需要解耦。

2.3 使用工厂模式解耦

创建一个Bean对象的工厂。
Bean:在计算机英语中,有可重用组件的含义。
JavaBean:用java语言编写的可重用组件
javabean 大于 实体类
他就是创建我们的service和dao对象的。
如何解耦?
第一个:需要一个配置文件来配置我们的service和dao配置的内容:唯一标识=全限定类名(key=value)
第二个: 通过读配置文件中配置的内容,反射创建对象。
我们的配置文件可以是xml也可以是properties.

在maven工程里的resources文件夹中建一个properties文件。如图所示,按照key=value的形式保存
在这里插入图片描述
在maven工程里建立一个bean工厂。注意:在获取properties文件的流对象过程中,我们使用beanFactory对象的类加载器加载bean.properties配置文件。这是开发中加载文件的一种主要方式。注意在加载完成后使用try-catch捕获异常,否则程序不能向下执行。
SSM框架之Spring框架超详解(part one)_第4张图片
书写getBean方法 。注意我们通过key得到value,然后使用反射的方式得到实体类对象,最后返回该对象。
SSM框架之Spring框架超详解(part one)_第5张图片
使用工厂中的getBean方法获取accountServiceImpl对象和accountDaoImpl对象
SSM框架之Spring框架超详解(part one)_第6张图片
SSM框架之Spring框架超详解(part one)_第7张图片
这就是工厂模式解耦的方式。

2.4 分析工厂模式中的问题并改造

经过上述代码测试,发现对于每执行一次,main方法,都会创建不同对象,也就是说这是多例的。如果是多例的,那么对象会被创建多次,执行效率就没有单例对象高。单例对象只会被创建一次,从而类中的成员也就只会初始化一次。
SSM框架之Spring框架超详解(part one)_第8张图片
但是,在本例中如果对象是单例的,那么可能会造成如下影响:
SSM框架之Spring框架超详解(part one)_第9张图片
例如:我在这里创建一个类的成员变量,其他人再次调用这个saveAccount方法时,这个成员变量的属性值就会发生改变。所以,如果是单例对象,我们在构建saveAccount方法时,要写成如下的形式:将变量写在方法体内,这样就不会造成创建单例对象后,成员变量i改变的问题,因为在每一次调用该方法时,i都初始化了。所以,在创建的对象是单例对象的过程中,我们要将变量定义在saveAccount中。
SSM框架之Spring框架超详解(part one)_第10张图片
基于上述思想,我们在过程中使用单例对象。如何完成单例对象的创建?经过分析,发现:
SSM框架之Spring框架超详解(part one)_第11张图片
因此,修改代码:

import java.util.Enumeration;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * 一个创建Bean对象的工厂
 *
 * Bean:在计算机英语中,有可重用组件的含义。
 * JavaBean:用java语言编写的可重用组件。
 *      javabean >  实体类
 *
 *   它就是创建我们的service和dao对象的。
 *
 *   第一个:需要一个配置文件来配置我们的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<String,Object>();
            //取出配置文件中所有的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的名称获取对象
     * @param beanName
     * @return
     */
    public static Object getBean(String beanName){
        return beans.get(beanName);
    }

    /**
     * 根据Bean的名称获取bean对象
     * @param beanName
     * @return

    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;
    }*/
}

3.IOC概念和spring中的IOC

3.1 IOC概念

如图所示,控制反转的概念就是对于本例来说,使用new的方式可以主动创建对象,但是却主动放弃这一创建对象的基本方式,而是通过使用工厂的方式,实现对象的创建。也就是说,我们不能主动创建对象,而把创建对象的权限交给工厂和配置文件,这被称为控制反转。控制反转的本质是解除程序之间的耦合。
SSM框架之Spring框架超详解(part one)_第12张图片

3.2 Spring中的IOC

3.2.1 spring中基于XML的IOC环境搭建(入门案例)

第一步:导入依赖坐标

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

第二步:配置propties文件,这里我们将配置文件明明为bean.xml文件,同时将其放在resources文件目录下
在这里插入图片描述
第三步:导入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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        http://www.springframework.org/schema/beans/spring-beans.xsd">
        <!--    把对象的创建交给spring来管理-->
    <bean id="accountService" class="com.itheima.service.impl.AccountServiceImpl"></bean>

    <bean id="accountDao" class="com.itheima.dao.impl.AccountDaoImpl"></bean>


</beans>

第四步:测试代码

/**
 * 模拟一个表现层,用于调用业务层
 */
public class Client {

    /**
     * 获取spring容器的ioc核心容器,根据id获取对象
     * @param args
     */
    public static void main(String[] args) {
        //1.获取核心容器对象
        ApplicationContext ac = new ClassPathXmlApplicationContext("bean.xml");
        //2.根据id获取Bean对象
        IAccountService as = (IAccountService) ac.getBean("accountService");
        IAccountDao accountDao = ac.getBean("accountDao", IAccountDao.class);
        //3.输出
        System.out.println(as);
        System.out.println(accountDao);


    }
}

备注:spring的实现类
SSM框架之Spring框架超详解(part one)_第13张图片
注意事项:我们如何找到接口的实现类?
答案:选中接口,右键:
SSM框架之Spring框架超详解(part one)_第14张图片
再次选中接口,右键,这样就得到了该接口的实现类。
SSM框架之Spring框架超详解(part one)_第15张图片

3.2.2 ApplicationContext的三个实现类:

ClassPathXmlApplicationContext:它可以加载类路径下的配置文件,要求配置文件必须在类路径下,不在的话加载不了。

FileSystemXmlApplicationContext:它可以加载磁盘任意路径下的配置文件(必须有访问权限)

public class Client {

    /**
     * 获取spring容器的ioc核心容器,根据id获取对象
     * @param args
     */
    public static void main(String[] args) {
        //1.获取核心容器对象
        ApplicationContext ac = new FileSystemXmlApplicationContext("D:\\workspace\\workspace_idea01\\day02_eesy_spring01\\src\\main\\resources\\bean.xml");
        //2.根据id获取Bean对象
        IAccountService as = (IAccountService) ac.getBean("accountService");
        IAccountDao accountDao = ac.getBean("accountDao", IAccountDao.class);
        //3.输出
        System.out.println(as);
        System.out.println(accountDao);


    }
}

AnnotationConfigApplicationContext:读取注解创建容器

3.2.3 ApplicationContext和BeanFactory的区别

ApplicationContext:在构建核心容器时,创建对象采取的策略是采用立即加载的方式。也就是说,只要一读取完配置文件马上就创建配置文件中配置的对象。实际开发中常用此接口来定义容器对象 使用场景:单例对象
BeanFactory:在构建核心容器时,创建对象采取的策略是采用延迟加载的方式。也就是说,什么时候使用对象了,什么时候才真正创建对象。
使用场景:多例对象

3.2.4 spring中bean的细节

三种创建bean对象的方式
第一种方式:使用默认构造函数创建。在spring的配置文件中,使用bean标签,配以id和class属性后,且没有其他属性和标签时。采用的就是默认构造函数创建bean对象。此时如果类中没有默认构造函数,则对象无法创建。
在这里插入图片描述
第二种方式:使用普通工厂中的方法创建对象(使用某个类中的方法创建对象,并存入spring容器。)
id表示key ,class表示实体类的全限定类名。factory-bean表示通过id找到工厂类的全限定类名,factory-method表示工厂类中返回创建对象的方法。
SSM框架之Spring框架超详解(part one)_第16张图片

在这里插入图片描述
第三种方法:使用工厂中的静态方法创建对象(使用某个类中的静态方法创建对象,并存入到spring容器中。)
SSM框架之Spring框架超详解(part one)_第17张图片
在这里插入图片描述

bean对象的作用范围
bean标签的scope属性
作用:用于指定bean的作用范围
取值:常用于前两个
singleton:单例的(默认值)
prototype:多例的
request:作用于web应用的请求范围
session:作用于web应用的会话范围
global-session:作用于集群环境的会话范围(全局会话范围),当不是集群环境时,他就是session.

如图所示为对global session的解析,由于用户的访问量过大,一台服务器不足以满足访问,所以可以设置多台服务器。当用户第一次访问网址时,负载均衡装置会自动检测哪台服务器处于空闲状态,并调动该服务器完成验证码保存到session域中的操作。当用户输入完验证码点击登录时,如果恰好刚才那台服务器满负荷了,而其他服务器空闲,那么负载均衡装置则会寻找空闲的其他服务器。这就会遇到一个问题。即session域并不能实现多台服务器之间的通用。那么,global session就由此而生。及多台服务器公用一个session域。
SSM框架之Spring框架超详解(part one)_第18张图片

bean对象的生命周期
单例对象
出生:当容器创建时对象出生
活着:只要容器还在对象一直活着
死亡:容器销毁,对象消亡
总结:单例对象的生命周期和容器相同
多例对象
出生:当我们使用对象时,spring框架为我们创建
活着:对象只要是在使用过程中就一直活着
死亡:对象长时间不用,且没有别的对象引用时,由Java垃圾回收器回收。

4.依赖注入(Dependency Injection)

SSM框架之Spring框架超详解(part one)_第19张图片

4.1 使用构造函数注入(set方法更为常用)

使用的标签: constructor-arg
标签出现的位置: bean标签的内部
标签中的属性:
type: 用于指定要注入的数据的数据类型,该数据类型也是构造函数中某个或某些参数的类型
index: 用于指定要注入的数据。给构造函数中指定索引位置的参数赋值,索引位置是从0开始。
name: 用于指定给构造函数中指定名称的参数赋值。(最为常用)
/==================================

value: 用于提供基本类型和String类型的数据
ref: 用于指定其他的bean类型的数据。它指的就是在spring的IOC核心容器中出现的bean对象

优势: 在获取bean对象时,注入数据时必须的操作,否则对象无法创建成功。
弊端: 改变了bean对象的实例化方式,使我们在创建对象时,如果用不到这些数据,也必须提供。
SSM框架之Spring框架超详解(part one)_第20张图片

4.2 使用set方法注入

涉及的标签: property
出现的位置: bean标签的内部
标签的属性:
name: 用于指定注入时所调用的set方法名称
value: 用于提供基本类型和string类型的数据
ref: 用于指定其他的bean类型的数据。它指的就是在spring的Ioc核心容器中出现过的bean对象。

优势: 创建对象时没有明确的限制,可以直接使用默认构造函数。
弊端: 如果某个成员必须有值,则获取对象时有可能set方法没有执行。
SSM框架之Spring框架超详解(part one)_第21张图片
在这里插入图片描述

4.3 复杂类型(集合类型)注入

为以下结构注入数据:
SSM框架之Spring框架超详解(part one)_第22张图片

用于给list结构集合注入的标签:list array set
用于给map结构集合注入的标签: map props

结构相同,标签可以互换
SSM框架之Spring框架超详解(part one)_第23张图片
SSM框架之Spring框架超详解(part one)_第24张图片

5.作业:

spring第二天:spring基于注解的IOC以及IOC的案例

1.spring中IOC的常用注解
2.案例:使用xml和注解方式实现单表的crud操作,持久层技术选择dbutils。
3.改造基于注解的ioc案例,使用纯注解的方式实现:spring的一些新注解的使用。
4.spring和junit的整合

1 spring中IOC的常用注解

注解的几种类型:
SSM框架之Spring框架超详解(part one)_第25张图片
千万注意!!!!!!!!!!!!!!!!!!使用注解之前需要先进行配置操作:
我们首先要做到告知spring在创建容器时要 扫描的包,注意:配置所需要扫描的包不是在bean中配置的,而是在context的名称空间和约束中配置的。因此,我们要在beans中,导入xmlns:context这个配置,这样才能使用context标签完成导入工作。
SSM框架之Spring框架超详解(part one)_第26张图片

1.1.1用于创建对象的:

@Component
作用: 用于把当前类对象存入spring容器中。
属性: value 用于指定bean的id.当我们不写时,它的默认值是当前类名,且首字母改小写。
使用图片如下:在使用过程中,我们如果说只有这一个属性需要写,那么我们也可以不用写value名,只需要在小括号内部填入字符串即可。但是如果有两个属性,那么就必须写上value
在这里插入图片描述
由component衍生的注解
@Controller一般用在表现层
@Service一般用在业务层
@Repository一般用在持久层
以上三个注解他们的作用和属性与Component是一摸一样的。他们三个是spring框架为我们提供明确的三层使用的注解,使我们的三层对象更加清晰。如果有些对象不属于三层中的任何一层,那么就用component来创建对象即可。

1.1.2 用于注入数据的:

他们的作用就和在xml配置文件中的bean标签中写一个标签的作用是一样的
@Autowired:
作用:自动按照类型注入。只要容器中有唯一的一个bean对象类型注入的变量类型匹配,就可以注入成功。如果ioc容器中没有任何bean的类型和要注入的变量类型匹配,就报错。
出现位置: 可以是变量上,也可以是方法上
细节: 使用注解注入时,set方法不是必须的
注入理解: 如图所示,首先根据IAccountDao在IOC容器中寻找相对应的数据类型,发现有两项是匹配的。这时,会继续按照变量名称寻找,看变量名称与容器中的key是否匹配,如果匹配则找到对应的数据,找不到就会报错。(容器中的key指的就是我们在注解中写的value属性值)
SSM框架之Spring框架超详解(part one)_第27张图片
@Qualifier:
作用: 按照类中注入的基础之上再按照名称注入。它在给类成员注入时不能单独使用,但是在给方法参数注入时可以单独使用。
属性: value:用于指定注入bean的id.
如图所示,可以看到在给类成员注入的过程中,他必须要有autowired注解,然后再按照他的value属性值注入。如果只写Qualifier注解,则会导致测试时,无法注入。
SSM框架之Spring框架超详解(part one)_第28张图片
@Resource
作用: 直接按照bean的id注入,它可以独立使用。
属性 name:用于指定bean的id。
在这里插入图片描述
以上三个注入都只能注入其他bean类型的数据,而基本类型和String类型无法使用上述注解实现。
另外,集合类型的注入只能通过XML来实现。

@Value
作用: 用于注入基本类型和String类型的数据
属性: value:用于指定数据的值,它可以使用spring中的SpEl(也就是spring中的el表达式)
SpEl的写法:${表达式}

1.1.3 用于改变作用范围的注解

他们的作用就和在bean标签中使用scope属性实现的功能是一样的
@Scope
作用: 用于指定bean的作用范围
属性: value:指定范围的取值。常用取值:singleton prototype
在这里插入图片描述

1.1.4 和生命周期相关的注解(了解)

@PreDestroy
作用: 用于指定销毁方法
@PostConstruct
作用: 用于指定初始化方法
SSM框架之Spring框架超详解(part one)_第29张图片

1.2 spring基于XML的ioc案例

===================================================================
写在案例之前:
注意事项:
①对于QueryRunner对象而言,我们可以带参创建构造函数,也可以无参创建构造函数。但二者有区别。看你希望每条语句独立一个事务,还是所有的语句在同一个事务中。由于我们仅展示单表(一条语句的crud操作),所以我们可以选择传入数据源(连接池)的。

QueryRunner(DataSource ds) 带连接池的构造方法
如果使用的是带连接池的构造方法,那么QueryRunnery类会自动的从连接池中获取conn对象
使用完毕会自动把conn对象归还给连接池

②在配置QueryRunner时,需要把scope设置成prototype的形式。因为不同的dao在调用queryRunner时,如果queryRunner是单例的,会导致线程安全问题。(为什么?)
首先,我们知道,对于dao层和service层来说,这两层的配置是单例的。这是为了提高性能,因为单例总比多例的效率高。

如果一个对象被声明为单例的时候,在处理多次请求的时候在spring容器里只实例化出一个对象,后续的请求都公用这个对象,这个对象会保存在一个map里面。当有请求来的时候会先从缓存(map)里查看有没有,有的话直接使用这个对象,没有的话才实例化一个新的对象,所以这是个单例的。

那么,单例模式也有劣势。它会导致线程的安全问题。比如,我们实现增删改查时,单例对象有多个可操作方法,可能会导致线程的不安全。因此,为了解决线程不安全的问题,我们需要将QueryRunner类设置成多例对象的形式解决线程的不安全问题。
在这里插入图片描述

===================================================================
在数据库中创建表。三个属性 Integer id ,String name, float money.

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('aaa',1000);
INSERT INTO account(NAME,money) VALUES('bbb',1000);
INSERT INTO account(NAME,money) VALUES('ccc',1000);

1.建立文件,导入依赖

 <groupId>com.lihao</groupId>
    <artifactId>day02_eesy_02account_myxmlioc</artifactId>
    <version>1.0</version>
    <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>
    </dependencies>

2.建立domain类

package com.lihao.domain;

import java.io.Serializable;

/**
 * @author lhstart
 * @create 2021-11-10-23:27
 */
public class Account implements Serializable {
    private Integer id;
    private String name;
    private Float money;

    public Account() {
    }

    public Account(Integer id, String name, Float money) {
        this.id = id;
        this.name = name;
        this.money = 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 +
                '}';
    }
}

3.建立文件结构

SSM框架之Spring框架超详解(part one)_第30张图片

4.建立dao层

package com.lihao.dao;

import com.lihao.domain.Account;

import java.util.List;

/**
 * @author lhstart
 * @create 2021-11-10-23:36
 */
public interface IAccountDao {

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

    /**
     * 按照id查询账户
     */
    Account findAccountById(Integer accountId);

    /**
     * 保存账户
     * @param account
     */
    void saveAccount(Account account);

    /**
     * 修改账户
     * @param account
     */
    void updateAccount(Account account);

    /**
     * 删除账户
     * @param accountId
     */
    void deleteAccount(Integer accountId);
}
package com.lihao.dao.impl;

import com.lihao.dao.IAccountDao;
import com.lihao.domain.Account;
import org.apache.commons.dbutils.QueryRunner;
import org.apache.commons.dbutils.handlers.BeanHandler;
import org.apache.commons.dbutils.handlers.BeanListHandler;

import java.sql.SQLException;
import java.util.List;

/**
 * @author lhstart
 * @create 2021-11-10-23:37
 */
public class IAccountDaoImpl implements IAccountDao {

    QueryRunner queryRunner;

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

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

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

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

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

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

5.建立service层

package com.lihao.service;

import com.lihao.domain.Account;

import java.util.List;

/**
 * @author lhstart
 * @create 2021-11-11-18:06
 */
public interface IAccountService {
    /**
     * 查询所有账户
     */
    List<Account> findAll();

    /**
     * 按照id查询账户
     */
    Account findAccountById(Integer accountId);

    /**
     * 保存账户
     * @param account
     */
    void saveAccount(Account account);

    /**
     * 修改账户
     * @param account
     */
    void updateAccount(Account account);

    /**
     * 删除账户
     * @param accountId
     */
    void deleteAccount(Integer accountId);
}

package com.lihao.service.impl;

import com.lihao.dao.IAccountDao;
import com.lihao.domain.Account;
import com.lihao.service.IAccountService;

import java.util.List;

/**
 * @author lhstart
 * @create 2021-11-11-18:07
 */
public class IAccountServiceImpl implements IAccountService {
    private IAccountDao accountDao;


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

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

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

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

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

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

6.书写配置文件

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

<!--    创建accountService对象-->
    <bean id="accountService" class="com.lihao.service.impl.IAccountServiceImpl">
<!--        使用set方法注入-->
        <property name="accountDao" ref="accoutDao"></property>
    </bean>
<!--创建accountDao对象-->
    <bean id="accoutDao" class="com.lihao.dao.impl.IAccountDaoImpl">
<!--      使用set方法注入  -->
        <property name="queryRunner" ref="queryRunner"></property>
    </bean>

<!--创建queryRunner对象,使用多例模式,并且使用构造函数注入的方式注入数据库连接池-->
    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--    给queryrunner中的参数传入连接池对象        -->
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>

<!--    给连接池对象配置参数-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy_mybatis"></property>
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="user" value="root"></property>
        <property name="password" value="041811"></property>
    </bean>


</beans>

7.测试代码

package com.lihao.test;

import com.lihao.domain.Account;
import com.lihao.service.IAccountService;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * @author lhstart
 * @create 2021-11-11-22:06
 */
public class testMethod {

    @Test
    public void testFindAll(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
        IAccountService iAccountService = applicationContext.getBean("accountService",IAccountService.class);


        for (Account account : iAccountService.findAll()) {
            System.out.println(account);
        }
    }

    @Test
    public void testFindAccountById(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
        IAccountService iAccountService = applicationContext.getBean("accountService",IAccountService.class);


        System.out.println(iAccountService.findAccountById(1));
    }

    @Test
    public void saveAccount(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
        IAccountService iAccountService = applicationContext.getBean("accountService",IAccountService.class);

        Account account = new Account();
        account.setName("刘爽");
        account.setMoney(12345f);

        iAccountService.saveAccount(account);
    }

    @Test
    public void updateAccount(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
        IAccountService iAccountService = applicationContext.getBean("accountService",IAccountService.class);

        Account account = new Account();
        account.setName("刘正");
        account.setMoney(1234500000f);
        account.setId(4);

        iAccountService.updateAccount(account);
    }
    @Test
    public void deleteAccount(){
        ApplicationContext applicationContext = new ClassPathXmlApplicationContext("bean.xml");
        IAccountService iAccountService = applicationContext.getBean("accountService",IAccountService.class);


        iAccountService.deleteAccount(4);
    }

}

1.2 spring基于注解的ioc案例

1.修改xml配置文件注意context的导入

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

2.注意注解识别包的过程

<!--    千万注意!!!!!!!!!!!!!必须导入包-->
    <context:component-scan base-package="com.lihao"></context:component-scan>


<!--创建queryRunner对象,使用多例模式,并且使用构造函数注入的方式注入数据库连接池-->
    <bean id="queryRunner" class="org.apache.commons.dbutils.QueryRunner" scope="prototype">
<!--    给queryrunner中的参数传入连接池对象        -->
        <constructor-arg name="ds" ref="dataSource"></constructor-arg>
    </bean>

<!--    给连接池对象配置参数-->
    <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/eesy_mybatis"></property>
        <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
        <property name="user" value="root"></property>
        <property name="password" value="041811"></property>
    </bean>


</beans>

3.添加注解给AccountDaoImpl类和AccountServiceImpl类

@Component("accountService")
public class IAccountServiceImpl implements IAccountService {

    @Autowired
    private IAccountDao accountDao;
@Component("accountDao")
public class IAccountDaoImpl implements IAccountDao {

    @Autowired
    QueryRunner queryRunner;


4.再次测试,得到结果

2. spring中的新注解

经过上述编程 ,发现有两个问题需要解决。

1.测试类代码重复
2.无法脱离配置文件实现注解

我们先解决脱离配置文件的问题。

2.1spring中的新注解相关解析和配置操作:

我们可以尝试新建立一个配置类,它的作用和bean.xml是一样的。
@ Configuration
作用:指定当前类是一个配置类
细节:当配置类作为AnnotationConfigApplicationContext对象创建的参数时,该注解可以不写。
@ComponentScan
作用:用于通过注解指定spring在创建容器时要扫描的包
属性: value:它和basePackages的作用是一样的,都是用于指定创建容器时要扫描的包。
我们使用此注解就等同于在xml中配置了:

@Bean
作用:用于把当前方法的返回值作为bean对象存入spring的ioc容器中
属性:
name:用于指定bean的id。当不写时,默认值是当前方法的名称
细节:
当我们使用注解配置方法时,如果方法有参数,spring框架会去容器中查找有没有可用的bean对象。并实现注入操作。 查找的方式和Autowired注解的作用是一样的。

@Import
作用:用于导入其他的配置类
属性:
value:用于指定其他配置类的字节码。
当我们使用Import的注解之后,有Import注解的类就父配置类,而导入的都是子配置类
@PropertySource
作用:用于指定properties文件的位置
属性:value:指定文件的名称和路径。
关键字:classpath,表示类路径下

2.2spring中的新注解运用到实际案例中:

1.配置SpringConfiguration父配置类

package 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;

/**
 * @author lhstart
 * @create 2021-11-13-18:06
 */
@ComponentScan("com.lihao")//用于通过注解指定spring在创建容器时,要扫描的包
@Configuration//用于指定当前类是一个配置类
@Import(JdbcConfig.class)//用于导入子配置类JdbcConfig.java
@PropertySource("classpath:jdbcConfig.properties")//用于给配置文件指定相关类路径,这样value注解才可以注入数据。
public class SpringConfiguration {



}

2.配置JdbcConfig子文件配置类

package 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;

/**
 * @author lhstart
 * @create 2021-11-13-18:11
 */
public class JdbcConfig {
    @Value("${jdbc.driver}")//value注解表示通过eI表达式来对字符串类型的数据实现注入操作
    private String driver;

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

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

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

    /**
     * 用于创建一个QueryRunner对象
     * @param dataSource
     * @return
     */
    @Bean(name="runner")//bean注解表示将QueryRunner对象注入到容器中。name属性默认是方法名称,
    // name属性对应的是核心容器中的key,value属性对应的是返回值
    @Scope("prototype")//scope注解将QueryRunner设置为多例对象
    public QueryRunner createQueryRunner(DataSource dataSource){
        return new QueryRunner(dataSource);
    }

    /**
     * 创建数据源对象
     * @return
     */
    @Bean
    public DataSource createDataSource(){
        try {
            ComboPooledDataSource ds = new ComboPooledDataSource();
            ds.setDriverClass(driver);
            ds.setJdbcUrl(url);
            ds.setUser(username);
            ds.setPassword(password);
            return ds;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }


}

3.配置数据库连接池配置文件

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

4.由于是使用注解配置的文件,所以相应修改测试方法

public class testMethod {

    @Test
    public void testFindAll(){
        ApplicationContext applicationContext = new AnnotationConfigApplicationContext(SpringConfiguration.class);
        IAccountService iAccountService = applicationContext.getBean("accountService",IAccountService.class);


        for (Account account : iAccountService.findAll()) {
            System.out.println(account);
        }
    }
 }   

2.3补充说明:@Qualifier中注解的另一种使用方法

对如下代码中的使用情况进行说明:
@Qualifier中注解并不是不能独立使用, 而是在给类成员注入的时候,必须和@Autowired一起使用。给方法参数注入的时候,可以直接使用。
②在使用过程中,我们认为,spring框架会先按照类型注入,(注意查看@Bean注解),当没有类型匹配或者有多个匹配且形参名称无法在多个匹配中找到符合名称的id时,就会报错。在这种情况下,qualifier就起到了它的作用。

package config;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.apache.commons.dbutils.QueryRunner;
import org.springframework.beans.factory.annotation.Qualifier;
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;

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

    /**
     * 创建数据源对象
     * @return
     */
    @Bean(name="ds2")
    public DataSource createDataSource(){
        try {
            ComboPooledDataSource ds = new ComboPooledDataSource();
            ds.setDriverClass(driver);
            ds.setJdbcUrl(url);
            ds.setUser(username);
            ds.setPassword(password);
            return ds;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    @Bean(name="ds1")
    public DataSource createDataSource1(){
        try {
            ComboPooledDataSource ds = new ComboPooledDataSource();
            ds.setDriverClass(driver);
            ds.setJdbcUrl("jdbc:mysql://localhost:3306/eesy02");
            ds.setUser(username);
            ds.setPassword(password);
            return ds;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }
}

2.4 Spring整合Junit问题

为什么要整合?
因为在使用测试类进行测试的过程中,我们为了避免代码冗余,需要对代码进行简化。为此,可以考虑整合。

2.4.1 Spring整合Junit问题的分析

SSM框架之Spring框架超详解(part one)_第31张图片

2.4.1 Spring整合Junit问题的解决方案

1、导入spring整合junit的jar(坐标)

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

2、使用Junit提供的一个注解把原有的main方法替换了,替换成spring提供的
@Runwith

3、告知spring的运行器,spring和ioc创建是基于xml还是注解的,并且说明位置 。
使用@ContextConfiguration注解

locations:指定xml文件的位置,加上classpath关键字,表示在类路径下
classes:指定注解类所在地位置

@RunWith(SpringJUnit4ClassRunner.class)//这个注解时为了实现容器的创建
@ContextConfiguration(classes = SpringConfiguration.class)//这个注解是为了指明使用注解类
public class AccountServiceTest {
	//注意还需要进行数据注入
    @Autowired
    private IAccountService as = null;


    @Test
    public void testFindAll() {
        //3.执行方法
        List<Account> accounts = as.findAllAccount();
        for(Account account : accounts){
            System.out.println(account);
        }
    }

    @Test
    public void testFindOne() {
        //3.执行方法
        Account account = as.findAccountById(1);
        System.out.println(account);
    }

    @Test
    public void testSave() {
        Account account = new Account();
        account.setName("test anno");
        account.setMoney(12345f);
        //3.执行方法
        as.saveAccount(account);

    }

    @Test
    public void testUpdate() {
        //3.执行方法
        Account account = as.findAccountById(4);
        account.setMoney(23456f);
        as.updateAccount(account);
    }

    @Test
    public void testDelete() {
        //3.执行方法
        as.deleteAccount(4);
    }
}
}

注意!!!当我们使用spring 5.x版本的时候,要求junit的jar必须是4.12及以上。

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

补充:xml配置,实现Spring的junit整合。只需修改如下代码即可
SSM框架之Spring框架超详解(part one)_第32张图片
//========================================================================

三、spring第三天

1.完善我们的account案例
2.分析案例中的问题
3.回顾之前讲过的一个技术:动态代理
4.动态代理的另一种实现方式
5.解决案例中的问题
6.AOP的概念
7.spring中AOP的相关属于
8.spring中基于XML和注解的AOP配置

1. account案例的完善

1.在accountService接口中添加转账方法
SSM框架之Spring框架超详解(part one)_第33张图片
2.在accountDao接口中添加根据名称查询账户方法
SSM框架之Spring框架超详解(part one)_第34张图片

3.在accountDaoImpl类中实现根据名称查询账户方法
SSM框架之Spring框架超详解(part one)_第35张图片
4.在accountServiceImpl实现类中实现transfer方法
SSM框架之Spring框架超详解(part one)_第36张图片
5.在测试类中进行测试
SSM框架之Spring框架超详解(part one)_第37张图片
6.测试结果发现aaa 向bbb转账100元。引入异常,继续测试。
SSM框架之Spring框架超详解(part one)_第38张图片
7.测试结果发现只有aaa转出100,而bbb没有收到。

2. account案例的分析

如图所示,进行分析。我们发现,在accountDao执行findAccountByName方法时,都会通过queryRunner对象执行。而queryRunner对象是多例的,这也就意味着,在每次与数据库的交互过程中,都会创建一个queryRunner对象,该对象调用数据库中的数据来获取连接实现交互。所以,这也就意味着在每次执行与数据库交互的方法时,都会获取一个连接。所以,当出现异常时,没有获取到连接,也就会导致执行方法的失败。所以,为了保证完整的业务完成。需要使得这个业务仅通过一个连接来完成。这样,业务内部出现异常,该业务也不会提交。

要确保所有操作,要么都成功,要么都失败,就必须使用数据库的事务。
要确保所有操作都在一个事务内,就必须要确保所有操作都使用一个Connection连接对象。
如何确保所有操作都用同一个对象的前提条件是所有操作都必须在同一个线程中完成。
解决方案:
1.需要使用ThreadLocal类。

SSM框架之Spring框架超详解(part one)_第39张图片
2.编写ConnectionUtils工具类

package com.itheima.utils;

import javax.sql.DataSource;
import java.sql.Connection;

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

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

    private DataSource dataSource;

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    /**
     * 获取当前线程上的连接
     * @return
     */
    public Connection getThreadConnection() {
        try{
            //1.先从ThreadLocal上获取
            Connection conn = tl.get();
            //2.判断当前线程上是否有连接
            if (conn == null) {
                //3.从数据源中获取一个连接,并且存入ThreadLocal中
                conn = dataSource.getConnection();
                tl.set(conn);
            }
            //4.返回当前线程上的连接
            return conn;
        }catch (Exception e){
            throw new RuntimeException(e);
        }
    }

    /**
     * 把连接和线程解绑
     */
    public void removeConnection(){
        tl.remove();
    }
}

3.编写事务管理工具类并分析连接和线程解绑
细节:我们的连接都使用了连接池。连接池的好处是把消耗连接、获取连接的部分在一开始就应用加载。在web工程时,当我们启动tomcat,就创建一些连接,在后面的阶段和数据库交互时,不再获取连接。 保证使用connection的执行效率。

这时,我们使用服务器。服务器有线程池。tomcat启动时,会启动线程池,并初始化加载一大堆的线程。而连接调用close方法并不是关闭连接,而是把连接返回连接池中。同样,线程用完,还回线程池。当我们把连接关闭,线程还回线程池中时,线程池上是有连接的。只不过这个连接已经被关闭了。当我们下次再获取线程有没有连接时,连接有,但是不能使用了。所以在整个用完之后,把线程和连接进行一个解绑操作。当然,我们现在是java,不涉及这个问题。当然,把眼光放长远,我们做javaee开发,就会涉及到这个问题。
使用如下方法完成连接和线程的解绑操作


    /**
     * 把连接和线程解绑
     */
    public void removeConnection(){
        tl.remove();
    }
}

4.创建事务管理相关的工具类

package com.itheima.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();
        }
    }
}

5.编写业务层和持久层事务控制代码并配置spring中的ioc
修改对应的业务层代码

package com.itheima.service.impl;

import com.itheima.dao.IAccountDao;
import com.itheima.domain.Account;
import com.itheima.service.IAccountService;
import com.itheima.utils.TransactionManager;

import java.util.List;

/**
 * 账户的业务层实现类
 *
 * 事务控制应该都是在业务层
 */
public class AccountServiceImpl_OLD 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> findAllAccount() {
        try {
            //1.开启事务
            txManager.beginTransaction();
            //2.执行操作
            List<Account> accounts = accountDao.findAllAccount();
            //3.提交事务
            txManager.commit();
            //4.返回结果
            return accounts;
        }catch (Exception e){
            //5.回滚操作
            txManager.rollback();
            throw new RuntimeException(e);
        }finally {
            //6.释放连接
            txManager.release();
        }

    }

    @Override
    public Account findAccountById(Integer accountId) {
        try {
            //1.开启事务
            txManager.beginTransaction();
            //2.执行操作
            Account account = accountDao.findAccountById(accountId);
            //3.提交事务
            txManager.commit();
            //4.返回结果
            return account;
        }catch (Exception e){
            //5.回滚操作
            txManager.rollback();
            throw new RuntimeException(e);
        }finally {
            //6.释放连接
            txManager.release();
        }
    }

    @Override
    public void saveAccount(Account account) {
        try {
            //1.开启事务
            txManager.beginTransaction();
            //2.执行操作
            accountDao.saveAccount(account);
            //3.提交事务
            txManager.commit();
        }catch (Exception e){
            //4.回滚操作
            txManager.rollback();
        }finally {
            //5.释放连接
            txManager.release();
        }

    }

    @Override
    public void updateAccount(Account account) {
        try {
            //1.开启事务
            txManager.beginTransaction();
            //2.执行操作
            accountDao.updateAccount(account);
            //3.提交事务
            txManager.commit();
        }catch (Exception e){
            //4.回滚操作
            txManager.rollback();
        }finally {
            //5.释放连接
            txManager.release();
        }

    }

    @Override
    public void deleteAccount(Integer acccountId) {
        try {
            //1.开启事务
            txManager.beginTransaction();
            //2.执行操作
            accountDao.deleteAccount(acccountId);
            //3.提交事务
            txManager.commit();
        }catch (Exception e){
            //4.回滚操作
            txManager.rollback();
        }finally {
            //5.释放连接
            txManager.release();
        }

    }

    @Override
    public void transfer(String sourceName, String targetName, Float money) {
        try {
            //1.开启事务
            txManager.beginTransaction();
            //2.执行操作

            //2.1根据名称查询转出账户
            Account source = accountDao.findAccountByName(sourceName);
            //2.2根据名称查询转入账户
            Account target = accountDao.findAccountByName(targetName);
            //2.3转出账户减钱
            source.setMoney(source.getMoney()-money);
            //2.4转入账户加钱
            target.setMoney(target.getMoney()+money);
            //2.5更新转出账户
            accountDao.updateAccount(source);

            int i=1/0;

            //2.6更新转入账户
            accountDao.updateAccount(target);
            //3.提交事务
            txManager.commit();

        }catch (Exception e){
            //4.回滚操作
            txManager.rollback();
            e.printStackTrace();
        }finally {
            //5.释放连接
            txManager.release();
        }


    }
}

6修改对应的持久层代码

package com.itheima.dao.impl;

import com.itheima.dao.IAccountDao;
import com.itheima.domain.Account;
import com.itheima.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> findAllAccount() {
        try{
            return runner.query(connectionUtils.getThreadConnection(),"select * from account",new BeanListHandler<Account>(Account.class));
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Account findAccountById(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 saveAccount(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 updateAccount(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 deleteAccount(Integer accountId) {
        try{
            runner.update(connectionUtils.getThreadConnection(),"delete from account where id=?",accountId);
        }catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Override
    public Account findAccountByName(String accountName) {
        try{
            List<Account> accounts = runner.query(connectionUtils.getThreadConnection(),"select * from account where name = ? ",new BeanListHandler<Account>(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);
        }
    }
}

7修改相应的配置文件

<?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="com.itheima.service.impl.AccountServiceImpl">
        <!-- 注入dao -->
        <property name="accountDao" ref="accountDao"></property>
    </bean>

    <!--配置Dao对象-->
    <bean id="accountDao" class="com.itheima.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/eesy"></property>
        <property name="user" value="root"></property>
        <property name="password" value="1234"></property>
    </bean>

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

    <!-- 配置事务管理器-->
    <bean id="txManager" class="com.itheima.utils.TransactionManager">
        <!-- 注入ConnectionUtils -->
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>
</beans>

3. account案例的再简练

经过上述处理,发现service层代码量过多。我们希望简化代码,既能够实现代码的功能,又能够让代码得到简化。此外,如果代码量过多,还会出现如果修改transcationManager实体类中的一个方法就会导致所有的调用方法的地方全部修改的问题。

3.1 使用动态代理的方式对代码进行简化

3.1.1 基于接口的动态代理回顾

动态代理特点: 字节码随用随创建,随用随加载。与装饰着模式不同,装饰着模式一上来必须先创建好一个类。
作用: 不修改源码的基础上,对方法增强。
分类:基于接口的动态代理和基于子类的动态代理
基于接口的动态代理:
涉及的类:proxy
提供者:JDK官方
如何创建代理对象:使用proxy类中的newProxyInstance方法
创建代理对象的要求
被代理类最少实现一个接口,如果没有则不能使用

newProxyInstance方法的参数:
ClassLoader 它是用于加载代理对象字节码的,写的是被代理对象的类加载器,和被代理对象使用相同的类加载器。xxx.getClass().getClassLoader();

Class[] : 字节码数组。它是用于让代理对象和被代理对象有相同的方法xxx.getClass.getInterfaces();

InvocationHandler: 用于提供增强的代码:
让我们写如何代理。我们一般都是写一个该接口的实现类,通常情况下都是匿名内部类,但不必须。此接口的实现类都是谁用谁写。

invoke作用: 执行被代理对象的任何接口方法都会经过该方法

proxy: 代理对象的引用
method: 表示当前执行的方法(注意:匿名内部类访问外部成员变量时,外部成员必须是最终的,用final修饰。
args: 当前执行方法所需的参数
reutrn返回值: 和被代理对象有相同的返回值

如下代码是一个动态代理的例子:
代理可以这样理解。首先,代理类和被代理类有相同的接口,被代理类实现了这个接口。但是我们在调用方法时,只是调用代理类执行的方法,这个方法已经对被代理类的方法强化了。

package com.itheima.proxy;

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

public class Client {

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

        /**
         * 动态代理:
         *  特点:字节码随用随创建,随用随加载
         *  作用:不修改源码的基础上对方法增强
         *  分类:
         *      基于接口的动态代理
         *      基于子类的动态代理
         *  基于接口的动态代理:
         *      涉及的类:Proxy
         *      提供者:JDK官方
         *  如何创建代理对象:
         *      使用Proxy类中的newProxyInstance方法
         *  创建代理对象的要求:
         *      被代理类最少实现一个接口,如果没有则不能使用
         *  newProxyInstance方法的参数:
         *      ClassLoader:类加载器
         *          它是用于加载代理对象字节码的。和被代理对象使用相同的类加载器。固定写法。
         *      Class[]:字节码数组
         *          它是用于让代理对象和被代理对象有相同方法。固定写法。
         *      InvocationHandler:用于提供增强的代码
         *          它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
         *          此接口的实现类都是谁用谁写。
         */
         注意:必须是IProducer接口。不能是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;

                        //1.获取方法执行的参数
                        Float money = (Float)args[0];
                        
                        if("saleProduct".equals(method.getName())) {
                            returnValue = method.invoke(producer, money*0.8f);
                        }
                        return returnValue;
                    }
                });
        proxyProducer.saleProduct(10000f);
    }
}

要实现的功能是代理对象proxyProducer 调用被代理对象的saleProduct方法时,对这个方法进行增强。(这里演示的例子是方法增强之后,钱变为原来的80%)

基于接口的动态代理方式有一个弊端:如果没有接口,那这个方式无法使用。

3.1.2 基于子类的动态代理回顾

这种动态代理方式需要导坐标:
SSM框架之Spring框架超详解(part one)_第40张图片
基于子类的动态代理实现及讲解:

public class Client {

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

        /**
         * 动态代理:
         *  特点:字节码随用随创建,随用随加载
         *  作用:不修改源码的基础上对方法增强
         *  分类:
         *      基于接口的动态代理
         *      基于子类的动态代理
         *  基于子类的动态代理:
         *      涉及的类:Enhancer
         *      提供者:第三方cglib库
         *  如何创建代理对象:
         *      使用Enhancer类中的create方法
         *  创建代理对象的要求:
         *      被代理类不能是最终类
         *  create方法的参数:
         *      Class:字节码
         *          它是用于指定被代理对象的字节码。
         *
         *      Callback:用于提供增强的代码
         *          它是让我们写如何代理。我们一般都是些一个该接口的实现类,通常情况下都是匿名内部类,但不是必须的。
         *          此接口的实现类都是谁用谁写。
         *          我们一般写的都是该接口的子接口实现类:MethodInterceptor
         */
        Producer cglibProducer = (Producer)Enhancer.create(producer.getClass(), new MethodInterceptor() {
            /**
             * 执行北地阿里对象的任何方法都会经过该方法
             * @param proxy
             * @param method
             * @param args
             *    以上三个参数和基于接口的动态代理中invoke方法的参数是一样的
             * @param methodProxy :当前执行方法的代理对象
             * @return
             * @throws Throwable
             */
            @Override
            public Object intercept(Object proxy, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                //提供增强的代码
                Object returnValue = null;

                //1.获取方法执行的参数
                Float money = (Float)args[0];
                //2.判断当前方法是不是销售
                if("saleProduct".equals(method.getName())) {
                    returnValue = method.invoke(producer, money*0.8f);
                }
                return returnValue;
            }
        });
        cglibProducer.saleProduct(12000f);
    }
}
3.1.3 动态代理的优点:

(1)close方法在关闭时,不能正常关闭,那就可以使用动态代理的方式对connection的close方法进行增强,把他加回到池中去。
(2)解决全栈中文乱码的request对象的方法增强,用装饰者模式和动态代理模式都可以实现,就是对getParameter、getParameterValues和getParameterMap。

3.1.4 动态代理实现事务控制:

1.使用BeanFactory工厂创建accountService的代理对象

package com.itheima.factory;

import com.itheima.service.IAccountService;
import com.itheima.utils.TransactionManager;

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

/**
 * 用于创建Service的代理对象的工厂。注意在解读代码的过程中,method.invoke调用的是被代理类的对象。也就是在强化的方法的过程中,还调用了被代理类需要执行的方法(实现增删改查)。而整体上,如果我们使用的是业务层的代理对象进行测试,那么就使用的方法是已经强化过后的方法。
 */
public class BeanFactory {

    private IAccountService accountService;

    private TransactionManager txManager;

    public void setTxManager(TransactionManager txManager) {
        this.txManager = txManager;
    }

//注意加上final
    public final void setAccountService(IAccountService accountService) {
        this.accountService = accountService;
    }

    /**
     * 获取Service代理对象
     * @return
     */
    public IAccountService getAccountService() {
        return (IAccountService)Proxy.newProxyInstance(accountService.getClass().getClassLoader(),
                accountService.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 {

                        if("test".equals(method.getName())){
                            return method.invoke(accountService,args);
                        }

                        Object rtValue = null;
                        try {
                            //1.开启事务
                            txManager.beginTransaction();
                            //2.执行操作
                            rtValue = method.invoke(accountService, args);
                            //3.提交事务
                            txManager.commit();
                            //4.返回结果
                            return rtValue;
                        } catch (Exception e) {
                            //5.回滚操作
                            txManager.rollback();
                            throw new RuntimeException(e);
                        } finally {
                            //6.释放连接
                            txManager.release();
                        }
                    }
                });

    }
}

修改service方法

package com.itheima.service.impl;

import com.itheima.dao.IAccountDao;
import com.itheima.domain.Account;
import com.itheima.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.findAllAccount();
    }

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

    }

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

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

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

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

//            int i=1/0;

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

2.修改配置文件

<?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="proxyAccountService" factory-bean="beanFactory" factory-method="getAccountService"></bean>

    <!--配置beanfactory-->
    <bean id="beanFactory" class="com.itheima.factory.BeanFactory">
        <!-- 注入service -->
        <property name="accountService" ref="accountService"></property>
        <!-- 注入事务管理器 -->
        <property name="txManager" ref="txManager"></property>
    </bean>

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

    <!--配置Dao对象-->
    <bean id="accountDao" class="com.itheima.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/eesy"></property>
        <property name="user" value="root"></property>
        <property name="password" value="1234"></property>
    </bean>

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

    <!-- 配置事务管理器-->
    <bean id="txManager" class="com.itheima.utils.TransactionManager">
        <!-- 注入ConnectionUtils -->
        <property name="connectionUtils" ref="connectionUtils"></property>
    </bean>
</beans>

3.进行测试

package com.itheima.test;

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

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

    @Autowired
    @Qualifier("proxyAccountService")
    private  IAccountService as;

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

}

你可能感兴趣的:(框架,spring,java-ee,java)