Spring入门到入坟 一站式基础及进阶——囊括面试点与初学基础——源码分析——从0到1

发布时间:2022-06-21 发布网站:脚本宝典
脚本宝典收集整理的这篇文章主要介绍了Spring入门到入坟 一站式基础及进阶——囊括面试点与初学基础——源码分析——从0到1脚本宝典觉得挺不错的,现在分享给大家,也给大家做个参考。

📖本篇内容F1a;SPRing入门到入坟 一站式基础及进阶——囊括面试点与初学基础 📑 文章专栏:SSM入门到入坟——一站式基础以及进阶栏目 📆 更新周期:2022年1月9日起~2022年1月14日 🙊个人简介:一只二本院校在读的大三程序猿,本着注重基础,打卡算法,分享技作为个人的经验总结性的博文博主,虽然可能有时会犯懒,但是还是会坚持下去的,如果你很喜欢博文的话,建议看下面一行~(疯狂暗示QwQ) 🌇 点赞 👍 收藏 ⭐留言 📝 一键三连 关爱程序猿,从你我做起

@H_360_14@本文目录
  • Spring 入门到入坟 一站式基础及进阶
    • 写在前面
    • 1、Spring 的基本介绍
      • 1.1、Spring是什么
      • 1.2、Spring的特点有哪些
      • 1.3、Spring的组织架构
      • 1.4、Spring的官方下载地址
      • 1.5、Spring中必须掌握的核心模块
    • 2、Spring 中的IOC
      • 2.1、IOC与DI的区别
      • 2.2、快速使用Spring
      • 2.3、bean中出现的标签以及属性介绍
      • 2.4、Spring中创建对象的方式
        • 2.4.1、通过静态工厂模式来创建对象
        • 2.4.2、动态工厂创建对象
      • 2.5、Spring的生命周期
        • 2.5.1、场景面试
      • 2.6、Spring支持的五种bean作用域
    • 3、Spring DI依赖注入
      • 3.1、set()方法注入
      • 3.2、构造器注入
      • 3.3、使用p命名空间注入
      • 3.4、使用c命名空间注入
      • 3.5、复杂类型注入
      • 3.6、自动注入(程序给属性自动注入 赋值)
    • 4、通过注解实现IOC
      • 4.1、操作步骤
      • 4.2、开发常见使用
        • 4.2.1、场景面试
    • 5、AOP介绍
      • 5.1、AOP的实现原理与机制
        • 5.1.1、JDK动态代理的实现
        • 5.1.2、CGlib实现代理
        • 5.1.3、两种代理模式的区别
    • 6、Spring中使用AOP
      • 6.1、快速上手使用AOP
      • 6.2、获取切入点信息
      • 6.3、特殊前置增强通知->Advisor前置增强
      • 6.4、使用AsPEctJ依赖进行注解开发
    • 7、Spring整合Mybatis
      • 7.1、什么是MyBatis-spring呢?
      • 7.2、快速入门整合
        • 整合的配置文件
      • 7.3、配置改进方式
    • 8、Spring的声明式事务管理
      • 8.1、事务(Transaction)是什么?
      • 8.2、事务的四大特性ACID
      • 8.3、事务中的隔离级别
      • 8.4、Spring中的事务的实现
      • 8.5、使用注解方式添加事务
    • 写在最后

Spring 入门到入坟 一站式基础及进阶

写在前面

哇咔咔,🙊小付又来了哦~继前两天整理完毕的Mybatis入门到入坟后,终于也将这个整理了近一个多星期的Spring入门到入坟的一站式进阶学习更新完了,也学有所获,学有所得,二刷Spring的感悟比之前还要多,更贴近了Spring的源码分析,也会利用自己追踪码来理解Spring的工作流程等等,我也相信这篇文章能带给初学的你、甚至二刷三刷的小伙伴们带来不一样的感受!冲冲冲,耗时5天,加油!!!

1、Spring 的基本介绍

1.1、Spring是什么

Spring是什么?由何而来?

Spring是一个开放源代码的设计底层框架,他解决的是业务逻辑层与其它层之间的耦合度关系,spring是由Rod Johnson在2003年这个获得过悉尼大学音乐教授创建而成的。

Spring 是一个轻量级的控制反转(IOC)面向切面编程(AOP)的容器框架.

1.2、Spring的特点有哪些

Spring的特点有哪些?

  • 方便解耦便于开发
  • 支持AOP(Aspect Oriented Programming)编程
  • 支持声明式事务开发
  • 便于程序测试
  • 非入侵式框架(可以继承其它的框架、而不影响程序正常运行)
  • 便于开发人员的使用

1.3、Spring的组织架构

Spring的组织架构

Spring入门到入坟 一站式基础及进阶——囊括面试点与初学基础——源码分析——从0到1

目前绝大部分企业公司使用的都是spring全家桶一站式开发

Spring全家桶:SpringSpring DataSpring MVCSpring BootSpring Cloud

1.4、Spring的官方下载地址

Spring的官方下载地址

Spring官网:Spring官网

Spring官方下载地址:Spring官方下载地址

1.5、Spring中必须掌握的核心模块

Spring中必须掌握的核心模块

  • spring-core:依赖注入IOC与DI的最基本实现(核心)

  • spring-beans:Bean工厂与bean的装配(Bean的工厂模式)

  • spring-context:spring的context上下文——IoC容器

  • spring-exPression:spring表达式语言

2、Spring 中的IOC

2.1、IOC与DI的区别

IOC与DI的区别

IOC(Inversion of Control):字面翻译控制反转面向对象编程的一种设计原则,可以用来降低计算机各个组件之间的耦合度

DI(Dependency Injection):字面翻译为依赖注入为了实现IOC这种设计原则的一种实现策略,对于我们来说就是创建对象实例过程中,同时这个对象注入它依赖的属性

2.2、快速使用Spring

快速使用Spring

步骤1:通过Maven工程中的pom.XMl文件添加jar包

<!-- Spring的核心工具包-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-core</artifactId>
    <version>5.3.9</version>
</dependency>
<!--在基础IOC功能上提供扩展服务-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context</artifactId>
    <version>5.3.9</version>
</dependency>
<!-- Spring IOC的基础实现,包含访问配置文件、创建和管理bean等 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-beans</artifactId>
    <version>5.3.9</version>
</dependency>
<!-- Spring context的扩展支持,用于MVC方面 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-context-support</artifactId>
    <version>5.3.9</version>
</dependency>
<!-- Spring表达式语言 -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-expression</artifactId>
    <version>5.3.9</version>
</dependency>

或者直接导入spring-webmvc这个包也是一样可以的

<!--springmvc-->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-webmvc</artifactId>
    <version>5.3.9</version>
</dependency>

步骤2:在resources文件夹目录下创建Spring的配置文件applicationContext.xML

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

</beans>

步骤3:在配置文件中装载对象bean

<bean id="对象名" class="类的完整路径">
    <!--ref是引用bean对象--> 
    <property name="属性名" ref="对象的id值"/>
</bean>
<!-- IOC将对象统一管理,统一分配-->
    <bean id="userDAO" class="com.alascanfu.dao.impl.UsersDaoImpl"></bean>
    <bean id="userService" class="com.alascanfu.service.impl.UsersServiceimpl">
        <property name="usersDaoImpl" ref="userDao"/>
    </bean>

步骤4:通过ClassPathXmlApplicationContext来加载配置文件,获得上下文以此来获取对象。

public class test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = (UserService) context.getBean("userServiceImpl");
        userService.getUser();
    }
}

2.3、bean中出现的标签以及属性介绍

bean中出现的标签以及属性介绍

Spring入门到入坟 一站式基础及进阶——囊括面试点与初学基础——源码分析——从0到1

2.4、Spring中创建对象的方式

Spring中创建对象的方式

  • 通过无参构造器
  • 通过有参构造器

当注入属性为对象赋值时,对象类型需要进行引用ref,非对象类型选择value

User.class

package com.alascanfu.pojo;

public class User {
    private String name ;
    
    public User() {
    }
    
    public User(String name) {
        this.name = name;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    @override
    public String toString() {
        return "User{" +
            "name='" + name + ''' +
            '}';
    }
}

applicationContext.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
    <bean id="user1" class="com.alascanfu.pojo.User">

    </bean>

    <bean id="user2" class="com.alascanfu.pojo.User">
        <constructor-arg index="0" value="Alascanfu"/>
    </bean>

    <bean id="user3" class="com.alascanfu.pojo.User">
        <constructor-arg name="name" value="Alascanfu"/>
    </bean>

    <bean id="user4" class="com.alascanfu.pojo.User">
        <constructor-arg type="java.lang.String" value="Alascanfu"/>
    </bean>
</beans>

2.4.1、通过静态工厂模式来创建对象@H_126_1544@
  • 通过静态工厂模式来创建对象

StatiCFactory.java

package com.alascanfu.pojo;

public class StaticFactory {
    public static User createUserByStatic(){
        return new User();
    }
}

Test.java

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        User user5 = (User) context.getBean("user5");
        System.out.println(user5);
    }
}

返回结果:

静态工厂模式创建Bean
User{name='null'}

Process finished wITh exit code 0

2.4.2、动态工厂创建对象

  • 动态工厂创建对象

DynamicFactory.java

package com.alascanfu.pojo;

public class DynamicFactory {
    public User createUserByDynamic(){
        System.out.println("动态工厂模式创建Bean");
        return new User();
    }
}

applicationContext.xml

    <bean id="dynamicFactory" class="com.alascanfu.pojo.DynamicFactory"/>
    <bean id="user6" class="com.alascanfu.pojo.DynamicFactory" factory-method="createUserByDynamic" factory-bean="dynamicFactory"/>

Test.java

public class Test {
    public static void main(String[] args) {
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        User user6 = (User) context.getBean("user6");
        System.out.println(user6);
    }
}

运行结果:

动态工厂模式创建Bean
User{name='null'}

Process finished with exit code 0

2.5、Spring的生命周期

Spring的生命周期

Spring入门到入坟 一站式基础及进阶——囊括面试点与初学基础——源码分析——从0到1

Bean 生命周期的整个执行过程描述如下:

1) 根据配置情况调用 Bean 构造方法或工厂方法实例化 Bean

2) 利用依赖注入完成 Bean 中所有属性值的配置注入。

3) 如果 Bean 实现了 BeanNameAware 接口,则 Spring 调用 Bean 的 setBeanName() 方法传入当前Bean 的 id 值

4) 如果 Bean 实现了 BeanFactoryAware 接口,则 Spring 调用 setBeanFactory() 方法传入当前工厂实例的引用。

5) 如果 Bean 实现了 ApplicationContextAware 接口,则 Spring 调用 setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。

6) 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的预初始化方法postProcessBeforeInitialzation() 对 Bean 进行加工操作,此处非常重要Spring 的 AOP 就是利用它实现的

7) 如果 Bean 实现了 InitializingBean 接口,则 Spring 将调用 afterPropertiesSet() 方法。初始化bean的时候执行,可以针对某个具体的bean进行配置。afterPropertiesSet 必须实现 InitializingBean接口。实现 InitializingBean接口必须实现afterPropertiesSet方法。

8) 如果在配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。

9) 如果 BeanPostProcessor 和 Bean 关联,则 Spring 将调用该接口的初始化方法postProcessAfterInitialization()。此时,Bean 已经可以被应用系统使用了。

10) 如果指定了该 Bean 的作用范围为 scope=“singleton”,则将该 Bean 放入 Spring IoC 的缓存池中,将触发 Spring 对该 Bean 的生命周期管理;如果指定了该 Bean 的作用范围为scope=“prototype”,则将该 Bean 交给调用者,调用者管理该 Bean 的生命周期,Spring 不再管理该Bean。

11) 如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法将 Spring 中的 Bean销毁;如果在配置文件中通过 destory-method 属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。

这里是摘自小王曾是少年的笔记 膜拜大佬!地址链接: Spring基础学习笔记

2.5.1、场景面试

小伙子,你知道在Spring框架中,Bean组件的生命周期是什么样子的么?来聊一聊你的看法。

Spring入门到入坟 一站式基础及进阶——囊括面试点与初学基础——源码分析——从0到1

(小付假装挠了下脑阔~)说道:

bean的生命周期一般分为三个大的部分

一、bean创建过程:

步骤1:调用bean的构造方法创建bean。

步骤2:通过反射调用setter方法进行属性的依赖注入

步骤3:如果实现了BeanNameAware接口的话,则Spring会调用bean的setBeanName()方法传入Bean当前的id值

public interface BeanNameAware extends Aware {
    void setBeanName(String VAR1);
}

步骤4:如果实现了BeanFactoryAware接口的话,Spring会调用setBeanFactory()方法,传入当前工厂实例引用。

public interface BeanFactoryAware extends Aware {
    void setBeanFactory(BeanFactory var1) throws BeansException;
}

步骤5:如果实现了ApplicationContextAware接口,Spring会调用setApplicationContext() 方法传入当前 ApplicationContext 实例的引用。

public interface ApplicationContextAware extends Aware {
	void setApplicationContext(ApplicationContext applicationContext) throws BeansException;

}

步骤6:如果实现了BeanPostProcessor接口,Spring会先调用预初始化方法postProcessBeforeInitialzation()对 Bean 进行加工操作,执行前置处理方法,此处非常重要,Spring 的 AOP 就是利用它实现的

public interface BeanPostProcessor {
    @Nullable
    default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }

    @Nullable
    default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        return bean;
    }
}

步骤7:如果实现了InitializingBean接口,Spring则会调用afterPropertiesSet()方法针对具体的Bean进行配置。

public interface InitializingBean {
    void afterPropertiesSet() throws Exception;
}

步骤8:执行自定义init方法 配置文件中通过 init-method 属性指定了初始化方法,则调用该初始化方法。

步骤9:如果实现了BeanPostProcessor接口,Spring会先调用预初始化方法postProcessAfterInitialzation()对 Bean 进行加工完成操作。

此时,就完成了bean的创建过程

二、bean的使用过程

三、bean的销毁过程

步骤1:如果 Bean 实现了 DisposableBean 接口,则 Spring 会调用 destory() 方法将 Spring 中的 Bean销毁。

步骤2:自定义的destory方法,在配置文件中通过destory-method属性指定了 Bean 的销毁方法,则 Spring 将调用该方法对 Bean 进行销毁。

2.6、Spring支持的五种bean作用域

singleton:单例模式

prototype:原型模式

request:对于每次 HTTP 请求,使用 request 定义的 bean 都将产生一个新实例,即每次 HTTP 请求将会产生不同的 bean 实例。

session:同一个 Session 共享一个 bean 实例。

global-session:同 session 作用域不同的是,所有的Session共享一个Bean实例。

Spring入门到入坟 一站式基础及进阶——囊括面试点与初学基础——源码分析——从0到1

3、Spring DI依赖注入

DI(Dependency Injection)是IOC这种设计思想的具体实现的一种策略。

主要有两种方式为属性注入赋值。

  • 通过调取属性的set()方法进行属性赋值
  • 通过构造器来对属性赋值

初始化一个User类:

public class User {
    private Long id;
    private String name ;
    private Address address;
    
    public User() {
    }
    
    public User(Long id, String name,Address address) {
        this.name = name;
        this.id = id;
        this.address = address;
    }
    
    public Long getId() {
        return id;
    }
    
    public void setId(Long id) {
        this.id = id;
    }
    
    public String getName() {
        return name;
    }
    
    public void setName(String name) {
        this.name = name;
    }
    
    public Address getAddress() {
        return address;
    }
    
    public void setAddress(Address address) {
        this.address = address;
    }
    
    @Override
    public String toString() {
        return "User{" +
            "id=" + id +
            ", name='" + name + ''' +
            ", address=" + address +
            '}';
    }
}

初始化一个引用类Address

public class Address {
    private String province;
    private String city;
    private String road;
    
    public Address() {
    }
    
    public Address(String province, String city, String road) {
        this.province = province;
        this.city = city;
        this.road = road;
    }
    
    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;
    }
    
    public String getRoad() {
        return road;
    }
    
    public void setRoad(String road) {
        this.road = road;
    }
    
    @Override
    public String toString() {
        return "Address{" +
            "province='" + province + ''' +
            ", city='" + city + ''' +
            ", road='" + road + ''' +
            '}';
    }
}

3.1、set()方法注入

基本属性类型值注入

<bean class="com.alascanfu.pojo.Address" id="address">
    <property name="province" value="Si Chuan"/>
    <property name="city" value="Cheng Du"/>
    <property name="road" value="TianFu Road"/>
</bean>

引用属性类型值注入

<bean class="com.alascanfu.pojo.User" id="user2">
    <property name="id" value="201901094106"/>
    <property name="name" value="Alascanfu"/>
    <property name="address" ref="address"/>
</bean>

3.2、构造器注入

通过name属性,按照参数名 赋值

<bean class="com.alascanfu.pojo.User" id="user2">
    <constructor-arg name="id" value="201901094106"/>
    <constructor-arg name="name" value="Alascanfu"/>
    <constructor-arg name="address" ref="address"/>
</bean>

通过index属性,按照参数索引位置 赋值

<bean class="com.alascanfu.pojo.User" id="user2">
    <constructor-arg name="id" value="201901094106" index="0"/>
    <constructor-arg name="name" value="Alascanfu" index="1"/>
    <constructor-arg name="address" ref="address" index="2"/>
</bean>

通过type属性,按照参数属性 赋值

<bean class="com.alascanfu.pojo.User" id="user2">
    <constructor-arg type="java.lang.Long" value="201901094106" index="0"/>
    <constructor-arg type="java.lang.String" value="Alascanfu" index="1"/>
    <constructor-arg type="com.alascanfu.pojo.Address" ref="address" index="2"/>
</bean>

3.3、使用p命名空间注入

使用p命名空间:属性名 完成注入,通过set()方法完成注入

基本类型: p:属性名=“值”

引用类型:p:属性名-ref=“bean的id”

步骤1:导入名称空间到applicationContext中

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

步骤2:通过上述类型进行注入 赋值

基本类型:

<bean class="com.alascanfu.pojo.Address" id="address" p:province="Si Chuan" p:city="Cheng Du" p:road="TianFu Road"/>

引用类型:

<bean class="com.alascanfu.pojo.User" p:id="201901094106" p:name="Alascanfu" p:address-ref="address"/>

3.4、使用c命名空间注入

步骤1:导入名称空间到applicationContext中

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

步骤2:通过上述类型进行注入 赋值

<bean class="com.alascanfu.pojo.User" c:id="201901094106" c:name="Alascanfu" c:address-ref="address" />

3.5、复杂类型注入

数组、列表、集合、map、Properties

等复杂类型注入

<bean id="u1" class="com.alascanfu.pojo.Users"></bean>
     <bean id="t1" class="com.alascanfu.pojo.Teacher">
         <property name="objects">
              <list>
                  <value>小付1</value>
                  <value>123</value>
                  <value>abc</value>
                  <ref bean="u1"></ref>
              </list>
         </property>
        <property name="list">
            <list>
                <value>小付2</value>
                <value>1234</value>
                <value>abcd</value>
                <ref bean="u1"></ref>
            </list>
        </property>
         <property name="set">
             <set>
                 <value>小付3</value>
                 <value>12345</value>
                 <value>abcde</value>
                 <ref bean="u1"></ref>
             </set>
         </property>
         <property name="map">
             <map>
                 <entry key="班长" value="康康"></entry>
                 <entry key="团支书" value="术达"></entry>
                 <entry key="user" value-ref="u1"></entry>
             </map>
         </property>
         <property name="properties">
               <props>
                    <prop key="username">root</prop>
                    <prop key="password">root</prop>
               </props>
         </property>
     </bean>

3.6、自动注入(程序给属性自动注入 赋值)

autowire:

  • no 不自动装配(默认值)

  • byName 属性名=id名 ,调取set方法赋值

  • byType属性的类型和id对象的类型相同,当找到多个同类型的对象时报错,调取set方法赋值

  • constructor 构造方法的参数类型和id对象的类型相同,当没有找到时,报错。调取构造方法赋值

4、通过注解实现IOC

4.1、操作步骤

步骤1:向配置文件中添加context上下文命名空间以及约束配置

<beans xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd">

</beans>

步骤2:配置注解扫描:指定扫描包下所有类中的注解,扫描包时,会扫描所有的子孙包

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

步骤3:在合适的地方添加适合的注解

添加在类上

@Component("对象名")
@Service("person") // service层
@Controller("person") // controller层
@Repository("person") // dao层
@Scope(scopeName="singleton") //单例对象
@Scope(scopeName="prototype") //多例对象

添加在属性上

User.java

@Componet
public class User {
    @Value("201901094106")
    private Long id;
    @Value("Alascanfu")
    private String name ;
    
    @Autowired
    @Qualifier("address")
    private Address address;
}

Address.java

@Component("address")
public class Address {
    @Value("四川")
    private String province;
    @Value("成都")
    private String city;
    @Value("天府新区")
    private String road;
}

Test.java

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

执行结果

User{id=201901094106, name='Alascanfu', address=Address{province='四川', city='成都', road='天府新区'}}

添加在方法上:

@PostConstruct //等价于init-method属性
public void init(){
    System.out.println("自定义初始化方法");
}

@PreDestroy //等价于destroy-method属性
public void destroy(){
    System.out.println("自定义销毁方法");
}

4.2、开发常见使用

开发常见使用

在Dao实现类上添加注解:

@Repository("userDaoImpl")
public class UserDaoImpl implements UserDao{
    public void add() {
        System.out.println("添加数据中...");
    }
}

在Service层实现类添加注解

@Service("userServiceImpl")
public class UserServiceImpl implements UserService{
    @Autowired
    private UserDao userDao;
    
    public void add() {
        userDao.add();
    }
}

注意事项

当使用@Autowired进行注入属性时,默认是byType,如果有另一个dao层的实现类,也配置了@Repository("userDaoImpl2")的话就会报错,此时就要用到@Qualifier

@Autowired  // 默认使用byType
@Qualifier("userDaoImpl")
private UsersDao usersDao;

或者使用@Resource来代替他们两个

@Autowired  // 默认使用byType
@Qualifier("userDaoImpl")
private UsersDao usersDao;

Test.java

public static void main(String[] args) {
    ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
    UsersService userService = (UsersService)context.getBean("userServiceImpl");
    userService.add();
}

添加在类上,非单例模式,利用原型模式创建。

User.java

@Component("user")
@Scope(scopeName = "prototype")
public class User {
    @Value("201901094106")
    private Long id;
    @Value("Alascanfu")
    private String name ;
    
    @Autowired
    @Qualifier("address")
    private Address address;
    
    public User() {
    }
}

Test.java

public class Test {
    @org.junit.Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        User user1 = context.getBean("user", User.class);
        User user2 = context.getBean("user", User.class);
        System.out.println(user1.hashCode() == user2.hashCode());//false
    }
}

使用@Value注解指定属性为默认值

public class User {
    @Value("201901094106")
    private Long id;
    @Value("Alascanfu")
    private String name ;
    
    @Autowired
    @Qualifier("address")
    private Address address;
    
    public User() {
    }
}

4.2.1、场景面试

通常我们在开发的时候是注解与配置文件共同配置,那你能聊聊@Autowired与@Resource的区别么?

相同点:二者均可以写在字段和setter方法上

不同点:

@Auitowired

其是按照类型来装配对象的,默认情况下他要求依赖的对象必须存在,如果允许了null值,可以设置他的required属性为false。如果我们想使用按照名称来装配,就需要结合@Qualifier注解一起使用

@Service
public class UserServiceImpl implements UserService{
    @Autowired
    @Qualifier("userDaoImpl")
    private UserDao userDao;
    
    public void add() {
        userDao.add();
    }
}

@Resource

默认是按照名称来进行自动注入的,有两个重要的属性nametype,Spring会将@Resource 注解的name属性解析为bean的名字,而type属性则解析为bean的类型。

如果使用了name属性,则会使用名称的自动注入策略,而是用了type属性时则会使用按照类型进行自动注入策略,如果什么都不设置其会通过反射来按照名称自动注入

@Service
public class UserServiceImpl implements UserService{
    @Resource(name = "userDaoImpl")
    private UserDao userDao;
    
    public void add() {
        userDao.add();
    }
}

5、AOP介绍

什么是AOP?

AOP(Aspect Oriented Programming) 面向切面编程,在不改变原来程序的基础上可以为代码增加新的功能。应用在权限认证、日志、事务

AOP的作用在于分离系统中的各种关注点,将核心关注点和横切关注点分离开来

Spring入门到入坟 一站式基础及进阶——囊括面试点与初学基础——源码分析——从0到1

5.1、AOP的实现原理与机制

AOP的实现原理与机制

  • JDK的动态代理:针对于实现了接口类产生代理对象。InvocationHandler接口。
  • CGlib的动态代理,针对的是没有实现接口的类产生代理,应用的是底层的字节码增强技术,生成当前类的子类对象,MethodInterceptor接口。

这里用到了面向对象编程的23种设计模式中的——代理模式

如果还不知道代理模式是什么请看小付的专栏:

《(1条消息) Java23种设计模式一栏拿捏_Alascanfu的博客-CSDN博客》

5.1.1、JDK动态代理的实现

ProxyUtils.java

public class ProxyUtil implements InvocationHandler {
    //需要代理的对象
    public Object target;
    //返回代理对象
    public Object getProxy(Object target){
        this.target = target;
        System.out.println("利用动态工厂创建对象,返回实例对象的方法");
        //这里返回调用的是静态类Proxy的newProxyInstance方法
        return Proxy.newProxyInstance(target.getClass().getClassLoader(), target.getClass().getInterfaces(),this);
    }
    //在方法执行的前后进行代理
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        return method.invoke(target,args);
    }
}

这是JDK自带的动态代理方法的通用实现

在调用接口方法的前后都会添加代理类的方法

public class Test {
    @org.junit.Test
    public void test(){
        ProxyUtil proxyUtil = new ProxyUtil();
        UserService userService = (UserService) proxyUtil.getProxy(new UserServiceImpl());
        userService.add();
    }
}

注意事项:

这里获得得到的代理对象不能强转为类实例,只能转换为接口调用方法否则会报错,代理对象无法进行转换。

5.1.2、CGlib实现代理

  • 使用JDK创建代理有一个限制,它只能为接口创建代理实例.这一点可以从Proxy的接口方法

newProxyInstance(ClassLoader loader,Class [] interfaces,InvocarionHandler h)中看的很清楚

  • 第二个入参 interfaces就是需要代理实例实现的接口列表.
  • 对于没有通过接口定义业务方法的类,如何动态创建代理实例呢?JDK动态代理技术显然已经黔驴技穷,CGLib作为一个替代者,填补了这一空缺.
  • CGLib采用底层的字节码技术,可以为一个类创建子类,在子类中采用方法拦截的技术拦截所有父类方法的调用并顺势织入横切逻辑.

如何进行代理呢?

CGlibProxy.java

public class CglibProxy implements MethodInterceptor {
    public Object intercept(Object target, Method method, Object[] args, MethodProxy methodProxy) throws Throwable {
                //参数:Object为由CGLib动态生成的代理类实例,;method为上文中实体类所调用的被代理的方法
        //引用,Object[]为参数值列表,MethodProxy为生成的代理类对方法的代理引用。
	Object obj        = methodProxy.invokeSuper(target,args);
        
        return obj;
    }
}

Test.java

public class Test {
    @org.junit.Test
    public void test(){
        Enhancer enhancer = new Enhancer();
        enhancer.setSuperclass(User.class);
        enhancer.setCallback(new CglibProxy());
        User user = (User) enhancer.create();
        System.out.println(user.toString());
    }
}

Spring同时使用了这两种方式,可以通过配置文件选择代理模式。

<aop:aspectj-autoproxy proxy-target-class="false"/>

默认为false,是调用的JDK的代理模式进行创建的。

当将其改为true时,则是通过CGlib通过字节码技术,代理创建其对象实例。

5.1.3、两种代理模式的区别

1、JDK动态 代理生成的代理类和委托类实现了相同的接口

2、CGlib动态代理中生成了的字节码更加的复杂,生成的代理类是委托类的子类,且不能被final进行修饰

3、JDK动态代理采用的是反射机制调用委托类的方法,CGlib采用类似索引的方式直接调用委托类方法

6、Spring中使用AOP

6.1、快速上手使用AOP

步骤1:添加需要使用AOP相关的jar包

<dependencies>
    <dependency>
        <groupId>aopalliance</groupId>
        <artifactId>aopalliance</artifactId>
        <version>1.0</version>
    </dependency>
    <dependency>
        <groupId>org.aspectj</groupId>
        <artifactId>aspectjweaver</artifactId>
        <version>1.8.13</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aspects</artifactId>
        <version>5.3.9.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-aop</artifactId>
        <version>5.3.9.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>5.3.9.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.9.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>5.3.9.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context-support</artifactId>
        <version>5.3.9.RELEASE</version>
    </dependency>
</dependencies>

步骤2:添加原有的功能方法

步骤3:配置文件中的aop名称空间以及相关约束

<beans
        xmlns:aop="http://www.springframework.org/schema/aop"
       xsi:schemaLocation="
       http://www.springframework.org/schema/aop
       http://www.springframework.org/schema/aop/spring-aop.xsd
">
    <aop:aspectj-autoproxy proxy-target-class="false"/>
    <!-- 采用的是默认的JDK动态代理方式 -->
</beans>

步骤4:创建增强类,对之前的功能方法进行切面,切入通知

AOPconfig.java

@Aspect
public class AOPConfig {
    public void before(){
        System.out.println("前置通知");
    }

    public void afterReturning(){
        System.out.println("后置通知");
    }
    
    public void after(){
        System.out.println("最终通知");
    }
    
    public Object around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("环绕之前");
        Object proceed = point.proceed();
        System.out.println("环绕之后");
        return proceed;
    }
    
    public void afterThrowing(){
        System.out.println("异常通知");
    }

}

配置文件中进行配置通知

	<bean id="aopConfig" class="com.alascanfu.config.AOPConfig"/>

    <aop:config>
        <aop:pointcut id="p" expression="execution(* com.alascanfu.service.UserServiceImpl.*(..))"/>
        <aop:aspect ref="aopConfig">
            <!-- 前置增强 -->
            <aop:after method="after" pointcut-ref="p"/>
            <!-- 最终增强 -->
            <aop:before method="before" pointcut-ref="p"/>
            <!-- 后置增强 -->
            <aop:after-returning method="afterReturning" pointcut-ref="p"/>
            <!-- 异常增强 -->
            <aop:after-throwing method="afterThrowing" pointcut-ref="p"/>
            <!-- 环绕增强 -->
            <aop:around method="around" pointcut-ref="p"/>
        </aop:aspect>
    </aop:config>

第一步:将写好的增加类加入到IOC容器当中,随后配置aop的相关设置

第二步:配置好aop:pointcut作为需要切入功能的位置

第三步:配置将增强类植入目标对象

Test.java

public class Test {
    @org.junit.Test
    public void test(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserService userService = context.getBean("userServiceImpl", UserService.class);
        userService.add();
    }
}
前置通知
环绕之前
添加数据中...
环绕之后
后置通知
最终通知

Process finished with exit code 0

6.2、获取切入点信息

通过向通知的方法中添加JoinPoint参数获取对象的信息:

System.out.println("切入点对象:"+point.getTarget().getClass().getSimpleName());
System.out.println("切入点方法:"+point.getSignature());
System.out.println("切入点的参数:"+point.getArgs()[0]);
前置通知
环绕之前
切入点对象:UserServiceImpl
切入点方法:void com.alascanfu.service.UserService.add()
后置通知
最终通知

6.3、特殊前置增强通知->Advisor前置增强

用于只有前置,而且必须是前置的情况。事务管理中的开启事务。

如何实现?

步骤1:创建特殊的前置增强类,要求其需要实现MethodBeforeAdvice接口。

步骤2:修改配置文件

​ 创建增强类对象

​ 定义增强通知和切入点之间的联系

public class BeforeLOG implements MethodBeforeAdvice {
    public void before(Method method, Object[] args, Object target) throws Throwable {
        System.out.println("MethodBeforeAdvice方法执行前...");
        System.out.println("特殊的前置增强");
        System.out.println("目标方法:" + method.getName());
        System.out.println("参数:" + args);
        System.out.println("对象:" + target);
    }
}
    <bean id="bflog" class="com.alascanfu.log.BeforeLog"/>
    <aop:config>
        <aop:pointcut id="point" expression="execution(* com.alascanfu.service.UserServiceImpl.*(..))"/>
        <aop:advisor advice-ref="bflog" pointcut-ref="point"/>
    </aop:config>

6.4、使用AspectJ依赖进行注解开发

注意:

  • 增强类也需要添加到IOC容器当中才可以进行注解开发
  • 要扫描对应包下的所有注解来进行开发
  • 配置好配置文件
    <context:component-scan base-package="com.alascanfu"/>
    <context:annotation-config/>
    <aop:aspectj-autoproxy proxy-target-class="false"/>
  • 除了启动spring的注解之外 还要启动aspectJ的注解方式

  • 在切面类(增强类)上添加:@Aspect

  • 定义一个任意方法

  • 在其方法上添加@Pointcut作为切入点

@Aspect
public class AOPConfig {
    @Before("execution(* com.alascanfu.service.UserServiceImpl.*(..))")
    public void before(){
        System.out.println("前置通知");
    }

    @AfterReturning("execution(* com.alascanfu.service.UserServiceImpl.*(..))")
    public void afterReturning(){
        System.out.println("后置通知");
    }
    
    @After("execution(* com.alascanfu.service.UserServiceImpl.*(..))")
    public void after(){
        System.out.println("最终通知");
    }
    
    @Around("execution(* com.alascanfu.service.UserServiceImpl.*(..))")
    public Object around(ProceedingJoinPoint point) throws Throwable {
        System.out.println("环绕之前");
        Object proceed = point.proceed();
        System.out.println("环绕之后");
        return proceed;
    }
    
    @AfterThrowing("execution(* com.alascanfu.service.UserServiceImpl.*(..))")
    public void afterThrowing(){
        System.out.println("异常通知");
    }

}

注解方式中注解执行顺序:

  • 没有异常存在的情况下:

    • 环绕开始
    • 前置增强通知
    • 被代理的方法功能执行
    • 环绕结束
    • 最终增强通知
    • 后置增强通知
  • 有异常的情况下:

    • 前置增强开始
    • 被代理的方法功能执行
    • 最终增强
    • 异常增强

Spring入门到入坟 一站式基础及进阶——囊括面试点与初学基础——源码分析——从0到1

AOP的应用场景:事务底层实现,日志,权限的控制,mybatis中SQL绑定,性能的监测等等…

7、Spring整合Mybatis

中文官方文档:

mybatis-spring 中文官方文档

7.1、什么是MyBatis-Spring呢?

MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapperSqlSession注入到 bean中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException。 最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring。

7.2、快速入门整合

步骤1:导入mybatis-spring 的 jar包 与 spring-jdbc 的jar包

    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis-spring</artifactId>
        <version>2.0.6</version>
    </dependency>
    <dependency>
       <groupId>org.springframework</groupId>
        <artifactId>spring-jdbc</artifactId>
        <version>5.3.9</version>
    </dependency>

步骤2:构建测试的用户类以及用户接口

User.java

public class User {
    private int id ;
    private String username ;
    private List<Role> userList ;
    
    public User() {
    }
    
    public User(int id, String username, List<Role> userList) {
        this.id = id;
        this.username = username;
        this.userList = userList;
    }
    
    public int getId() {
        return id;
    }
    
    public void setId(int id) {
        this.id = id;
    }
    
    public String getUsername() {
        return username;
    }
    
    public void setUsername(String username) {
        this.username = username;
    }
    
    public List<Role> getUserList() {
        return userList;
    }
    
    public void setUserList(List<Role> userList) {
        this.userList = userList;
    }
    
    @Override
    public String toString() {
        return "User{" +
            "id=" + id +
            ", username='" + username + ''' +
            ", userList=" + userList +
            '}';
    }
}

UserMapper.java

public interface UserMapper {
    User getUserById(int id);
}

UserMapperImpl.java

@Repository("userMapper")
public class UserMapperImpl implements UserMapper{
    @Autowired
    private SqlSessionFactory sqlSessionFactory;
    
    public User getUserById(int id) {
        SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory);
        List<User> user = sqlSessionTemplate.selectList("com.alascanfu.pojo.mapper.UserMapper.getUserById",id);
        return user.get(0) == null ? null : user.get(0);
    }
}

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.alascanfu.pojo.mapper.UserMapper">
    <cache readOnly="true" eviction="FIFO" size="512" flushInterval="60000"/>
    <resultMap id="userById" type="User">
        <id column="id" property="id"/>
        <result property="username" column="username"/>
        <collection property="userList" ofType="Role">
            <result property="rid" column="id"/>
            <result property="name" column="name"/>
            <result property="nameZh" column="nameZh"/>
        </collection>
    </resultMap>
    <select id="getUserById" resultMap="userById">
        SELECT u.id,u.username,r.name ,r.nameZh,r.id From user u ,role r , user_role ur where r.id = ur.rid and ur.uid = u.id and u.id = #{id}
    </select>

</mapper>

数据库初始数据

MySQL> select * from user;
+----+-----------+
| id | username  |
+----+-----------+
|  1 | Alascanfu |
|  2 | HHXF      |
+----+-----------+
2 rows in set (0.00 sec)

mysql> select * from role;
+----+-------+--------------------+
| id | name  | nameZh             |
+----+-------+--------------------+
|  1 | admin | 系统管理员         |
|  2 | DBA   | 数据库管理员       |
|  3 | user  | 普通用户           |
+----+-------+--------------------+
3 rows in set (0.00 sec)

mysql> select * from user_role;
+----+-----+-----+
| id | uid | rid |
+----+-----+-----+
|  1 |   1 |   3 |
|  2 |   1 |   1 |
|  3 |   2 |   1 |
|  4 |   2 |   2 |
|  5 |   1 |   2 |
+----+-----+-----+
5 rows in set (0.00 sec)

步骤3:配置初始Mybatis必要设置环境到Spring的IOC容器当中

在之前学过Mybatis的小伙伴们都知道Mybatis的工作流程以及原理,这里就不再赘述了。

如有疑问可以去看看博主之前写的文章:

Mybatis入门到入坟 一站式基础及进阶——囊括面试点与初学基础、框架分析——从0到1 不会私我 我手把手教你

这里主要说一下

  • 当我们要利用Mybatis来对数据库进行增删改查的操作时,我们需要通过SqlSession这个会话连接来完成。
  • SqlSession是由SqlSessionFactory这个工厂模式创建的,但是这并非是线程安全的,同一个工厂可以在同一时间创建多个SqlSession进行连接,操作数据库,所以这里也要考虑到线程安全的问题。
  • 再者就是SqlSessionFactory这个工厂是由实现了23种设计模式中的建造者模式SqlSesssionFactoryBuilder这个类来build()出来的。

所以就需要将必要的环境配置到IOC容器当中

比如说:一个 SqlSessionFactory 和至少一个数据映射器类(Mapper)。

<!-- 将SqlSessionFactory注入到IOC容器当中 -->
	<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

    <!-- 配置数据源 -->
        <property name="dataSource" ref="dataSource"/>
	<!-- 配置mybatis配置文件路径 -->
        <property name="configLocation" value="classpath:mybatis-config.xml"/>
    <!-- 配置映射路径文件 -->
        <property name="mapperLocations" value="classpath:com/alascanfu/pojo/mapper/*.xml"/>
    </bean>

将SqlSession一并注入到IOC容器当中

注意:因为SqlSessionTemplate这个类没有set()方法所以只能通过构造器来进行注入了

<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
    <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>

因为此时的这个配置文件是属于spring-mybatis的一个整合的spring的配置文件可以将其拖出来单独配置然后再导入总的spring配置文件当中

  • 此时的mybatis-config.xml文件中无需要进行额外的配置,博主比较喜欢将setting与typealiases写在当中 ,其余的一律交给spring-mybatis.xml的配置文件中处理。

整合的配置文件

所以最后的配置文件如下

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>

    <settings>
        <setting name="logImpl" value="LOG4J"/>
    </settings>

    <typeAliases>
        <package name="com.alascanfu.pojo"/>
    </typeAliases>

</configuration>

spring-mybatis

<?xml version="1.0" encoding="UTF-8"?>
<beans
        xmlns="http://www.springframework.org/schema/beans"
        xmlns:context="http://www.springframework.org/schema/context"
        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
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
">

    <!-- 配置数据源到IOC容器当中 调用的是spring-jdbc包下的DriverManagerDataSource进行的配置 -->
    <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="username" value="root"/>
        <property name="password" value="fujiawei2013"/>
        <property name="driverclassname" value="com.mysql.cj.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/test?characterEncoding=utf-8"/>
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">

        <property name="dataSource" ref="dataSource"/>

        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="mapperLocations" value="classpath:com/alascanfu/pojo/mapper/*.xml"/>
    </bean>


    <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
        <constructor-arg name="sqlSessionFactory" ref="sqlSessionFactory"/>
    </bean>

</beans>

步骤4:进行测试

public class TestForSpringAndMyBatis {
    @Test
    public void testGetUserById(){
        ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
        UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
        System.out.println(userMapper.getUserById(1).toString());
    }
}

执行结果

User{id=1, username='Alascanfu', userList=[Role{name='user', nameZh='普通用户'}, Role{name='admin', nameZh='系统管理员'}, Role{name='DBA', nameZh='数据库管理员'}]}

Process finished with exit code 0

7.3、配置改进方式

首先我们还是要知其原理才能做出改进方案

Spring整合Mybatis最重要的是什么? 将Mybatis的必要配置注入到IOC容器当中,也就是我们常用的SqlSessionFactory那这一步就是必不可少的,所以这一处就不能进行改进简化了。

之后你会发现我们每次使用Mybatis查询数据库时都要写一个Mapper接口的实现类,让其中调用SqlSessionTemplate去进行操作数据库,这无疑增加了代码量的同时,造成了内存冗余

解决方案

正因为出现了上述的问题,为了避免手动编写SqlSessionTemplate操作数据库的Mapper实现类,spring-mybatis为我们提供了一个叫做MapperScannerConfigurer这个类。

MapperScannerConfigurer:它 将 会 查 找类 路 径 下 的 映 射 器自 动 将 它 们 创 建 成 MapperFactoryBean没 有 必 要 去 指 定 SqlSessionFactory 或 SqlSessionTemplate, 因 为 MapperScannerConfigurer 将会创建 MapperFactoryBean,之后自动装配

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="com.alascanfu.pojo.mapper"/>
</bean>

那么为什么mapper接口可以直接调用,而无需实现类呢?

MapperFactoryBean

因为代替手工使用 SqlSessionDaoSupportSqlSessionTemplate编写数据访问对象 (DAO)的代码,MyBatis-Spring 提供了一个动态代理的实现:MapperFactoryBean。这个类 可以直接注入数据映射器接口到你的 service 层 bean 中。当使用映射器时,你仅仅如调 用你的 DAO 一样调用它们就可以了,但是你不需要编写任何 DAO 实现的代码,因为 MyBatis-Spring 将会为你创建代理

8、Spring的声明式事务管理

8.1、事务(Transaction)是什么?

事务(Transaction)是什么?

学过MySQL的同学应该知道在数据库服务器中也有事务管理,比如操作数据库开启事务start transaction、提交事务commit、回滚rollback等等操作都是事务管理的方式之一。

事务控制的工作流程一般分为如下几步:

  • 开启事务
  • 执行增删改查等操作数据库信息的操作
  • 提交事务|回滚

举个🌰:

如果小付想通过网上购物去购买商品时,其主要步骤操作如下:

1、首先服务器会更新库存信息,查看是否还有库存完成交易

2、然后获取小付同志的交易订单信息

3、生成订单信息保存到数据库

4、更新用户的已购买信息等等

5、上述都顺利的话提交事务,等待下一次订单。

8.2、事务的四大特性ACID

  • A(atomicity)原子性:对于事务数据,要么全部执行,要么全部不执行。
  • C(consistency)一致性:事务数据操作完成后,所有关联的数据库当中,数据保持一致性,对所有事务的修改,保证所有数据的完整性。
  • I(isolation)隔离性:事务执行过程中被能被其他事务的修改所影响。
  • D(durability)持久性:事务一经提交,事务操作的数据就将永久保存在数据库当中,一经提交,事务回滚也不能撤销修改。

8.3、事务中的隔离级别

MySQL数据库中定义的四种隔离级别:

  • 读未提交:read uncommitted
  • 读已提交:read committed
  • 可重复读:repeatable read
  • 串行化:serializable

8.4、Spring中的事务的实现

我们必须先了解一下Spring中的事务定义

TransactionDefinition.java 源码分析

public interface TransactionDefinition {
    int PROPAGATION_REQUIRED = 0; //事务的传播行为Required 支持事务 不存在 就新建
    int PROPAGATION_SUPPORTS = 1;//事务的传播行为Support支持事务 不存在 就不用
    int PROPAGATION_MANDATORY = 2;//事务的传播行为MANDATORY 支持事务 不存在 就抛错
    int PROPAGATION_REQUIRES_NEW = 3;//如果事物存在 挂起当前 新建事务
    int PROPAGATION_NOT_SUPPORTED = 4;//不使用事务运行 如果存在事务 挂起事务执行
    int PROPAGATION_NEVER = 5;// 不使用事务运行 如果存在事务 抛错
    int PROPAGATION_NESTED = 6;// 事务存在就嵌套使用
    int ISOLATION_DEFAULT = -1; //默认的隔离级别
    int ISOLATION_READ_UNCOMMITTED = 1;
    int ISOLATION_READ_COMMITTED = 2;
    int ISOLATION_REPEATABLE_READ = 4;
    int ISOLATION_SERIALIZABLE = 8;
    int TIMEOUT_DEFAULT = -1; //默认超时时间 默认不超时

    //获取当前事务传播方式
    default int getPropagationBehavior() {
        return 0;
    }
	//获取当前事务的隔离级别
    default int getIsolationLevel() {
        return -1;
    }
	//获取超时时间
    default int getTimeout() {
        return -1;
    }
	//是否只读
    default boolean isReadOnly() {
        return false;
    }

    @Nullable
    default String getName() {
        return null;
    }

}

了解Spring中的事务定义后就来进行配置

步骤1:在applicationContext.xml文件中配置命名空间以及约束条件

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

步骤2:在Spring-Mybatis.xml文件中配置事务管理器

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <constructor-arg name="dataSource" ref="dataSource"/>
    </bean>

这里调用的是DataSourceTransactionManager用于操作对JDBC的数据库进行的事务管理。

步骤3:配置事务通知(通过AOP方式)也被称之为声明式事务

<!-- 事务管理器注入到IOC容器当中 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <constructor-arg name="dataSource" ref="dataSource"/>
</bean>
<!-- 声明式事务 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <!-- propagation:事务传播行为 -->
        <!-- rollback-for:将被触发进行回滚的 Exception(s) -->
        <!-- read-only:只读 -->
        <!-- isolation:隔离级别 -->
        <tx:method name="getUserById" propagation="REQUIRED"/>
    </tx:attributes>
</tx:advice>
<!-- 利用AOP完成声明式事务 声明切入点 以及切入时事务通知 -->
<aop:config>
    <aop:pointcut id="txPointCut" expression="execution(* com.alascanfu.*.*(..))"/>
    <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/></aop:config>

上述的tx:method中的属性都是在Spring中的事务定义哦~

<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.8.M1</version>
</dependency>

最后整合的spring-mybatis.xml

<?xml version="1.0" encoding="UTF-8"?>
<beans
        xmlns="http://www.springframework.org/schema/beans"
        xmlns:aop="http://www.springframework.org/schema/aop"
        xmlns:context="http://www.springframework.org/schema/context"
        xmlns:tx="http://www.springframework.org/schema/tx"
        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
        http://www.springframework.org/schema/context
        http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/aop
        http://www.springframework.org/schema/aop/spring-aop.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="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
        <property name="driverClassName" value="${driverClass}"/>
        <property name="url" value="${url}"/>
        <property name="username" value="root"/>
        <property name="password" value="${password}"/>
    </bean>

    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <property name="dataSource" ref="dataSource"/>

        <property name="configLocation" value="classpath:mybatis-config.xml"/>
        <property name="mapperLocations" value="classpath:com/alascanfu/pojo/mapper/*.xml"/>
    </bean>


    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <property name="basePackage" value="com.alascanfu.pojo.mapper"/>
    </bean>

    <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <constructor-arg name="dataSource" ref="dataSource"/>
    </bean>
    
    <tx:advice id="txAdvice" transaction-manager="transactionManager">
        <tx:attributes>
            <tx:method name="getUserById" propagation="REQUIRED" />
        </tx:attributes>
    </tx:advice>
    
    <aop:config>
        <aop:pointcut id="txPointCut" expression="execution(* com.alascanfu.service.*.*(..))"/>
        <aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
    </aop:config>
</beans>

8.5、使用注解方式添加事务

步骤1:配置xml文件开启事务注解

<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <constructor-arg name="dataSource" ref="dataSource"/>
</bean>

<tx:annotation-driven transaction-manager="transactionManager"/>

步骤2:将@Transactional注解添加到服务层动态代理方式面向切面插入通知

@Transactional //对业务类进行事务增强的标注
@Service("userService")
public class userServiceImpl implements UserService {}

步骤3:对需要的事务属性进行配置

@Transactional 注解源码

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(Retentionpolicy.RUNTIME)
@Inherited
@Documented
public @interface Transactional {
    @AliasFor("transactionManager")
    String value() default "";

    @AliasFor("value")
    String transactionManager() default "";

    String[] label() default {};

    Propagation propagation() default Propagation.REQUIRED;

    Isolation isolation() default Isolation.DEFAULT;

    int timeout() default -1;

    String timeoutString() default "";

    boolean readOnly() default false;

    Class<? extends Throwable>[] rollbackFor() default {};

    String[] rollbackForClassName() default {};

    Class<? extends Throwable>[] noRollbackFor() default {};

    String[] noRollbackForClassName() default {};
}

还是和Spring事务定义有关 可以逐一进行配置

@Transacation可以标注在哪里呢?

@Transactional注解可以直接用于接口定义和接口方法,类定义和类的public方法上.但Spring建议在业务实现类(Service层)上使用@Transactional注解,当然也可以添加到业务接口上,但是这样会留下一些容易被忽视的隐患,因为注解不能被继承,所以业务接口中标注的@Transactional注解不会被业务类实现继承.

写在最后

哇咔咔~终于肝掉了SSM框架中的Spring框架了

这是小付的二刷框架的心得笔记 与 学习经历

本文全文 37000 字

请细心 放心食用

从开始整理复习到现在五天左右的时间

多注重于实践操作 绝对没有坏处

自己没事多整合几遍 就好了

加油嗷~ 兄弟们

最后

每天进步点 每天收获点

愿诸君 事业有成 学有所获

如果觉得不错 别忘啦一键三连哦~

脚本宝典总结

以上是脚本宝典为你收集整理的Spring入门到入坟 一站式基础及进阶——囊括面试点与初学基础——源码分析——从0到1全部内容,希望文章能够帮你解决Spring入门到入坟 一站式基础及进阶——囊括面试点与初学基础——源码分析——从0到1所遇到的问题。

如果觉得脚本宝典网站内容还不错,欢迎将脚本宝典推荐好友。

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
如您有任何意见或建议可联系处理。小编QQ:384754419,请注明来意。