【狂神说】Spring5笔记

Spring5

Spring中文文档Spring Framework 中文文档 - Spring Framework 5.1.3.RELEASE Reference | Docs4dev

Spring官方文档Core Technologies (spring.io)
如果笔记有错误欢迎提出!!!!!!!

简介

  • Spring:春天—> 给软件行业带来了春天

  • 2002年,Rod Jahnson首次推出了Spring框架雏形interface21框架。

  • 2004年3月24日,Spring框架以interface21框架为基础,经过重新设计,发布了1.0正式版。

  • 开发者Rod Johnson的学历 , 他是悉尼大学的博士,然而他的专业不是计算机,而是音乐学。。。

  • Spring理念 : 使现有技术更加实用 . 本身就是一个大杂烩 , 整合现有的框架技术

名词解释:

SSH : Struct2 + Spring + Hibernate! (老式

SSM : SpringMvc + Spring + Mybatis!

三个地址:

Spring官方地址:Spring | Home

Spring下载地址(当然我们用不到)repo.spring.io

Spring的github地址Spring (github.com)

Maven的依赖:(不知道的看前面JavaWeb视频)

<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.10</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>5.3.10</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <version>4.12</version>
    <scope>test</scope>
</dependency>

优点?

  • Spring是一个轻量级非侵入式的开源免费的框架 (容器) (即可以插入工程而不对原有文件运行产生干扰
  • 控制反转 IoC , 面向切面 Aop (重点)
  • 对事物的支持 , 对框架的支持

总结:Spring是一个轻量级的控制反转(IoC)和面向切面(AOP)的框架(容器)。

Spring的组成

在这里插入图片描述

讲的不清楚。建议百度。/

(7条消息) Spring模块组成_FYHannnnnn的博客-CSDN博客_spring组成

Spring的扩展

原页面已经找不到了,这个页面已经改变了)

在这里插入图片描述

Spring Boot

  • 一个快速开发的脚手架

  • 基于SpringBoot可以快速开发单个微服务

  • 约定大于配置!

  • Spring Cloud
    Spring CLoud是基于SpringBoot实现的

  • 因为现在大所述公司都在使用SpringBoot进行快速开发,学习SpringBoot的前提,需要完全掌握Spring及SpringMVC!承上启下的作用

弊端:发展太久了之后,违背了原来的理念!配置十分繁琐,人称:“配置地狱”!

IOC理论推导

控制反转,将程序的主动权交给用户,程序根据用户输入的值自动(被动)选择相应代码去执行。这是Spring框架的核心内容。

这里的用户指的是那些买这个项目的人吧,他们不懂代码,因此让他们直接在配置文件中修改相应的值就可以达到代码实现不同效果。

IOC的两种策略:(来自百度百科

  1. 依赖查找:容器提供回调接口上下文条件给组件。EJB和Apache Avalon 都使用这种方式。这样一来,组件就必须使用容器提供的API来查找资源和协作对象,仅有的控制反转只体现在那些回调方法上(也就是上面所说的 类型1):容器将调用这些回调方法,从而让应用代码获得相关资源。
  2. 依赖注入:组件不做定位查询,只提供普通的Java方法让容器去决定依赖关系。容器全权负责的组件的装配,它会把符合依赖关系的对象通过JavaBean属性或者构造函数传递给需要的对象。通过JavaBean属性注射依赖关系的做法称为设值方法注入(Setter Injection);将依赖关系作为构造函数参数传入的做法称为构造器注入(Constructor Injection)

憋了很久,终于弄懂什么是IOC(控制反转) - 戎"码"一生 - 博客园 (cnblogs.com)

IoC原理 - 廖雪峰的官方网站 (liaoxuefeng.com)

IOC是一种编程思想,由主动编程变成了被动接收。在本视频中,狂神通过set方式作为例子。而后面实际使用其实就是使用修改xml配置文件。

利用IOC,我们不用再去程序中根据用户的要求进行改动了,要实现不同的操作,只需要在xml配置文件中进行修改,所谓的IOC,一句话搞定:对象是由Spring创建,管理,装配!

在此过程中,对象仅通过构造函数参数,工厂方法的参数或在构造或从工厂方法返回后在对象实例上设置的属性来定义其依赖项(即,与它们一起使用的其他对象) 。然后,容器在创建 bean 时注入那些依赖项。此过程从根本上讲是通过使用类的直接构造或诸如服务定位器模式之类的控件来控制其依赖项的实例化或位置的 bean 本身的逆过程(因此称为 Control Inversion)。

HelloSpring

Spring 提供了ApplicationContext接口的几种实现。在独立应用程序中,通常创建ClassPathXmlApplicationContextFileSystemXmlApplicationContext的实例。

在大多数应用场景中,不需要实例化用户代码即可实例化一个 Spring IoC 容器的一个或多个实例。
在这里插入图片描述

如上图所示,Spring IoC 容器使用一种形式的配置元数据。此配置元数据表示您作为应用程序开发人员如何告诉 Spring 容器实例化,配置和组装应用程序中的对象。

传统上,配置元数据以简单直观的 XML 格式提供,这是本章大部分内容用来传达 Spring IoC 容器的关键概念和功能的内容。

编写HelloSpring:

新建一个项目Spring_study .检查一下这里的Maven是否是自己的。

在这里插入图片描述

然后删掉src将该工程作为父工程,在pom.xml文件中导入上面提到的依赖。

然后在项目中新建一个子工程,Spring-01-hello。

在java中创建包com.song.pojo 在里面创建hello.java类

public class Hello { 
    private String str;
    public String getStr() {//alt+insert快速生成  
        return str;
    }
    public void setStr(String str) {
        this.str = str;
    }
    @Override
    public String toString() {//ctrl+o 快速生成
        return getStr();
    }
}

在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
    id属性:是标识单个 bean 定义的字符串。
    class属性:定义 bean 的类型并使用完全限定的类名。
    id属性的值是指协作对象。
    property 相当于给对象中的属性设置一个值
    -->
    <bean id="hello" class="com.song.pojo.Hello">
        <property name="str" value="This is HelloSpring.."/>
    </bean>
</beans>

在test文件夹中的java文件创建一个测试类 test.java

public static void main(String[] args) { //psvm + 回车快速生成
    //获取Spring的上下文对象,这里是已经写死了,不能够再改变了
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
    //我们的对象已经在Spring中了,我们只需要取出来就可以了
    Hello hello = (Hello) context.getBean("hello");
    System.out.println(hello.toString());
}

输出结果:This is HelloSpring..

运行完之后发现实体类Hello的左侧边出现了绿色的叶子,当出现绿色的叶子说明已经被Spring所托管了。如果没有就点击xml文件上方的黄色提示信息,它会自动帮助改正/

接下来狂神将上个视频中讲解IOC理论时所使用的关于用户自己选择数据库使用的例子用Spring实现:

在java的com.song.pojo包里创建UserDao接口以及UserDaoMysqlImpl类和UserDaoOracleImpl类。

public interface UserDao {
void getUser();
}
public class UserDaoMysqlImpl implements UserDao{
    public void getUser(){
        System.out.println("通过mysql得到数据!");
    }
}

public class UserDaoOracleImpl implements UserDao {
    public void getUser(){
        System.out.println("通过Oracle得到数据!");
    }
}

在java的com.song.service包里创建UserService接口以及UserServiceImpl类。

public interface UserService {
    void getUser();
}
public class UserServiceImpl implements  UserService{
    private  UserDao userdao;
    public void setUserDao(com.song.pojo.UserDao userDao) {
        userdao = userDao;
    }
    public void getUser() {
        userdao.getUser();
    }
}

beans.xml文件

<bean id="mysql" class="com.song.pojo.UserDaoMysqlImpl"/>
<bean id="oracle" class="com.song.pojo.UserDaoOracleImpl"/>
<bean id="userserviceImpl" class="com.song.service.UserServiceImpl">
    <property name="userDao" ref="mysql"/>
</bean>
<!-- ref就是帮助不认识代码的客户可以不走代码而直接修改这里,进而达到更换数据库的目的
    ref:引用的是Spring已经托管的类型
    value:用于引用一些基本数据类型(具体的值
    -->

测试类test.java

public static void main(String[] args) { //psvm + 回车快速生成
    //可以通过输入 CPX+回车 快速补全 注意补全后的数据类型要自己改为 ApplicationContext
    //当运行这句话时,beans.xml里的 所有类 都已经被构造出来了,就存放在容器中!!!
    ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
	//在Spring容器(context)中直接取出来
    UserServiceImpl userServiceImpl = (UserServiceImpl) context.getBean("userserviceImpl");
    userServiceImpl.getUser();
}

输出结果:通过mysql得到数据!

我当时有疑惑,为什么要强转为UserServiceImpl,UserService不行吗?我试了一下,也行。。。然后在网上搜到了下面的文章,先贴在这里//

(8条消息) Spring框架getBean()方法返回对象为什么只能转成接口对象,转换成接口的实例会报错?_你好,我们在哪里见过啊!-CSDN博客

IOC注册对象的方式

这节课讲的就是关于上面的bean.xml文件中java类注册时采用何种方式。

  1. 默认通过无参构造函数,如果java类只有有参构造函数会报错。
  2. 如果想通过有参函数来进行构造,有三种方法

在pojo包里创建User类:

public class User {
    private String name;
    public User(String name) {//有参构造函数
        this.name = name;
    }
    public void setName(String name) {
        this.name = name;
    }
    public void show(){
        System.out.println("name="+ name );
    }
}
  • 下标赋值

很简单的就可以理解

index指构造方法的第几个参数 初始以0开始

 <bean id="user" class="com.song.pojo.User">
    <constructor-arg index="0" value="song"/>
 </bean>
  • 参数类型创建

这里不能偷懒使用String哦,必须写完整,不然爆红

 <bean id="user" class="com.song.pojo.User">
    <constructor-arg type="java.lang.String" value="song"/>
 </bean>

另外试了一下两个String参数时,他会按照bean里面的顺序挨个给两个String参数赋值。

虽然可以成功赋值,但是感觉不太好用的感觉///

<bean id="user" class="com.song.pojo.User">
    <constructor-arg type="java.lang.String" value="song"/>
    <constructor-arg type="java.lang.String" value="zhi"/>
</bean>
  • 参数名创建

这里的参数名就是有参构造函数的括号里的参数

<bean id="user" class="com.song.pojo.User">
    <constructor-arg name="name" value="song"/>
</bean>

看到弹幕在讨论是否能通过context中一个class对象只有一个对应的实例得出Spring默认使用的是单例模式,然后去网上搜了一下。

单例bean与原型bean的区别:

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

但是对于原型(prototype)bean来说当每次请求来的时候直接实例化新的bean,没有缓存以及从缓存查的过程。

Spring为什么默认是单例模式?

为了提高性能,少创建实例,垃圾回收,缓存快速获取

单例bean的优势?

  • 减少新生成实例的消耗
  • 减少jvm垃圾回收
  • 单例的获取bean操作除了第一次生成之外其余的都是从缓存里获取的所以很快

单例bean的劣势?

单例的bean一个很大的劣势就是他不能做到线程安全,由于所有请求都共享一个bean实例,所以这个bean要是有状态的一个bean的话可能在并发场景下出现问题,而原型的bean则不会有这样问题(但也有例外,比如他被单例bean依赖),因为给每个请求都新创建实例。

转自:面试题:Spring为什么默认bean为单例? - 简书 (jianshu.com)

知识有点多看不懂的一个大佬的博客:

(8条消息) Spring中Bean的单例、多例_longzhutengyue的博客-CSDN博客

Spring配置说明

  • 别名:

在beans.xml文件中加入这句代码,然后就会在注册对象的时候可以通过xml中的别名来当作真名字使用。

<alias name="user" alias="user2"/>
  • Bean的配置

id:bean的唯一标识符,也就是相当于我们学的对象名
class:bean 对象所对应的全限定名:包名+类型
name:也是别名,而且同时name可以同时取多个别名,很随意 逗号,分号,空格都可以起到分割不同别名的作用。

<bean id="userT" class="com.xsq.pojo.UserT" name="t,t1">
    <property name="name" value="erha"/>
</bean>
  • import导入

这个import,一般用于团队开发使用,他可以将多个配置文件,导入合并为一个(都在同一个resources文件目录下

假设,现在项目中有多个人开发,这三个人负责不同类的开发,不同的类需要注册在不同的bean中,我们可以利用import将所有人的beans.xml合并为一个总的!使用的时候直接使用总配置就可以了。

<import resource="beans_song.xml"/>
<import resource="beans_szg.xml"/>

依赖注入

(9条消息) Spring的五种依赖注入方式_shadow_zed的博客-CSDN博客_spring 注入

  • 构造器注入

上面IOC创建对象已经讲过了

  • Set方式注入【重点】

set注入搭建环境

在一个新的子工程里创建实体类,记得生成对应的get和set以及tostring方法

public class Student {
    private String name;
    private Address address;
    private String[] books;
    private List<String> hobbies;
    private Map<String, String> card;
    private Set<String> games;
    private Properties info;
}
public class Address {
    private String address;
    public String getAddress() {
    return address;
}
//记得使用alt+insert来快捷生成所需要的getset和tosting

然后beans.xml 中进行student类的注册(先对name进行赋值

<bean id="student" class="com.song.pojo.Student">
    <property name="name" value="song"/>
</bean>

编写测试类( test/java/test.java )

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("beans.xml");
        Student student = (Student) context.getBean("student");
        System.out.println(student.getName());
    }
}

测试成功就说明本测试环境完成

例子

beans.xml文件补全:

<bean id="address" class="com.song.pojo.Address">
    <property name="address" value="青岛"/>
</bean>
<bean id="student" class="com.song.pojo.Student">
    <!--第一种 普通的value注入-->
    <property name="name" value="song"/>
    <!--第二种 引用类型注入-->
    <property name="address" ref="address"/>
    <!--第三种 数组注入-->
    <property name="books">
        <array>
            <value>《红楼梦》</value>
            <value>《三国演义》</value>
            <value>《西游记》</value>
            <value>《水浒传》</value>
        </array>
    </property>
    <!--第四种 list注入-->
    <property name="hobbies">
        <list>
            <value>games</value>
            <value>code</value>
        </list>
    </property>
    <!--第五种 Map注入-->
    <property name="card">
        <map>
            <entry key="身份证" value="215345615"/>
            <entry key="银行卡" value="32152156132"/>
        </map>
    </property>
    <!--第六种 set注入-->
    <property name="games">
        <set>
            <value>L</value>
            <value>C</value>
        </set>
    </property>
    <!--第7种 properties注入-->
    <property name="info">
        <props>
            <prop key="name">song</prop>
            <prop key="sex"></prop>
            <prop key="id">20190106</prop>
        </props>
    </property>
</bean>

然后运行测试类 调用toSting()方法,可以做到全部输出。

拓展注入方式

p命名空间注入:使用这个注入方式需要在约束条件中加入一条约束代码

然后注册时可以比较方便…代码比较简短(emmm)

xmlns:p="http://www.springframework.org/schema/p"
<?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="address" class="com.song.pojo.Address"   p:address="青岛"/>

c命名空间注入:使用这个注入方式也需要在约束条件中加入一条约束代码…(和p的约束代码差不多

通过构造器注入,所以我们必须在实体类中加入有参及无参构造

xmlns:c="http://www.springframework.org/schema/c"
public class Address {
    private String address;

    public Address() {
    }
    public Address(String address) {
        this.address = address;
    }
}
<?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="address" class="com.song.pojo.Address" c:address="青岛"/>

bean的作用域

浅析Spring中bean的作用域|spring|websocket|session|xml_网易订阅 (163.com)

在这里插入图片描述

singleton(单例):在spring IoC容器仅存在一个Bean实例,Bean以单例方式存在,bean作用域范围的默认值。
prototype(多例):每次从容器中调用Bean时,都返回一个新的实例,即每次调用getBean()时,相当于执行newXxxBean()。
request:每次HTTP请求都会创建一个新的Bean,该作用域仅适用于web的Spring WebApplicationContext环境。
session:同一个HTTP Session共享一个Bean,不同Session使用不同的Bean。该作用域仅适用于web的Spring WebApplicationContext环境。
application:限定一个Bean的作用域为ServletContext的生命周期。该作用域仅适用于web的Spring WebApplicationContext环境。

websocket:Scopes a single bean definition to the lifecycle将单个bean定义作用于WebSocket的生命周期。在Web感知的SpringApplicationContext上下文中有效。

  • 单例模式

这里所说的单例,和设计模式中所提到的单例模式不同。设计模式中的单例,是强制一个类有且只有一个对象,我们如果不通过特殊的手段,将无法为这个单例类创建多个对象。而 Spring 中的单例作用域不同,这里的单例指的是在一个 Spring 容器中,只会缓存 bean 的唯一对象,所有通过容器获取这个 bean 的方式,最终拿到的都是同一个对象。但是在不同的 Spring 容器中,每一个 Spring 容器都可以拥有单例 bean 的一个实例对象,也就是说,这里的单例限定在一个 Spring 容器中,而不是整个应用程序。并且我们依然可以通过 new 的方式去自己创建 bean 。

  • 原型模式

每次从容器中get都会产生一个新对象

关于这两个模式的选择使用scope属性来确定,Spring默认为单例模式

 <bean id="address" class="com.song.pojo.Address" c:address="青岛" scope="singleton"/>
 <bean id="address" class="com.song.pojo.Address" c:address="青岛" scope="prototype"/>

再然后就是两种模式下都同时get一个同样的实例类,单例模式get到的是同一个对象。

  • request、session、application这些都是只能在web开发中才能使用到的

bean的自动装配

当Bean的属性很少的时候,我们对它进行配置的时候就使用很少的或者元素进行装配,但是随着工程体积的增大,Bean也可能变得复杂,这时候配置文件也会变得复杂,和 就会变得很多,写起来就会很费劲,这个时候利用自动装配就方便很多

自动装备就是自动寻找容器中的类

例如人拥有猫和狗,人可以让猫叫。

首先测试咱不使用自动装配,使用手动装配的效果。

public class Dog {
    public void shout(){
        System.out.println("wang!");
    }
}
public class Cat {
    public void shout(){
        System.out.println("miao!");
    }
}
public class People {
    private Cat cat;
    private Dog dog;//省略get,set
}

测试类test:

public static void main(String[] args) {
    ApplicationContext Context = new ClassPathXmlApplicationContext("beans.xml");
    People people = (People) Context.getBean("people");
    people.getCat().shout();
    people.getDog().shout();
}

beans.xml 手动装配:

在下面的代码中我们使用property 手动装配了人所拥有的cat类和dog类

<bean id="dog" class="com.song.pojo.Dog"/>
<bean id="cat" class="com.song.pojo.Cat"/>
<bean id="people" class="com.song.pojo.People">
    <property name="cat" ref="cat"/>
    <property name="dog" ref="dog"/>
</bean>

beans.xml自动装配:

  • 在下面的代码中我们使用 <… autowire=“byName”> 自动装配人所拥有的cat类和dog类

在本例子中就是在people类中找到setDog(Dog dog)方法然后得到括号里面的参数dog,在beans.xml中寻找到id=dog的bean,然后自动匹配。

要求找到的bean_id必须为dog,多一个字母也不行。(

<bean id="dog" class="com.song.pojo.Dog"/>
<bean id="cat" class="com.song.pojo.Cat"/>
<bean id="people" class="com.song.pojo.People" autowire="byName"/>
  • 在下面的代码中我们使用 <… autowire=“byType”> 自动装配人所拥有的cat类和dog类

在本例子中,就是在beans.xml中寻找和自己对象属性类型相同的 bean

这种方法不要求bean_id 必须为dog了,但是只适用于存在一种dog的情况。你只能养一条狗了。。。

<bean id="dog" class="com.song.pojo.Dog"/>
<bean id="cat" class="com.song.pojo.Cat"/>
<bean id="people" class="com.song.pojo.People" autowire="byType"/>

使用注解实现自动装配

继续使用上面的子工程。

使用注解需要在beans.xml中导入comtext约束,增加注解支持。(一共在原beans.xml的基础上加了四条语句。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       
    xmlns:context="http://www.springframework.org/schema/context"
       
    xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
                        
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
	<!---激活那些已经在spring容器里注册过的bean上面的注解,也就是显示的向Spring注册-->
    <context:annotation-config/>

</beans>
  • @Autowired

这个注解是属于Spring的,测试的时候配置文件:

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

使用Autowired我们就可以不用编写set方法了,People类中使用@Autowired注解

public class People {
   @Autowired
   private Cat cat;
   @Autowired
   private Dog dog;
   //节省篇幅,省略了get方法,测试的时候自己加上
}

运行测试test可以发现,尽管没有对bean进行装配,但是运行成功。

自己更改配置文件后,知道首先按照byType进行自动装配,如果装配失败则进行byName自动装配。

扩展:

@Autowired(required=false):表示忽略当前要注入的bean,如果有直接注入,没有跳过,不会报错。

在启动Spring后,注入容器的过程中,扫描到公共方法中要注入的bean,并未找到,强行注入就会注入失败。我们又不能单独的去除改方法,

所以我们有bean就注入,没有就不注入。解决办法就是@Autowired(required=false)。

  • @Autowired+@Qualifier

Spring 注解 @Qualifier 详细解析 - 知乎 (zhihu.com)

@Autowired加上@Qualifier 直接确定了会根据byName的方式自动装配。@Qualifier不能单独使用。

测试的时候,修改beans.xml文件:

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

然后进行测试,发现报错

再修改People类 value值匹配beans.xml里的id。(如果没有匹配的值,就会直接爆红,运行都运行不了

public class People {
    @Autowired
    @Qualifier(value = "cat2")
    private Cat cat;
    @Autowired
    @Qualifier(value = "dog2")
    private Dog dog;
    //节省篇幅,省略了get方法,测试的时候自己加上
}

然后再进行测试,成功运行。

  • @Resource

这个注解属于j2EE,JSR-250标准注解,推荐使用它来代替Spring专有的@Autowired注解

但是!!!我的jdk11没有这个注解!!!!

@Resource装配顺序:(–来源网上

(1)如果同时指定了name和type,则从Spring上下文中找到唯一匹配的bean进行装配,找不到则抛出异常

(2)如果指定了name,则从Spring上下文中查找名称(id)匹配的bean进行装配,找不到则抛出异常

(3)如果指定了type,则从Spring上下文中找到类型匹配的唯一bean进行装配,找不到或找到多个,都抛出异常

(4)如果既没指定name,也没指定type,则自动按照byName方式进行装配。如果没有匹配,则回退为一个原始类型(UserDao)进行匹配,如果匹配则自动装配。

@Resource的作用相当于@Autowired,只不过@Autowired按byType自动注入。

使用注解开发

属性注入

在Spring4之后,要使用注解开发,必须要保证aop的包导入了。(依赖使导入Spring-webmvc就直接全部导入了

(1)< context:annotation-config />:仅能够在已经在已经注册过的bean上面起作用。对于没有在spring容器中注册的bean,它并不能执行任何操作。
(2)
< context:component-scan base-package=“XX.XX”/>
:除了具有上面的功能之外,还具有自动将带有@component,@service,@Repository等注解的对象注册到spring容器中的功能。

< context:annotation-config />和 < context:component-scan>同时存在的时候,前者会被忽略。如@autowire,@resource等注入注解只会被注入一次!

创建一个子工程,然后将applicationContext.xml (就是原来的beans.xml)基础的配置一下。

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
        https://www.springframework.org/schema/beans/spring-beans.xsd
        http://www.springframework.org/schema/context
        https://www.springframework.org/schema/context/spring-context.xsd">
    <context:annotation-config/>
    <!--指定要扫描的包,这个包下的注解就会生效-->
    <context:component-scan base-package="com.song"/>
</beans>

在com.song.pojo包下创建User.java类

//@Component 组件
//加上后等价于  <bean id="user" class="com.song.pojo.User"/>
//注意首字母小写
@Component
public class User {
    //等价于  <property name="name" value="szg"/>
    //也可以加在set方法上面,两者同时存在时,优先使用set方法上面的
    @Value("szg")
    public String name;
}

写一个测试方法

public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    User user = (User) context.getBean("user");//注意这里时小写user
    System.out.println(user.name);
}

运行成功。

@Component 注解的衍生

@Component有几个衍生注解,我们在web开发中,会按照mvc三层架构分层!

这几个衍生注解在效果都是一样的,都是代表将某个类注册到Spring中,装配Bean。

当然,需要都在配置文件的context:component-scan指定范围内

  • dao层 【@Repository】
  • service层 【@Service】
  • controller层 【@Controller】

作用域

通过scope注解中value来定义作用域,详解可以看bean的作用域

将该@Scope("singleton") 加在Component下面就行。

加上注解就不用再去配置文件中配置作用域了

@Component
@Scope("singleton")
public class User {
    @Value("szg")
    public String name;
    public void setName(String name) {
        this.name = name;
    }
}

xml更加万能,适用于任何场合维护简单。

注解更加方便,但是维护起来很麻烦

两者比较合理的分配:

  1. xml用来管理bean;
  2. 注解只负责完成属性的注入;
  3. 我们在使用的过程中,只需要注意一个问题:必须让注解生效,就需要开启注解的扫描支持

JavaConfig代替配置文件

JavaConfig是Spring的一个子项目。它基于Java代码和Annotation注解来描述Bean之间的依赖绑定关系。

使用JavaConfig就可以实现不需要beans配置文件,就可以将对象注册到容器中。

  • 创建一个新的子工程,然后创建com.song.pojo包

@Component 与后面的@ComponentScan 相配套使用、两者一起出现

这个注解也会注册一个Bean,id属性为实体类的小写

@Component
public class User {
    public String name;
    @Value("szg")
    public void setName(String name) {
        this.name = name;
    }
    @Override
    public String toString() {
        return "User{" +
                "name='" + name + '\'' +
                '}';
    }
}
  • 创建com.song.config包,写了一个Song_Config类

@Configuration将该文件标记为配置类(代替xml配置文件

@Bean作为Spring的XML配置文件中的<bean>

这个方法的名字就相当于bean中的id属性

这个方法的返回值,就相当于bean标签中的class属性

(ps:可以使用@Bean("BeanName") 来手动指定Bean的名字)

@ComponentScan("com.song.pojo")相当于之前配置文件的<context:component-scan base-package="com.song.pojo"/>

@Import 相当于之前的 xml配置文件里的 标签

@Configuration
@ComponentScan("com.song.pojo")
@Import("Song_Config2.class")
public class Song_Config {
    @Bean
    public User getUser(){
        return new User();
    }
}
  1. @Bean 注解默认作用域为单例singleton 作用域,可通过@Scope(”prototype“)设置为原型作用域
  2. 既然@Bean的作用是注册bean对象,那么完全可以使用@Component@Controller@Service@Ripository等注解注册bean,当然需要配置@ComponentScan注解进行自动扫描。
  3. 但是扫描之后,会出现两个Bean对象,一个是getUser一个是user。一个是bean创建的,一个是Component创建的。
  • 创建一个测试类:

这里因为我们用的是java配置类去做,所以通过AnnotationConfigApplicationContext类进行解析并注册到Bean的注册表,通过java配置类的class对象加载。

public static void main(String[] args) {
    ApplicationContext context = new AnnotationConfigApplicationContext(Song_Config.class);
    User user1 = (User) context.getBean("getUser");
    User user2 = (User) context.getBean("user");
    System.out.println(user1);
    System.out.println(user2);
    System.out.println(user1==user2);//两个不一样的user对象
    System.out.println(user1.hashCode());
    System.out.println(user2.hashCode());
}

静态代理模式

要求被代理类和代理类同时实现相应的一套接口,通过代理类调用重写接口的方法,实际上调用的是原始对象的同样的方法。

例子:房东想要出租房子,但是没时间,因此扔给了中介,中介需要帮助房东实现出租房子的任务。

在这里中介就是静态代理了房东来做出租房子这件事情。

租房子的接口:

public interface Rent {
    public void rent();
}

房东:

public class Host implements Rent{
    public void rent() {
        System.out.println("房东出租房子!");
    }
}

中介:

public class Proxy implements Rent{
    private Host host;
    public Proxy() {
    }
    public Proxy(Host host) {
        this.host = host;
    }
    public void rent() {//代理房东实现出租
         System.out.println("中介发放出租房源");
        host.rent();
        //可以在函数里面加一些附加的内容
         System.out.println("房东记得给中介费!");
    }
}

房子被出租:

public class Test {
    public static void main(String[] args) {
        Host host = new Host();
        //代理,中介帮房东出租房子,并且代理角色也可以加上一些附有的操作(比如签合同、收费等)!
        Proxy proxy = new Proxy(host);
        //不用面对房东,直接找中介租房即可!
        proxy.rent();
    }
}

再例如可以在上面的代理类中对于每种操作都进行输出(printf)来达到了不通过修改房东的源代码而实现了输出关于房子出租过程的作用。

静态代理总结:

1.可以做到在不修改目标对象的功能前提下,对目标功能扩展.
2.缺点:

  • 每次我要新加入一个实现接口的对象的话,都要重新创建一个代理对象,效率太低
  • 同时,一旦接口增加方法,目标对象与代理对象都要维护.

动态代理模式

建议先看前面的注解和反射的视频再继续向下看。

动态代理和静态代理角色一样
动态代理的代理类是动态生成的,不是我们手动写好的
动态代理分为俩大类:基于接口的动态代理,基于类的动态代理

  • ​ 基于接口—-JDK动态代理
  • ​ 基于类:cglib
  • ​ 现在用的比较多:java字节码实现:javassist
  • ​ 我们使用JDK的原生代码来实现,其余的道理都是一样的!

首先我们需要了解俩个类:Proxy:代理,InvocationHandler:调用处理程序

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

(13条消息) java中getClass()方法简介_expect521的博客-CSDN博客_getclass

(13条消息) 动态代理模式newProxyInstance及invoke方法参数详解_mRambo的博客-CSDN博客

主要是理解 InvocationHandler newProxyInstance .

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

关于代码:

租房子的接口房东对象 都是和前面一样

动态代理类:

public class AutoProxy implements InvocationHandler {
    private  Rent rent;
    public void setRent(Rent rent){
        this.rent=rent;
    }
    //生成代理类,重点是第二个参数,获取要代理的抽象角色!之前都是一个角色,现在可以代理一类角色
    //格式几乎是死的 注意狂神写的时候第一个参数写的this.getClass().getClassLoader()
    public Object getProxy(){
        //三个参数 :代理类的类加载器   代理类的接口列表   调度方法调用的调用处理程序(就是自己
         return  Proxy.newProxyInstance(rent.getClass().getClassLoader(), rent.getClass().getInterfaces(), this);
    }
    // 处理代理实例上的方法调用并返回结果
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        seeHouse();
        //核心:本质利用反射实现!
        Object result = method.invoke(rent, args);
        fare();
        return result;
    }
    public void seeHouse(){
        System.out.println("带房客看房");
    }
    public void fare(){
        System.out.println("记得给中介费!");
    }  
}
public class Client {
    //一个动态代理 , 一般代理某一类业务 , 一个动态代理可以代理多个类,代理的是接口!
    //由代码动态的生成 接口相应的 代理类
    public static void main(String[] args) {
        //真实角色 对应接口的实现类
        Host host = new Host();
        //动态代理 :代理实例的调用处理程序
        AutoProxy ap = new AutoProxy();
        //多态  因为host实现了rent的接口
        ap.setRent(host); //将真实角色放置进去
        // 动态生成对应的代理类! 记得强转一下
        Rent proxy = (Rent)ap.getProxy();
        proxy.rent();
    }
}

深化理解(模板)

我们来使用动态代理实现代理我们后面写的UserService!

我们也可以编写一个通用的动态代理实现的类!所有的代理对象设置为Object即可!

public interface UserService {
    public void add();
    public void delete();
    public void update();
    public void query();
}


//真实对象
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("查询了一个用户");
    }
}

//模板  直接可以拿过来用!
public class ProxyInvocationHandler implements InvocationHandler {
    private Object target;

    public void setTarget(Object target) {
        this.target = target;
    }
    //生成代理类
    public Object getProxy(){ //JDK 动态代理的局限性,只支持接口
        return Proxy.newProxyInstance(target.getClass().getClassLoader(),
                target.getClass().getInterfaces(),this);
    }
    // proxy : 生成的代理类
    // method : 代理类的调用处理程序的方法对象.
    // 被代理对象 target 调用其自身方法时都会执行 invoke
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        log(method.getName());//返回调用的函数方法的名称
        Object result = method.invoke(target, args);
        return result;
    }
    public void log(String methodName){
        System.out.println("执行了"+methodName+"方法");
    }
}
public class Test {
    public static void main(String[] args) {
        //每次只需要更改真实对象  而不用每次都要写一个静态代理的代理类
        UserServiceImpl userService = new UserServiceImpl();
        //代理对象的调用处理程序
        ProxyInvocationHandler pih = new ProxyInvocationHandler();
        pih.setTarget(userService); //设置要代理的对象
        UserService proxy = (UserService)pih.getProxy(); //动态生成代理类!
        proxy.delete();
        proxy.query();
    }
}

动态代理的好处

  • 可以使真实角色的操作更加纯粹!不用去关注一些公共的业务
  • 公共也就交给代理角色!实现了业务的分工!
  • 公共业务发生扩展的时候,方便集中管理!
  • 一个动态代理类代理的是一个接口,一般就是对应的一类业务
  • 一个动态代理类可以代理多个类,只要是实现了同一个接口即可【核心】

AOP

什么是AOP

代理模式核心是AOP思想:(改人源代码相当于刨祖坟(–来自弹幕

因此当需要对源代码的业务实现进行修改时,需要面向切片编程

给源代码套个娃,即再写一个套娃的代理类,从而对源代码的业务进行修改.

AOP 实现机制 - 简书 (jianshu.com)

  • AOP(Aspect Oriented Programming):可以通过预编译方式运行其动态代理实现在不修改源代码的情况下给程序动态统一添加某种特定功能的一种技术。

  • AOP是c(面向对象编程)的延续,是软件开发中的一个热点,也是Spring框架中的一个重要内容,是函数式编程的一种衍生范型。

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

在这里插入图片描述

AOP在Spring中的作用

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

以下名词需要了解下: (狂神的理解-)

  • 横切关注点:跨越应用程序多个模块的方法或功能。即是,与我们业务逻辑无关的,但是我们需要关注的部分,就是横切关注点。如日志 , 安全 , 缓存 , 事务等等 …

  • 切面(ASPECT):横切关注点 被模块化 的特殊对象。即,它是一个类。

  • 通知(Advice):切面必须要完成的工作。即,它是类中的一个方法。

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

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

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

  • 连接点(JointPoint):与切入点匹配的执行点。

在这里插入图片描述
在这里插入图片描述

理解几种增强方式!

Spring中的5种Aop常见应用方式 - 知乎 (zhihu.com)

通过 Spring API 实现

  • 首先创建一个新的子工程 然后导入AOP的依赖(不知道为啥不导入id为spring-aop的包)
<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.7</version>
</dependency>
  • 在java文件夹中创建com.song.service包,在包里创建接口和实现类
public interface UserService {
    public void add();
    public void delete();
    public void update();
}
public class UserServiceImpl implements UserService{
    @Override
    public void add() {
        System.out.println("增加用户");
    }
    @Override
    public void delete() {
        System.out.println("删除用户");
    }
    @Override
    public void update() {
        System.out.println("更新用户");
    }
}
  • 然后去写我们的增强类:创建com.song.log包 , 我们编写两个java文件 , 一个前置增强 一个后置增强
public class BeforeLog implements MethodBeforeAdvice {
    //method : 要执行的目标对象的方法
    //args : 被调用的方法的参数
    //target : 目标对象
    @Override
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println( target.getClass().getName() + "的" + method.getName() + "方法被执行");
    }
}
public class AfterLog implements AfterReturningAdvice {
    @Override
    public void afterReturning(Object returnValue, Method method, Object[] args, Object target) throws Throwable {
        System.out.println("执行了" + target.getClass().getName() +"的"+method.getName()+"方法,"+"返回值为:"+returnValue);
    }
}
  • 最后去spring的文件中注册 , 并实现aop切入实现 , 注意导入约束 .

在这里插入图片描述

创建Spring基本配置文件然后编辑

    <bean id="userservice" class="com.song.service.UserServiceImpl"/>
    <bean id="beforelog" class="com.song.log.BeforeLog"/>
    <bean id="afterlog" class="com.song.log.AfterLog"/>

     <!--方式一:使用原生Spring API接口-->
    <!--配置AOP导入AOP的约束-->
    <!--aop刚敲出来的时候会报错 然后敲出aop:config之后将敲回车就可以自动导入 xmlns:aop属性-->
    <aop:config>
<!--        切入点:expression-->
<!--        execution(修饰符  返回值  包名.类名/接口名.方法名(参数列表)) 注意老师忽略掉修饰符了-->
<!--        (..)可以代表所有参数,(*)代表一个参数,(*,String)代表第一个参数为任何值,第二个参数为String类型.-->
<!--         在这里第一个*号代表返回值不限制 第二个型号代表方法名称不限制-->
        <aop:pointcut id="pointcut" expression="execution(* com.song.service.UserServiceImpl.*(..))"/>
<!--        执行环绕增加-->
        <aop:advisor advice-ref="afterlog" pointcut-ref="pointcut"/>
        <aop:advisor advice-ref="beforelog" pointcut-ref="pointcut"/>
    </aop:config>

创建Test测试文件

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
//        写接口
        UserService userservice = (UserService) context.getBean("userservice");
        userservice.add();
    }
}

结果:

com.song.service.UserServiceImpl的add方法被执行
增加用户
执行了com.song.service.UserServiceImpl的add方法,返回值为:null

因此在没有改变UserService的代码的基础上,增加了日志!

自定义类来实现

  • 创建一个com.song.config的包然后写我们自己的一个切入类。
public class DiyPointcut {
    public void before(){
        System.out.println("---------方法执行前---------");
    }
    public void after(){
        System.out.println("---------方法执行后---------");
    }
}
  • 去spring中配置
<!--第二种方式自定义实现-->
    <!--注册bean-->
    <bean id="diy" class="com.song.config.DiyPointcut"/>
    <!--aop的配置-->
    <aop:config>
        <!--第二种方式:aspect切面-->
        <aop:aspect ref="diy">
            <aop:pointcut id="diyponitcut" expression="execution(* com.song.service.UserServiceImpl.*(..))"/>
            <aop:before pointcut-ref="diyponitcut" method="before"/>
            <aop:after pointcut-ref="diyponitcut" method="after"/>
        </aop:aspect>
    </aop:config>

然后测试即可

使用注解实现

  • 在com.song.config的包中,我们写一个自己的一个注解切入
//如果没有Aspect的就是导入依赖生效的范围不对,去掉pom.xml文件里的scope标签然后刷新Maven就行了。
//定义切面类
@Aspect
public class AnnotationPointCut {
    //如果导入了junit依赖会有junit包下的Before注解  要看清楚 不要导错了
    @Before("execution (* com.song.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("====方法执行前====");
    }
    @After("execution(* com.song.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("----方法执行后------");
    }
    @Around("execution(* com.song.service.UserServiceImpl.*(..))")
    public void around(ProceedingJoinPoint jp) throws Throwable{
        System.out.println("环绕前");
        //签名:返回参数的类型 和 类的执行了的方法名称
        System.out.println("获得签名:"+jp.getSignature());
        System.out.println("环绕后");
    }
}
  • Spring配置文件
<!--第三种方式:注解实现-->
<bean id="annotationPointcut" class="com.song.config.AnnotationPointCut"/>
<!--开启注解支持-->
<aop:aspectj-autoproxy/>

然后测试即可

结果:

环绕前
获得签名:void com.song.service.UserService.add()
====方法执行前====
增加用户
----方法执行后------
环绕后
null

在环绕增强中,可以执行业务方法,而在前置增强和后置增强中则不可以;这里可以通过环绕增强实现数据库事务的实现,也可以通过环绕增强实现程序运行时间的记录;****

  • 关于<aop:aspectj-autoproxy />

通过aop命名空间的<aop:aspectj-autoproxy />声明自动为spring容器中那些配置**@aspectJ切面的bean创建代理,织入切面**。

当然,spring 在内部依旧采用AnnotationAwareAspectJAutoProxyCreator进行自动代理的创建工作,但具体实现的细节已经被<aop:aspectj-autoproxy />隐藏起来了

<aop:aspectj-autoproxy />有一个proxy-target-class属性,默认为false表示使用jdk动态代理织入增强,当配为<aop:aspectj-autoproxy poxy-target-class=“true”/>时,表示使用CGLib动态代理技术织入增强。不过即使proxy-target-class设置为false,如果目标类没有声明接口,则spring将自动使用CGLib动态代理。

整合Mybatis

Spring-Mybatis中文文档:mybatis-spring –

Mybatis回顾

创建一个新的子工程在子工程的pom.xml导入依赖:(父工程的依赖有spring-webmvc spring-jdbc 和junit 三种依赖

<dependencies>
    <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.7</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/mysql/mysql-connector-java -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>8.0.26</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.9.7</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.mybatis/mybatis-spring -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.6</version>
    </dependency>
    <!-- https://mvnrepository.com/artifact/org.springframework/spring-jdbc -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.3.12</version>
    </dependency>

    <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
    <dependency>
        <groupId>org.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <version>1.18.22</version>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>org.junit.jupiter</groupId>
        <artifactId>junit-jupiter-api</artifactId>
        <version>5.8.1</version>
        <scope>compile</scope>
    </dependency>

</dependencies>

然后紧接着在配置文件后面加上:

<build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>true</filtering>
            </resource>
        </resources>
    </build>

然后刷新Maven!!!!

然后连接数据库,(如果找不到database的话)点击idea右上角的搜索然后搜索出来。。。。。。(先去看Mybatis课程

  • 创建com.song.pojo包 来编写一个实体类:
@Data
@AllArgsConstructor
@NoArgsConstructor
public class User {
    private int id;
    private String name;
    private String pwd;
}
  • 在resources资源文件中实现mybatis-config.xml配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <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?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
<!--        <mapper resource="com/song/mapper/UserMapper.xml"/>-->
        <mapper class="com.song.mapper.UserMapper"/>
<!--        <package name="com.song.mapper"/>-->
    </mappers>
</configuration>
  • 创建com.song.mapper 进行UserMapper接口编写
public interface UserMapper {
    List<User> getUserList();
}
  • 对接口UserMapper在同一个包下 编写UserMapper.xml配置文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.song.mapper.UserMapper">
    <select id="getUserList" resultType="com.song.pojo.User">
        select * from mybatis.user
    </select>
</mapper>
  • 创建com.song.utils包,然后在里面编写mybatis-utils文件 (在之前的mybatis视频里有介绍 )
public class MybatisUtils {
    // 提升作用域 提升到全局变量 static只执行一次
    private static SqlSessionFactory sqlSessionFactory ;
   static {
        try {String resource = "mybatis-config.xml";
            InputStream   inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
    //SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
    public static SqlSession getSqlSession(){
       return sqlSessionFactory.openSession();
    }
}public class MybatisUtils {
    // 提升作用域 提升到全局变量 static只执行一次
    private static SqlSessionFactory sqlSessionFactory ;
   static {
        try {String resource = "mybatis-config.xml";
            InputStream   inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //既然有了 SqlSessionFactory,顾名思义,我们可以从中获得 SqlSession 的实例。
    //SqlSession 提供了在数据库执行 SQL 命令所需的所有方法。你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。
    public static SqlSession getSqlSession(){
       return sqlSessionFactory.openSession();
    }
}
  • 编写一个测试类Test

一个快捷创建测试类的方法。。。。mapper接口里面右键。选择go to,再点test,然后create new test

public void testGetUserList() throws IOException {
    //下面这句代码是直接调用了mybatis-utils文件
    SqlSession sqlSession = MybatisUtils.getSqlSession();

    UserMapper mapper = sqlSession.getMapper(UserMapper.class);
    List<User> userList = mapper.getUserList();
    for (User user: userList){
        System.out.println(user);
    }
    sqlSession.close();
}

测试成功。(没内容的看一下是不是自己的数据库就是空的

Mybatis-Spring

要和 Spring 一起使用 MyBatis,需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类。

在上面基础的 MyBatis 用法中,是通过 SqlSessionFactoryBuilder 来创建 SqlSessionFactory 的。在 MyBatis-Spring 中,可使用SqlSessionFactoryBean来创建 SqlSessionFactory。

SqlSessionFactory需要一个 必要的 DataSource(数据源)属性。

一个常用的属性是 configLocation,它用来指定 MyBatis 的 XML 配置文件路径。它在需要修改 MyBatis 的基础配置非常有用。通常,基础配置指的是 < settings> 或 < typeAliases>元素。

这时的MyBatis 配置文件并不需要是一个完整的 ,因为后面Spring帮助它实现了很多,任何环境配置(),数据源()和 MyBatis 的事务管理器()都会被忽略。

SqlSessionTemplate 是 MyBatis-Spring 的核心。作为 SqlSession 的一个实现,这意味着可以使用它无缝代替你代码中已经在使用的 SqlSession,它内置了常用的增删改出以及事务,并且更加安全。

可以使用 SqlSessionFactory 作为构造方法的参数来创建 SqlSessionTemplate 对象。

  • 测试mybatis成功的基础上,在resources的文件夹中创建Spring配置文件Spring-dao.xml 这个配置文件一般写好之后就不需要再改动了。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <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?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
        <property name="username" value="root"/>
        <property name="password" value="123456"/>
    </bean>
    
    <!--        sqlSessionFactory-->
    <!--    在 MyBatis-Spring 中,可使用 SqlSessionFactoryBean来创建 SqlSessionFactory。-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>
        <!--            绑定Mybatis的配置文件-->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <!--            我这里classpath不加*会报错!-->
        <property name="mapperLocations" value="classpath*:com/song/mapper/*.xml"/>
    </bean>
    
    <!--    sqlSessionTemplate就是我们平时使用的sqlSession-->
    <!--    SqlSessionTemplate没有set方法只能构造器注入-->
    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg index="0" ref="sqlSessionFactory"/>
    </bean>
</beans>
  • 然后将Mybatis的配置文件mybatis-config.xml里的代码全部删掉了(狂神建议保留别名(typeAliases)、设置(settings))

  • 在com.song.mapper中创建一个实现类UserMapperImpl.java 私有化sqlSessionTemplate

public class UserMapperImpl implements UserMapper{
    //整合到类里面,到时候直接调用类的方法即可。
    //我们所有的操作在原来都是用sqlSession 现在采用SQL SessionTemplate
    private SqlSessionTemplate sqlSession;
    public void setSqlSession(SqlSessionTemplate sqlSession){
        this.sqlSession=sqlSession;
    }
    @Override
    public List<User> getUserList() {
       UserMapper mapper=sqlSession.getMapper(UserMapper.class);
       return mapper.getUserList();
    }
}
  • 然后再回到Spring-dao文件注册实现类
<!--    所指定的映射器类必须是一个接口,而不是具体的实现类。-->
<bean id="userMapper" class="com.song.mapper.UserMapperImpl">
    <!--        这里的name需要与实现类里的set方法的参数相同-->
    <property name="sqlSession" ref="sqlSession"/>
</bean>
  • 编写测试文件Test
public static void main(String[] args) {
    //其实就是将SqlSession工厂和SqlSession交给了Spring托管
    ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
    UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
    for (User user : userMapper.getUserList()) {
        System.out.println(user);
    }
}

测试成功。

另一种实现方法

mybatis-spring1.2.3版以上的才有这个方法 .

官方文档截图 :

在这里插入图片描述

dao继承Support类 , 直接利用 getSqlSession() 获得 , 然后直接注入SqlSessionFactory . 比起方式1 , 不需要管理SqlSessionTemplate , 而且对事务的支持更加友好 . 可跟踪源码查看

测试:

  • 创建一个UserMapperImpl2.java文件
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {
    public List<User> selectUser() {
        UserMapper mapper = getSqlSession().getMapper(UserMapper.class);
        return mapper.selectUser();
    }
}
  • 注册到spring配置文件中
<bean id="userDao" class="com.song.mapper.UserMapperImpl2">
    <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>
  • 将测试文件中的getbean中userMapper改为userDao运行测试

声明式事务

回顾事务

  • 事务在项目开发过程非常重要,涉及到数据的一致性的问题,不容马虎!

  • 事务管理是企业级应用程序开发中必备技术,用来确保数据的完整性和一致性。

事务就是把一系列的动作当成一个独立的工作单元,这些动作要么全部完成,要么全部不起作用。

事务四个属性ACID

  • 原子性(atomicity)

事务是原子性操作,由一系列动作组成,事务的原子性确保动作要么全部完成,要么完全不起作用

  • 一致性(consistency)

一旦所有事务动作完成,事务就要被提交。数据和资源处于一种满足业务规则的一致性状态中

  • 隔离性(isolation)

可能多个事务会同时处理相同的数据,因此每个事务都应该与其他事务隔离开来,防止数据损坏

  • 持久性(durability)

事务一旦完成,无论系统发生什么错误,结果都不会受到影响。通常情况下,事务的结果被写到持久化存储器中

Spring中的事务管理

两种事务管理:

  • 声明式事务:AOP【一般用这个】

一般情况下比编程式事务好用。
将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理。
将事务管理作为横切关注点,通过aop方法模块化。Spring中通过Spring AOP框架支持声明式事务管理

  • 编程式事务:需要在代码中,进行事务的管理

将事务管理代码嵌到业务方法中来控制事务的提交和回滚
缺点:必须在每个事务操作业务逻辑中包含额外的事务管理代码

搭建测试环境:

  • 更改UserMapper接口
public interface UserMapper {
     List<User> getUserList();
     int addUser(User user);
     int deleteUser(int id);
}
  • 在UserMapper.xml 添加语句
<insert id="addUser" parameterType="com.song.pojo.User" >
    insert into mybatis.user(id,name,pwd) values(#{id},#{name},#{pwd})
</insert>
<delete id="deleteUser" parameterType="int" >
    <!--故意写错-->
    deletes from mybatis.user where id=#{id}
</delete>
  • 在UserMapperImpl2.java文件添加相应语句
@Override
public int addUser(User user) {
    return getSqlSession().getMapper(UserMapper.class).addUser(user);
}
@Override
public int deleteUser(int id) {
    return getSqlSession().getMapper(UserMapper.class).deleteUser(id);
}
  • 更改Test文件
public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("spring-dao.xml");
    UserMapper userMapper = context.getBean("userDao", UserMapper.class);
    User user= new User(7,"小王","1234562");
    userMapper.addUser(user);
    userMapper.deleteUser(7);//这条语句一定不会成功
}
  • 进行测试,检查数据库

在没有开始事务之前,当代码中有错误,尽管报错但是报错之前的数据修改仍然正常进行了

因此我们要求开启事务,若后面报错,则之前的数据要进行回溯。

  • 关于Spring-dao.xml 配置文件的头部:因为后面自动生成tx(事务管理)的头部是错的,最后还得自己改。
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx"
       xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/tx
       http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd">
  • 在Spring-dao配置文件中添加下面的语句
<!--配置声明式事务-->
<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>
        <!--                给所有方法都配置事务 -->
        <!--                propagation="REQUIRED" 是默认的设 即传播特性-->
        <tx:method name="*" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>

<!--配置事务的切入-->
<aop:config>
    <aop:pointcut id="txPointCut" expression="execution(* com.song.mapper.*.*(..))"/>
</aop:config>
  • 进行测试,检查数据库

如果报错之后仍然插入成功就检查一下mysql的控制台下的表的引擎,不是innoDB的话可能不支持事务回滚。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值