Spring5快速入门【基础总结】

1. Spring5

1.1 简介

  • Spring(春天)—>给软件行业带来了春天!
  • Spring框架以interface21为基础,经过重新设计而成!
  • Spring理念: 使现有的技术更加容易使用,本身是个大杂烩,整合了现有的技术框架!
  • Spring是一个轻量级的IoC和AOP容器框架。是为Java应用程序提供基础性服务的一套框架,目的是用于简化企业应用程序的开发,它使得开发者只需要关心业务需求
  • Spring是为了简化企业级应用开发的复杂性而创建的,简化开发!

Spring是如何简化Java开发的?

  • 基于POJO的轻量级和最小侵入性编程;
  • 通过IOC,依赖注入(DI)和面向接口编程实现松耦合
  • 基于切面(AOP)和惯例进行声明式编程;
  • 通过切面和模板减少样式代码

Spring官网: Spring

官方下载: 官方下载地址

GitHub: GitHub

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc
推荐导入以下这个包,其会自动导入和Spring相关的许多包 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.0.RELEASE</version>
</dependency>


<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.2.0.RELEASE</version>
</dependency>

1.2 优点

  • Spring是一个开源的免费的框架(容器)!
  • Spring是一个轻量级的,非入侵式的框架!
  • 控制反转(IOC), 面向切面编程(AOP)!
  • 支持事务的处理, 对框架整合的支持!

即: Spring就是一个轻量级的控制反转(IOC)和面向切面编程(AOP)的框架!

1.3 组成

Spring框架是一个分层架构,由以下七大模块组成。

七大模块

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-32MHFCdN-1638720613311)(Spring%E7%AC%94%E8%AE%B0.assets/image-20211125145245849.png)]

组成 Spring 框架的每个模块(或组件)都可以单独存在,或者与其他一个或多个模块联合

每个模块的功能如下:

  • Spring Core:核心容器提供 Spring 框架的基本功能。核心容器的主要组件是 BeanFactory,它是工厂模式的实现。
  • Spring Context:Spring 上下文是一个配置文件,向 Spring 框架提供上下文信息。Spring 上下文包括企业服务,例如 JNDI、EJB、电子邮件、国际化、校验和调度功能。
  • Spring AOP:通过配置管理特性,Spring AOP 模块直接将面向切面的编程功能 , 集成到了 Spring 框架中。所以,可以很容易地使 Spring 框架管理任何支持 AOP的对象。Spring AOP 模块为基于 Spring 的应用程序中的对象提供了事务管理服务。通过使用 Spring AOP,不用依赖组件,就可以将声明性事务管理集成到应用程序中。
  • Spring DAO:Spring DAO 抽象层提供了有意义的异常层次结构,可用该结构来管理异常处理和不同数据库供应商抛出的错误消息。异常层次结构简化了错误处理,并且极大地降低了需要编写的异常代码数量(例如打开和关闭连接)。Spring DAO 的面向 JDBC 的异常遵从通用的 DAO 异常层次结构。
  • Spring ORM:Spring 框架插入了若干个 ORM 框架,从而提供了 ORM 的对象关系工具,其中包括 JDO、Hibernate 和 iBatis SQL Map。所有这些都遵从 Spring 的通用事务和 DAO 异常层次结构。
  • Spring Web:Web 模块建立在应用程序上下文模块之上,为基于 Web 的应用程序提供了上下文。Web 模块简化了处理多部分请求以及将请求参数绑定到域对象的工作。
  • Spring MVC:Spring MVC 框架是一个全功能的构建 Web 应用程序的实现。其容纳大量视图技术,其中包括 JSP、Velocity等。

备注:

  • Spring Boot
    • 一个快速开发的脚手架,
    • 基于springboot可以快速的开发单个微服务。
    • 约定大于配置.
  • Spring Cloud
    • Spring Cloud是基于SpringBoot实现的

故Spring的学习起着承上启下的作用。

Spring的弊端: 配置十分繁琐,“配置地狱”!


2. IOC理论推导

在之前 JavaWeb阶段根据MVC开发一般需要:

  1. UserDao接口

  2. UserDaoImpl实现类

  3. UserService业务层接口

  4. UserServiceImpl业务接口实现类(且其中需要调用Dao层逻辑,需要new一个Dao层对象)

  5. Servlet控制层(其中调用业务层逻辑,需要new一个业务层对象)

在JavaWeb的Mvc阶段的业务实现中, 用户需求的变化可能会引起我们去改动原来的代码;

如果代码量十分大,修改代码的成本会很高!


而如果通过提供一个Set方法,实现动态值的注入,就不用去直接改动源代码。

//利用Set动态实现Dao层对象的注入
public void setUserDao(UserDao userDao){
    this.userDao = userDao;
}

之前,程序是主动创建对象,意味着控制权在程序猿手中!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-5XOKOg7R-1638720613313)(Spring%E7%AC%94%E8%AE%B0.assets/image-20211125173841822.png)]

使用了set方法动态注入值后,程序不再具有主动性,而是变成了被动的接受对象!

在这里插入图片描述

这种思想,从本质上解决了原来需要去改动源代码的问题。

程序员不用再去管理对象的创建了。

系统的耦合性大大降低,可以更加专注于业务逻辑的实现。

而这就是IOC控制反转的原型!!

IOC本质

控制反转IOC(Inversion of Control)其实是一种设计思想,

DL(依赖注入)是实现IOC的一种方法。

在没有IOC的程序中,我们使用面向对象编程, 对象的创建与对象间的依赖关系完全硬编码在程序中,即对象的创建由程序自己控制。

而在有IOC控制反转的程序中,对象的创建和管理转移给了第三方。

IOC通俗理解为: 获得依赖对象的方式反转了!

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AoeqZLkz-1638720613314)(Spring%E7%AC%94%E8%AE%B0.assets/image-20211125173015025.png)]

控制反转是一种通过(XML或注解)等进行描述并通过第三方去生产或获取特定对象的方式。

在Spring中实现控制反转的是IOC容器,其实现方式是依赖注入(Dependency injection, DI)。

IOC是Spring框架的核心内容,其使用多种方式完美的实现了IOC,即可以使用XML配置,也可以使用注解,甚至可以零配置实现IOC。

Spring容器在初始化时先读取配置文件,根据配置文件或元数据创建与组织对象存入容器中,待程序需使用时就直接从IOC容器中取出对象使用即可。

3. HelloSpring

  1. 导入Spring相关jar包
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc
Spring 需要导入commons-logging进行日志记录,我们通过maven导入以下这个包,其会自动下载对应的依赖项 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.2.0.RELEASE</version>
</dependency>
  1. 编写相关代码

2.1 编写一个Hello实体类。

Hello.java

package com.carson.pojo;

public class Hello {
    private String str;

    public String getStr() {
        return str;
    }
//对象的属性通过Spring容器进行设置,即配置文件bean中的property标签
//这里一定要有Set,不然spring的配置文件property标签传值会报红
    public void setStr(String str) {
        this.str = str;
    }

    @Override
    public String toString() {
        return "Hello{" +
                "str='" + str + '\'' +
                '}';
    }
}

2.2 通过创建xml文件的方式编写一个spring配置文件,

示例命名为: beans.xml

: 在beans.xml中书写bean标签,就相当于创建对应的类的对象;
所以beans.xml中的各种bean标签就相当于各个类的已创建好的对象;
所以感觉beans.xml就是一个对象的容器的感觉!

beans.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
       https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--bean就是java对象,由Spring创建和管理-->
    <!--
	id: java对象的自定义变量名
    class: new的对象所在的全路径
    property: 相当于给java对象中的属性传入值,使用前提是java类中要有属性的set方法
	-->
    <bean id="hello" class="com.carson.pojo.Hello">
        <!--
	property标签传值有两个属性value和ref
  ref: 传入的是已注册的bean对象id,即引用Spring容器中已创建好的对象
  value: 传入的是基本数据类型等具体的值-->
        <property name="str" value="Spring"/>
    </bean>
</beans>

2.3 编写测试文件进行测试

public class MyTest {
    public static void main(String[] args) {
        //获取Spring的上下文对象,传入的参数可以为一个或多个xml配置文件,下面的API函数为读取配置文件来创建上下文对象的API, Spring还有其它通过读取注解或文件的方式的相关API
        //Spring容器就类似于婚介所,管理众多的对象
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        //现在我们的对象都在Spring中进行管理了,我们要使用,直接去里面取出来就可以了
        //getBean():传入的参数为spring配置文件中对象的bean标签的id
        Hello hello = (Hello)context.getBean("hello");
        System.out.println(hello.toString());//测试输出

    }
}

注:

  • Hello对象是由Spring创建的。【即Spring配置文件】
  • Hello对象的属性是通过Spring容器进行设置的,即bean标签中的property标签。使用前提是原先对象中存在相应属性的Set方法。

以上的过程就叫 控制反转


控制:谁来控制对象的创建,传统应用程序的对象是由程序本身控制创建,即手动new对象。

​ 使用Spring后,对象的创建和管理是由Spring来负责的。

反转:程序本身不创建对象,而变成了被动的接受对象。

DI依赖注入: 就是利用set方法来对特定属性的属性值进行注入。

IOC控制反转是一种编程思想,由主动的编程变成被动的接收。

所谓的IOC,一句话理解: 对象由Spring来创建,管理和装配!!


4. IOC创建对象的配置方式

创建测试用的实体类User.java

package com.carson.pojo;

public class User {
    private String name;

    public User() {
        System.out.println("User的无参构造被初始化了!");
    }

    public User(String name) {
        this.name = name;
        System.out.println("User的有参构造被初始化了");
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void show(){
        System.out.println("name="+name);
    }
}
  1. 使用无参构造器+属性的Set方法来创建对象

即通过这种方式创建对象,原本bean实体类必须具有无参构造器和相应属性的Set方法!

<!--通过无参构造器的对象配置-->
<bean id="user" class="com.carson.pojo.User" >
    <!--name属性绑定对象的属性参数名-->
    <property name="name" value="Carson无参构造对象"></property>
</bean>
  1. 使用有参构造器创建对象

    即通过这种方式创建对象,原本bean实体类必须具有 有参构造器!

    以下是三种配置对象属性值的方式,即属性值的注入方式:

    1. 传递 参数位置下标注入属性值【推荐
    <!--    通过有参构造器的对象属性配置
            方式一:通过参数位置下标(从0开始),来注入属性值
            -->
    <bean id="user" class="com.carson.pojo.User">
        <constructor-arg index="0" value="Carson通过参数下标创造有参构造对象"></constructor-arg>
    </bean>
    
    1. 传递 参数类型注入属性值【不推荐使用
    (简单数据类型直接写,复杂数据类型需要写成全类名)
    
    <!--    通过有参构造器的对象属性配置
            方式二:通过参数类型(简单数据类型直接写,复杂数据类型需要写成全类名)
                  不推荐使用,因为万一多个参数的类型一致就麻烦了
            -->
    <bean id="user" class="com.carson.pojo.User">
        <constructor-arg type="java.lang.String" value="Carson通过参数类型创造有参构造对象"></constructor-arg>
    </bean>
    
    1. 参数名
    <!--    通过有参构造器的对象属性配置        方式三:通过name属性绑定参数名字        --><bean id="user" class="com.carson.pojo.User">    <constructor-arg name="name" value="Carson通过name属性绑定参数名字创造有参构造对象"></constructor-arg></bean>
    

:

​ 1. 加载spring的配置文件的时候,spring容器中的对象(配置文件中的对象)就都被创建了,需要使用特定的对象的时候,就使用getBean()方法获取对象即可。

  1. spring配置文件中对象的创建默认为单例模式,故使用getBean()获取同个类对象的话,都是相同的同一个实例对象。

下面是测试使用getBean()获取同个类对象:

public class MyTest {
    public static void main(String[] args) {
        //获取Spring的上下文对象(传入spring的xml配置文件),加载配置文件
        //在加载配置文件的时候,spring容器中的对象(配置文件中的对象)就都被创建了,且每个对象有且只有一个(单例模式)
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        //getBean获取对象
        User user = (User) context.getBean("user");
        User user1 = (User) context.getBean("user");
        user.show();
        //即只存在一个实例对象(故获取的两个对象是一样的,结果为true)
        System.out.println("获取的两个对象是否相同:"+(user==user1));
    }
}

5. Spring配置说明

5.1 别名【alias】

<!--通过alias标签对一个bean取别名
        name:对应原来bean的id名
        alias: 对应别名
    -->
<alias name="user" alias="user2"></alias>
//getBean获取对象(取了别名之后对象就多了个名字,就都可以获取的到)
User user = (User) context.getBean("user");
User user2 = (User) context.getBean("user2");

5.2 Bean的配置【bean】

<!-- bean标签的属性说明
    id: bean的唯一标识符,相当于创建的对象的标识符
    class: bean对象对应的类的全限定名,即包名+类名
    name: 也是对bean取别名,可以同时写多个别名,多个别名间的分隔符可以是逗号,空格,分号
    scope: 更改对象的创建模式,默认是singleton单例模式创建
        -->

<bean id="user" class="com.carson.pojo.User" name="user2 user3,user4;user5" scope="singleton">
    <property name="name" value="Carson"></property>
</bean>

5.3 Bean的作用域

在这里插入图片描述

  1. 单例模式(singleton, Spring的默认机制)

正如单例设计模式的性质一般, 容器中对象的存在有且只有一个,

故通过getBean()多次获取同个对象都是相同的。

<!--显式声明bean对象创建为单例模型-->
<bean id="user" class="com.carson.pojo.User"  scope="singleton">
</bean>
  1. 原型模型(prototype)

正如原型设计模式的性质一般,容器中的对象不只存在一个,其它对象的创建以一个对象为原型进行复制而得。

故通过getBean()多次获取同个对象都是不同的!

  1. 其余的request, session, application等只能在web开发中使用!

5.4 import

import标签, 一般用于团队开发使用, 它可以将多个beans.xml等spring配置文件, 导入合并为一个总的spring配置文件, 即一般命名为applicationContext.xml的总配置文件。


假设现在项目中有多个人开发,每个人负责不同的类的开发,而每个人都会有一个beans.xml的配置文件,从而导致不同的类被注册在不同的配置文件中,从而我们可以利用import将所有人的beans.xml合并为一个总的applicationContext.xml!

这样在加载spring配置文件的时候,导入总的配置文件即可。

<!--bean会根据导入配置文件的先后顺序进行导入-->
<import resource="beans.xml"/>
<import resource="beans2.xml"/>
<import resource="beans3.xml"/>

:

多个beans.xml配置文件合并时, 可能会发生多个beans.xml文件中的bean出现id相同状况,这时后面进入的配置文件中的bean会覆盖掉先进入的配置文件中的bean!

6. 依赖注入(DI)

什么是依赖?

bean对象的创建依赖于容器!

什么是注入?

bean对象中所有属性值,由容器来负责注入!

6.1 有参构造器属性注入

通过这种有参构造器的方式创建对象,原本bean实体类必须具有 有参构造器!

以下是通过有参构造器的三种属性值的注入方式:

  1. 传递 参数位置下标注入属性值【推荐
<!--    通过有参构造器的对象属性配置
        方式一:通过参数位置下标(从0开始),来注入属性值
        -->
<bean id="user" class="com.carson.pojo.User">
    <constructor-arg index="0" value="Carson通过参数下标创造有参构造对象"></constructor-arg>
</bean>
  1. 传递 参数类型注入属性值【不推荐使用
(简单数据类型直接写,复杂数据类型需要写成全类名)
<!--    通过有参构造器的对象属性配置
        方式二:通过参数类型(简单数据类型直接写,复杂数据类型需要写成全类名)
              不推荐使用,因为万一多个参数的类型一致就麻烦了
        -->
<bean id="user" class="com.carson.pojo.User">
    <constructor-arg type="java.lang.String" value="Carson通过参数类型创造有参构造对象"></constructor-arg>
</bean>
  1. 参数名
<!--    通过有参构造器的对象属性配置
        方式三:通过name属性绑定参数名字
        -->
<bean id="user" class="com.carson.pojo.User">
    <constructor-arg name="name" value="Carson通过name属性绑定参数名字创造有参构造对象"></constructor-arg>
</bean>

6.2 无参构造器+Set方式注入【重点】

由于原本bean实体类默认自带无参构造器,所以只要不写有参构造器就不会覆盖掉无参构造器!

故使用Set方式注入时,原本bean类注意不要写 有参构造器!


测试环境搭建

  1. 被引用的数据对象

Address.java

package com.carson.pojo;

public class Address {
    //待注入值的属性
    private String address;
	
    //1.要有无参构造器
    public String getAddress() {
        return address;
    }
	
    //2.要有属性值的set方法
    public void setAddress(String address) {
        this.address = address;
    }

    @Override
    public String toString() {
        return "Address{" +
                "address='" + address + '\'' +
                '}';
    }
}
  1. 真实创建的对象

Student.java

package com.carson.pojo;

import java.util.*;

public class Student {
    //普通简单数据类型
    private String name;
    //引用数据类型
    private Address address;
    //数组
    private String[] books;
    //List
    private List<String> hobbies;
    //Map
    private Map<String,String> card;
    //Set
    private Set<String> games;
    //设置null
    private String wife;
    //Properties
    private Properties info;


    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Address getAddress() {
        return address;
    }

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

    public String[] getBooks() {
        return books;
    }

    public void setBooks(String[] books) {
        this.books = books;
    }

    public List<String> getHobbies() {
        return hobbies;
    }

    public void setHobbies(List<String> hobbies) {
        this.hobbies = hobbies;
    }

    public Map<String, String> getCard() {
        return card;
    }

    public void setCard(Map<String, String> card) {
        this.card = card;
    }

    public Set<String> getGames() {
        return games;
    }

    public void setGames(Set<String> games) {
        this.games = games;
    }

    public String getWife() {
        return wife;
    }

    public void setWife(String wife) {
        this.wife = wife;
    }

    public Properties getInfo() {
        return info;
    }

    public void setInfo(Properties info) {
        this.info = info;
    }

    @Override
    public String toString() {
        return "Student{" +
                "name='" + name + '\'' +
                ", address=" + address +
                ", books=" + Arrays.toString(books) +
                ", hobbies=" + hobbies +
                ", card=" + card +
                ", games=" + games +
                ", wife='" + wife + '\'' +
                ", info=" + info +
                '}';
    }
}
  1. 配置文件

beans.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
       https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="addressBean" class="com.carson.pojo.Address">
        <property name="address" value="深圳"/>
    </bean>

    <bean id="student" class="com.carson.pojo.Student">
        <!--普通简单数据类型值注入,通过value-->
        <property name="name" value="Carson"/>
        <!--其与下面的形式等价-->
        <property name="name">
            <value>Carson</value>
        </property>

        <!--bean注入,引用数据类型的属性值注入,需要通过ref-->
        <property name="address" ref="addressBean"/>

        <!--数组-->
        <property name="books">
            <array>
                <value>JAVA</value>
                <value>Spring</value>
                <value>Python</value>
                <value>NOdeJS</value>
            </array>
        </property>

        <!--List-->
        <property name="hobbies">
            <list>
                <value>码代码</value>
                <value>听歌</value>
                <value>健身</value>
            </list>
        </property>

        <!--Map-->
        <property name="card">
            <map>
                <entry key="姓名" value="Carson"/>
                <entry key="学号" value="1111111"/>
            </map>
        </property>

        <!--Set-->
        <property name="games">
            <set>
                <value>张三</value>
                <value>李四</value>
                <value>王五</value>
            </set>
        </property>

        <!--传递null值-->
        <property name="wife">
            <null/>
        </property>

        <!--Properties-->
        <property name="info">
            <props>
                <prop key="driver">com.mysql.jdbc.Driver</prop>
                <prop key="url">http://localhost:3306</prop>
                <prop key="username">root</prop>
                <prop key="password">root</prop>
            </props>
        </property>
    </bean>
</beans>
  1. 测试类
public class MyTest {
    public static void main(String[] args) {
        //读取配置文件,创建上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        //getBean()获取对象
        Student student = (Student) context.getBean("student");
        //打印输出
        System.out.println(student.toString());
    }
}

6.3 拓展方式注入

我们还可以使用p命名空间和c命名空间进行依赖注入!

c命名空间: 是对应有参构造器注入的简化版!

p命名空间: 是对应无参构造器+Set方法注入的简化版!

注意点:

c命名和p命名空间不能直接使用,需要在xml文件上方进行导入!


使用的bean实体类:

User.java

public class User {
    private String name;
    private int age;

    public User() {
    }

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public int getAge() {
        return age;
    }


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

测试:

@Test
public void test(){
    //读取配置文件获取上下文对象
    ApplicationContext context = new ClassPathXmlApplicationContext("userbeans.xml");
    //getBean()获取对象返回Object,如果参数传递bean的class对象的话就不用再进行对象的强制转换了
    User user = context.getBean("user", User.class);
    System.out.println(user);
}

6.3.1 c命名空间注入

beans.xml中导入c命名空间并进行bean对象配置

<?xml version="1.0" encoding="UTF-8"?>
<beans ....
       xmlns:c="http://www.springframework.org/schema/c"
       ....
       
    <!--c命名空间注入:
        本质: c对应construct-args,本质通过有参构造器方式注入
        使用格式: c:属性名="属性值"  -->
    <bean id="user" class="com.carson.pojo.User" c:name="C: carson" c:age="21" />

</beans>

:

  1. c命名空间注入的本质是: 通过有参构造器方式注入!
  2. 所以bean实体类中必须要有 有参构造方法!

6.3.2 p命名空间注入

beans.xml中导入p命名空间并进行bean对象配置

<?xml version="1.0" encoding="UTF-8"?>
<beans ....
       xmlns:p="http://www.springframework.org/schema/p"
       ....
       
   <!--p命名空间注入:
        本质: p对应property属性,本质通过set方法注入
        使用格式: p:属性名="属性值"-->
    <bean id="user" class="com.carson.pojo.User" p:name="P: carson" p:age="21"/>

</beans>

:

  1. p命名空间注入的本质是: 通过无参构造器+Set方法的方式注入!
  2. 所以bean实体类中要有无参构造器和对应属性的set方法!

7. Bean的自动装配

什么是Bean的自动装配?

即: 自动给bean的属性装配值

Spring会在上下文自动寻找,并自动给bean装配对应的引用数据类型变量的属性值!

在Spring中有三种给bean装配属性值的方式:

  1. 在spring的xml配置文件中手动显式的配置bean及其属性值,也就是上面的做法!
  2. 通过Java程序给bean装配属性值。
  3. 这里要讲的是隐式的自动装配bean属性值【重要】【针对引用数据类型的属性进行装配赋值

7.1 未使用自动装配

示例测试

  1. 创建测试用的相关的实体类

Car.java

public class Cat {
    public void shout(){
        System.out.println("小猫叫了!");
    }
}

Dog.java

public class Dog {
    public void shout(){
        System.out.println("小狗叫了!");
    }
}

People.java

public class People {
    //引用数据类型
    private Cat cat;
    //引用数据类型
    private Dog dog;
    //基本数据类型
    private String name;

    public Cat getCat() {
        return cat;
    }

    public void setCat(Cat cat) {
        this.cat = cat;
    }

    public Dog getDog() {
        return dog;
    }

    public void setDog(Dog dog) {
        this.dog = dog;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }
}
  1. 编写beans.xml配置文件,需要手动配置引用数据类型对应的 bean
<bean id="cat" class="com.carson.pojo.Cat"/>

<bean id="dog" class="com.carson.pojo.Dog"/>

<bean id="people" class="com.carson.pojo.People" >
        <property name="name" value="Carson"/>
    	<!--需要手动配置引用数据类型对应的bean,如下:-->
        <property name="cat" ref="cat"/>
        <property name="dog" ref="dog"/>
    </bean>

7.2 byName自动装配

具体如何使用byName进行自动装配?

: 在bean标签的autowire属性进行配置,选择属性值为byName.

<bean id="cat" class="com.carson.pojo.Cat"/>

<bean id="dog" class="com.carson.pojo.Dog"/>

<!--使用byName的话,被注入的bean的id要和当前对象的特定的引用数据类型属性的set方法set后面的单词一致且首字母要小写-->

    <bean id="people" class="com.carson.pojo.People" autowire="byName">
        <property name="name" value="Carson"/>
    </bean>

:

  1. 使用byName自动装配可以省略手动传递引用数据类型属性值。
  2. 使用byName的话,bean的id要和对象的特定属性的set方法set后面的单词一致且首字母要小写。
  3. 以本例而言, 即第一个bean的id属性cat对应了People类中cat属性的setCat这个方法中Cat的首字母小写的形式。
  4. 以本例而言, 即第二个bean的id属性dog对应了People类中dog属性的setDog这个方法中Dog的首字母小写的形式。

7.3 byType自动装配

具体如何使用byType进行自动装配?

: 在bean标签的autowire属性进行配置,选择属性值为byType.

<bean  class="com.carson.pojo.Cat"/>

<bean  class="com.carson.pojo.Dog"/>

<!--使用byType的话,bean的class全路径要和对象的特定属性的类型相一致
        1:使用byType的话要确保bean全局唯一,不能出现class相同的重复的bean
        2: 使用byType的话,bean可以省略id属性匹配
        -->

    <bean id="people" class="com.carson.pojo.People" autowire="byType">
        <property name="name" value="Carson"/>
    </bean>

:

  1. 使用byType自动装配可以省略手动传递引用数据类型的属性值。
  2. 使用byType的话要确保bean全局唯一,不能出现class相同的重复的bean。
  3. 使用byType的话,bean可以省略id属性,如上所示。
  4. 使用byType的话,bean的class全路径要和对象的特定属性的类型相一致。

7.4 使用注解实现自动装配

使用注解进行自动装配的常用注解有哪些?

  • @Autowired【Spring的注解】
  • @Resource 【Java的注解】

:

​ 自动装配的对象是有要求的。

【即:只针对引用数据类型的属性进行装配赋值
在属性/set方法上放置这两个注解,就相当于去绑定了Spring容器中已经创建好的对象;即相当于创建了实体类对象


7.4.1 @Autowired和@Resource的比较

相同点:

  1. 都是针对引用数据类型数据属性值的自动装配。
  2. 注解要么都可以放在属性字段上面,要么都可以放在属性对应的Set方法上面。

不同点:

  1. @Autowired默认先通过byType进行匹配, 如果匹配不到容器中对应的bean标签,则通过byName进行匹配;如果还匹配不到,就需要结合另外一个注解@Qualifier(value=“要匹配的bean的id名”)来与指定的特定bean进行匹配。
  2. @Resource默认先通过byName进行匹配, 如果匹配不到容器中对应的bean标签,则通过byType进行匹配;如果还匹配不到,就需要给@Resource注解中的name属性赋值来与指定的特定bean进行匹配, 即@Resource(name=“匹配的bean的id”)

7.4.2 @Autowired【Spring的注解】

由于@Autowired属于Spring的注解,而使用Spring的注解之前需要在beans.xml中进行相关的配置

使用Spring注解配置beans.xml配置文件步骤

  1. beans标签中导入与注解的相关的约束,如下:
<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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
    
</beans>
  1. beans标签需要配置使用Spring注解的标签,如下:
<beans>
	.....
	<!--Spring使用注解需要配置的标签-->
	<context:annotation-config/>
    
    ....
</beans>
  1. 最终使用Spring注解的初始配置完成的beans.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
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <!--Spring使用注解需要配置的标签-->
    <context:annotation-config/>
    
    <!--配置相关的bean对象-->
    <bean id="cat" class="com.carson.pojo.Cat"></bean>
    <bean id="dog" class="com.carson.pojo.Dog"></bean>
    <bean id="people" class="com.carson.pojo.People"></bean>

</beans>

@Autowired注解的具体使用:

  1. @Autowired默认按照byType与容器中bean标签进行匹配并进行自动装配,示例如下:

beans.xml

<!--Spring使用注解需要配置的标签-->
<context:annotation-config/>

<!--bean对象配置-->
<bean id="cat" class="com.carson.pojo.Cat"></bean>
<bean id="dog" class="com.carson.pojo.Dog"></bean>

People.java

public class People {
    @Autowired
    private Cat cat;
    @Autowired
    private Dog dog;
}
  1. 当出现多个相同类型的bean就按照byName进行匹配并进行自动装配, 示例如下:

beans.xml

<!--Spring使用注解需要配置的标签-->
<context:annotation-config/>

<!--bean对象配置-->
<bean id="cat" class="com.carson.pojo.Cat"></bean>
<bean id="cat1" class="com.carson.pojo.Cat"></bean>
<bean id="dog" class="com.carson.pojo.Dog"></bean>
<bean id="dog1" class="com.carson.pojo.Dog"></bean>

People.java

public class People {
    @Autowired
    private Cat cat;
    @Autowired
    private Dog dog;
}
  1. 如果此时byName也匹配不到,可以再加上一个@Qualifier(value=“要匹配的bean的id名”)来指定特定的bean进行匹配

    示例如下:

beans.xml

<!--Spring使用注解需要配置的标签-->
<context:annotation-config/>

<!--bean对象配置-->
<bean id="cat1" class="com.carson.pojo.Cat"></bean>
<bean id="cat2" class="com.carson.pojo.Cat"></bean>
<bean id="dog1" class="com.carson.pojo.Dog"></bean>
<bean id="dog2" class="com.carson.pojo.Dog"></bean>

People.java

public class People {
    @Autowired
    @Qualifier("cat1")
    private Cat cat;
    @Autowired
    @Qualifier("dog1")
    private Dog dog;
}
  1. @Autowired注解的特殊用法
@Autowired(required=false)

beans.xml中没有配置相应的对象的bean标签时,这里以Cat属性为例。

<!--配置bean,没有配置Cat对应的bean标签-->
<bean id="dog" class="com.carson.pojo.Dog"></bean>
<bean id="people" class="com.carson.pojo.People"></bean>

原先的People.java Cat字段中用@Autowired注解,但不显式指定required属性为false:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BHn8gmCd-1638720613316)(Spring%E7%AC%94%E8%AE%B0.assets/image-20211129155805310.png)]

发现Cat属性会报红,再运行测试程序:

测试程序

public class MyTest {
    public static void main(String[] args) {
        //读取配置文件创建上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        //getBean()获取对象
        People people = context.getBean("people", People.class);
        //打印引用数据类型对象
        System.out.println(people.getCat());
        System.out.println(people.getDog());
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-oyud6IrS-1638720613316)(Spring%E7%AC%94%E8%AE%B0.assets/image-20211129160023189.png)]

发现运行测试测试程序也会出错。

但如果显式定义了Autowired的required属性为false,这个Cat对象就可以为null,

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-JTqe7GeB-1638720613317)(Spring%E7%AC%94%E8%AE%B0.assets/image-20211129160222429.png)]

发现Cat属性不会报红了。

同时再运行下上面的测试程序:

测试程序

public class MyTest {
    public static void main(String[] args) {
        //读取配置文件创建上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        //getBean()获取对象
        People people = context.getBean("people", People.class);
        //打印引用数据类型对象
        System.out.println(people.getCat());
        System.out.println(people.getDog());
    }

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XS3OwMgj-1638720613317)(Spring%E7%AC%94%E8%AE%B0.assets/image-20211129160345425.png)]

这样运行测试程序打印对象属性值也不会报错。

发现程序正常运行,且Cat属性可以为null。


7.4.3 @Resource【Java的注解】

由于@Resource属于Java的注解,故不需要在beans.xml中做任何配置

@Resource注解的具体使用:

  1. @Resource默认先按byName进行匹配

    示例如下:

beans.xml

<!--bean对象配置-->
<bean id="cat" class="com.carson.pojo.Cat"></bean>
<bean id="dog" class="com.carson.pojo.Dog"></bean>

People.java

public class People {
    @Resource
    private Cat cat;
    @Resource
    private Dog dog;
}
  1. byName匹配不到再按byType进行匹配.

    示例如下:

beans.xml

<!--bean对象配置-->
<bean id="cat1212" class="com.carson.pojo.Cat"></bean>
<bean id="dog1212" class="com.carson.pojo.Dog"></bean>
<bean id="people" class="com.carson.pojo.People"></bean>

People.java

public class People {
    @Resource
    private Cat cat;
    @Resource
    private Dog dog;
}

虽然上面的bean的id不一样,但Type是唯一的,所以属性值是可以正常注入的!

  1. @Resource如果通过byType进行匹配也匹配不到,就需要给@Resource注解中的name属性赋值来指定匹配的bean

    格式即: @Resource(name=“匹配的bean的id”)

    示例如下:

beans.xml

<bean id="cat1" class="com.carson.pojo.Cat"></bean>
<bean id="cat2" class="com.carson.pojo.Cat"></bean>
<bean id="dog1" class="com.carson.pojo.Dog"></bean>
<bean id="dog2" class="com.carson.pojo.Dog"></bean>

People.java

@Resource(name="cat1")
private Cat cat;
@Resource(name="dog1")
private Dog dog;

8.Spring使用注解开发

Spring注解使用前提?

  1. beans.xml配置文件导入context约束和注解的支持
<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:Context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">

    <!--context:component-scan 指定要扫描的包,这个包下的注解就会生效-->
    <context:component-scan base-package="com.carson.pojo"/>
    <!--context:annotation-config 使用其让Spring注解生效-->
    <context:annotation-config/>

</beans>
  1. 在Spring4之后,要使用注解开发,必须要保证aop的包导入了.

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-xacLPWCE-1638720613318)(Spring%E7%AC%94%E8%AE%B0.assets/image-20211130160144863.png)]

  1. 建立测试的实体类

User.java

public class User {
    private String name;
    
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
}

8.1 使用注解进行bean注册

使用的注解: @Component,放在类的上方,说明这个类被Spring管理了!

注:

  1. @Component注解, 等价于 <bean id=“user” class="“com.carson.pojo.User”/>
  2. 注册的bean的id名字默认为这个类名的小写形式

示例如下:

applicationContext.xml

<!--context:component-scan 指定要扫描的包,这个包下的注解就会生效-->
<context:component-scan base-package="com.carson.pojo"/>
<!--context:annotation-config 使用Spring注解的配置-->
<context:annotation-config/>

User.java

//@Component注解
//等价于 <bean id="user" class=""com.carson.pojo.User"/>
//注册的bean的id名字默认为这个类名的小写形式,即: user

@Component
public class User {
    private String name;
    .....
}

8.2 注解限制bean作用域

使用的注解: @Scope(“具体值”),放在类的上方。

示例如下:

User.java

@Component
@Scope("singleton")//单例模式
@Scope("prototype")//原型模式
public class User {
    private String name;
    ....
}

8.3 注解进行属性值注入

使用的注解: @Value("属性值")

:

  1. 针对的属性是简单数据类型的属性值注入!
  2. @Value(“属性值”)等价<propertyname=“name” value=“属性值” />
  3. @Value(“属性值”) 可以直接加在属性上方
  4. @Value(“属性值”) 也可以直接加在属性对应的Set方法上方

示例如下:

User.java

public class User {
    //@Value("属性值") 可以直接加在属性上方
    //等价于<property name="name" value="属性值" />
    @Value("Carson")
    private String name;

    public String getName() {
        return name;
    }
	
    //@Value("属性值") 也可以直接加在属性对应的Set方法上方
    //@Value("Carson的Set方法!")
    public void setName(String name) {
        this.name = name;
    }
}

8.4 自动装配

: 针对的属性是引用数据类型的属性值的自动装配!

自动装配的相关注解使用 上面已记录,参考上面即可。


8.5 衍生的注解

@Component 有几个衍生的注解。

常用于Web开发中的MVC三层架构进行分层的相关Java类/接口 中。

<!--更改扫描的包,范围扩大为: com.carson下的所有包的内容-->
<context:component-scan base-package="com.carson"/>
<!--context:annotation-config 使用Spring注解的配置-->
<context:annotation-config/>
  • dao层。【@Repository
@Repository
public interface UserDao {
  ...  
}

  • Service层。【@Service
@Service
public interface UserService {
    ...
}
  • Controller层。【@Controller
@Controller
public class UserController {
    
}

:

​ @Component, @Repository, @Service, @Controller

​ 这四个注解的功能都是一样的, 都是代表将某个类注册到Spring中进行管理


8.6 对比小结

对比XML方式配置Spring和注解方式Spring

  • XML配置更加万能,适用于任何场合!维护简单方便!
  • 注解配置的话,不是自己类使用不了,维护相对复杂!
  • 推荐:
    • XML用来管理bean.
    • 注解只负责完成属性的注入!

9. 使用Java的方式配置Spring

使用Java的方式配置Spring,即现在我们完全不使用Spring的XML配置文件了,而是将Spring的配置全权交给 Java来做 !

实体类

package com.carson.pojo;

import org.springframework.beans.factory.annotation.Value;

public class User {
    @Value("Carson") //使用注解来进行属性值注入
    private String name;

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

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

Spring配置类

package com.carson.config;

import com.carson.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

//通过使用@Configuration注解代表这是一个Spring配置类,这个配置类等价于之前的beans.xml配置文件
//这个Spring配置类也会被Spring容器接管,从而注册到容器中,因为@Configuration这个注解包含了@Component
@Configuration
//使用@ComponentScan("com.carson.pojo")注解就是扫描包,其等价于<context:component-scan base-package="com.carson.pojo"/>
@ComponentScan("com.carson.pojo")
//使用@Import(xx.class)注解,可以导入其它的Spring配置类,其等价于import标签<import resource="beans.xml"/>
@Import(CarsonConfig2.class)
public class CarsonConfig {
    //@Bean注解代表注册一个Bean,就相当于在容器中注册的bean标签
    //@Bean注解只能放在方法的上面使用
    //这个方法的名字,就相当于bean标签中的id属性
    //这个方法的返回值, 就相当于bean标签中的class属性
    //@Bean 基础声明
     //Spring的@Bean注解用于告诉方法,产生一个Bean对象,然后这个Bean对象交给Spring管理。产生这个Bean对象的方法Spring只会调用一次,随后这个Spring将会将这个Bean对象放在自己的IOC容器中。

   //SpringIOC 容器管理一个或者多个bean,这些bean都需要在@Configuration注解下进行创建,在一个方法上使用@Bean注解就表明这个方法需要交给Spring进行管理。
    @Bean
    public User user(){
        return new User(); //就是返回要注入到bean的对象
    }

}

@Bean是一个方法级别上的注解,主要用在@Configuration注解的类里,也可以用在@Component注解的类里。添加的bean的id为方法名
`

@Configuration
public class AppConfig {
 
    @Bean
    public TransferService transferService() {
        return new TransferServiceImpl();
    }
 
}

这个配置就等同于之前在xml里的配置

<beans>
    <bean id="transferService" class="com.acme.TransferServiceImpl"/>
</beans>

测试类

public class MyTest {
    public static void main(String[] args) {
        //如果完全使用了配置类方式来配置Spring,我们就只能通过如下的 AnnotationConfig 来获取容器的上下文对象(通过传入配置类的Class对象)
        ApplicationContext context = new AnnotationConfigApplicationContext(CarsonConfig.class);
        User user = context.getBean("user", User.class);
        System.out.println(user.toString());
    }
}

这种纯Java的配置方式, 在SpringBoot中随处可见 !!


10. 代理模式

10.1 介绍

为什么要学习代理模式?

答: 其就是Spring AOP的底层原理!

代理模式的分类?

  • 静态代理模式
  • 动态代理模式

代理模式的图示, 以顾客租房为例:

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kXoq8EG0-1638720613319)(Spring%E7%AC%94%E8%AE%B0.assets/image-20211201211008517.png)]

代理模式的角色

  • 抽象角色: 一般使用接口或者抽象类来实现。
  • 真实角色: 被代理的角色
  • 代理角色: 代理真实角色,一般会有一些附属的操作!
  • 客户: 访问代理角色的人

10.2 静态代理模式

顾客租房的静态代理实现

  1. 接口
//抽象角色: 即代理角色和真实角色需共同实现的方法
//租房
public interface Rent {
    //租房的方法
    public void rent();
}
  1. 真实角色
//真实角色: 房东。需要实现代理角色即接口的方法
//房东要出租房
public class Host implements Rent {
    public void rent() {
        System.out.println("房东出租房子!");
    }
}
  1. 代理角色
//代理角色: 需要实现抽象角色即接口的方法
//中介要代理出租房子
public class Proxy implements Rent {

    //根据合成复用原则,不去继承类,而是内部实例化类
    private Host host;

    public Proxy() {
    }

    public Proxy(Host host) {

        this.host = host;
    }

    //中介帮房东出租房子
    public void rent() {
        /*中介还可以有其它附属操作*/
        seeHouse();
        signContract();
        //代理房东出租房子
        host.rent();
        fee();
    }

    /*中介还可以有其它附属操作*/
    //看房
    public void seeHouse(){
        System.out.println("中介带你看房!");
    }

    //签属租凭合同
    public void signContract(){
        System.out.println("签属租凭合同!");
    }

    //收取中介费
    public void fee(){
        System.out.println("中介收取中介费!");
    }
}
  1. 客户端访问代理角色
//顾客要租房子
public class Client {
    public static void main(String[] args) {
        //真实角色(房东)
        Host host = new Host();
        //代理角色(代理房东出租房子,一般会有一些附属的操作)
        Proxy proxy = new Proxy(host);
        //面对中介租房
        proxy.rent();

    }
}

在MVC中控制层调用业务逻辑层的静态代理实现

需求: 现在需要在CRUD方法的每个方法上增加一个打印日志的log需求,但如果直接改动原有代码的话,成本和风险过高,故需通过代理模式来扩展代码而不是改动代码。

这种思想即: AOP(Aspect Oriented Program)思想。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-vxkQfTEM-1638720613319)(Spring%E7%AC%94%E8%AE%B0.assets/image-20211201214227829.png)]

具体代码如下:

  1. 接口
//抽象角色: 业务逻辑层的接口 
//即 UserService
public interface UserService {
    //CRUD方法
    public void add();
    public void del();
    public void update();
    public void query();
}
  1. 真实角色
//真实角色:UserServiceImpl
public class UserServiceImpl implements UserService {
    public void add() {
        System.out.println("增加的方法!");
    }

    public void del() {
        System.out.println("删除的方法!");
    }

    public void update() {
        System.out.println("更改的方法!");
    }

    public void query() {
        System.out.println("查询的方法!");
    }
}
  1. 代理角色
//代理角色: UserServiceProxy
public class UserServiceProxy implements UserService {

    //合成复用原则,要代理一个角色,进行内部实例化
    private UserService userServiceImpl;

    //Spring建议注入对象用Set方法而不是构造方法
    public void setUserServiceImpl(UserService userServiceImpl) {
        this.userServiceImpl = userServiceImpl;
    }

    public void add() {
        log("add");
        userServiceImpl.add();

    }

    public void del() {
        log("del");
        userServiceImpl.del();

    }

    public void update() {
        log("update");
        userServiceImpl.update();

    }

    public void query() {
        log("query");
        userServiceImpl.query();

    }

    //举例需要增加: 打印日志方法
    public void log(String msg){
        System.out.println("[Debug]: 使用了"+msg+"方法!");
    }
}
  1. 客户端访问代理角色
//客户端访问代理角色
public class Client {
    public static void main(String[] args) {
        //真实角色
        UserService userServiceImpl = new UserServiceImpl();
        //代理角色
        UserServiceProxy userServiceProxy = new UserServiceProxy();
        //代理角色设置被代理的对象
        userServiceProxy.setUserServiceImpl(userServiceImpl);
        //客户端与代理角色交互
        userServiceProxy.add();
    }
}

静态代理模式的好处?

  • 可以使真实角色的操作更加纯粹简单!不用去关注公共的业务。
  • 公共的业务即附属的操作交给了代理角色!实现了业务的分工。
  • 公共业务发生扩展的时候,方便集中管理!

静态代理模式的缺点?

一个真实角色就会产生一个代理角色,代码量增加,导致开发效率低下!

10.3 动态代理模式

动态代理模式?

  • 动态代理和静态代理角色成分是一样的。
  • 动态代理的代理类是动态生成的,而不是像静态代理是直接写好的!
  • 动态代理的实现
    • 基于接口的动态代理。 举例:–> JDK动态代理。
    • 基于类的动态代理。 举例:–> cglib
    • Java字节码实现。 举例:–> javassist

动态代理的好处?

  • 解决了静态代理的缺点。
  • 一个动态代理类代理的是一个接口,一般就是对应一个业务。
  • 一个动态代理类可以代理多个类,只要是实现了同一个接口即可!

基于接口的动态代理实现

这里的动态代理需要涉及到反射的基础知识,可参考个人博文,
附上链接:反射机制

动态代理需要了解和运用两个类:

  • Proxy: 用来生成动态代理类的。

  • InvocationHandler: 即自动调用invoke()处理程序并返回结果的。


顾客租房的动态代理实现

  1. 动态代理类
//通过接口实现动态代理
//动态生成代理类的类,需要实现:InvocationHandler这个接口
//通过这个处理程序类来自动生成代理类实例
public class ProxyInvocationHandler implements InvocationHandler {
    //被代理的接口
    private Rent rent;
    
	//Spring建议注入对象用Set方法而不是构造方法
    public void setRent(Rent rent) {
        this.rent = rent;
    }

    //生成得到代理类
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),rent.getClass().getInterfaces(),this);
    }

    //处理代理实例,并返回结果
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //动态代理的本质,就是利用反射机制实现的
        //这里的method就是java.lang.reflection反射机制包下的对象
        Object result = method.invoke(rent, args);
        return result;
    }
}
  1. 客户端访问动态生成的代理类
public class Client {
    public static void main(String[] args) {
        //真实角色
        Host host = new Host();
        
        //动态生成代理角色
          // 1.实例化 实现动态生成代理类的类 的实例对象
        ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler();
          // 2. 调用方法设置其需要代理的角色
        proxyInvocationHandler.setRent(host);
          // 3. 调用方法获得代理类(这里的proxy就是动态生成的代理类,需要进行对象类型的强制转换)
        Rent proxy = (Rent) proxyInvocationHandler.getProxy();

        //客户端访问代理类
        proxy.rent();
    }
}

在MVC中控制层调用业务逻辑层的动态代理实现

  1. 动态代理类
//通过接口实现动态代理
//动态生成代理类的类,需要实现:InvocationHandler这个接口
//通过这个处理程序类来自动生成代理类实例
public class ProxyInvocationHandler implements InvocationHandler {
    //被代理的接口
    private UserService userService;
    
    //Spring推荐使用Set方法
    public void setUserService(UserService userService) {
        this.userService = userService;
    }

    //生成得到代理类
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),userService.getClass().getInterfaces(),this);
    }

    //处理代理实例,并返回结果

    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //这里的method就是java.lang.reflection反射机制包下的对象
        log(method.getName());
        Object result = method.invoke(userService, args);
        return result;
    }

    //增加打印日志
    public void log(String msg){
        System.out.println("[Debug]: 使用了"+msg+"方法!");
    }
}
  1. 客户端访问动态生成的代理类
public class Client {
    public static void main(String[] args) {
        //真实角色
        UserServiceImpl userService = new UserServiceImpl();

        //动态生成代理角色
          // 1.实例化 实现动态生成代理类的类 的实例对象
        ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler();
          // 2. 调用方法设置其需要代理的角色
        proxyInvocationHandler.setUserService(userService);
          // 3. 调用方法获得代理类(这里的proxy就是动态生成的代理类,需要进行对象类型的强制转换)
        UserService proxy = (UserService) proxyInvocationHandler.getProxy();

        //客户端访问代理类
        proxy.del();
    }
}

封装动态代理类成工具类

动态代理类,接收Object

//通过接口实现动态代理
//动态生成代理类的类,需要实现:InvocationHandler这个接口
//通过这个处理程序类来自动生成代理类实例
public class ProxyInvocationHandler implements InvocationHandler {

    //被代理的接口【即: 代理中介实现的接口】
    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }

    //生成得到代理类【即:动态生成代理类】
      //方法的参数(ClassLoader loader,@NotNull Class<?>[] interfaces,@NotNull reflect.InvocationHandler h)
      // loader:用哪个类加载器去加载代理对象
      // interfaces: 动态代理类需要实现的接口(通过反射机制获取)
      // h: 动态代理方法在执行时,会调用h里面的invoke()方法去执行
    public Object getProxy(){
        return Proxy.newProxyInstance(this.getClass().getClassLoader(),target.getClass().getInterfaces(),this);
    }

    //处理代理实例,并返回结果【即:这个方法会被自动调用,故需要执行的代码需要放在这个方法里面】
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //动态代理的本质,就是利用反射机制实现的
        Object result = method.invoke(target,args);
        return result;

    }
}

Client

public class Client {
    public static void main(String[] args) {
        //真实角色
        UserServiceImpl userServiceImpl = new UserServiceImpl();

        //动态生成代理角色
          // 1.实例化 实现动态生成代理类的类 的实例对象
        ProxyInvocationHandler proxyInvocationHandler = new ProxyInvocationHandler();
          // 2. 调用方法设置其需要代理的角色
        proxyInvocationHandler.setTarget(userServiceImpl);
          // 3. 调用方法获得代理类(这里的proxy就是动态生成的代理类,需要进行对象类型的强制转换)
        UserService proxy = (UserService) proxyInvocationHandler.getProxy();

        //客户端访问代理类
        proxy.del();
    }
}

11. AOP

11.1 什么是AOP

  • AOP是OOP(Object Oriented Programming) 的延续, 是软件开发中的一个热点,也是Spring框架中的一个重要内容。

  • AOP(Aspect Oriented Programming) 意为: 面向切面编程

  • AOP:是一种横向编程思想。 其可以在不影响原来的业务逻辑的情况下,实现业务逻辑的增强。AOP是一个实现代码解耦的利器。

  • AOP 使得在 不改变原代码功能流程的基础上去加入新的功能,其实就是一种 扩展性 的表现,其 底层基本原理就是上面讲的"动态代理"技术,跟设计模式里"代理模式"的思想是一样的。

  • 利用AOP可以对业务逻辑的各个部分进行隔离, 从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,提高开发效率。

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bpDblNwk-1638720613320)(Spring%E7%AC%94%E8%AE%B0.assets/image-20211201214227829.png)]

在这里插入图片描述

11.2 AOP在Spring中的作用

允许用户自定义切面。

  • 横切关注点: 跨越了应用程序多个模块的方法或功能。即是,与我们已实现的业务逻辑无关的,但是需要我们关注的部分。如: 日志,安全,缓存,事务等等。
  • 切面(Aspect): 横切关注点 被模块化了的特殊对象。即,将横切关注点作为一个类。
  • 通知(Advice): 切面必须要完成的工作。即,它是切面类中的一个方法。
  • 目标(Target): 被通知的对象。
  • 代理(Proxy): 向目标对象应用通知之后创建的对象。
  • 切入点(PointCut): 切面通知 执行的 “地点” 的定义。
  • 连接点(Joint): 与切入点匹配的执行点。

在这里插入图片描述

SpringAOP中, 通过通知(Advice)即方法定义横切逻辑, Spring中支持5种类型的通知(Advice):

通知类型连接点对应需要实现的Spring AOP中的接口
前置通知方法前org.springframework.aop.MethodBeforeAdvice
后置通知方法后org.springframework.aop.AfterReturningAdvice
环绕通知方法前后org.aopalliance.intercept.MethodInterceptor
异常抛出通知方法抛出异常org.springframework.aop.ThrowAdvice
引介通知类中增加新的方法属性org.springframework.aop.IntroductionInterceptor

AOP在不改变原有代码的情况下,去增加新的功能。

11.3 使用Spring实现AOP

重点】使用AOP织入,需要先导入一个依赖包!

<!--使用Spring AOP 必须引入的依赖 aop织入-->
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.6</version>
</dependency>

实现AOP的方式

方式一: 使用Spring的API接口 来实现AOP

  1. 被代理的接口(抽象角色)

UserService.java

public interface UserService {
    //增删改查方法
    public void add();
    public void del();
    public void update();
    public void select();
}
  1. 真实角色(即原来的代码,原来的已存在的业务逻辑,是不能改动的)
public class UserServiceImpl implements UserService {
    public void add() {
        System.out.println("增加的方法!");
    }

    public void del() {
        System.out.println("删除的方法!");
    }

    public void update() {
        System.out.println("更改的方法!");
    }

    public void select() {
        System.out.println("查询的方法!");
    }
}
  1. 待增加的方法功能即Advice,需要各作为一个单独的类

Log.java,在已存在的原来的方法之前打印日志,即前置日志

//在我们的方法执行之前执行
//需要继承的Spring的API接口:MethodBeforeAdvice
public class Log implements MethodBeforeAdvice {
    //重写方法(这个方法会在我们的执行方法之前自动调用)
    //method: 要执行的目标对象的方法
    //args: 参数
    //target: 目标对象
    public void before(Method method, Object[] args, Object target) throws Throwable {
        //这个方法专注于执行我们方法之前需要干什么
        System.out.println(target.getClass().getName()+"的"+method.getName()+"方法被执行了!");

    }
}

AfterLog.java,在原来已存在的方法之后打印日志,即后置日志

//在我们的方法执行之后执行
//需要继承的Spring的API接口:AfterReturningAdvice或者 AfterAdvice
public class AfterLog implements AfterReturningAdvice {
    //重写方法(这个方法会在我们执行的方法之后去执行,且这个方法可以接收返回值)
    //returnValue: 返回值
    //method: 要执行的目标对象的方法
    //args: 参数
    //target: 目标对象
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了"+method.getName()+"方法,返回结果为:"+returnValue);
    }
}
  1. 配置文件(代理角色的配置,即代理角色的实现交给Spring AOP)

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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd
                           
       http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd
">

    <!--注册bean对象-->
    <bean id="userServiceImpl" class="com.carson.service.UserServiceImpl"></bean>
    <bean id="log" class="com.carson.log.Log"></bean>
    <bean id="afterLog" class="com.carson.log.AfterLog"></bean>

    <!--配置aop: 需要在文件的最上方先导入aop的相关约束-->
    <!--方式一: 使用原生Spring API接口 实现AOP-->
    <aop:config>
        <!--1. 定义切入点 (即配置要给哪个类插入一些方法)
        其中expression格式: execution(修饰词 返回值 类名 方法名 参数)  *符号代表任意, 而.. 代表可以有任意个参数
                          execution()中填写代表的是: 要执行的位置!
                          这里按照方法定义顺序考虑即可: 即 execution(返回值 类全路径.方法名(参数))
        -->
        <aop:pointcut id="pointcut" expression="execution(* com.carson.service.UserServiceImpl.*(..))"/>
        <!--2.通知对应到切入点 (将advice-ref中对应的方法类对应到pointcut-ref代表的切入点的对应的方法中)-->
        <aop:advisor advice-ref="log" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointcut"/>
    </aop:config>
</beans>
  1. 测试类
public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //注意点: 动态代理代理的是接口(故下面必须要返回接口,即返回代理角色)
        UserService userService = context.getBean("userServiceImpl", UserService.class);
        //客户端访问代理角色
        userService.select();
    }
}

在这里插入图片描述


方式二: 使用自定义类 来实现AOP推荐

  1. 被代理的接口(抽象角色)

UserService.java

public interface UserService {
    //增删改查方法
    public void add();
    public void del();
    public void update();
    public void select();
}
  1. 真实角色(即原来的代码,原来的已存在的业务逻辑,是不能改动的)
public class UserServiceImpl implements UserService {
    public void add() {
        System.out.println("增加的方法!");
    }

    public void del() {
        System.out.println("删除的方法!");
    }

    public void update() {
        System.out.println("更改的方法!");
    }

    public void select() {
        System.out.println("查询的方法!");
    }
}
  1. 自定义的切面类(里面书写待增加的新的功能方法)
//自定义类,作为切面
public class DiyPointCut {
    public void before(){
        System.out.println("方法执行前!!");
    }

    public void after(){
        System.out.println("方法执行后");
    }
}
  1. 配置文件(代理角色的配置,即代理角色的实现交给Spring AOP)

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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd
">
    
    <!--注册bean对象-->
    <bean id="userServiceImpl" class="com.carson.service.UserServiceImpl"></bean>
    <bean id="diy" class="com.carson.diy.DiyPointCut"></bean>
    
    <!--方式二:使用自定义类实现AOP-->
    <aop:config>
        <!--自定义切面,其中 ref:代表要引用的类-->
        <aop:aspect ref="diy">
            <!--1.设置切入点 -->
            <aop:pointcut id="pointcut" expression="execution(* com.carson.service.UserServiceImpl.*(..))"/>
            <!--2.配置通知到切入点,新增的方法和切入点的对应-->
            <aop:before method="before" pointcut-ref="pointcut"/>
            <aop:after method="after" pointcut-ref="pointcut"/>
        </aop:aspect>
    </aop:config>
</beans>
  1. 测试类
public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //注意点: 动态代理代理的是接口(故下面必须要返回接口,即返回代理角色)
        UserService userService = context.getBean("userServiceImpl", UserService.class);
        //客户端访问代理角色
        userService.select();
    }
}

在这里插入图片描述


方式三: 使用注解实现AOP

  1. 被代理的接口(抽象角色)

UserService.java

public interface UserService {
    //增删改查方法
    public void add();
    public void del();
    public void update();
    public void select();
}
  1. 真实角色(即原来的代码,原来的已存在的业务逻辑,是不能改动的)
public class UserServiceImpl implements UserService {
    public void add() {
        System.out.println("增加的方法!");
    }

    public void del() {
        System.out.println("删除的方法!");
    }

    public void update() {
        System.out.println("更改的方法!");
    }

    public void select() {
        System.out.println("查询的方法!");
    }
}
  1. 使用AOP注解的切面类
//@Aspect注解标注这个类是一个切面
@Aspect
public class AnnotationPointCut {
    //@Before注解的内容就是新增的方法的切入点
    @Before("execution(* com.carson.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("=====方法执行前=====");
    }


    //@After注解的内容就是新增的方法的切入点
    @After("execution(* com.carson.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("=====方法执行后======");
    }

    //@Around即环绕通知
    //在环绕中,我们可以给方法加一个参数,代表处理的切入的点
    @Around("execution(* com.carson.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable {
        System.out.println("环绕前");
        //执行方法,其相当于过滤器的doFilter()
        Object proceed = jp.proceed();
        System.out.println("环绕后");
    }
}
  1. 配置文件(代理角色的配置,即代理角色的实现交给Spring AOP)

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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/aop
       https://www.springframework.org/schema/aop/spring-aop.xsd
">
    
    <!--注册bean对象-->
    <bean id="userServiceImpl" class="com.carson.service.UserServiceImpl"></bean>
    <bean id="annotationPointCut" class="com.carson.diy.AnnotationPointCut"></bean>

    <!--方式三: 使用注解实现AOP-->
    <!--开启AOP注解支持 proxy-target-class参数可以配置动态代理的实现: JDK(默认,对应false) cglib(对应true)-->
    <aop:aspectj-autoproxy proxy-target-class="false"/>

</beans>
  1. 测试类
public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //注意点: 动态代理代理的是接口(故下面必须要返回接口,即返回代理角色)
        UserService userService = context.getBean("userServiceImpl", UserService.class);
        //客户端访问代理角色
        userService.select();
    }
}

在这里插入图片描述

12. 整合Mybatis

12.1 回顾Mybatis基本使用

  1. pojo目录下编写实体类。

User.java

public class User {
    private int id;
    private String name;
    private String pwd;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}
  1. resources目录下编写mybatis核心配置文件。

mybatis-config.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核心配置文件-->
<configuration>
    <!--别名管理: 通过扫描包方式设置别名-->
    <typeAliases>
        <!--name属性对应包的位置-->
        <package name="com.carson.pojo"/>
    </typeAliases>
    
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/> <!--事务管理是JDBC-->
            <dataSource type="POOLED">
                <!--更换连接数据库的各个属性值-->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <!--注:这里的&连接符要用&amp;进行表示,&amp; 是 HTML 中 & 的表示方法-->
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&amp;useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=true"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <!--每一个Mapper.xml都需要在mybatis的核心配置文件中注册-->
    <mappers>
        <!--路径要用/进行分割-->
        <mapper resource="com/carson/mapper/UserMapper.xml"/>
    </mappers>
</configuration>
  1. mapper目录下编写接口文件

UserMapper.java

public interface UserMapper {
    public List<User> getUsers();
}
  1. mapper目录下编写接口对应的Mapper.xml(mybatis中每一个接口都要对应一个xml文件)

注意:每一个Mapper.xml都需要在mybatis的核心配置文件中注册 ,这里已在上面注册】

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--namespace要对应接口文件的全路径-->
<mapper namespace="com.carson.mapper.UserMapper">
    <select id="getUsers" resultType="user">
        select * from user;
    </select>
</mapper>
  1. 测试
public class MyTest {
    public static void main(String[] args) throws IOException {
        //读取mybatis-config配置文件
        String resources = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resources);
        //创建sqlSessionFactory实例对象
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        //创建sqlSession对象,其相当于Connection数据库连接对象,参数为true代表自动提交事务
        SqlSession sqlSession = sqlSessionFactory.openSession(true);
        try{
            //填入接口类的class对象
            UserMapper mapper = sqlSession.getMapper(UserMapper.class);
            //利用对象调用方法
            List<User> users = mapper.getUsers();

            //打印测试
            for(User user:users){
                System.out.println(user);
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            //关闭sqlSession
            sqlSession.close();
        }
    }
}

12.2 Spring整合Mybatis

附上Spring整合mybatis的官网学习文档链接:mybatis-spring

注意: 各个依赖版本之间有一定的依赖关系,如下:

在这里插入图片描述

spring和mybatis整合使用前需要在pom.xml文件中导入相关依赖和进行配置,如下:

pom.xml

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

    <artifactId>Spring-10-mybatis</artifactId>

    <!--导入相关依赖-->
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <!--mysql依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.19</version>
        </dependency>
        <!--mybatis依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.6</version>
        </dependency>
        <!--导入spring-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.0.RELEASE</version>
        </dependency>
        <!--Spring操作数据库的话,还需要一个spring-jdbc的依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.0.RELEASE</version>
        </dependency>
        <!--Spring AOP的织入包-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.6</version>
        </dependency>
        <!--mybatis和spring整合的包-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.6</version>
        </dependency>
    </dependencies>

    <!--解决资源不放在resources目录下的导致的资源导出失败问题-->
    <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>
</project>

整合mybatis方式一

  1. pojo目录下编写测试的实体类。

User.java

public class User {
    private int id;
    private String name;
    private String pwd;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}
  1. mapper目录下编写接口文件

usermapper.java

public interface UserMapper {
    public List<User> getUsers();

}
  1. mapper目录下编写接口对应的Mapper.xml(mybatis中每一个接口都要对应一个xml文件)

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--namespace要对应接口文件的全路径-->
<mapper namespace="com.carson.mapper.UserMapper">
    <select id="getUsers" resultType="user">
        select * from user;
    </select>
</mapper>
  1. resources目录下编写mybatis核心配置文件。

mybatis-config.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">

<!--由于spring配置文件中可以进行相关的mybatis设置,故原先的mybatis核心配置文件只保留部分设置-->
	<!--一般在mybatis的配置文件只留下两项配置: 即别名管理和设置项-->

<!--configuration标签是核心配置文件-->
<configuration>
    
    <!--1.别名管理: 通过扫描包方式设置别名-->
    <typeAliases>
        <!--name属性对应包的位置-->
        <package name="com.carson.pojo"/>
    </typeAliases>

    <!--2.settings标签是对mybatis的相关设置-->
    <!--<settings>
        <setting name="" value=""/>
    </settings>-->
    
</configuration>

  1. resources目录下建立spring配置文件spring-dao.xml

spring-dao.xml中做如下几个配置:

: 由于此类的相关内容是固定写死的, 故可直接作为连接数据库的工具类使用!

  • 数据源配置

  • sqlSessionFactory

  • sqlSessionTemplate

spring-dao.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
       https://www.springframework.org/schema/beans/spring-beans.xsd">
    

    <!--1 DataSource:使用spring-jdbc依赖提供的数据源类替换Mybatis原先数据源类-->
    
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!--类中注入连接数据库的相关参数属性值-->
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&amp;useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=true"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>
    
    

    <!--2.sqlSessionFactory-->
    
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--注入dataSource属性值-->
        <property name="dataSource" ref="dataSource"/>
        <!--绑定Mybatis配置文件 也可以不绑定 使用property标签去替代mybatis配置文件中的配置
           如果绑定了mybatis的配置文件,那么mybatis的配置文件和spring的配置文件就连接起来了-->
        <!--注: 下面标签的value值的固定形式: classpath:文件路径。(如果文件位于资源目录下,则直接写文件名即可)-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!--由于每一个Mapper.xml都需要在mybatis的核心配置文件中注册,下面等价于注册各个Mapper.xml-->
       <!-- 注意:classpath:后的文件路径要用/进行分割-->
        <property name="mapperLocations" value="classpath:com/carson/mapper/*.xml"/>
    </bean>
	
    <!-- 3. SqlSessionTemplate-->
    
    <!--这里的SqlSessionTemplate:就是我们之前mybatis中使用的sqlSession-->
    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
        <!--只能使用有参构造器方式注入sqlSessionFactory,因为SqlSessionTemplate中没有set方法-->
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>

</beans>
  1. 需要给对应接口添加对应的实现类UserMapperImpl.java

UserMapperImpl.java

//现在要多写一个对应的接口实现类
public class UserMapperImpl implements UserMapper {
    
    //我们所有的操作,都用sqlSessionTemplate来执行,其就相当于之前mybatis中的sqlSession
    private SqlSessionTemplate sqlSessionTemplate;

    //来个set方法,方便在spring配置文件中注入
    public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
        this.sqlSessionTemplate = sqlSessionTemplate;
    }

    public List<User> getUsers() {
        //这里的getMapper和方法调用和之前mybatis中的使用相同
        UserMapper mapper = sqlSessionTemplate.getMapper(UserMapper.class);
        return mapper.getUsers();
    }
}
  1. resources目录下新建一个spring的总的配置文件applicationContext.xml
  • 在其中导入之前先创建的spring配置文件spring-dao.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--导入之前创建的spring-dao.xml-->
    <import resource="spring-dao.xml"/>

    <!--注册接口的实现类 -->
    <bean id="userMapperImpl" class="com.carson.mapper.UserMapperImpl">
        <property name="sqlSessionTemplate" ref="sqlSessionTemplate"/>
    </bean>

</beans>
  1. 测试
public class MyTest2 {
    public static void main(String[] args) {
        //读取总的spring配置文件,创建上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //getBean()来获取接口的实现类的对象
        UserMapperImpl userMapperImpl = context.getBean("userMapperImpl", UserMapperImpl.class);
        //调用实现类对象的方法
        List<User> users = userMapperImpl.getUsers();
        //打印输出
        for(User user:users){
            System.out.println(user);
        }
    }
}

在这里插入图片描述

整合mybatis方式二

注:

  1. 方式二通过继承SqlSessionDaoSupport类,再调用其getSqlSession()方法获取sqlSession对象。
  2. 故spring-dao.xml配置文件中可省去对 sqlSessionTemplate的配置!
  1. pojo目录下编写测试的实体类。

User.java

public class User {
    private int id;
    private String name;
    private String pwd;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}
  1. mapper目录下编写接口文件

usermapper.java

public interface UserMapper {
    public List<User> getUsers();

}
  1. mapper目录下编写接口对应的Mapper.xml(mybatis中每一个接口都要对应一个xml文件)

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!--namespace要对应接口文件的全路径-->
<mapper namespace="com.carson.mapper.UserMapper">
    <select id="getUsers" resultType="user">
        select * from user;
    </select>
</mapper>
  1. resources目录下编写mybatis核心配置文件。

mybatis-config.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">

<!--由于spring配置文件中可以进行相关的mybatis设置,故原先的mybatis核心配置文件只保留部分设置-->
	<!--一般在mybatis的配置文件只留下两项配置: 即别名管理和设置项-->

<!--configuration标签是核心配置文件-->
<configuration>
    
    <!--1.别名管理: 通过扫描包方式设置别名-->
    <typeAliases>
        <!--name属性对应包的位置-->
        <package name="com.carson.pojo"/>
    </typeAliases>

    <!--2.settings标签是对mybatis的相关设置-->
    <!--<settings>
        <setting name="" value=""/>
    </settings>-->
    
</configuration>

  1. resources目录下建立spring配置文件spring-dao.xml

spring-dao.xml中做如下几个配置:

: 方式二不用在配置文件中配置sqlSessionTemplate对象了!

  • 数据源配置

  • sqlSessionFactory

spring-dao.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
       https://www.springframework.org/schema/beans/spring-beans.xsd">
    

    <!--1 DataSource:使用spring-jdbc依赖提供的数据源类替换Mybatis原先数据源类-->
    
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!--类中注入连接数据库的相关参数属性值-->
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&amp;useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=true"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>
    
    

    <!--2.sqlSessionFactory-->
    
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--注入dataSource属性值-->
        <property name="dataSource" ref="dataSource"/>
        <!--绑定Mybatis配置文件 也可以不绑定 使用property标签去替代mybatis配置文件中的配置
           如果绑定了mybatis的配置文件,那么mybatis的配置文件和spring的配置文件就连接起来了-->
        <!--注: 下面标签的value值的固定形式: classpath:文件路径。(如果文件位于资源目录下,则直接写文件名即可)-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!--由于每一个Mapper.xml都需要在mybatis的核心配置文件中注册,下面等价于注册各个Mapper.xml-->
       <!-- 注意:classpath:后的文件路径要用/进行分割-->
        <property name="mapperLocations" value="classpath:com/carson/mapper/*.xml"/>
    </bean>

</beans>
  1. 需要给对应接口添加对应的实现类UserMapperImpl2.java

UserMapperImpl2.java

//现在要多写一个对应的接口实现类
//方式二需要继承SqlSessionDaoSupport类
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper{
    public List<User> getUsers() {
        //通过SqlSessionDaoSupport类中的getSqlSession()获取sqlSession对象
        return getSqlSession().getMapper(UserMapper.class).getUsers();
    }
}
  1. resources目录下新建一个spring的总的配置文件applicationContext.xml
  • 在其中导入之前先创建的spring配置文件spring-dao.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--导入之前创建的spring-dao.xml-->
    <import resource="spring-dao.xml"/>

    <!--注册接口的实现类 -->
    <bean id="userMapperImpl2" class="com.carson.mapper.UserMapperImpl2">
        <!--注入sqlSessionFactory-->
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>

</beans>
  1. 测试
public class MyTest3 {
    public static void main(String[] args) {
        //读取总的spring配置文件,创建上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //getBean()来获取接口的实现类的对象
        UserMapper userMapper = context.getBean("userMapperImpl2", UserMapper.class);
        //调用实现类对象的方法
        List<User> users = userMapper.getUsers();
        //打印输出
        for(User user:users){
            System.out.println(user);
        }
    }
}

在这里插入图片描述

13. 声明式事务

为什么需要配置事务管理?

  • 如果不配置事务,可能存在数据提交不一致的情况!
  • 如果不在Spring中去配置声明式事务,我们就需要在原代码中手动配置事务管理。
  • 事务在项目的开发中十分重要,涉及到数据的一致性和完整性等问题!

spring中的事务管理方式:

  • 声明式事务(重点): 事务结合AOP的应用,交由Spring容器管理事务。
    • 一般情况下比编程式事务好用。
    • 将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
    • 将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式的事务管理。
  • 编程式事务: 事务需要在原Java代码中手动进行管理。
    • 将事务管理代码嵌到业务方法中来控制事务的提交和回滚
    • 缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码

由于需要操作数据库,pom.xml中需要导入的相关依赖和相关配置,如下:

pom.xml

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

    <artifactId>Spring-11-transaction</artifactId>

    <!--导入相关依赖-->
    <dependencies>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <!--mysql依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.19</version>
        </dependency>
        <!--mybatis依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.6</version>
        </dependency>
        <!--导入spring-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.2.0.RELEASE</version>
        </dependency>
        <!--Spring操作数据库的话,还需要一个spring-jdbc的依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.2.0.RELEASE</version>
        </dependency>
        <!--Spring AOP的织入包-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.6</version>
        </dependency>
        <!--mybatis和spring整合的包-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.6</version>
        </dependency>
    </dependencies>

    <!--解决资源不放在resources目录下的导致的资源导出失败问题-->
    <build>
        <resources>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>
</project>

未进行事务管理应用示例

示例步骤:

  1. pojo目录下编写测试的实体类。

User.java

public class User {
    private int id;
    private String name;
    private String pwd;

    public User() {
    }

    public User(int id, String name, String pwd) {
        this.id = id;
        this.name = name;
        this.pwd = pwd;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}
  1. mapper目录下编写接口文件

新增两个方法,增加和删除用户!

usermapper.java

public interface UserMapper {
    //查询
    public List<User> getUsers();
    //增加
    public int insertUser(User user);
    //删除(简单数据类型要加 @param())
    public int delUser(@Param("id") int id);
}
  1. mapper目录下编写接口对应的Mapper.xml(mybatis中每一个接口都要对应一个xml文件)

mapper文件,我们故意把 delete删除的sql语句 写错,方便测试!

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.carson.mapper.UserMapper">
    <select id="getUsers" resultType="user">
        select * from user;
    </select>

    <!--id对应方法名-->
    <insert id="addUser" parameterType="user">
        insert into user values(#{id},#{name},#{pwd});
    </insert>
    
	<!--这里故意将删除的sql语句写错,给delete加个s-->
    <delete id="delUser" parameterType="int">
        deletes from user where id=#{id};
    </delete>

</mapper>
  1. resources目录下编写mybatis核心配置文件。

mybatis-config.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">

<!--由于spring配置文件中可以进行相关的mybatis设置,故原先的mybatis核心配置文件只保留部分设置-->
	<!--一般在mybatis的配置文件只留下两项配置: 即别名管理和设置项-->

<!--configuration标签是核心配置文件-->
<configuration>
    
    <!--1.别名管理: 通过扫描包方式设置别名-->
    <typeAliases>
        <!--name属性对应包的位置-->
        <package name="com.carson.pojo"/>
    </typeAliases>

    <!--2.settings标签是对mybatis的相关设置-->
    <!--<settings>
        <setting name="" value=""/>
    </settings>-->
    
</configuration>

  1. resources目录下的spring配置文件spring-dao.xml中增加事务管理的配置

使用Spring管理事务,注意头文件的约束导入 : tx

xmlns:tx="http://www.springframework.org/schema/tx"

http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

spring-dao.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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd  http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd
">

    <!--DataSource:使用spring-jdbc依赖提供的数据源类替换Mybatis原先的数据源类-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!--类中注入连接数据库的相关参数属性值-->
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&amp;useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=true"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>

    <!--sqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--注入dataSource属性值-->
        <property name="dataSource" ref="dataSource"/>
        <!--绑定Mybatis配置文件 也可以不绑定 使用property标签去替代mybatis配置文件中的配置
           如果绑定了mybatis的配置文件,那么mybatis的配置文件和spring的配置文件就连接起来了-->
        <!--注: 下面标签的value值的固定形式: classpath:文件路径。(如果文件位于资源目录下,则直接写文件名即可)(且classpath:和文件路径之间不能有空格,否则会出错)-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!--由于每一个Mapper.xml都需要在mybatis的核心配置文件中注册,下面等价于注册各个Mapper.xml-->
       <!-- 注意:classpath:后的文件路径要用/进行分割-->
        <property name="mapperLocations" value="classpath:com/carson/mapper/*.xml"/>
    </bean>

    <!--这里的SqlSessionTemplate:就是我们之前mybatis中使用的sqlSession-->
    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
        <!--只能使用有参构造器方式注入sqlSessionFactory,因为SqlSessionTemplate中没有set方法-->
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>
</beans>
  1. 需要给对应接口添加对应的实现类UserMapperImpl.java

在查询User方法中间加上增加和删除User的相关方法,这里示例增加9号用户然后删除5号用户!

UserMapperImpl.java

//现在要多写一个对应的接口实现类
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{

    public List<User> getUsers() {
        UserMapper mapper = getSqlSession().getMapper(UserMapper.class);

        //添加测试代码(在查询User中加上增加和删除User的相关方法)
        User user = new User(9, "增加的小王", "1212112");
        addUser(user);//增加一个用户
        delUser(5);//删除5号用户

        return mapper.getUsers();//返回信息

    }

    public int addUser(User user) {
        return getSqlSession().getMapper(UserMapper.class).addUser(user);

    }

    public int delUser(int id) {
        return getSqlSession().getMapper(UserMapper.class).delUser(id);
    }
}
  1. resources目录下新建一个spring的总的配置文件applicationContext.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--导入spring-dao.xml-->
    <import resource="spring-dao.xml"/>

    <!--注册接口的实现类 -->
    <bean id="userMapperImpl" class="com.carson.mapper.UserMapperImpl">
        <!--注入sqlSessionFactory-->
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>
</beans>
  1. 测试
public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapper userMapper = context.getBean("userMapperImpl", UserMapper.class);
        
        List<User> users = userMapper.getUsers();
        for(User user:users){
            System.out.println(user);
        }
    }

结果:

意料之中因为delete语句多写个s的报错:
在这里插入图片描述

由于插入用户方法在前,故虽然删除用户报错,但9号用户成功插入!

在这里插入图片描述

虽然报错了,但是9号用户的也成功插入了,这就影响了数据的一致性和完整性!

为此,我们需要引入事务管理,满足事务的原子性要求!即要么都执行成功,要么失败回滚!

Spring给我们提供了事务管理,我们只需要配置即可!

声明式事务应用示例

删除前面增加的9号用户,重新准备进行测试。

在这里插入图片描述

示例步骤:

  1. pojo目录下编写测试的实体类。

User.java

public class User {
    private int id;
    private String name;
    private String pwd;

    public User() {
    }

    public User(int id, String name, String pwd) {
        this.id = id;
        this.name = name;
        this.pwd = pwd;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", pwd='" + pwd + '\'' +
                '}';
    }
}
  1. mapper目录下编写接口文件

同样还是新增两个方法,增加和删除用户!

usermapper.java

public interface UserMapper {
    //查询
    public List<User> getUsers();
    //增加
    public int insertUser(User user);
    //删除(简单数据类型要加 @param())
    public int delUser(@Param("id") int id);
}
  1. mapper目录下编写接口对应的Mapper.xml(mybatis中每一个接口都要对应一个xml文件)

mapper文件,我们这里还是故意把 delete删除的sql语句 写错,方便测试!

UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.carson.mapper.UserMapper">
    <select id="getUsers" resultType="user">
        select * from user;
    </select>

    <!--id对应方法名-->
    <insert id="addUser" parameterType="user">
        insert into user values(#{id},#{name},#{pwd});
    </insert>
	
    <!--这里故意将delete语句写错,多加了个s-->
    <delete id="delUser" parameterType="int">
        deletes from user where id=#{id};
    </delete>

</mapper>
  1. resources目录下编写mybatis核心配置文件。

mybatis-config.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">

<!--由于spring配置文件中可以进行相关的mybatis设置,故原先的mybatis核心配置文件只保留部分设置-->
	<!--一般在mybatis的配置文件只留下两项配置: 即别名管理和设置项-->

<!--configuration标签是核心配置文件-->
<configuration>
    
    <!--1.别名管理: 通过扫描包方式设置别名-->
    <typeAliases>
        <!--name属性对应包的位置-->
        <package name="com.carson.pojo"/>
    </typeAliases>

    <!--2.settings标签是对mybatis的相关设置-->
    <!--<settings>
        <setting name="" value=""/>
    </settings>-->
    
</configuration>

  1. resources目录下的spring配置文件spring-dao.xml中增加事务管理的配置

使用Spring管理事务,注意头文件的约束导入 : tx

xmlns:tx="http://www.springframework.org/schema/tx"

http://www.springframework.org/schema/tx
http://www.springframework.org/schema/tx/spring-tx.xsd">

spring-dao.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:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd  http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
       http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd
">

    <!--DataSource:使用spring-jdbc依赖提供的数据源类替换Mybatis原先的数据源类-->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <!--类中注入连接数据库的相关参数属性值-->
        <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&amp;useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=true"/>
        <property name="username" value="root"/>
        <property name="password" value="root"/>
    </bean>

    <!--sqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--注入dataSource属性值-->
        <property name="dataSource" ref="dataSource"/>
        <!--绑定Mybatis配置文件 也可以不绑定 使用property标签去替代mybatis配置文件中的配置
           如果绑定了mybatis的配置文件,那么mybatis的配置文件和spring的配置文件就连接起来了-->
        <!--注: 下面标签的value值的固定形式: classpath:文件路径。(如果文件位于资源目录下,则直接写文件名即可)(且classpath:和文件路径之间不能有空格,否则会出错)-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!--由于每一个Mapper.xml都需要在mybatis的核心配置文件中注册,下面等价于注册各个Mapper.xml-->
       <!-- 注意:classpath:后的文件路径要用/进行分割-->
        <property name="mapperLocations" value="classpath:com/carson/mapper/*.xml"/>
    </bean>

    <!--这里的SqlSessionTemplate:就是我们之前mybatis中使用的sqlSession-->
    <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate">
        <!--只能使用有参构造器方式注入sqlSessionFactory,因为SqlSessionTemplate中没有set方法-->
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>
    
    
    
    <!--配置声明式事务-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--注入dataSource数据源对象,可以通过有参构造器注入,也可set方法注入-->
        <!--<constructor-arg ref="dataSource"/>-->
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--结合AOP实现事务的织入-->
    <!--配置事务通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--给哪些方法配置事务 并配置事务的传播特性(表示这些方法要怎么使用事务),一般为默认的REQUIRED,其适用于绝大多数的情况-->
        <tx:attributes>
            <!--其中*为通配符,匹配所有的方法-->
            <tx:method name="query" read-only="true"/>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>

    <!--AOP配置:将事务切入-->
    <aop:config>
        <!--1.设置切入点-->
        <aop:pointcut id="txPointCut" expression="execution(* com.carson.mapper.*.*(..))"/>
        <!--2.配置Advice(通知)到切入点-->
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
    </aop:config>

</beans>
  1. 需要给对应接口添加对应的实现类UserMapperImpl.java

在查询User方法中间加上增加和删除User的相关方法,示例增加9号用户然后删除5号用户!

UserMapperImpl.java

//现在要多写一个对应的接口实现类
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper{

    public List<User> getUsers() {
        UserMapper mapper = getSqlSession().getMapper(UserMapper.class);

        //添加测试代码(在查询User中加上增加和删除User的相关方法)
        User user = new User(9, "增加的小王", "1212112");
        addUser(user);//增加一个用户
        delUser(5);//删除5号用户

        return mapper.getUsers();//返回信息

    }

    public int addUser(User user) {
        return getSqlSession().getMapper(UserMapper.class).addUser(user);

    }

    public int delUser(int id) {
        return getSqlSession().getMapper(UserMapper.class).delUser(id);
    }
}
  1. resources目录下新建一个spring的总的配置文件applicationContext.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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!--导入spring-dao.xml-->
    <import resource="spring-dao.xml"/>

    <!--注册接口的实现类 -->
    <bean id="userMapperImpl" class="com.carson.mapper.UserMapperImpl">
        <!--注入sqlSessionFactory-->
        <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>
</beans>
  1. 测试
public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapper userMapper = context.getBean("userMapperImpl", UserMapper.class);
        
        List<User> users = userMapper.getUsers();
        for(User user:users){
            System.out.println(user);
        }
    }

结果:

意料之中的报错:

在这里插入图片描述

但发现,由于增加了事务管理,数据表中并没有像之前一样增加了9号用户,还是保持原样!

而这就是我们通过spring引入事务管理后想看到的!
在这里插入图片描述


欢迎关注个人公众号,回复“Spring”,获取本文所有的完整测试代码!

在这里插入图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值