一文讲明 Spring 的使用 【全网超详细教程】

我 | 在这里

🕵️ 读书 | 长沙 ⭐软件工程 ⭐ 本科

🏠 工作 | 广州 ⭐ Java 全栈开发(软件工程师)

🎃 爱好 | 研究技术、旅游、阅读、运动、喜欢流行歌曲

🏷️ 标签 | 男 自律狂人 目标明确 责任心强

✈️公众号 | 热爱技术的小郑

🚀 邮箱 | 2977429967@qq.com

✈️ GitHub项目仓库 开源项目 + 实战Demo

为何而写?

🍍 好记性不如烂笔头,记录学习的相关知识 、项目 BUG 解决

🍇 复盘总结,加深记忆,方便自己查看

🍑 分享知识,咱就是这么乐于助人、专注填坑20年、哈哈哈哈

目标描述

🏆 没有伞的孩子、只能用力奔跑。向着架构师的方向努力、做一个有始有终的人。

前言

目录结构 Spring 的相关代码 都公开在了 GitHub上的代码仓库:GitHub源代码地址

目录

1、搭建一个简单的项目

项目代码在Github上的位置: 源码地址

1.1 导入依赖包 依赖包下载地址:Spring所需要的核心jar包

将项目导入jar包

1.2 添加一个实体

cpp 复制代码
package com.zyz.SpringDemo1.entity;

/**
 * @author zyz
 * @version 1.0
 * @data 2023/11/8 13:58
 * @Description: 用户实体
 */
public class Person {
    private Integer id;
    private String name;
    private String age;

    public Person() {
    }

    public Person(Integer id, String name, String age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public Integer getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

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

    public String getAge() {
        return age;
    }

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

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

    //定义一个方法
    public void say() {
        System.out.println("我是Person中的方法");
    }
}

1.3 添加配置文件 创建配置文件:application.xml 。注意配置文件顶部 不要留有空格

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

    <!-- 配置Person对象创建-->
    <bean id="person" class="com.zyz.SpringDemo1.entity.Person"></bean>

</beans>

1.4 测试单元

@Test public void TestDemo(){ //1、加载spring配置文件 ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("application.xml");

ini 复制代码
//2、获取配置创建的对象
Person person = context.getBean("person", Person.class);
person.setId(1);
person.setName("张三");
person.setAge("18");
System.out.println(person);
person.say();

}

1.5 测试效果

2、IOC

2.1 ioc概念

(1)控制反转,把对象创建和对象之间的调用过程,交给 Spring 进行管理 (2)使用 IOC 目的:为了耦合度降低 (3)做入门案例就是 IOC 实现:例子

IoC (Inversion of Control) : 控制反转, 是一个理论,概念,思想。把对象的创建,赋值,管理工作都交给代码之外的容器实现, 也就是对象的创建是有其它外部资源完成,这样做实现了与解耦合。 正转:对象的创建、赋值等操作交由程序员手动完成,即使用类似new Xxx(Xxx Xxx)、Xxx.setXxx()语句完成对象的创建与赋值,缺点是一旦程序功能发生改变,涉及到的类就要修改代理,耦合度高,不便于维护和管理。 反转:对象的创建、赋值等操作交由代码之外的容器实现,有容器代替程序员完成对象的创建、赋值;且当程序功能发生变化时,只需要修改容器的配置文件即可。

2.2 Di 依赖注入

2.3.1 依赖注入是什么?

DI(Dependency Injection) : 依赖注入, 只需要在程序中提供要使用的对象名称就可以, 至于对象如何在容器中创建, 赋值,查找都由容器内部实现。 DI是ioc技术的实现方式(即容器如何创建对象这一问题的实现方式)

2.3.2 入门案例 及 环境搭建参考

入门案例:使用ioc容器创建对象,调用对象的方法

环境搭建 具体过程 参考 目录一 1、创建maven项目,目前都是javase项目,推荐使用骨架,选择quickstart 2、加入maven依赖:分别是spring依赖、junit依赖 3、创建类(接口和它的实现类) 4、创建spring需要使用的配置文件 5、测试

项目的大致结构

创建类

cpp 复制代码
public interface PersonService {
     void doWork();
}


public class PersonServiceImpl implements PersonService {

    @Override
    public void doWork(){
        System.out.println("认真摸鱼,跳槽、涨工资!!!");
    }
}

配置文件

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

    <!--
        声明bean(告诉spring要创建某个类的对象)
        1、id:自定义名称,唯一值,spring通过该id的属性值找到对象
        2、class:要创建类的全限定类名
        3、下述的声明语句在spring底层类似与执行了以下代码:
            PersonServiceImpl personService = new PersonServiceImpl();
        4、对象的保存:
            spring将对象保存到内部的map中,map.put(id值,对象)
            map.put("personService",new PersonServiceImpl())
        5、一个bean标签声明一个java对象
        6、spring容器根据bean标签创建对象,尽管存在class属性相同的bean标签,只要是id值不同,
           spring容器就会创建该class的对象
    -->
    <bean id="personService" class="com.zyz.SpringDemo1.service.impl.PersonServiceImpl"></bean>
    <bean id="personService2" class="com.zyz.SpringDemo1.service.impl.PersonServiceImpl"></bean>

    <!--
       spring容器也可以创建非自定义类的对象,例如java.lang.String类的对象,只要指定了
       class属性,spring容器就可以创建该类的对象
   -->
    <bean id="myString" class="java.lang.String"/>

</beans>

测试单元

cpp 复制代码
  /**
     * 使用spring容器创建对象
     */
    @Test
    public void TestDemo1(){
        //1、指定spring配置文件的名称
        String config = "personBean.xml";
        //2、创建表示spring容器的对象 ApplicationContext
        //ClassPathXmlApplicationContext:表示从类路径中加载spring配置文件
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        //3、从容器中获取对象
        PersonService personService = (PersonService)ac.getBean("personService");
        //4、调用方法
        personService.doWork();
    }


    //获取spring容器中java对象的信息
    @Test
    public void TestDemo2() {
        String config = "personBean.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        //获取spring容器中对象的个数
        int beansCount = ac.getBeanDefinitionCount();
        System.out.println("spring容器中的对象个数="+beansCount);
        //获取spring容器中对象的名称(即bean标签的id值)
        String[] beansNames = ac.getBeanDefinitionNames();
        for (String beanName : beansNames) {
            System.out.println(beanName);
        }
    }

输出效果

2.3.3 案例小结

spring配置文件中一个bean标签就代表一个对象,该对象有bean标签的id值唯一标识,从spring拿对象是使用getBean("bean标签的id值") spring默认是使用类的无参构造来创建对象的

2.3.4 简单类型属性赋值(set注入)

在入门案例的总结我们说过了spring容器默认是使用无参构造构造来实例化对象的,那么对象的属性必定为初始值,例如int类型为0,boolean类型为false等,那么当我们想使用相关属性进行操作时必然要手动使用set方法给属性赋值,那么有没有办法让容器帮我们完成对象属性的赋值呢?让我们直接就能够从容器中拿到有属性值的对象?答案是肯定的,下面就通过代码演示简单类型的属性赋值。

set注入要求 JavaBean必须要有set方法,因为ioc容器是使用javabean的set方法进行属性赋值的 spring容器调用的是setXxx()方法,而不管对象是否具有Xxx属性(即对象没有的属性只要有set方法也可以实现注入),Xxx不区分大小写

实体类

cpp 复制代码
package com.zyz.SpringDemo1.entity;

/**
 * @author zyz
 * @version 1.0
 * @data 2023/11/9 8:42
 * @Description: 学生类
 */
public class Student {
    private String name;
    private Integer age;

    public String getName() {
        return name;
    }

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

    public Integer getAge() {
        return age;
    }

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

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

Spring配置文件

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

    <!--声明Student对象-->
    <bean id="student" class="com.zyz.SpringDemo1.entity.Student">
        <!--
           1、简单类型使用property和value标签给对象属性赋值
           2、简单类型:8个基本类型+String
           3、当spring容器加载到这一行时会在创建完对象的同时使用对象的set方法给属性赋值,底层
              调用的是对象的set方法
           4、spring容器调用的是setXxx()方法,而不管对象是否具有Xxx属性,Xxx不区分大小写
       -->
        <property name="name" value="张三"></property>
        <property name="age" value="18"></property>

        <!--测试对象没有属性的set方法   这部分代码会爆红 报错-->
        <property name="graName" value="s1"/> 
    </bean>

</beans>

爆红信息如下

测试单元

cpp 复制代码
/**
 * 注入的方式 set方式 注入属性测试
 */
@Test
public void TestDemo3() {
    String config = "StudentBean.xml";
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);

    Student student = (Student) ac.getBean("student");
    System.out.println(student);

}

输出如下

2.3.4.1 正常注入

Spring 配置文件

cpp 复制代码
 <!--声明Student对象-->
    <bean id="student" class="com.zyz.SpringDemo1.entity.Student">
        <!--
           1、简单类型使用property和value标签给对象属性赋值
           2、简单类型:8个基本类型+String
           3、当spring容器加载到这一行时会在创建完对象的同时使用对象的set方法给属性赋值,底层
              调用的是对象的set方法
           4、spring容器调用的是setXxx()方法,而不管对象是否具有Xxx属性,Xxx不区分大小写
       -->
        <property name="name" value="张三"></property>
        <property name="age" value="18"></property>
    </bean>

测试单元

ini 复制代码
@Test
public void TestDemo3() {
    String config = "StudentBean.xml";
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    Student student = (Student) ac.getBean("student");
    System.out.println(student); // 输出结果:Student{name='张三', age=18}
}

2.3.4.2 属性注入空值

Spring配置文件

cpp 复制代码
<!--声明Student2对象  测试属性值是 null-->
<bean id="student2" class="com.zyz.SpringDemo1.entity.Student">
    <!--
        两种写法赋予null 值
            第一种 value赋值 写成 null
            第二种 不写value ,直接使用 <null/>
     -->
    <property name="name" value="null"></property>
    <property name="age">
        <null/>
    </property>
</bean>

测试单元

cpp 复制代码
@Test
public void TestDemo4() {
    String config = "StudentBean.xml";
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    Student student = (Student) ac.getBean("student2");
    System.out.println(student); // 输出结果:Student{name='null', age=null}
}

2.3.4.3 注入 特殊符号

Spring配置文件

cpp 复制代码
<!--声明Student3对象  测试属性值是 特殊符号-->
<bean id="student3" class="com.zyz.SpringDemo1.entity.Student">
    <!--
        属性值包含特殊符号
            1、把<> 进行转义 &lt;&gt;
            2、把带特殊符号内容写到 CDATA 比如:<![CDATA[<<王五>>]]>
     -->
    <property name="name">
        <value><![CDATA[<<王五>>]]></value>
    </property>
    <property name="age">
        <value>23</value>
    </property>
</bean>

单元测试

ini 复制代码
@Test
public void TestDemo5() {
    String config = "StudentBean.xml";
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    Student student = (Student) ac.getBean("student3");
    System.out.println(student); // 输出结果:Student{name='<<王五>>', age=23}

}

错误演示

2.3.4.4 属性集合类注入的方式

实体类

cpp 复制代码
package com.zyz.SpringDemo1.entity;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @author zyz
 * @version 1.0
 * @data 2023/11/9 10:01
 * @Description: 大学生实体类
 */
public class UniversityStudent {
    private String name;

    /**
     * 1、数组类型属性 课程
     */
    private String [] courses;

    /**
     * 2、list集合类型属性 水果
     */
    private List<String> fruitList;

    /**
     * 3、map集合类型属性 体育运动
     */
    private Map<String,String> sportMaps;

    /**
     * 4、set集合类型属性 爱好
     */
    private Set<String> hobbySets;

    public String getName() {
        return name;
    }

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

    public String[] getCourses() {
        return courses;
    }

    public void setCourses(String[] courses) {
        this.courses = courses;
    }

    public List<String> getFruitList() {
        return fruitList;
    }

    public void setFruitList(List<String> fruitList) {
        this.fruitList = fruitList;
    }

    public Map<String, String> getSportMaps() {
        return sportMaps;
    }

    public void setSportMaps(Map<String, String> sportMaps) {
        this.sportMaps = sportMaps;
    }

    public Set<String> getHobbySets() {
        return hobbySets;
    }

    public void setHobbySets(Set<String> hobbySets) {
        this.hobbySets = hobbySets;
    }

    @Override
    public String toString() {
        return "UniversityStudent{" +
                "name='" + name + '\'' +
                ", courses=" + Arrays.toString(courses) +
                ", fruitList=" + fruitList +
                ", sportMaps=" + sportMaps +
                ", hobbySets=" + hobbySets +
                '}';
    }
}

Spring 配置文件

cpp 复制代码
 <!-- 配置 UniversityStudent 对象创建-->
    <bean id="universityStudent" class="com.zyz.SpringDemo1.entity.UniversityStudent">
        <property name="name" value="张三"></property>

        <!--1、数组类型属性注入-->
        <property name="courses">
            <array>
                <value>C语言</value>
                <value>Go语言</value>
                <value>Java语言</value>
            </array>
        </property>

        <!--2、list类型属性注入  水果-->
        <property name="fruitList">
            <list>
                <value>🍎苹果</value>
                <value>🍑桃子</value>
                <value>🍇葡萄</value>
            </list>
        </property>

        <!--3、map类型属性注入 体育运动 -->
        <property name="sportMaps">
            <map>
                <entry key="basketball" value="篮球"/>
                <entry key="football" value="足球"/>
                <entry key="badminton" value="羽毛球"/>
            </map>
        </property>

        <!--4、set类型属性注入 爱好-->
        <property name="hobbySets">
            <set>
                <value>读书</value>
                <value>敲代码</value>
                <value>听音乐</value>
            </set>
        </property>

    </bean>

测试单元

cpp 复制代码
  @Test
    public void TestDemo6() {
        String config = "UniversityStudentBean.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        UniversityStudent universityStudent = (UniversityStudent) ac.getBean("universityStudent");
        System.out.println(universityStudent);
        /**
         * UniversityStudent{name='张三',
         *                   courses=[C语言, Go语言, Java语言],
         *                   fruitList=[🍎苹果, 🍑桃子, 🍇葡萄],
         *                   sportMaps={basketball=篮球, football=足球, badminton=羽毛球},
         *                   hobbySets=[读书, 敲代码, 听音乐]
         *                  }
         */
    }

2.3.5 非简单类型属性的赋值(set注入)

上文中的set注入使用property标签的name和value属性给对象属性赋值,但是value知识给简单类型属性赋值,对于非简单类型我们是使用property标签的name和ref属性给对象属性赋值。我们现在给Student类增加一个属性address,该属性是一个引用类型,那当ioc容器创建Student对象时如何给address属性赋值呢?

2.3.5.1 注入外部对象

创建一个外部对象 Address ,然后在 Student类中,引入这个外部属性 Address 代码如下

cpp 复制代码
package com.zyz.SpringDemo1.entity;

/**
 * @author zyz
 * @version 1.0
 * @data 2023/11/9 11:13
 * @Description:  学生家庭地址
 */
public class Address {
    private String province;
    private String city;

    public String getProvince() {
        return province;
    }

    public void setProvince(String province) {
        this.province = province;
    }

    public String getCity() {
        return city;
    }

    public void setCity(String city) {
        this.city = city;
    }

    @Override
    public String toString() {
        return "Address{" +
                "province='" + province + '\'' +
                ", city='" + city + '\'' +
                '}';
    }
}

Student 中添加 外部属性
private Address address; // 引用类型属性

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

Spring 配置文件

cpp 复制代码
<!--声明Student对象-->
<bean id="student" class="com.zyz.SpringDemo1.entity.Student">
    <property name="name" value="张三"></property>
    <property name="age" value="18"></property>
    <!--注入 Address 对象
               name 属性:类里面属性名称
               ref 属性:创建 Address 对象 bean 标签 id 值
     -->
    <property name="address" ref="address"></property>
</bean>

<!--声明Address对象-->
<bean id="address" class="com.zyz.SpringDemo1.entity.Address">
    <property name="province" value="河南"></property>
    <property name="city" value="郑州"></property>
</bean>

测试单元

cpp 复制代码
@Test
public void TestDemo7() {
    String config = "StudentAndAddress.xml";
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    Student student = (Student) ac.getBean("student");
    System.out.println(student);
    // 输出结果:Student{name='张三', age=18, address=Address{province='河南', city='郑州'}}

}

上文执行流程分析:当ioc容器创建id为student的对象时,会进行set注入,当执行到最后一个propert标签时发现使用了ref属性,则ioc容器知道了name为address的属性是非简单类型,它就会暂时跳过address属性的赋值以及Student对象的创建,转而去配置文件的下文去找bean标签id值等于ref属性值的对象,现将该对象创建,再将该对象赋值给之前的address属性并将Student对象创建。

2.3.5.2 注入外部对象

基本上和外部对象的使用过程相似,不同点是 在Spring 配置中,是否使用了ref 。

Spring配置文件

cpp 复制代码
  <!--内部 Bean 的形式-->
    <bean id="student2" class="com.zyz.SpringDemo1.entity.Student">
        <property name="name" value="李四"></property>
        <property name="age" value="23"></property>
        <!--注入 Address 对象
                   name 属性:类里面属性名称
                   直接在内部创建一个对象
         -->
        <property name="address">
            <bean id="address2" class="com.zyz.SpringDemo1.entity.Address">
                <property name="province" value="广东"></property>
                <property name="city" value="广州"></property>
            </bean>
        </property>
    </bean>

测试单元

cpp 复制代码
 @Test
    public void TestDemo8() {
        String config = "StudentAndAddress.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        Student student = (Student) ac.getBean("student2");
        System.out.println(student);
        //输出结果:Student{name='李四', age=23, address=Address{province='广东', city='广州'}}

    }

2.3.6 构造注入

顾名思义,构造注入是使用javabean的构造方法进行属性的赋值的。与set注入一样,构造注入要求javabean必须提供构造方法,且必须是有参构造(如果是无参构造还怎么给属性赋值,对吧),构造注入使用较少,了解就可以了,我们一般使用set注入。看看代码吧,将Student类的set方法注释,加入构造方法,别的地方不用改变,只需要改变spring配置文件即可(这里就可以看出ioc容器与程序的解耦合的好处了)。

实体类 School 使用构造方法

cpp 复制代码
public class School {
    private String name;
    private Integer hisotry;
    private Address address;

    public School(String name, Integer hisotry, Address address) {
        this.name = name;
        this.hisotry = hisotry;
        this.address = address;
    }

    @Override
    public String toString() {
        return "School{" +
                "name='" + name + '\'' +
                ", address='" + address + '\'' +
                ", hisotry=" + hisotry +
                '}';
    }
}

Spring 配置类 还可以使用 ref 引入 外部bean

xml 复制代码
<!--
    构造注入
    1、使用constructor-arg标签完成构造注入
    2、构造注入方式一:根据形参名字
    3、构造注入方式二:根据形参顺序,默认下标从0开始递增
-->
<!--根据形参名构造注入,形参的出现顺序不是必须的-->
<bean id="school" class="com.zyz.SpringDemo1.entity.School">
    <constructor-arg name="name" value="国防科技大学"></constructor-arg>
    <constructor-arg name="hisotry" value="70"></constructor-arg>
    <constructor-arg name="address" ref="address"></constructor-arg>
</bean>

<bean id="school2" class="com.zyz.SpringDemo1.entity.School">
    <constructor-arg name="address" ref="address"></constructor-arg>
    <constructor-arg name="hisotry" value="70"></constructor-arg>
    <constructor-arg name="name" value="国防科技大学"></constructor-arg>
</bean>

<!--构造注入,使用下标,出现的顺序没要求,因为已经通过下标绑定起来了-->
<bean id="school3" class="com.zyz.SpringDemo1.entity.School">
    <constructor-arg index="0" value="国防科技大学"/>
    <constructor-arg index="1" value="70"/>
    <constructor-arg index="2" ref="address"/>
</bean>

<!--外部bean-->
<bean id="address" class="com.zyz.SpringDemo1.entity.Address">
    <constructor-arg name="province" value="湖南"></constructor-arg>
    <constructor-arg name="city" value="长沙"></constructor-arg>
</bean>

单元测试

cpp 复制代码
  @Test
    public void TestDemo9() {
        String config = "SchoolBean.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        School school = (School) ac.getBean("school");
        System.out.println(school);
        //构造参数按照顺序 输出结果:School{name='国防科技大学', address='Address{province='湖南', city='长沙'}', hisotry=70}

        School school2 = (School) ac.getBean("school2");
        System.out.println(school2);
        //构造参数不按照顺序 输出结果:School{name='国防科技大学', address='Address{province='湖南', city='长沙'}', hisotry=70}

        School school3 = (School) ac.getBean("school3");
        System.out.println(school3);
        //构造参数按照下标 输出结果:School{name='国防科技大学', address='Address{province='湖南', city='长沙'}', hisotry=70}
    }

2.3.7 非简单类型 自动注入

对于非简单类型,我们在上面是使用ref属性指向一个非简单类型的对象来完成赋值的,那么当ioc容器每次给一个对象的非简单类型属性赋值时,就要在bean标签内部写一行ref这样的代码,这样会造成重复代码的大量堆积,可以使用引用类型的自动注入。 有两种方式的引用类型自动注入 byName形式的引用类型自动注入: 通过java对象引用类型的属性名与spring容器中bean标签对象的id值一样且数据类型是一致的,这样能够实现引用类型的自动注入

byType形式的引用类型自动注入 通过java对象引用类型属性的数据类型和spring容器中 bean标签的class属性值是同源关系;

常见的同源关系: 1)java引用类型属性数据类型和bean标签的class属性值数据类型一样 2)java引用类型属性数据类型和bean标签的class属性值数据类型是父子关系 3)java引用类型属性数据类型和bean标签的class属性值数据类型是接口和实现类关系

注意:在一个配置文件中,符合条件的同源关系只能有一个

2.3.7.1 通过byName 的形式

实体类 这里的 address 要和 bean对象的值一样。byName 就是通过外部属性的name ,去配置文件中找对应的bean对象。

cpp 复制代码
public class Student {
    private String name;
    private Integer age;

    private Address address; // 引用类型属性
    }

Spring配置文件

cpp 复制代码
   <!--
        创建一个Student对象
            1. 使用byName 自动装配  不写外部bean的情况下 对应的属性值为空
                测试结果:Student{name='张三', age=18, address=null}
            2.在写外部bean的情况下 外部bean 的 id  和类里边外部对象属性 一致.
                测试结果:Student{name='张三', age=18, address=Address{province='河南', city='郑州'}}
            3.写外部bean的情况下,但是 id 的值 和 类里外部对象属性值 不一样情况
                测试结果:Student{name='张三', age=18, address=null}
    -->
    <bean id="student" class="com.zyz.SpringDemo1.entity.Student" autowire="byName">
        <property name="name" value="张三"></property>
        <property name="age" value="18"></property>
    </bean>

    <!--外部bean 对象  id 要和 Student 类里边 对应的 Address 对象名一致.-->
    <bean id="address" class="com.zyz.SpringDemo1.entity.Address">
        <property name="province" value="河南"></property>
        <property name="city" value="郑州"></property>
    </bean>

单元测试

ini 复制代码
/**
 * 注入的方式 set方式 注入属性测试
 *  属性的值赋予 自动装配   byType  bean对象设置成 autowire="byName"  配置文件中寻找和实体类中对应类对象一样的 值
 */
@Test
public void TestDemo10() {
    String config = "AutomaticAssemblyBean.xml";
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    Student student = (Student) ac.getBean("student");
    System.out.println(student);
    //输出结果:Student{name='张三', age=18, address=Address{province='河南', city='郑州'}}

}

匹配详解: 当ioc容器在创建Student对象时,发现使用了autowire属性且属性值为byName,ioc容器就会去Student类中去拿 引用类型的属性名 与和spring配置文件中的 bean标签的id值进行比对,若发现有一致的且数据类型一致,则将该对象赋值给引用类型属性。

2.3.7.2 通过byType 的形式

Spring配置文件 这里一定要确保 配置文件中只有一个对象。多个对象会报错

xml 复制代码
<!--外部bean 对象  id 要和 Student 类里边 对应的 Address 对象名一致.-->
<bean id="address" class="com.zyz.SpringDemo1.entity.Address">
    <property name="province" value="河南"></property>
    <property name="city" value="郑州"></property>
</bean>


<!--使用 byType 自动装配-->
<bean id="student2" class="com.zyz.SpringDemo1.entity.Student" autowire="byType">
    <property name="name" value="李四"></property>
    <property name="age" value="24"></property>
</bean>

如果存在多个bean 对象 报错如下

单元测试

ini 复制代码
/**
 * 注入的方式 set方式 注入属性测试
 *  属性的值赋予 自动装配  byType  bean对象设置成 autowire="byType" 会在配置文件中,寻找该类型的对象
 */
@Test
public void TestDemo11() {
    String config = "AutomaticAssemblyBean.xml";
    ApplicationContext ac = new ClassPathXmlApplicationContext(config);
    Student student2 = (Student) ac.getBean("student2");
    System.out.println(student2);
    //输出结果: Student{name='李四', age=24, address=Address{province='河南', city='郑州'}}

}

2.3.8 基于注解的 DI 实现

除了使用配置文件实现 ioc 创建对象的功能外,使用spring提供的注解也可以实现di。下面来介绍注解方式的di实现,下面是spring提供的di实现的常用注解。

@Component:该注解的功能是使用spring容器创建对象 1)、在要创建对象的类的声明上方加入该注解,该注解有一个属性value,value为spring创建的该类对象的id值 2)、开发中使用将value省略,直接使用双引号将值键入即可 3)、该注解使用类的无参构造创建对象 @Repository 创建dao类对象,访问数据库的对象 @Service 创建service类对象,业务层对象 @Controller 创建控制器对象,用于分发用户的请求和显示处理结果

下面通过代码来看看@Component注解是怎么实现di的。

cpp 复制代码
@Component(value = "student")
public class Student {
    ...
}

//在注解里面 value 属性值可以省略不写 //默认值是类名称,首字母小写

cpp 复制代码
//UserService -- userService
@Component(value = "employee")//<bean id="employee" class=".."/>
public class Employee {
    private String ename;
    private String gender;
}



@Component("student")
public class Student {
}

该语句就等价为在spring配置文件中进行了以下声明

但是怎么让配置文件知道哪些类是使用注解进行创建对象的呢?需要在配置文件中声明组件扫描器 <context:component-scan base-package="com.zyz.SpringDemo1.entity"/>

当spring读取配置文件时,读取到组件扫描器声明语句时,就会去base-package指定的包和其子包下去递归的寻找有注解修饰的类,并根据注解的功能去执行相应的动作

2.3.8.1 简单类型的注解di实现

简单类型的注入使用@Value注解实现,哪些简单类型要设置属性值,直接在简单类型属性声明语句的上面加入注解@Value即可,并在@Value的括号内键入属性值,注意不论简单类型属性的数据类型,均由双引号将属性值括起来。例如之前的Student类使用注解注入如下。

cpp 复制代码
@Component("student")
public class Student {
    @Value("张三")
    private String name;
    @Value("23")
    private int age;
}

注意别忘了该类要加注解@Component注解,因为要创建该类对象。

2.3.8.2 引用类型的注解di实现

引用类型的注入使用@Autowired注解完成。 @Autowired @Autowired是spring提供的属性赋值,用于给引用类型赋值,有byName和byType两种方式,默认使用byType方式自动注入 若是要强制至于byName方式,要在@Autowired注解下面加入 @Qualifier(value = "bean的id")注解,若程序在给引用类型注入时在xml文件中找不到 该id的bean标签或者手找不到该id的@Component注解,则报错;若不想让程序在赋值失败时报错,可以在@Autowired注解的required属性值置为false 还是拿Student类的school属性的赋值来举例。 学生类

cpp 复制代码
@Component("student")
public class Student {
    /*引用类型注入(byType方式)
    @Autowired
    private School school;*/

    //引用类型赋值(byName方式)
    @Autowired(required = false)
    @Qualifier(value = "mySchool")
    private School school;
}

School类
@Component("mySchool")
public class School {

    //注入值
    @Value("西南大学")
    private String schoolAddress;
    @Value("新疆")
    private String homeAddress;

    @Override
    public String toString() {
        return "School{" +
                "schoolAddress='" + schoolAddress + '\'' +
                ", homeAddress='" + homeAddress + '\'' +
                '}';
    }
}

重点提示: 在注解里面 value 属性值可以省略不写 默认值是类名称,首字母小写 例如:UserService -- userService

测试单元 中 取对象的时候 如果取值 和命名不一致 则会报如下错误:

2.3.8.3 案例代码

参考GitHub 上的

@Qualifier("userDaoImplTwo") @Repository("userDaoImplTwo")

当一个类型 有多个对象的时候。这个时候通过类型自动装配。需要 让 @Autowired 和 @Qualifier 配合使用。同时需要指定对象的名称

自动装配,更具byType ,同时一个类型 有多个对象的时候。需要配合 @Repository

cpp 复制代码
@Repository("userDaoImplTwo") //起个名称
public class UserDaoImplTwo implements UserDao{
    @Override
    public void showTime() {
        System.out.println("UserDaoImplTwo showTime ......");

    }
}


@Service
public class UserService {
    @Autowired  //根据类型自动装配
    @Qualifier("userDaoImplTwo")  //根据起的名称
    private UserDao userDao;

    public void say(){
        userDao.showTime();
        System.out.println("UserService say .......");
    }

}

    @Test
    public void testDemo13(){
        ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("AnnotationDevelopment.xml");
        UserService userService1 = context.getBean("userService", UserService.class);
        userService1.say();
        //输出结果:UserDaoImplTwo showTime ......
        //        UserService say .......

    }

或者简化成这样写:  使用@Resource 
    // 这里的userDaoImplTwo是指bean的ID
    @Resource(name = "userDaoImplTwo")   
    private UserDao userDao;

3、AOP

3.1 动态代理

3.1.1 jdk动态代理

使用jdk中的Proxy,Method,InvocaitonHanderl创建代理对象。 jdk动态代理要求目标类必须实现接口

cpp 复制代码
/**
 * @author zyz
 * @version 1.0
 * @data 2023/11/16 20:41
 * @Description: 动态代理 Dao
 */
public interface DynamicProxiesDao {
     int add(int a,int b);

     void update(int a);
}


public class DynamicProxiesDaoImpl implements DynamicProxiesDao{
    @Override
    public int add(int a, int b) {
        System.out.println("执行了add方法");
        return a+b;
    }

    @Override
    public void update(int a) {
        System.out.println(a);
    }

}

public class JdkProxy {
    public static void main(String[] args) {
        //创建接口实现类代理对象
        Class[] interfaces = {DynamicProxiesDao.class};
//        Proxy.newProxyInstance(JdkProxy.class.getClassLoader(), interfaces,
//                new InvocationHandler() {
//                    @Override
//                    public Object invoke(Object proxy, Method method, Object[] args)
//                            throws
//                            Throwable {
//                        return null;
//                    }
//                });
        DynamicProxiesDaoImpl dynamicProxiesDao = new DynamicProxiesDaoImpl();
        DynamicProxiesDao dao = (DynamicProxiesDao) Proxy.newProxyInstance(JdkProxy.class.getClassLoader(), interfaces, new DynamicProxiesDaoProxy(dynamicProxiesDao));
        int rs = dao.add(4, 5);
        System.out.println("result:" + rs);
        //测试结果
        // 方法执行之前:add,传递的参数是:[4, 5]
        // 执行了add方法
        // 方法执行之后:com.zyz.SpringDemo1.dao.DynamicProxiesDaoImpl@63947c6b
        // result:9

    }
}

/**
 * 代理对象代码
 */
class DynamicProxiesDaoProxy implements InvocationHandler {
    /**
     * 1 把创建的是谁的代理对象,把谁传递过来
     *  有参数构造传递
     */
    private Object obj;

    public DynamicProxiesDaoProxy(Object obj) {
        this.obj = obj;
    }


    /**
     *  增强的逻辑
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //方法之前
        System.out.println("方法执行之前:" + method.getName() + ",传递的参数是:" + Arrays.toString(args));
        //被增强的方法执行
        Object rs = method.invoke(obj, args);
        //方法之后
        System.out.println("方法执行之后:" + obj);
        return rs;
    }


}

3.1.2 cglib动态代理

arduino 复制代码
第三方的工具库,创建代理对象,原理是继承。 通过继承目标类,创建子类。子类就是代理对象。 要求目标类不能是final的,方法也不能是final的

3.1.3 动态代理的好处

在目标类源代码不改变的情况下,增加功能。 减少代码的重复 专注业务逻辑代码 解耦合,让你的业务功能和日志,事务非业务功能分离。

3.2 什么是AOP

面向切面编程, 基于动态代理的,可以使用jdk,cglib两种代理方式。Aop就是动态代理的规范化, 把动态代理的实现步骤,方式都定义好了, 让开发人员用一种统一的方式,使用动态代理实现

3.2.1 AOP常用术语

Aspect: 切面,给你的目标类增加的功能,就是切面。 像日志,事务都是切面。切面的特点: 一般都是非业务方法,独立使用的。 JoinPoint:连接点 ,连接业务方法和切面的位置。需要给哪个方法增加切面,这个方法就是连接点。 Pointcut : 切入点 ,指多个连接点方法的集合。 目标对象: 给哪个类的方法增加功能, 这个类就是目标对象。 Advice:通知,通知表示切面功能执行的时间。

3.2.2 切面的构成

切面就是要给别的方法进行增强的方法,一个切面有以下三个要素。 切面的功能代码,切面干什么 切面的执行位置,使用Pointcut表示切面执行的位置 切面的执行时间,使用Advice表示时间,在目标方法之前,还是目标方法之后。

3.3 使用aspectJ框架实现AOP

3.3.1 aspectJ简介

复制代码
aspectJ是一个开源的专门做aop的框架。spring框架中集成了aspectj框架,通过spring就能使用aspectj的功能。aspectJ框架实现aop有两种方式:

使用xml的配置文件 : 配置全局事务 使用注解,我们在项目中要做aop功能,一般都使用注解, aspectj有5个注解。 再使用aspectJ做aop之前要先加入aspectJ依赖。

xml 复制代码
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>5.2.5.RELEASE</version>
</dependency>

3.3.2 @Before前置通知

前置通知注解修饰的切面在连接点方法之前执行。下面通过一段代码体验一下。

cpp 复制代码
声明接口IService
public interface IService {
    void doSome(String name, int age);
}

声明实现类ServiceImpl
@Component(value = "service")
public class ServiceImpl implements IService {
    @Override
    public void doSome(String name, int age) {
        System.out.println("===doSome()===");
    }
}

声明切面
@Component
@Aspect
public class MyAspectJ {

    /**
     * 定义功能增强方法(方法就是切面)
     * 1、方法的必须为public
     * 2、方法无返回值
     * 3、方法名称自定义
     * 4、方法可以有参数,也可以没有参数
     * 5、方法的定义上方加入注解,表示切入点的执行时机

    @Before(value = "execution(public void com.mms.ba01.ServiceImpl.doSome(String,int))")
    public void beforeLog() {
        System.out.println("前置通知->系统当前时间:" + new Date());
    }
    */

    /*
        前置通知,带方法参数的切面
        切面方法有参数时要求参数是JoinPoint类型,参数名自定义,该参数就代表了连接点方法,即doSome方法
        使用该参数可以获取切入点表达式、切入点方法签名、目标对象等
     */
    @Before(value = "execution(* *..ServiceImpl.doSome(..))")
    public void beforeLog(JoinPoint jp) {
        System.out.println("连接点方法的方法签名="+jp.getSignature());
        System.out.println("连接点方法的方法名="+jp.getSignature().getName());
        //获取连接点方法参数
        Object[] args = jp.getArgs();
        for (Object arg : args) {
            System.out.println("arg="+arg);
        }
    }
}

测试
public class MyTest {
    //aop前置通知
    @Test
    public void test01() {
        String config = "ba01/applicationContext.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        //获取目标对象,此时的service就是spring生成的代理对象
        //注意返回值类型是接口类型,不能是实现类接口,否则报错
        IService service = (IService) ac.getBean("service");
        //使用代理对象执行方法
        service.doSome("张三",23);
    }
}

3.3.3 @AfterReturning后置通知

cpp 复制代码
在IService接口中新增方法:
Student doStudent(Student student);

在ServiceImpl实现doStudent方法
@Override
    public Student doStudent(Student student) {
        return student;
    }

切面类代码
@Component
@Aspect
public class MyAspectJ {
    /**
     * @AfterReturning: 后置通知,在连接点方法执行之后执行后置通知方法
     * 方法定义格式:
     * 1、公共方法
     * 2、没有返回值
     * 3、方法名称自定义
     * 4、与前置通知一样,可以有JoinPoint类型参数,该参数表示连接点方法对象;还可以有一个
     *    Object类型参数,用于接收连接点方法的执行结果,注意该参数的参数名必须与切入点表达式
     *    的returning属性的属性值一致,表示将returning属性值赋给Object对象
     */
    /*@AfterReturning(value = "execution(* *..ServiceImpl.doOther(..))", returning = "obj")
        public void afterTransaction(JoinPoint jp, Object obj) {
        System.out.println("doOther方法的返回参数="+obj);
        System.out.println("事务已提交...");
        经过验证:在后置通知切面内不能改变连接点方法的返回值
    }*/

    @AfterReturning(value = "execution(* *..ServiceImpl.doStudent(..))", returning = "obj")
    public void afterTransaction(JoinPoint jp, Object obj) {
        System.out.println(obj);
        Student student = new Student();
        student.setName("李四");
        student.setAge(24);
        obj = student;
        System.out.println("===查看是否改变了连接点方法的返回值==="+obj);
        /*
            经过验证:在后置通知切面内不能改变连接点方法的返回值
         */
    }
}

3.3.4 @Around环绕通知(功能最强的通知)

环绕通知是功能最强的通知,它的本质就是jdk动态代理,他可以在连接点方法之前和之后都可以执行,最厉害的是他可以改变连接点方法的执行结果(返回结果)。还是拿上面的doStudent(Student student)方法来说明,经过验证前置通知和后置通知都不能改变doStudent(Student student)方法的返回值。下面看一下环绕通知是如何做的。

cpp 复制代码
切面类
@Aspect
public class MyAspectJ {

    /*
        环绕通知:@Around(切入点表达式)
        1、环绕通知是最重要的一个通知,他表示在连接点方法的前或者后都可以执行,它的本质就是jdk动态代理的invoke
           方法的method参数
        2、定义格式
            a、public
            b、必须有返回值,类型为Object

     */
    @Around(value = "pointCut()")

    /*
        再次回忆一下jdk动态代理的invoke方法的定义
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        是不是感觉与下面的方法定义眼熟啊,没错,环绕通知切面的定义实质上就是jdk动态代理
     */
    public Object around(ProceedingJoinPoint pj) throws Throwable {
        System.out.println("环绕通知在连接点方法之前执行了...");
        Object result = null;
        result = pj.proceed();
        Student student = new Student();
        student.setName("李四");
        student.setAge(24);
        //改变连接点方法返回值
        result = student;
        System.out.println("事务已提交...");
        return result;
    }

    /*
        使用pointcut管理切面表达式
        1、在一个切面类中,若多个切面的切面表达式均为同一个,每次都要写重复的代码,此时就可以使用pointcut来
          管理切面表达式了
        2、定义格式:
            公共public
            无返回值
            无参数
     */
    @Pointcut(value = "execution(* *.doStudent(..))")
    public void pointCut() {
        //空方法体
    }
}3

3.3.5 完全注解开发
添加一个配置类  。在类中 完成 开启包扫描 及 aop 注解 
@Configuration
@ComponentScan(basePackages = {"com.zyz"})
@EnableAspectJAutoProxy(proxyTargetClass = true)
public class ConfigAop {
}


测试单元
    // 完全注解开发 添加配置类 。开启包扫描 及 aop 注解
    @Test
    public void test04(){
        //加载配置类
        ApplicationContext context = new AnnotationConfigApplicationContext(ConfigAop.class);
        IService service = context.getBean("service",IService.class);
        service.doSome("张三",18);
        // 测试结果:
        // 连接点方法的方法签名=void com.zyz.SpringDemo1.service.impl.ServiceImpl.doSome(String,int)
        // 连接点方法的方法名=doSome
        // arg=张三
        // arg=18
        // ===doSome()===

    }

4、整合mybatis

4.1 梳理整合思路

所谓的spring整合mybatis就是把两者集成在一起,就像使用一个框架一样。

mybatis框架开发步骤 定义mapper接口,定义方法 定义mapper.xml映射文件 创建mybatis核心配置文件 创建SqlSession对象,使用该对象生成mapper接口的代理对象执行方法

spring整合mybatis的核心就是把mybatis开发用到的对象交由spring容器ioc来创建,这样就做到了整合的目的。 在开发中,我们一般不使用mybatis自带的数据源,而是使用别的数据源,比如c3p0,dbcp等,本人使用的是阿里的druid数据源。

4.2 整合实现

4.2.1 环境搭建

pom中导入相关依赖

cpp 复制代码
<dependencies>
    <!--单元测试-->
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    <!--spring核心ioc-->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>5.2.5.RELEASE</version>
    </dependency>
    <!--mybatis依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.1</version>
    </dependency>
    <!--mybatis和spring集成的依赖-->
    <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis-spring</artifactId>
      <version>1.3.1</version>
    </dependency>
    <!--mysql驱动-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>5.1.9</version>
    </dependency>
    <!--阿里公司的数据库连接池-->
    <dependency>
      <groupId>com.alibaba</groupId>
      <artifactId>druid</artifactId>
      <version>1.1.12</version>
    </dependency>
  </dependencies>

  <build>
    <!--目的是把src/main/java目录中的xml文件包含到输出结果中。输出到classes目录中-->
    <resources>
      <resource>
        <directory>src/main/java</directory><!--所在的目录-->
        <includes><!--包括目录下的.properties,.xml 文件都会扫描到-->
          <include>**/*.properties</include>
          <include>**/*.xml</include>
        </includes>
        <filtering>false</filtering>
      </resource>
    </resources>
    <!--指定jdk的版本-->
    <plugins>
      <plugin>
        <artifactId>maven-compiler-plugin</artifactId>
        <version>3.1</version>
        <configuration>
          <source>1.8</source>
          <target>1.8</target>
        </configuration>
      </plugin>
    </plugins>
  </build>

重点是注意resources标签的配置,很多人都是在这里出错导致程序运行报错找不到mapper.xml文件

4.2.2 案例分析

数据库脚本

cpp 复制代码
CREATE TABLE `student`  (
  `stuno` int(11) NOT NULL COMMENT '学号',
  `stuname` varchar(25) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL COMMENT '姓名',
  `cardid` int(11) NULL DEFAULT NULL,
  `classid` int(11) NULL DEFAULT NULL COMMENT '班级',
  PRIMARY KEY (`stuno`) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

本案例从student表中查询学生和新增学生功能。

cpp 复制代码
public class Student {
    private int stuNo;
    private String stuName;
    private int cardID;
    private int classID;
    public Student() {
    }
    public Student(int stuNo, String stuName, int cardID, int classID) {
        this.stuNo = stuNo;
        this.stuName = stuName;
        this.cardID = cardID;
        this.classID = classID;
    }
    public int getStuNo() {
        return stuNo;
    }
    public void setStuNo(int stuNo) {
        this.stuNo = stuNo;
    }
    public String getStuName() {
        return stuName;
    }
    public void setStuName(String stuName) {
        this.stuName = stuName;
    }
    public int getCardID() {
        return cardID;
    }
    public void setCardID(int cardID) {
        this.cardID = cardID;
    }
    public int getClassID() {
        return classID;
    }
    public void setClassID(int classID) {
        this.classID = classID;
    }
    @Override
    public String toString() {
        return "Student{" +
                "stuNo=" + stuNo +
                ", stuName='" + stuName + '\'' +
                ", cardID=" + cardID +
                ", classID=" + classID +
                '}';
    }
}

mapper接口

cpp 复制代码
public interface StudentMapper {
    /**
     * @description: 查询全部
     * @author: zhengyuzhu
     * @date: 2023/11/16 22:34
     * @return: java.util.List<com.zyz.SpringDemo2.entity.Student>
     **/
    List<Student> queryAll();

    /**
     * @description: 新增学生
     * @author: zhengyuzhu
     * @date: 2023/11/16 22:34
     * @param: student
     **/
    void addStudent(Student student);
}

mapper.xml映射文件

cpp 复制代码
<?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.zyz.SpringDemo2.mapper.StudentMapper">

    <!--查询全部-->
    <select id="queryAll" resultType="Student">
        select * from student
    </select>

    <!--新增学生-->
    <insert id="addStudent" parameterType="Student">
        insert into student (stuno,stuname,cardid,classid)
        values (#{stuNo},#{stuName},#{cardID},#{classID})
    </insert>
</mapper>

service接口

cpp 复制代码
public interface IStudentService {
    List<Student> queryAll();
    void addStudent(Student student);
}

service实现类

cpp 复制代码
public class StudentServiceImpl implements IStudentService {
    //mapper属性
    private StudentMapper mapper;
    //set注入给mapper对象赋值
    public void setMapper(StudentMapper mapper) {
        this.mapper = mapper;
    }

    @Override
    public List<Student> queryAll() {
        return mapper.queryAll();
    }

    @Override
    public void addStudent(Student student) {
        mapper.addStudent(student);
    }
}

mybatis核心配置文件 mybatis-config.xml

cpp 复制代码
<?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>
        <!--
            批量设置别名,会自动的将该包下的所有类定义了别名,别名就是其自身且不区分大小
        -->
        <package name="com.zyz.SpringDemo2.entity" />
    </typeAliases>
    <!--加载映射配置文件-->
    <mappers>
        <mapper resource="com/zyz/SpringDemo2/mapper/StudentMapper.xml"></mapper>
    </mappers>
</configuration>

在这里由于数据源对象我们是交由spring容器托管了,因此mybatsi核心配置文件中就没有environments标签了。

数据库配置相关 db.properties

cpp 复制代码
jdbc.driverClassName=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/spring_test?serverTimezone=UTC&amp;useUnicode=true&amp;characterEncoding=UTF-8&amp;useSSL=false
jdbc.username=root
jdbc.password=root

spring配置文件

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

    <!--加载数据库配置文件-->
    <context:property-placeholder location="classpath:db.properties"/>
    <!--声明数据源-->
    <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!--set注入给数据库信息赋值,不需要指定驱动类,spring根据url自动识别
        <property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf8&amp;useSSL=true&amp;serverTimezone=UTC"/>
        <property name="username" value="root"/>
        <property name="password" value="333"/>-->

        <!--使用db配置文件读取数据库信息,格式类似el表达式-->
        <property name="driverClassName" value="${jdbc.driverClassName}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

    <!--声明的是mybatis中提供的SqlSessionFactoryBean类,这个类内部创建SqlSessionFactory的-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--set注入赋值-->
        <!--set注入,把数据库连接池付给了dataSource属性-->
        <property name="dataSource" ref="myDataSource" />
        <!--mybatis主配置文件的位置
           configLocation属性是Resource类型,读取配置文件
           它的赋值,使用value,指定文件的路径,使用classpath:表示文件的位置
        -->
        <property name="configLocation" value="classpath:mybatis-config.xml" />
    </bean>

    <!--创建dao对象,使用SqlSession的getMapper(StudentDao.class)
        MapperScannerConfigurer:在内部调用getMapper()生成每个dao接口的代理对象。
    -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定SqlSessionFactory对象的id-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        <!--指定包名, 包名是dao接口所在的包名。
            MapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行
            一次getMapper()方法,得到每个接口的dao对象。
            创建好的dao对象放入到spring的容器中的。 dao对象的默认名称是 接口名首字母小写
        -->
        <property name="basePackage" value="com.zyz.SpringDemo2.mapper"/>
    </bean>

    <!--声明service-->
    <bean id="studentServiceImpl" class="com.zyz.SpringDemo2.service.impl.StudentServiceImpl">
        <property name="mapper" ref="studentMapper"/>
    </bean>
</beans>

测试

cpp 复制代码
 //执行查询全部,不使用service
    @Test
    public void test02() {
        String config = "SpringApplication.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        //获取mapper的代理对象
        StudentMapper mapper = (StudentMapper) ac.getBean("studentMapper");
        List<Student> students = mapper.queryAll();
        for (Student student : students) {
            System.out.println("student--->"+student);
        }
    }

    //执行增加学生,使用service
    @Test
    public void test03() {
        String config = "SpringApplication.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        //获取service对象
        IStudentService service = (IStudentService) ac.getBean("studentServiceImpl");
        Student student = new Student();
        student.setStuName("呵呵");
        student.setStuNo(1111);
        student.setCardID(1115);
        student.setClassID(1);
        service.addStudent(student);

    }

测试结果

5、处理事务

5.1 什么是事务

sql 复制代码
事务是指一组sql语句的集合, 集合中有多条sql语句可能是insert , update ,select ,delete, 我们希望这些多个sql语句都能成功,或者都失败, 这些sql语句的执行是一致的,作为一个整体执行。关于事务最经典的例子就是转账了。

5.2 使用spring内部注解处理事务

5.2.1 环境搭建

加入事务相关依赖

cpp 复制代码
<dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-tx</artifactId>
      <version>5.2.5.RELEASE</version>
</dependency>

案例:数据库有两张表,一张sale表,该表是商品订单信息;一张goods表,该表是商品库存信息。service类有一个业务方法buy,该方法指定要购买商品的id和数量,dao有三个方法分别是像sale表增加一条购买记录、更新goods表的库存信息、在goods表查询指定id商品是否存在。 下面是两张表的记录信息

sale表(初始默认添加了一些数据),id字段自增长

goods表

业务流程大致,用户向buy方法传递两个参数,分别是goods表的id字段和购买数量nums。buy方法体中的第一个方法是向sale表增加一条记录,类似与buy(1002,20),那么sale表应该多出来一条记录。

buy方法的定义

cpp 复制代码
public void buy(Integer goodsId, Integer nums) {
        //sale表新增购买记录
        //更新库存
        //先查询该编号的商品是否存在
        Goods goods = goodsMapper.queryGoods(goodsId);
        //进行判断
        if (goods == null) {
            //说明商品不存在
        } else if (goods.getAmount() < nums) {
            //说明库存不足
        }
        //能够进行到这里说明商品信息是合法的,可以更新库存
    }

首先根据用户传入的参数id和nums向sale表新增一条购买记录。即有商品卖出去了,那么我们理应更新一下库存对吧,不然怎么知道还有多少货,万一别人要买100件商品而你只有50件肯定会导致别人购买失败对吧。那么问题就就来了,当我们执行完向sale表新增记录后,就该更新库存了,我们必须要知道刚卖出去的商品是谁对吧,这个通过用户传入的id和商品表goods的id字段一一对应,所以先去数据库查询用户传入的id是否存在goods表中,若不存在应该将事务回滚,即前面向sale表增加的记录是不应该存在的;同理,若用户传入的id商品在goods表中,但是用户要求的数量大于该商品当前库存,事务也应该回滚;只有当用户传入的id商品和数量都满足条件时我们才应该更新库存并且提交事务。

实体类 Goods 和 Sale

cpp 复制代码
package com.zyz.SpringDemo3.entity;

/**
 * @BelongsProject: SpringDemo1
 * @BelongsPackage: com.zyz.SpringDemo3.entity
 * @Author: zhengyuzhu
 * @CreateTime: 2023-11-17  16:40
 * @Description: 商品实体类
 * @Version: 1.0
 */
public class Goods {
    private Integer id;
    private String name;
    private Integer amount;
    private Float price;
    public Goods() {
    }
    public Goods(Integer id, String name, Integer amount, Float price) {
        this.id = id;
        this.name = name;
        this.amount = amount;
        this.price = price;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public String getName() {
        return name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public Integer getAmount() {
        return amount;
    }
    public void setAmount(Integer amount) {
        this.amount = amount;
    }
    public Float getPrice() {
        return price;
    }
    public void setPrice(Float price) {
        this.price = price;
    }
    @Override
    public String toString() {
        return "Goods{" +
                "id=" + id +
                ", name='" + name + '\'' +
                ", amount=" + amount +
                ", price=" + price +
                '}';
    }
}

package com.zyz.SpringDemo3.entity;

/**
 * @BelongsProject: SpringDemo1
 * @BelongsPackage: com.zyz.SpringDemo3.entity
 * @Author: zhengyuzhu
 * @CreateTime: 2023-11-17  16:41
 * @Description: 销售实体类
 * @Version: 1.0
 */
public class Sale {
    private Integer id;
    private Integer gid;
    private Integer nums;
    public Sale() {
    }
    public Sale(Integer id, Integer gid, Integer nums) {
        this.id = id;
        this.gid = gid;
        this.nums = nums;
    }
    public Integer getId() {
        return id;
    }
    public void setId(Integer id) {
        this.id = id;
    }
    public Integer getGid() {
        return gid;
    }
    public void setGid(Integer gid) {
        this.gid = gid;
    }
    public Integer getNums() {
        return nums;
    }
    public void setNums(Integer nums) {
        this.nums = nums;
    }
    @Override
    public String toString() {
        return "Sale{" +
                "id=" + id +
                ", gid=" + gid +
                ", nums=" + nums +
                '}';
    }
}

mapper接口 GoodsMapper 和 SaleMapper

cpp 复制代码
package com.zyz.SpringDemo3.mapper;

import com.zyz.SpringDemo3.entity.Goods;

/**
 * @BelongsProject: SpringDemo1
 * @BelongsPackage: com.zyz.SpringDemo3.mapper
 * @Author: zhengyuzhu
 * @CreateTime: 2023-11-17  16:43
 * @Description: TODO
 * @Version: 1.0
 */
public interface GoodsMapper {
    /**
     * 更新库存
     * goods表示本次用户购买的商品信息
     * @param goods
     */
    void updateGoods(Goods goods);

    /**
     * @description:查询商品信息
     * @author: zhengyuzhu
     * @date: 2023/11/17 16:44
     * @param: id
     * @return: com.zyz.SpringDemo3.entity.Goods
     **/
    Goods queryGoods(Integer id);
}



package com.zyz.SpringDemo3.mapper;

import com.zyz.SpringDemo3.entity.Sale;

/**
 * @BelongsProject: SpringDemo1
 * @BelongsPackage: com.zyz.SpringDemo3.mapper
 * @Author: zhengyuzhu
 * @CreateTime: 2023-11-17  16:43
 * @Description: TODO
 * @Version: 1.0
 */
public interface SaleMapper {
    //增加销售记录
    void addSale(Sale sale);
}

mapper.xml映射文件 GoodsMapper.xml 和 SaleMapper.xml

cpp 复制代码
<?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.zyz.SpringDemo3.mapper.GoodsMapper">
    <!--更新商品信息-->
    <update id="updateGoods" parameterType="Goods">
        update goods set amount = amount - #{amount} where id = #{id}
    </update>
    <!--查询商品信息-->
    <select id="queryGoods" resultType="Goods">
        select * from goods where id = #{id}
    </select>
</mapper>


<?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.zyz.SpringDemo3.mapper.SaleMapper">
    <!--增加销售记录-->
    <insert id="addSale" parameterType="Sale">
        insert into sale (gid,nums) values (#{gid},#{nums})
    </insert>
</mapper>

service接口

cpp 复制代码
package com.zyz.SpringDemo3.service;

import com.zyz.SpringDemo3.entity.Goods;

/**
 * @author zyz
 * @version 1.0
 * @data 2023/11/17 16:48
 * @Description:
 */
public interface IBuyGoodsService {

    /**
     * @description: 根据 ID 查询商品
     * @author: zhengyuzhu
     * @date: 2023/11/17 23:09
     * @param: id
     * @return: com.zyz.SpringDemo3.entity.Goods
     **/
     Goods queryGoodsById(Integer id);
    /**
     * @description:购买商品的方法,goodsId是购买商品的编号,nums是购买商品的数量
     * @author: zhengyuzhu
     * @date: 2023/11/17 16:48
     * @param: goodsId
     * @param: nums
     **/
    void buy(Integer goodsId,Integer nums);
}

service实现类

cpp 复制代码
package com.zyz.SpringDemo3.service.impl;

import com.zyz.SpringDemo3.entity.Goods;
import com.zyz.SpringDemo3.entity.Sale;
import com.zyz.SpringDemo3.mapper.GoodsMapper;
import com.zyz.SpringDemo3.mapper.SaleMapper;
import com.zyz.SpringDemo3.service.IBuyGoodsService;
import org.springframework.transaction.annotation.Transactional;

/**
 * @BelongsProject: SpringDemo1
 * @BelongsPackage: com.zyz.SpringDemo3.service.impl
 * @Author: zhengyuzhu
 * @CreateTime: 2023-11-17  16:49
 * @Description: TODO
 * @Version: 1.0
 */
public class BuyGoodsServiceImpl implements IBuyGoodsService {

    private SaleMapper saleMapper;
    private GoodsMapper goodsMapper;

    //set注入赋值
    public void setSaleMapper(SaleMapper saleMapper) {
        this.saleMapper = saleMapper;
    }

    public void setGoodsMapper(GoodsMapper goodsMapper) {
        this.goodsMapper = goodsMapper;
    }

    /**
     * @description: 更具ID 查询商品
     * @author: zhengyuzhu
     * @date: 2023/11/17 23:08
     * @param: id
     * @return: com.zyz.SpringDemo3.entity.Goods
     **/
    @Override
    public Goods queryGoodsById(Integer id) {
        Goods goods = null;
        goods = goodsMapper.queryGoods(id);
        return goods;
    }


    /**
     * 购买商品方法
     * goodsId表示要购买的商品编号,nums表示购买的数量
     *
     * @param goodsId
     * @param nums
     */

    /*
        1、@Transactional定义在需要加入事务处理的方法上面
        2、rollbackFor
            1)当方法发生的异常属于rollbackFor,事务就会回滚
            2)当方法发生的异常不属于rollbackFor,spring就会判断发生的异常是不是运行时异常,
               若是运行时异常,事务就会回滚

    @Transactional(
            propagation = Propagation.REQUIRED,
            isolation = Isolation.DEFAULT,
            readOnly = false,
            rollbackFor = {
                    NullPointerException.class,
                    NotEnoughException.class
            }

    )*/

    //开发中推荐使用默认值,直接使用@Transactional注解
    @Transactional
    @Override
    public void buy(Integer goodsId, Integer nums) {
        System.out.println("buy开始...");

        /*
            记录销售的信息,向sale表添加记录,由于sale表id是自增长的,所以不需要设置id属性
            向sale表添加一条购买记录
         */
        Sale sale = new Sale();
        sale.setGid(goodsId);
        sale.setNums(nums);
        saleMapper.addSale(sale);

        //更新库存
        //先查询该编号的商品是否存在
        Goods goods = goodsMapper.queryGoods(goodsId);
        //进行判断
        if (goods == null) {
            //说明商品不存在
            throw new NullPointerException("编号为:" + goodsId + "商品不存在...");
        } else if (goods.getAmount() < nums) {
            //说明库存不足
            throw new NotEnoughException("编号为:"+goodsId+"商品库存不足...");
        }

        //能够进行到这里说明商品信息是合法的,可以更新库存
        Goods good = new Goods();
        good.setId(goodsId);
        good.setAmount(nums);
        goodsMapper.updateGoods(good);

        System.out.println("buy结束...");
    }
}

异常处理类 NotEnoughException

cpp 复制代码
package com.zyz.SpringDemo3.service.impl;

/**
 * @BelongsProject: SpringDemo1
 * @BelongsPackage: com.zyz.SpringDemo3.service.impl
 * @Author: zhengyuzhu
 * @CreateTime: 2023-11-18  15:57
 * @Description: 定义异常类
 * @Version: 1.0
 */
public class NotEnoughException extends RuntimeException {
    public NotEnoughException() {

    }
    public NotEnoughException(String msg) {
        super(msg);
    }
}

spring配置文件也和整合部分一致,只需要在根标签beans加入以下部分即可

cpp 复制代码
<!--声明使用spring的事务处理-->
    <!--1、声明事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--连接的数据库,指定数据源-->
        <property name="dataSource" ref="myDataSource"/>
    </bean>
    <!--2、开启事务注解驱动,告诉sprint使用注解管理事务,创建代理对象-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

mybatis核心配置文件 TransactionMybatis-config.xml 数据库配置文件同整合 mybatis 这里不在给出。

cpp 复制代码
<?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>
        <!--
            批量设置别名,会自动的将该包下的所有类定义了别名,别名就是其自身且不区分大小
        -->
        <package name="com.zyz.SpringDemo3.entity" />
    </typeAliases>
    <!--加载映射配置文件-->
    <mappers>
        <mapper resource="com/zyz/SpringDemo3/mapper/GoodsMapper.xml"></mapper>
        <mapper resource="com/zyz/SpringDemo3/mapper/SaleMapper.xml"></mapper>
    </mappers>


</configuration>

Spring 整合 配置文件

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




        <!--加载数据库配置文件-->
    <context:property-placeholder location="classpath:db.properties"/>
    <!--声明数据源-->
    <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!--set注入给数据库信息赋值,不需要指定驱动类,spring根据url自动识别
        <property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf8&amp;useSSL=true&amp;serverTimezone=UTC"/>
        <property name="username" value="root"/>
        <property name="password" value="333"/>-->

        <!--使用db配置文件读取数据库信息,格式类似el表达式-->
        <property name="driverClassName" value="${jdbc.driverClassName}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

    <!--声明的是mybatis中提供的SqlSessionFactoryBean类,这个类内部创建SqlSessionFactory的-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--set注入赋值-->
        <!--set注入,把数据库连接池付给了dataSource属性-->
        <property name="dataSource" ref="myDataSource" />
        <!--mybatis主配置文件的位置
           configLocation属性是Resource类型,读取配置文件
           它的赋值,使用value,指定文件的路径,使用classpath:表示文件的位置
        -->
        <property name="configLocation" value="classpath:TransactionMybatis-config.xml" />
    </bean>

    <!--创建dao对象,使用SqlSession的getMapper(StudentDao.class)
        MapperScannerConfigurer:在内部调用getMapper()生成每个dao接口的代理对象。
    -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定SqlSessionFactory对象的id-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        <!--指定包名, 包名是dao接口所在的包名。
            MapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行
            一次getMapper()方法,得到每个接口的dao对象。
            创建好的dao对象放入到spring的容器中的。 dao对象的默认名称是 接口名首字母小写
        -->
        <property name="basePackage" value="com.zyz.SpringDemo3.mapper"/>
    </bean>

    <!--声明service-->
    <bean id="buyGoodsService" class="com.zyz.SpringDemo3.service.impl.BuyGoodsServiceImpl">
        <!--dao对象的默认名称是 接口名首字母小写-->
        <property name="saleMapper" ref="saleMapper"/>
        <property name="goodsMapper" ref="goodsMapper"/>
    </bean>

    <!--声明使用spring的事务处理-->
    <!--1、声明事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <!--连接的数据库,指定数据源-->
        <property name="dataSource" ref="myDataSource"/>
    </bean>
    <!--2、开启事务注解驱动,告诉sprint使用注解管理事务,创建代理对象-->
    <tx:annotation-driven transaction-manager="transactionManager"/>

</beans>

测试类

cpp 复制代码
 //不使用事务测试 和使用事务  取决于是否添加  @Transactional  使用默认值就可以了
    @Test
    public void test01() {
        String config = "TransactionApplication.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        //获取service对象
        IBuyGoodsService service = (IBuyGoodsService) ac.getBean("buyGoodsService");

        //商品编号不存在情况
        //java.lang.NullPointerException: 编号为:1003商品不存在...
        // service.buy(1006,50);
        //商品库存不足情况
        //com.mms.exception.NotEnoughException: 编号为:1002商品库存不足...
        service.buy(1002,100);
        //正常情况
        // service.buy(1002,2);
    }

测试结果如下:

原始数据 商品表

订单表

购买不存在的商品 或者购买商品的数量超过库存,未开启事务的情况下:sql语句都将执行

cpp 复制代码
   //不使用事务测试
    @Test
    public void test01() {
        String config = "TransactionApplication.xml";
        ApplicationContext ac = new ClassPathXmlApplicationContext(config);
        //获取service对象
        IBuyGoodsService service = (IBuyGoodsService) ac.getBean("buyGoodsService");

        //商品编号不存在情况
        //java.lang.NullPointerException: 编号为:1006商品不存在...
        service.buy(1006,50);
         //商品库存不足情况
        //NotEnoughException: 编号为:1002商品库存不足...
        service.buy(1002,100);

    }

控制台报错信息如下:

查看数据库表情况

购买商品数量不足的情况下,也会添加一条记录

开启事务注解

5.3 使用aspectj框架处理事务(配置文件)

aspectJ处理事务是基于配置文件形式的,别的地方都和上面一样,只需要更改service实现类的buy方法和spring核心配置文件即可 1、取消buy方法上的事务注解,因为我们现在是使用aspectJ,基于配置文件 2、将spring注解形式事务开发的spring配置文件中加入的声明事务管理器的部分换成下面的配置

cpp 复制代码
<!--
       使用aspectj方式的事务(声明式事务)
       1、aspectj事务适合大型项目,因为其声明在配置文件中,大大与程序解耦合
    -->
    <!--1、声明事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="myDataSource"/>
    </bean>
    <!--
        2、声明业务方法的事务属性(隔离级别、传播行为、超时时间)
            id:自定义
            transaction-manager:事务故管理器对象的id值
    -->
    <tx:advice id="myAdvice" transaction-manager="transactionManager">
        <!--tx:attributes配置事务属性-->
        <tx:attributes>
            <!--
                tx:method:给具体的方法配置事务属性,method可以有多个,由于给多个方法配置事务
                name:方法名,不带包名和类名,方法可以使用通配符
                propagation:传播行为,枚举类
                isolation:隔离级别
                rollback-for:你指定的异常全限定类名,发生异常一定回滚
            -->
            <tx:method name="buy"
                       propagation="REQUIRED"
                       isolation="DEFAULT"
                       rollback-for="java.lang.NullPointerException,com.zyz.SpringDemo3.service.impl.NotEnoughException"/>
        </tx:attributes>
    </tx:advice>
    
    <aop:config>
        <!--
        3、配置切入点表达式
        为什么需要配置切入点表达式?
            因为步骤2中的方法不确定是哪一个类、哪一个包的方法,所以需要指定
        -->
        <aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>
        <!--
            配置增强器,用于关联advice和pointcut
            advice-ref:通知,上面的tx:advice的id值
            pointcut-ref:切入点表达式的id值
        -->
        <aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt"/>
    </aop:config>

完整配置文件

cpp 复制代码
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                            http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd
                            http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
                            http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd">
    <!--加载数据库配置文件-->
    <context:property-placeholder location="classpath:db.properties"/>
    <!--声明数据源-->
    <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!--set注入给数据库信息赋值,不需要指定驱动类,spring根据url自动识别
        <property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf8&amp;useSSL=true&amp;serverTimezone=UTC"/>
        <property name="username" value="root"/>
        <property name="password" value="333"/>-->

        <!--使用db配置文件读取数据库信息,格式类似el表达式-->
        <property name="driverClassName" value="${jdbc.driverClassName}"></property>
        <property name="url" value="${jdbc.url}"></property>
        <property name="username" value="${jdbc.username}"></property>
        <property name="password" value="${jdbc.password}"></property>
    </bean>

    <!--声明的是mybatis中提供的SqlSessionFactoryBean类,这个类内部创建SqlSessionFactory的-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--set注入赋值-->
        <!--set注入,把数据库连接池付给了dataSource属性-->
        <property name="dataSource" ref="myDataSource" />
        <!--mybatis主配置文件的位置
           configLocation属性是Resource类型,读取配置文件
           它的赋值,使用value,指定文件的路径,使用classpath:表示文件的位置
        -->
        <property name="configLocation" value="classpath:TransactionMybatis-config.xml" />
    </bean>

    <!--创建dao对象,使用SqlSession的getMapper(StudentDao.class)
        MapperScannerConfigurer:在内部调用getMapper()生成每个dao接口的代理对象。
    -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定SqlSessionFactory对象的id-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        <!--指定包名, 包名是dao接口所在的包名。
            MapperScannerConfigurer会扫描这个包中的所有接口,把每个接口都执行
            一次getMapper()方法,得到每个接口的dao对象。
            创建好的dao对象放入到spring的容器中的。 dao对象的默认名称是 接口名首字母小写
        -->
        <property name="basePackage" value="com.zyz.SpringDemo3.mapper"/>
    </bean>

    <!--声明service-->
    <bean id="buyGoodsService" class="com.zyz.SpringDemo3.service.impl.BuyGoodsServiceImpl">
        <!--dao对象的默认名称是 接口名首字母小写-->
        <property name="saleMapper" ref="saleMapper"/>
        <property name="goodsMapper" ref="goodsMapper"/>
    </bean>

    <!--
       使用aspectj方式的事务(声明式事务)
       1、aspectj事务适合大型项目,因为其声明在配置文件中,大大与程序解耦合
    -->
    <!--1、声明事务管理器-->
    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="myDataSource"/>
    </bean>
    <!--
        2、声明业务方法的事务属性(隔离级别、传播行为、超时时间)
            id:自定义
            transaction-manager:事务故管理器对象的id值
    -->
    <tx:advice id="myAdvice" transaction-manager="transactionManager">
        <!--tx:attributes配置事务属性-->
        <tx:attributes>
            <!--
                tx:method:给具体的方法配置事务属性,method可以有多个,由于给多个方法配置事务
                name:方法名,不带包名和类名,方法可以使用通配符
                propagation:传播行为,枚举类
                isolation:隔离级别
                rollback-for:你指定的异常全限定类名,发生异常一定回滚
            -->
            <tx:method name="buy"
                       propagation="REQUIRED"
                       isolation="DEFAULT"
                       rollback-for="java.lang.NullPointerException,com.zyz.SpringDemo3.service.impl.NotEnoughException"/>
        </tx:attributes>
    </tx:advice>

    <aop:config>
        <!--
        3、配置切入点表达式
        为什么需要配置切入点表达式?
            因为步骤2中的方法不确定是哪一个类、哪一个包的方法,所以需要指定
        -->
        <aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>
        <!--
            配置增强器,用于关联advice和pointcut
            advice-ref:通知,上面的tx:advice的id值
            pointcut-ref:切入点表达式的id值
        -->
        <aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt"/>
    </aop:config>
</beans>
相关推荐
序安InToo29 分钟前
第6课|注释与代码风格
后端·操作系统·嵌入式
xyy12330 分钟前
C#: Newtonsoft.Json 到 System.Text.Json 迁移避坑指南
后端
洋洋技术笔记32 分钟前
Spring Boot Web MVC配置详解
spring boot·后端
JxWang0532 分钟前
VS Code 配置 Markdown 环境
后端
navms36 分钟前
搞懂线程池,先把 Worker 机制啃明白
后端
JxWang0536 分钟前
离线数仓的优化及重构
后端
Nyarlathotep011337 分钟前
gin01:初探gin的启动
后端·go
JxWang0537 分钟前
安卓手机配置通用多屏协同及自动化脚本
后端
JxWang0539 分钟前
Windows Terminal 配置 oh-my-posh
后端
SimonKing1 小时前
OpenCode AI编程助手如何添加Skills,优化项目!
java·后端·程序员