Spring学习笔记

1 Spring 简介

image-20220430162805800

1.1 Spring 历史
  • 2002 年,首次推出了 Spring 框架的雏形:Interface 21 框架

  • Spring 框架以 Interface 21 框架为基础,经过重新设计并且不断丰富内涵,于 2004-03-24 正式发布了 1.0 版本

  • Rod Johson,Spring Framework 创始人,悉尼大学音乐学博士

  • Spring框架是用来简化软件开发的复杂性而创建的

  • Spring 是一个技术大杂烩,整合了现有的技术框架

1.2 Spring 下载
1.3 Spring 优点
  • Spring 是一个开源的免费的框架

  • Spring 是一个轻量级的非入侵式的框架

  • Spring 的两大特点:控制反转(IOC)面向切面编程(AOP)

  • Spring 支持事务的处理,对框架整合的支持

1.4 Spring 组成

20200710143347205636

2 IoC 理论

2.1 程序解耦

在之前的业务中,如果要实现用户查询 MySql 数据库的需求,我们需要去依次实现

- Dao 层
    - UserDao 数据库层接口
    - UserDaoImpl 数据库层接口实现类
- Service 层
    - UserService 业务层接口
    - UserServiceImpl 业务层接口实现类
- Servlet 层
    - UserServlt 用户行为控制类

假设,在 Dao 层有着以下四个接口的实现类 MysqlDaoImpl、OracleDaoImp、SQLServerDaoImpl、UserDaoByIdImpl

那么,想要在业务层调用这四个接口,在早期的时候,就要这样去写

//业务层一,调用 MysqlDaoImpl
public class MysqlServiceImpl implements MysqlService{
    
    private MysqlDao userDao = new MysqlDaoImpl();	

    public void getUser(){
        userDao.getUser();
    }
}
//业务层二,调用 OracleDaoImp
public class OracleServiceImpl implements OracleService{
    
    private OracleDao userDao = new OracleDaoImp();		
        
    public void getUser(){
        userDao.getUser();
    }
}
//业务层三,调用 SQLServerDaoImpl
public class SQLServerServiceImpl implements SQLServerService{
    
    private SQLServerDao userDao = new SQLServerDaoImpl();		
        
    public void getUser(){
        userDao.getUser();
    }
}
//业务层四,调用 UserDaoByIdImpl
public class UserByIdServiceImpl implements UserByIdService{
    
    private UserDaoById userDao = new UserDaoByIdImpl();			
        
    public void getUser(){
        userDao.getUser();
    }
}
//调用业务层测试
public class Test {
    //调用 MysqlServiceImpl
    MysqlServiceImpl mysqlSerivce = new MysqlServiceImpl();
    mysqlSerivce.getUser();
    
    //调用 OracleServiceImpl
    OracleServiceImpl oracleSerivce = new OracleServiceImpl();
    oracleSerivce.getUser();
    
    //调用 SQLServerServiceImpl
    SQLServerService sqlServerService = new SQLServerServiceImpl();
    sqlServerService.getUser();
    
    //调用 UserByIdService
    UserByIdService byIdService = new UserByIdServiceImpl();
    byIdService.getUser();
}

早期开发确实是这样的,但是这样无疑会增加开发业务的负担,不能完全把心思放在业务实现上,在这种方式下,用户每增加一个需求,Dao 层就要由接口、实现类,Service 也要有接口、实现类;如此下去,需求成千上万的时候,开发量不可谓不大!

所以,慢慢的就出现了一种基于 IoC 理论的编程思维,我们在 Service 层使用 set 接口后,发生了革命性的变化

//业务层
public class UserServiceImpl implements UserService{
    private UserDao userDao;
    
    //利用 Set 动态实现值的注入
    public void setUserDao(UserDao userDao) {
        this.userDao = userDao;
    }
    
    public void getUser(){
        userDao.getUser();
    }
}
//调用业务层测试
public class Test {
    UserService userService = new UserServiceImpl();
    
    // 调用 MysqlServiceImpl
    userService.setUserDao(new MysqlDaoImpl());
    userService.getUser();
    
    //调用 OracleServiceImpl
    userService.setUserDao(new OracleDaoImp());
    userService.getUser();
    
    //调用 SQLServerServiceImpl
    userService.setUserDao(new SQLServerDaoImpl());
    userService.getUser();
    
    //调用 UserByIdService
    userService.setUserDao(new UserDaoByIdImpl());
    userService.getUser();
}

很明显,不管用户需求如何变,只改 Dao 层查询数据库, Service 层根本无需改变,只需要在测试的时候将不同类型传入即可,大大简化流程

  • 之前开发,程序是由程序员手动创建的,控制权在程序员手中

  • 使用了 SET 注入之后,程序员不再具有主动性,直接由程序被动接受

  • 这就是 IoC 的思想原型,程序员不再去管对象的创建,系统耦合性大大降低,可以专注于业务逻辑的实现

image-20220430214708889

程序解耦:究其本质,程序解耦就是尽量减少程序与程序、对象与对象之间的依赖关系

image-20220430222421374

2.2 IoC 本质

控制反转(Inversion of Control),是一种设计思想,DI(依赖注入)是实现 IoC 的一种方法

在没有 IoC 的程序中,我们使用的是面向对象编程,对象的创建和对象之间的依赖关系完全硬编码在程序中,对象的创建由程序自己控制,控制反转后将对象的创建转移给第三方,即:获得依赖对象的方式反转了

采用 XML 方式配置的 Bean 的时候,Bean 的定义信息和实现是分离的,采用注解的方式可以将两者融为一体,Bean 的定义信息直接以注解的形式定义在实现类中,从而达到了零配置的目的

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

2.3 第一个 Spring 程序

创建一个实体类 Hello

package com.jiuxiao.pojo;

/**
 * 第一个 Spring
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/01 14:09
 * @since: 1.0.0
 */
public class Hello {
    private String str;

    public String getStr() {
        return str;
    }

    public void setStr(String str) {
        this.str = str;
    }

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

然后在 resources 下建立配置文件 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"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <!--使用 Spring 来创建对象,在 Spring 中这些都称之为 Bean-->
    <bean id="hello" class="com.jiuxiao.pojo.Hello">
        <property name="str" value="Hello Spring!"/>
    </bean>
</beans>

测试类中可以直接拿到该对象及其方法、属性

import com.jiuxiao.pojo.Hello;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;

/**
 * 测试类
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/01 14:15
 * @since: 1.0.0
 */
public class MyTest {
    public static void main(String[] args) {
        //获取 Spring 上下文对象
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        //对象都在 Spring 中进行管理了,想要使用直接去里面取出来即可
        Hello hello = (Hello) context.getBean("hello");
        System.out.println(hello.toString());
    }
}

image-20220501142253915

//传统方式下,创建一个对象
类型 变量名 = new 类型();
Hello hello = new Hello();

/*
使用 Spring
id : 相当于变量名
class : 相当于类型的全变量名
property : 相当于给对象属性赋值
*/
<bean id="hello" class="com.jiuxiao.pojo.Hello">
    <property name="str" value="Hello Spring!"/>
</bean>

Q:Hello 的对象是由谁创建的?

A:由 Spring 创建

Q:Hello 对象的属性是怎么设置的?

A:属性是由 Spring容器设置的

这个过程就叫做控制反转

控制:谁来控制对象的创建,传统应用程序是由程序本身控制创建的,使用 Spring 后,交给 Spring 来创建

反转:程序本身不创建对象,而是被动的接收对象

依赖注入:使用 set 方法来进行注入

image-20220501143613381

所谓的 IoC,本质就是,对象由 Spring 来创建、管理、装配!

3 IoC 创建对象的方式

3.1 使用无参构造创建对象
public class User {
    private String name;

    public User() {
        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);
    }
}
<bean id="user" class="com.jiuxiao.pojo.User">
    <property name="name" value="荒天帝"/>
</bean>
public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        User user = (User) context.getBean("user");
        user.show();
    }
}

image-20220501163256395

3.2 使用有参构造创建对象
public class User {
    public User(String name, int id) {
        System.out.println("User的有参构造");
        System.out.println("name = " + name + "\nid = " + id);
    }
}

方式一:使用下标赋值

<bean id="user" class="com.jiuxiao.pojo.User">
    <constructor-arg index="0" value="荒天帝"/>
    <constructor-arg index="1" value="18"/>
</bean>

方式二:使用参数类型赋值:不建议使用,如果参数有重复的类型,则无法使用

<bean id="user" class="com.jiuxiao.pojo.User">
    <constructor-arg type="java.lang.String" value="荒天帝"/>
    <constructor-arg type="int" value="18"/>
</bean>

方式三:使用参数名赋值

<bean id="user" class="com.jiuxiao.pojo.User">
    <constructor-arg name="name" value="荒天帝"/>
    <constructor-arg name="id" value="18"/>
</bean>

在配置文件加载的时候,Spring 容器中管理的所有对象就已经初始化了

4 Spring 配置

4.1 alias
<bean id="user" class="com.jiuxiao.pojo.User">
    <constructor-arg name="name" value="荒天帝"/>
    <constructor-arg name="id" value="18"/>
</bean>
<alias name="user" alias="Jack"/>
public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        User user = (User) context.getBean("Jack");	//使用别名获取对象
        
        User user = (User) context.getBean("user");
    }
}
4.2 bean
<!--
	id : 这个 bean 的唯一标识符
	class : bean 对象做对应的全限定名
	name : 与 alias 作用一样,也是取别名,但是比 alias 更强,支持 `, ; / # @ 空格` 等等多种的分隔符
-->
<bean id="user" class="com.jiuxiao.pojo.User" name="u1 u2, u3; u4/ u5# u6@ u7"></bean>
4.2 import

一般用于团队开发使用,可以将多个配置文件,合并为一个总的配置文件,配置文件如果内容相同,会被合并为一个

<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    <import resource="beans.xml"/>
    <import resource="beans2.xml"/>
    <import resource="beans3.xml"/>
</beans>
public class MyTest {
    public static void main(String[] args) {
        //直接使用总文件的配置
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    }
}

5 依赖注入(DI)

5.1 构造器注入

构造器注入方式就是 3 中所提到的方式

5.2 Set 方式注入(重点)

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

注入:bean 对象中所有属性由容器来注入

测试环境搭建

建立一个引用型的对象 Address,来表示 Student 类的一个属性

@Getter
@Setter
@ToString
public class Address {
    private String address;
}

然后建立测试类 Student

@Setter
@Getter
@ToString
public class Student {
    private int id;
    private String name;
    private Address address;
    private String[] books;
    private List<String> hobbies;
    private Map<String, String> card;
    private Set<String> games;
    private String wife;
    private Properties info;
}
<?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
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="address" class="com.jiuxiao.pojo.Address">
        <property name="address" value="上海市浦东新区"/>
    </bean>

    <bean id="student" class="com.jiuxiao.pojo.Student">
        <!--第一种 : 普通注入,使用 value -->
        <property name="id" value="21"/>
        <property name="name" value="张三"/>

        <!--第二种 : Bean 注入,使用 ref -->
        <property name="address" ref="address"/>

        <!--第三种 : 数组注入,使用 array + value -->
        <property name="books">
            <array>
                <value>数据库</value>
                <value>数据结构</value>
                <value>计算机组成原理</value>
                <value>计算机网络</value>
            </array>
        </property>

        <!--第四种 : list 注入,使用 list + value -->
        <property name="hobbies">
            <list>
                <value>游戏</value>
                <value>美女</value>
                <value>睡觉</value>
            </list>
        </property>

        <!--第五种 : map 注入,使用 map + entry -->
        <property name="card">
            <map>
                <entry key="身份证" value="541424424523232323"/>
                <entry key="银行卡" value="46846489779768"/>
                <entry key="校园卡" value="15364151"/>
            </map>
        </property>

        <!--第六种 : set 注入,使用 set + value -->
        <property name="games">
            <set>
                <value>GTA5</value>
                <value>古墓丽影</value>
                <value>荒野大镖客</value>
            </set>
        </property>

        <!--第七种 : null 注入,使用 <null/> 标签 -->
        <property name="wife">
            <null/>
        </property>

        <!--第八种 : properties 注入,使用 props + prop -->
        <property name="info">
            <props>
                <prop key="学号">455612225</prop>
                <prop key="邮箱">zs@edu.com</prop>
                <prop key="邮政编码">111222</prop>
            </props>
        </property>
    </bean>
</beans>
public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        Student student = (Student) context.getBean("student");
        System.out.println(student.toString());
    }
}

image-20220501212728265

5.3 p 命名空间注入

p 命名空间需要在配置文件中引入后使用 xmlns:p="http://www.springframework.org/schema/p"

它对应于 SET 方式注入 property

@ToString
@Getter
@Setter
public class User {
    private String name;
    private int age;
}
<?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:p="http://www.springframework.org/schema/p"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">
    
    <bean id="user" class="com.jiuxiao.pojo.User" p:name="荒天帝" p:age="18"/>
    
</beans>
@Test
public void getUser(){
    ApplicationContext context = new ClassPathXmlApplicationContext("user-beans.xml");
    User user = context.getBean("user", User.class);
    System.out.println(user.toString());
}

image-20220501214511043

5.4 c 命名空间注入

c 命名空间需要在配置文件中引入后使用 xmlns:c="http://www.springframework.org/schema/c"

它对应于构造器注入 constructor-arg,使用时必须要有有参构造

@ToString
@Getter
@Setter
@NoArgsConstructor
public class User {
    private String name;
    private int age;

    public User(String name, int age) {
        this.name = name;
        this.age = age;
    }
}
<?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:c="http://www.springframework.org/schema/c"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd">

    <bean id="user" class="com.jiuxiao.pojo.User" c:name="荒天帝" c:age="21"/>
    
</beans>
@Test
public void getUser(){
    ApplicationContext context = new ClassPathXmlApplicationContext("user-beans.xml");
    User user = context.getBean("user", User.class);
    System.out.println(user.toString());
}

image-20220501215840336

5.5 Bean 作用域

image-20220502091951473

单例模式 singleton

<!-- Spring 默认为单例模式 -->
<bean id="user" class="com.jiuxiao.pojo.User" c:name="荒天帝" c:age="21" scope="singleton"/>

原型模式 prototype

<!-- 每次使用 getBean 都会产生新的对象 -->
<bean id="user" class="com.jiuxiao.pojo.User" c:name="荒天帝" c:age="21" scope="prototype"/>

其余的 request、session、application、websocket 都在 Web 开发中使用

6 Bean 的自动装配

自动装配是 Spring 满足 Bean 依赖的一种方式,Spring 会在上下文中自动寻找,并自动给 bean 装配属性

Spring 中有三种装配方式

  • 在 xml 文件中显式配置

  • 在 java 文件中显式配置

  • 隐式自动装配 bean

6.1 传统手动装配
@Setter
@Getter
@NoArgsConstructor
public class Dog {
    public void shout() {
        System.out.println("汪汪汪");
    }
}
@Setter
@Getter
@NoArgsConstructor
public class Cat {
    public void shout() {
        System.out.println("喵喵喵");
    }
}
@ToString
@Setter
@Getter
public class People {
    private String name;
    private Cat cat;
    private Dog dog;
}
<bean id="cat" class="com.jiuxiao.pojo.Cat"/>
<bean id="dog" class="com.jiuxiao.pojo.Dog"/>
<bean id="people" class="com.jiuxiao.pojo.People">
    <property name="name" value="Jack"/>
    <property name="cat" ref="cat"/>
    <property name="dog" ref="dog"/>
</bean>
@Test
public void test01(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    People people = context.getBean("people", People.class);
    people.getCat().shout();
    people.getDog().shout();
}
6.2 byName 自动装配

byName 装配方式会自动在容器的上下文中寻找,和自己对象的 set 方法后面的值对应的 bean id

缺点:如果要装配的 bean 的 id 与 set方法 后面的不一致,就会装配失败!

例如:setDog、setCat,就会去上下文寻找 dog、cat,找到后自动装配

<bean id="cat" class="com.jiuxiao.pojo.Cat"/>
<bean id="dog" class="com.jiuxiao.pojo.Dog"/>
<bean id="people" class="com.jiuxiao.pojo.People" autowire="byName">
    <property name="name" value="Jack"/>
</bean>
6.3 byType 自动装配

byType 装配方式会自动在容器的上下文中寻找,和自己对象属性类型相同的 bean

缺点:如果要装配的 bean 类型不是全局唯一,就会装配失败!

例如:people 拥有属性 Cat、Dog,就会去上下文寻找 Dog 类型、Cat 类型,找到后自动装配

<bean id="cat" class="com.jiuxiao.pojo.Cat"/>
<bean id="dog" class="com.jiuxiao.pojo.Dog"/>
<bean id="people" class="com.jiuxiao.pojo.People" autowire="byType">
    <property name="name" value="Jack"/>
</bean>
6.4 注解实现自动装配

要使用注解进行自动装配,就需要先导入约束和配置注解的支持

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

    <context:annotation-config/>

    <bean id="dog" class="com.jiuxiao.pojo.Dog"/>
    <bean id="cat" class="com.jiuxiao.pojo.Cat"/>
    <bean id="people" class="com.jiuxiao.pojo.People"/>
</beans>

然后给需要装配的 bean 加上 @AutoWired 注解

@ToString
@Setter
@Getter
public class People {
    private String name;
    @Autowired
    private Cat cat;
    @Autowired(required = false)
    private Dog dog;
}

@Autowired

  • 直接在属性上使用,也可以在 set 方法上使用

  • 使用该注解后可以省略 set 方法,但是前提是自动装配的属性名在 IoC 容器中存在,且命名一致,默认为 byName 方式

  • @Autowired 有一个属性值:required,默认为 true,当为 false 时,表示它所标记的属性可以为 null

  • 如果使用 @Autowired 自动装配的环境较为复杂,一个 @Autowired 注解无法完成时,可以搭配 @Qualifier(value = "xxx") 一起来使用,从而指定一个唯一的 bean 注入

<bean id="dog" class="com.jiuxiao.pojo.Dog"/>
<bean id="dog02" class="com.jiuxiao.pojo.Dog"/>
<bean id="cat" class="com.jiuxiao.pojo.Cat"/>
<bean id="cat02" class="com.jiuxiao.pojo.Cat"/>
<bean id="people" class="com.jiuxiao.pojo.People"/>
@ToString
@Setter
@Getter
public class People {
    private String name;
    
    @Autowired
    @Qualifier(value = "cat02")
    private Cat cat;
    
    @Autowired
    @Qualifier(value = "dog02")
    private Dog dog;
}

@Nullable

若某字段标记了该注解,则说明该字段可以为 null

@Resource

<bean id="dog" class="com.jiuxiao.pojo.Dog"/>
<bean id="cat" class="com.jiuxiao.pojo.Cat"/>
<bean id="cat02" class="com.jiuxiao.pojo.Cat"/>
<bean id="people" class="com.jiuxiao.pojo.People"/>
@ToString
@Setter
@Getter
public class People {
    private String name;

    @Resource(name = "cat02")
    private Cat cat;

    @Resource
    private Dog dog;
}

@Autowired 和 @Resource 异同点

  • 两者都是用来自动装配的,都可以放在属性字段上

  • @Autowired 是通过 byName 来实现的(常用)

  • @Resource 是 Java 的原生注解,默认通过 byName 实现,如果找不到名字,则通过 byType 实现,两种都找不到,则报错

6.5 使用 Java 配置

现在完全不使用 Spring 的 xml 配置文件,如何实现配置?

JavaConfig 是 Spring 的一个子项目,在 Spring 4 之后,它成为了一个核心功能

User 实体类

// @Component 注解:说明这个类被 Spring 接管了,注册到了容器中
@Component
public class User {

    private String name;

    public String getName() {
        return name;
    }

    @Value("荒天帝")   //@Value 注解:注入值
    public void setName(String name) {
        this.name = name;
    }

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

配置文件

// @Configuration 注解:说明这个类被 Spring 接管了,注册到了容器中,它的本质就是一个 @Component
// @Configuration 代表这是一个配置类,就和之前的 applicationContext.xml 作用一样
@Configuration
@ComponentScan("com.jiuxiao.pojo")
public class MyConfig {

    //@Bean 注解:相当于之前 xml 中的一个 <bean> 标签
    //该方法的名字,就相当于 bean 标签中的 id
    //该方法的返回值,就相当于 bean 标签中的 class
    @Bean
    public User getUser(){
        return new User();
    }
}

7 使用注解开发

7.1 bean 如何注入

原来我们要注册一个 bean,就要去 Spring 的配置文件中去使用 <bean> 标签去注册,那么,怎么使用注解进行注册呢?

首先,在 Spring 的配置文件中导入 context 支持,然后使用 <context:component-scan> 标签去进行包扫描,Spring 会去扫描该包之下的所有 Component,所以,只要给我们要进行注册的类加上 @Component 注解即可

//因为我们使用了 Component 扫描,因此该注解等价于 <bean id="user" class="com.jiuxiao.pojo.User"/>
@Component
public class User {
    public String name = "荒天帝";
}

在配置文件中加入 Component 包扫描

<context:component-scan base-package="com.jiuxiao.pojo"/>

启动测试类进行测试,发现成功注册 bean

@Test
public void text01(){
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    User user = context.getBean("user", User.class);
    System.out.println(user.name);
}

image-20220502213243865

7.2 属性如何注入

在原来,我们为类的属性赋值的时候,要先有 set 方法,然后去 bean 标签中赋值 <property name="name" value="荒天帝"/>

现在世界使用 @Value 注解即可实现属性的赋值

@Component
public class User {
    @Value("荒天帝")
    public String name;
    
    @Value("荒天帝")	//直接在 set 方法上注入也可以,两者一样的效果
    public void setName(String name){
        this.name = name;
    }
}

启动测试类进行测试,发现成功为 bean 的属性赋值

image-20220502213243865

7.3 衍生注解

@Component 有三个衍生的注解,它们和 @Component 注解本质与功能都是一致的,都是声明某个类注册为一个 Spring 的组件,只不过使用场景不同

  • @Repository :一般使用在 Dao 层(数据操作层)

  • @Controller :一般使用在 Controller 层(行为控制层)

  • @Service :一般使用在 Service 层(业务服务层)

7.4 自动装配的注解
  • @Autowired :通过类型、名称,实现自动装配

  • @Qualifier :通常与 @Autowired 注解配合使用,适用于多个同类型 bean 不同名的情况下

  • @Nullable :字段使用这个注解,说明该字段可以为 null

  • @Resource :通过名字实现自动装配,是 Javax 内置的注解

7.5 作用域

@Scope 注解可以标注某个类的作用域,比如单例模式、代理模式等

  • singleton :单实例模式

  • prototype :多实例模式

  • requset :同一个请求创建一个实例

  • session :同一个 session 创建一个实例

@Scope("prototype")
public class User{}

8 代理模式

代理模式:SpringAOP 的底层原理就是代理模式, SpringAOP 和 SpringMVC 是面试重点!

image-20220503094907571

8.1 静态代理模式

角色分析:

  • 抽象角色:一般使用接口或者抽象类来解决(租房)

  • 真实角色:被代理的角色(房东)

  • 代理角色:代理真实角色,代理之后,一般会做一些附属操作(中介)

  • 客户:访问代理对象的人(租客)

例子一:以租客租房为例来学习代理模式

首先要有一个抽象角色:租房这件事情,要使用接口来实现

/**
 * 租房:抽象角色
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/03 10:01
 * @since: 1.0.0
 */
public interface Rent {

    public void rent();
}

然后要有一个房东的真实角色,要出租房

/**
 * 房东:真实角色
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/03 10:01
 * @since: 1.0.0
 */
public class Host implements Rent{

    public void rent() {
        System.out.println("我是房东,我要出租房");
    }
}

理想的情况下,租客 Client 直接找房东 Host 租房就行,如下

/**
 * 租客:客户
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/03 10:01
 * @since: 1.0.0
 */
public class Client {
    public static void main(String[] args) {
        Host host = new Host();
        host.rent();
    }
}

image-20220503100747806

但是,真实的情况是,租客很难直接找到房东去租房,这个时候,就需要一个代理角色:中介

/**
 * 中介:代理角色
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/03 10:02
 * @since: 1.0.0
 */
public class Proxy implements Rent {
    private Host host;

    public Proxy() {
    }

    public Proxy(Host host) {
        this.host = host;
    }

    public void rent() {
        host.rent();
        seeHouse();
        signAgreement();
        agencyFee();
    }
    
    public void seeHouse(){
        System.out.println("中介带我看房");
    }

    public void signAgreement() {
        System.out.println("签订租赁合同");
    }

    public void agencyFee() {
        System.out.println("收中介费");
    }
}

有了中介以后,我们作为客户(租客),就要去找中介租房

/**
 * 租客:客户
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/03 10:01
 * @since: 1.0.0
 */
public class Client {
    public static void main(String[] args) {
        //房东要出租房子
        Host host = new Host();
        //代理角色:中介   中介帮助房东租出了房子,一般会有一些附属操作
        Proxy proxy = new Proxy(host);
        //租客不用面对房东,直接找中介租房即可
        proxy.rent();
    }
}

image-20220503102912210

例子二:以常用的增删改查业务为例,来学习代理模式

众所周知,用户的业务一般为增、删、改、查四种

/**
 * 用户业务接口
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/03 10:48
 * @since: 1.0.0
 */
public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void query();
}

用户业务接口实现

/**
 * 用户业务接口实现
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/03 10:48
 * @since: 1.0.0
 */
public class UserServiceImpl implements UserService{

    public void add() {
        System.out.println("增加了一个用户");
    }

    public void delete() {
        System.out.println("删除了一个用户");
    }

    public void update() {
        System.out.println("更新了一个用户");
    }

    public void query() {
        System.out.println("查询了一个用户");
    }
}

假设,现在项目已经上线,但是现在想要去加一个打印日志的功能,显然不能直接去修改线上代码,怎么办呢?

这个时候,就可以使用代理模式,来实现增加日志功能的需求

/**
 * 用户业务代理
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/03 10:49
 * @since: 1.0.0
 */
public class UserServiceProxy implements UserService {

    private UserServiceImpl userService;

    public void setUserService(UserServiceImpl userService) {
        this.userService = userService;
    }

    public void printLog(String msg) {
        System.out.println("[DEBUG]:执行的是 " + msg + " 功能\t[TIME]:" + new Date(System.currentTimeMillis()));
    }

    public void add() {
        printLog("add");
        userService.add();
    }

    public void delete() {
        printLog("delete");
        userService.delete();
    }

    public void update() {
        printLog("update");
        userService.update();
    }

    public void query() {
        printLog("query");
        userService.query();
    }
}

现在,回到线上,假设用户执行了增删改查方法,可以看到,日志功能已经成功添加

/**
 * 客户
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/03 10:49
 * @since: 1.0.0
 */
public class Client {
    public static void main(String[] args) {
        UserServiceImpl userService = new UserServiceImpl();
        UserServiceProxy proxy = new UserServiceProxy();
        proxy.setUserService(userService);      //代理用户业务的日志输出功能

        proxy.add();
        proxy.delete();
        proxy.update();
        proxy.query();
    }
}

image-20220503110653158

代理模式有何 特点

优点

  • 可以使真实的角色更加纯粹,不用去关注一些公共的业务

  • 公共业务交给代理角色来做,实现了业务的明确分工

  • 公共业务发生扩展的时候,便于集中管理

缺点:一个真实的角色就会产生一个代理角色,代码量翻倍,效率低

代理模式的大致原理如下图,这也是 AOP 编程的核心思想

image-20220503141936811

8.2 动态代理模式
  • 动态代理模式和静态代理模式的角色分类一致

  • 动态代理的类是动态生成的,而不是直接写好的

  • 动态代理分为两大类:基于接口的动态代理(JDK 动态代理)、基于类的动态代理(cglib)

  • 动态代理也使用 Java 字节码实现:Javasist

仍然以租客租房为例,来学习动态代理类

/**
 * 房东:真实角色
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/03 10:01
 * @since: 1.0.0
 */
public class Host implements Rent {

    public void rent() {
        System.out.println("我是房东,我要出租房");
    }
}
/**
 * 租房:抽象角色
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/03 10:01
 * @since: 1.0.0
 */
public interface Rent {

    public void rent();
}

接下来就是要创建动态代理类

/**
 * 由该类生成动态代理类
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/03 14:49
 * @since: 1.0.0
 */
public class ProxyInvocationHandler implements InvocationHandler {

    //被代理的接口
    private Rent rent;

    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 {
        return method.invoke(rent, args);
    }
}
/**
 * 客户
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/03 14:57
 * @since: 1.0.0
 */
public class Client {
    public static void main(String[] args) {
        Host host = new Host();     //真实的对象
        ProxyInvocationHandler handler = new ProxyInvocationHandler();
        handler.setRent(host);
        //这个 proxy 是动态生成的,不是我们自己写的
        Rent proxy = (Rent) handler.getProxy();
        proxy.rent();
    }
}

生成动态代理类的工具类模板

该类只做三件事:代理哪个接口、生成代理类、执行代理类方法

//由该类生成动态代理类
public class ProxyInvocationHandler implements InvocationHandler {

    //1 被代理的接口
    private Object target;

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

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

    //3 执行代理类方法
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(target, args);
    }
}

动态代理的 特点

  • 拥有静态代理的全部优点

  • 一个动态代理类代理的是一个接口,一般对应的是一类业务

  • 一个动态代理类可以同时代理多个类,只要被代理的类实现了同一个接口

9 AOP

9.1 什么是 AOP
  • AOP(Aspect Oriented Programming),意为面向切面编程

  • 通过预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术

  • AOP 是 OOP 的延续,是软件开发中的一个热点,也是 Spring 框架中的一个重要内容,是函数式编程的一种衍生范型

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

image-20220503155056659

9.2 Spring 中的 AOP

提供声明式事务,允许用户自定义切面

  • 横切关注点:跨越应用程序多个模块的方法或者功能(与业务无关,但却要时刻关注的部分,例如日志、缓存、事务...)

  • 切面(Aspect):横切关注点被模块化后的特殊对象(本质是一个类)

  • 通知(Advice):切面必须要完成的工作(本质是类中的方法)

  • 目标(Target):被通知的对象

  • 代理(Proxy):向目标应用对象通知之后所创建的对象

  • 切入点(PointCut):切面通知执行的 “地点”

  • 连接点(JoinPoint):与切入点所匹配的执行点

image-20220503161542466

在 SpringAOP 中,通过 Advice 定义横切逻辑, Spring 支持五种类型的 Advice

通知类型 连接点 实现接口
前置通知 方法之前 org.springframework.aop.MethodBeforeAdvice
后置通知 方法之后 org.springframework.aop.AfterRetuningAdvice
后置通知环绕通知 方法之后方法前后 org.aopalliance.intercept.MethodInterceptor
异常抛出通知 方法抛出异常 org.springframework.aop.ThrowsAdvic
引介通知 类中增加新的方法属性 org.springframework.aop.IntroductionInterceptor
9.3 使用 AOP

要使用 AOP 织入,首先需要导入 AOP 的依赖

<!--AOP织入依赖-->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.9</version>
</dependency>

仍然以最基本的增、删、改、查操作来学习 AOP,尝试使用 AOP 加入日志业务

/**
 * 用户业务接口
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/03 16:32
 * @since: 1.0.0
 */
public interface UserService {
    void add();
    void delete();
    void update();
    void select();
}
/**
 * 用户业务接口实现类
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/03 16:32
 * @since: 1.0.0
 */
public class UserServiceImpl implements UserService{

    public void add() {
        System.out.println("增加了一个用户");
    }

    public void delete() {
        System.out.println("删除了一个用户");
    }

    public void update() {
        System.out.println("更新了一个用户");
    }

    public void select() {
        System.out.println("查询了一个用户");
    }
}
9.3.1 Spring 接口实现

现在使用 AOP 增加前置日志、后置日志功能

/**
 * 前置通知
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/03 16:41
 * @since: 1.0.0
 */
public class BeforeLog implements MethodBeforeAdvice {

    public void before(Method method, Object[] args, Object target) throws Throwable {
        assert target != null;
        System.out.println(target.getClass().getName() + "的" + method.getName() + "方法执行了");
    }
}
/**
 * 后置通知
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/03 16:47
 * @since: 1.0.0
 */
public class AfterLog implements AfterReturningAdvice {

    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了" + method.getName() + "方法,返回结果为" + returnValue);
    }
}

现在我们要将两个功能设置为 Spring 接管,即注册 bean,并且设置切面和切入点相关的信息

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       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="userService" class="com.jiuxiao.service.UserServiceImpl"/>
    <bean id="beforeLog" class="com.jiuxiao.log.BeforeLog"/>
    <bean id="afterLog" class="com.jiuxiao.log.AfterLog"/>

    <!--方式一:使用原生的 Spring api 接口-->
    <!--导入AOP约束-->
    <aop:config>
        <!--设置切入点-->
        <aop:pointcut id="pointCut" expression="execution(* com.jiuxiao.service.UserServiceImpl.*(..))"/>
        <!--执行环绕增加-->
        <aop:advisor advice-ref="beforeLog" pointcut-ref="pointCut"/>
        <aop:advisor advice-ref="afterLog" pointcut-ref="pointCut"/>
    </aop:config>
</beans>

启动测试类,发现前置日志、后置日志功能被成功切入

public class MyTest {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        //动态代理,代理的是接口,而不是实现类,所以这里应该为 UserService,而非 UserServiceImpl
        UserService userService = context.getBean("userService", UserService.class);
        userService.add();
    }
}

image-20220503172409569

9.3.2 自定义类实现(切面)
/**
 * 自定义AOP
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/03 20:17
 * @since: 1.0.0
 */
public class DiyPointCut {
    public void before() {
        System.out.println("方法执行前");
    }

    public void after() {
        System.out.println("方法执行后");
    }
}

在 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="userService" class="com.jiuxiao.service.UserServiceImpl"/>
    <bean id="beforeLog" class="com.jiuxiao.log.BeforeLog"/>
    <bean id="afterLog" class="com.jiuxiao.log.AfterLog"/>
    <bean id="diy" class="com.jiuxiao.diy.DiyPointCut"/>
    
    <aop:config>
        <!--定义切面,ref : 要引用的类-->
        <aop:aspect ref="diy">
            <!--切入点-->
            <aop:pointcut id="pointCut" expression="execution(* com.jiuxiao.service.UserServiceImpl.*(..))"/>
            <!--通知-->
            <aop:before method="before" pointcut-ref="pointCut"/>
            <aop:after method="after" pointcut-ref="pointCut"/>
        </aop:aspect>
    </aop:config>
</beans>

image-20220503203226156

9.3.3 使用注解实现
/**
 * 注解实现AOP
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/03 20:36
 * @since: 1.0.0
 */
@Aspect
public class AnnotationPointCut {

    @Before("execution(* com.jiuxiao.service.UserServiceImpl.*(..))")
    public void before() {
        System.out.println("@Before 执行了");
    }

    @After("execution(* com.jiuxiao.service.UserServiceImpl.*(..))")
    public void after() {
        System.out.println("@After 执行了");
    }

    @AfterReturning("execution(* com.jiuxiao.service.UserServiceImpl.*(..))")
    public void afterRetuning(){
        System.out.println("@AfterReturning 执行了");
    }

    @Around("execution(* com.jiuxiao.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("环绕前");
        point.proceed();
        System.out.println("环绕后");
    }
}
<?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="userService" class="com.jiuxiao.service.UserServiceImpl"/>
    <bean id="beforeLog" class="com.jiuxiao.log.BeforeLog"/>
    <bean id="afterLog" class="com.jiuxiao.log.AfterLog"/>
    <bean id="annotation" class="com.jiuxiao.diy.AnnotationPointCut"/>
    
    <!--开启注解支持-->
    <aop:aspectj-autoproxy/>
</beans>

image-20220503211432973

10 整合 MyBatis

首先要导入相关的依赖

<?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>com.jiuxiao</groupId>
        <version>1.0-SNAPSHOT</version>
    </parent>
    <modelVersion>4.0.0</modelVersion>

    <artifactId>spring-mybatis</artifactId>

    <dependencies>
        <!--Mybatis-Spring整合依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.7</version>
        </dependency>
        <!--Mysql依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.28</version>
        </dependency>
        <!--junit依赖-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
        <!--Mybatis依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.9</version>
        </dependency>
        <!--spring-mvc依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.3.19</version>
        </dependency>
        <!--Spring-jdbc依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.19</version>
        </dependency>
        <!--Spring AOP织入依赖-->
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.9</version>
        </dependency>
        <!--lombok依赖-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.20</version>
        </dependency>
    </dependencies>
</project>
10.1 回顾 Mybatis

以 User 为例

首先依次编写接口、接口对对应的 xml 文件

/**
 * 用户类接口
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/05 09:05
 * @since: 1.0.0
 */
public interface UserMapper {
    List<User> selectUser();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jiuxiao.mapper.UserMapper">
    <select id="selectUser" resultType="user">
        select *
        from mybatis.user;
    </select>
</mapper>

然后就是 Mybatsi 核心配置文件(不要忘记注册 Mapper)

<?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>
    <typeAliases>
        <typeAlias type="com.jiuxiao.pojo.User" alias="user"/>
    </typeAliases>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url"
                          value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&amp;useSSL=true&amp;characterEncoding=utf8&amp;serverTimezone=Asia/Shanghai"/>
                <property name="username" value="root"/>
                <property name="password" value="0531"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper class="com.jiuxiao.mapper.UserMapper"/>
    </mappers>
</configuration>

测试,成功

@Test
public void selectUser() throws IOException {
    String resources = "mybatis-config.xml";
    InputStream resourceAsStream = Resources.getResourceAsStream(resources);
    SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    SqlSession sqlSession = factory.openSession(true);
    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> users = mapper.selectUser();

    for (User user : users) {
        System.out.println(user);
    }
}

image-20220505100947430

10.2 Mybatis-Spring
  • MyBatis-Spring 会将 MyBatis 代码无缝地整合到 Spring 中

  • 它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean 中

方式一:使用 SqlSesssionTemplate(重要)

MyBatis 整合到 Spring 中后,不需要我们再去创建 SqlSessionFactory,而是在 Spring 配置文件中直接注册后就可使用

首先,这里要将 User 类的接口进行实现

/**
 * 用户类接口
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/05 09:05
 * @since: 1.0.0
 */
public interface UserMapper {
    List<User> selectUser();
}

实现类

/**
 * 用户类接口实现类
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/05 10:39
 * @since: 1.0.0
 */
public class UserMapperImpl implements UserMapper{

    private SqlSessionTemplate sqlSessionTemplate;

    public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
        this.sqlSessionTemplate = sqlSessionTemplate;
    }

    public List<User> selectUser() {
        return sqlSessionTemplate.getMapper(UserMapper.class).selectUser();
    }
}

现在,原有的 Mybatis 配置文件中只配置了别名

<?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>
    <typeAliases>
        <typeAlias type="com.jiuxiao.pojo.User" alias="user"/>
    </typeAliases>
</configuration>

原来在 Mybatis 的配置文件中的所有配置,均可以转到 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
       https://www.springframework.org/schema/beans/spring-beans.xsd">

    <!-- 使用 Spring 自带的 jdbc-->
    <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?useUnicode=true&amp;useSSL=true&amp;characterEncoding=utf8&amp;serverTimezone=Asia/Shanghai"/>
        <property name="username" value="root"/>
        <property name="password" value="0531"/>
    </bean>

    <!--创建 SqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!-- 这里可以导入 mybatis 的配置-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="mapperLocations" value="classpath:com/jiuxiao/mapper/*.xml"/>
    </bean>

    <!--SqlSessionTemplate : 就是 SqlSession-->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <!-- 这里只能使用构造器注入,因为没有 set 方法-->
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>

    <!--将 UserMapperImpl 注入-->
    <bean id="userMapper" class="com.jiuxiao.mapper.UserMapperImpl">
        <property name="sqlSessionTemplate" ref="sqlSession"/>
    </bean>
</beans>

最后使用一个总的配置文件,去将其他的配置依次导入,团队开发一般这样做,每个配置文件专注于自己的事情

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

    <!-- 只管操作数据库-->
    <import resource="spring-dao.xml"/>
    <!-- 其他的配置文件....->
</beans>

然后去测试类测试

@Test
public void selectUser() throws IOException {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
    List<User> users = userMapper.selectUser();

    for (User user : users) {
        System.out.println(user);
    }
}

运行测试类,测试成功

image-20220505100947430

方式二:使用 SqlSessionDaoSupport(了解)

该方式比方式一更为精简,直接让接口实现类继承 SqlSessionDaoSupport 类即可

/**
 * 实现类
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/05 13:25
 * @since: 1.0.0
 */
public class UserMapperImplTwo extends SqlSessionDaoSupport implements UserMapper {

    public List<User> selectUser() {
        return getSqlSession().getMapper(UserMapper.class).selectUser();
    }
}

然后去配置文件中注册该类

<bean id="userMapper2" class="com.jiuxiao.mapper.UserMapperImplTwo">
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

在该方式下,直接省略了写 SqlSessionTemplate 的步骤

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

    <!-- 使用 Spring 自带的 jdbc-->
    <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?useUnicode=true&amp;useSSL=true&amp;characterEncoding=utf8&amp;serverTimezone=Asia/Shanghai"/>
        <property name="username" value="root"/>
        <property name="password" value="0531"/>
    </bean>

    <!--创建 SqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!-- 这里可以导入 mybatis 的配置-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="mapperLocations" value="classpath:com/jiuxiao/mapper/*.xml"/>
    </bean>
</beans>

测试成功

image-20220505100947430

11 声明式事务

11.1 回顾事务

可以把事务视为一组业务,要么都成功,要么都失败

事务具有 ACID 原则:

  • 原子性:一个事务是一个不可分割的工作单位,其中的操作要么都做,要么都不做

  • 一致性:数据库的完整性约束没有被破坏,事务执行的前后都是合法的数据状态

  • 隔离性:事务内部的操作与其他事务是隔离的,并发执行的各个事务之间不能互相干扰

  • 持久性:持久性是指事务一旦提交,它对数据库的改变就应该是永久性的

11.2 声明事务

以最基本的增删改查为例,要实现此需求

插入一个 3 号用户,然后根据 Id 删除 7 号用户,如果删除操作出错,则回滚事务

创建一个新项目,然后是搭建最基本的 Mybatis-Spring 环境

首先就是接口的定义

/**
 * User接口
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/05 13:47
 * @since: 1.0.0
 */
public interface UserMapper {

    List<User> getUser();

    void addUser(User user);

    void deleteUserById(@Param("id") int id);
}

接口注册到配置文件中去,然后故意写错删除语句

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.jiuxiao.mapper.UserMapper">
    <select id="getUser" resultType="user">
        select *
        from mybatis.user
    </select>

    <insert id="addUser" parameterType="user">
        insert into mybatis.user(id, name, age, sex, pwd) value (#{id}, #{name}, #{age}, #{sex}, #{pwd})
    </insert>

    <delete id="deleteUserById" parameterType="_int">
        deletesss
        from mybatis.user
        where id = #{id}
    </delete>
</mapper>

然后是接口的实现类

/**
 * 实现类
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/05 13:53
 * @since: 1.0.0
 */
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {

    public List<User> getUser() {
        User user = new User(3, "王腾", 54, "男", "45154");
        UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
        mapper.addUser(user);		
        mapper.deleteUserById(7);
        return mapper.getUser();
    }

    public void addUser(User user) {
        getSqlSession().getMapper(UserMapper.class).addUser(user);
    }

    public void deleteUserById(int id) {
        getSqlSession().getMapper(UserMapper.class).deleteUserById(id);
    }
}

接下来,就需要去 speing配置文件中配置声明式事务

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

    <!-- 使用 Spring 自带的 jdbc-->
    <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?useUnicode=true&amp;useSSL=true&amp;characterEncoding=utf8&amp;serverTimezone=Asia/Shanghai"/>
        <property name="username" value="root"/>
        <property name="password" value="0531"/>
    </bean>

    <!--创建 SqlSessionFactory-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!-- 这里可以导入 mybatis 的配置-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="mapperLocations" value="classpath:com/jiuxiao/mapper/*.xml"/>
    </bean>

    <!--SqlSessionTemplate : 就是 SqlSession-->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <!-- 这里只能使用构造器注入,因为没有 set 方法-->
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>

    <!--声明事务-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="dataSource"/>
    </bean>

    <!--结合AOP实现事务的织入-->
    <!--配置事务通知-->
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <!--要给哪些方法配置事务和事物的传播特性-->
        <tx:attributes>
            <tx:method name="*" propagation="REQUIRED"/>
        </tx:attributes>
    </tx:advice>
    <!--配置事务切入-->
    <aop:config>
        <aop:pointcut id="txPointCut" expression="execution(* com.jiuxiao.mapper.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
    </aop:config>
</beans>

然后我们运行测试类

/**
 * 测试类
 *
 * @author: WuDaoJiuXiao
 * @Date: 2022/05/05 13:47
 * @since: 1.0.0
 */
public class MyTest {

    @Test
    public void getUser(){
        ApplicationContext context = new ClassPathXmlApplicationContext("application.xml");
        UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
        List<User> users = userMapper.getUser();
        for (User user : users) {
            System.out.println(user);
        }
    }
}

运行结果不出意料的报错,这不是重点,重点是:我们的 add 操作是在 delete 操作之前执行的,此时 delete 操作出错,按照事物的四大原则(要么全部成功,要么全部失败),add 对于数据库的修改应该被撤销!

查看数据库信息,发现既没有增加 3 号用户信息,也没有删除 7 号用户信息,说明事务回滚成功

image-20220505144814984

然后我们将 delete 语句修改正确,再次执行,发现已经达到了预期:3 号被添加,7 号被删除

image-20220505145433784

Q:为什么需要事务?

A1:如果不配置事务,就会存在数据提交不一致的情况

A2:如果不在 Spring 中声明事务,就需要去代码中手动配置事务

A3:事务在项目开发中非常重要,涉及到数据的一致性和完成性原则

posted @ 2022-05-05 15:09  悟道九霄  阅读(23)  评论(0编辑  收藏  举报