Java框架(Spring)学习笔记

本文涉及的产品
云数据库 RDS MySQL Serverless,0.5-2RCU 50GB
简介: Spring

typora-root-url: ..\media

Spring

一.简介

1.是什么

业务逻辑组件框架

程序员的春天

Spring官网

Spring-API文档

  • SSH

    • Spring Struts1 Hibernate
  • SSH

    • Spring Struts2 Hibernate
  • SSH

    • Spring SpringMVC Hibernate
  • SSM

    • Spring SpringMVC MyBatis

Test:spring的单元测试模块

Core Container:核心容器(IOC),黑色代表这部分功能有哪些jar包,要使用这部分的完整功能,要导入这些jar包。

  • spring-context-5.2.5.RELEASE.jar
  • spring-beans-5.2.5.RELEASE.jar
  • spring-core-5.2.5.RELEASE.jar
  • spring-expression-5.2.5.RELEASE.jar

AOP+Aspects:面向切面编程模块

DataAccess 数据访问

  • spring-jdbc-5.2.5.RELEASE
  • spring-orm(object-relation-mapping)-5.2.5.RELEASE.
  • Transaction spring-tx-5.2.5.RELEASE 事务

WEB

  • spring-websocket 新技术
  • spring-web-5.2.5.RELEASE.jar 和原生的web相关(servlet)
  • spring-webmvc-5.2.5.RELEASE.jar 开发web项目的(mvc)
  • spring-webmvc-portlet-5.2.5.RELEASE.jar 开发web应用的组件集成

2.主要内容

  • IOC

    • Inversion of Control
    • 控制反转
    • 使得组件的关系松散
  • AOP

    • Aspect Orientied Programming
    • 面向切面编程
    • 使得应用易于扩展
  • 通用支持

    • 整合其他框架

二.IOC

1.简介

Inversion of Control

控制反转

凡是你所需要的对象并不是由自己所准备的

而是由其他组件控制你所获取的对象的时候

简单来讲:我们所需要进行的操作是受到其他组件控制的

这种情况就称之为控制反转

注重的是结果

2.DI

Dependency injection

依赖注入

凡是你所需要使用的对象的属性的值不是由自己所准备的

而是由其他组件控制你的属性的值的时候

简单来讲:我们所需要操作的过程是收到其他组件所控制的

这种情况称之为依赖注入

注重的是过程

3.IDEA中使用Spring

第一步:导包(加入依赖),

第二步:配置

第三步:写测试方法

初始情况下,IDEA中无法自动创建Spring的配置文件

但是当IDEA中对应的工程导入了Spring的基本依赖的时候

此时IDEA将会提供自动创建Spring配置文件的方式

导入Spring基本依赖

<properties>
    <spring.version>5.2.5.RELEASE</spring.version>
</properties>

<dependencies>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-core</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-beans</artifactId>
        <version>${spring.version}</version>
    </dependency>
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>${spring.version}</version>
    </dependency>
       <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-expression</artifactId>
      <version>${spring.version}</version>
    </dependency>

</dependencies>

4.Spring配置文件

<bean id="userDao" class="dao.impl.UserDaoImpl"></bean>

<!--
   id属性:当前bean的key
   class属性:当前bean所对应的Java类的包名.类名
   标签体:配置当前bean的属性的值
-->
<bean id="userService" class="service.impl.UserServiceImpl">
    <!--
        property标签:配置某一个属性,一个property标签对应一个属性
        name属性:当前的属性名是谁
        value属性:为当前的属性赋予一个简单值
        ref属性:其他bean的引用
            表示当前的属性的值是另一个由Spring所管理的bean
            此时ref属性的值对应的是所管理的bean的id属性值
    -->
    <property name="userDao" ref="userDao"/>
</bean>

5.加载Spring容器

  • BeanFactory

    • Spring容器核心
    • 用于解析Spring容器
    • 读取容器中的内容
  • ApplicationContext

    • BeanFactory的子接口
    • 简化了解析的操作
    • 使用ClassPathXmlApplicationContext实现类

      • 其参数可以是一个字符串
      • 也可以是一个字符串数组
      • 也可以是一个可变长字符串
      • 还支持通配符
public static void main(String[] args) {

    ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext*.xml");
    UserService userService = (UserService) ac.getBean("userService");
    userService.login();

}

几个注意点:

  1. ApplicationContext:ioc容器的接口
  2. 给容器中注册了一个组件,我们也从容器中按照id拿到了这个组件。组建的创建工作是容器完成的
  3. 容器中对象的创建在容器创建完成的时候就已经创建好了
  4. 同一个组件在容器中是单实例的
  5. 容器中如果没有组件,获取组件会报异常
  6. ioc容器在创建对象的时候会用set方法为javabean的属性赋值
  7. javaBean的属性名是由getter/setter方法决定的,不要随意更改。getter/setter都自动生成。

6.装配

6-1 简单值装配

简单值:基本数据类型、String、包装类型、Class、Resource

通过value属性或者value子标签实现简单值装配

<property name="name" value="张三"/>
<property name="age" value="18"/>
<property name="gender" value="男"/>

<!-- 简单值装配方式一 -->
<bean id="someBean" class="ioc01.SomeBean">
    <!-- value属性:为当前指定的属性赋予一个简单值 -->
    <property name="id" value="1"/>
    <property name="name" value="admin"/>
    <property name="salary" value="1000.0"/>
</bean>

<!-- 简单值装配方式二 -->
<bean id="someBean2" class="ioc01.SomeBean">
    <!-- 可以使用value子标签进行简单值装配,效果与value属性一致 -->
    <property name="id">
        <value>2</value>
    </property>
    <property name="name">
        <value>alice</value>
    </property>
    <property name="salary">
        <value>2000.0</value>
    </property>
</bean>

6-2 其他bean的引用

当当前的bean中的属性的值是另一个已经存在的bean的实例的时候

可以使用ref属性或者ref标签进行其他bean的引用

通过ref属性或者ref子标签实现其他

<!-- 其他bean的引用 -->
<bean id="otherBean" class="ioc02.OtherBean">
    <property name="id" value="1"/>
    <property name="name" value="admin"/>
</bean>

<!-- 方式一 -->
<bean id="someBean" class="ioc02.SomeBean">
    <!--
        ref属性:指向当前容器中已经存在的另一个bean的id属性值
    -->
    <property name="otherBean" ref="otherBean"/>
</bean>

<!-- 方式二 -->
<bean id="someBean2" class="ioc02.SomeBean">
    <!--
        ref子标签:指定当前属性所引用的bean
        bean属性:指向当前容器中已经存在的另一个bean的id属性值
    -->
    <property name="otherBean">
        <ref bean="otherBean"></ref>
    </property>
</bean>

7.实例化

实例化-->DI-->初始化-->使用-->销毁

7-1 实例化的途径

无参构造函数实例化对象
<bean id="someBean" class="ioc04.SomeBean"></bean>
有参构造函数实例化对象
  • 通过constructor-arg标签来实现有参构造函数的配置
  • 一个标签对应构造函数中的一个参数
  • index属性:指定当前参数的索引位置

    • 索引从0开始
    • 如果不设置该属性,默认按照参数顺序进行执行
  • type属性:指定当前参数的类型

    • 该属性一般可以不配置,Spring自动检测参数的类型
<bean id="someBean2" class="ioc04.SomeBean">
    <constructor-arg index="1" type="java.lang.String">
        <value>admin</value>
    </constructor-arg>
    <constructor-arg index="0" type="java.lang.Integer">
        <value>1</value>
    </constructor-arg>
</bean>
静态工厂实例化对象

工厂类.静态方法()

实例工厂对象.方法()

静态工厂:工厂本身不用创建对象。通过工厂类的静态方法调用。

实例工厂:工厂本身需要创建对象。

  • 静态工厂创建对象的时候,class指向的是当前对应的工厂类的class
  • 但是如果仅仅只是通过class指向工厂,根本无法获取到我们想要的bean
  • 该bean是根据工厂中对应的方法获取的
  • 因此需要通过factory-method进行指定所调用的静态方法名
<!--    factory-method:返回的不是class配置的类型,而是通过工厂方法返回的类型-->
    <bean id="person" class="com.hy.entity.PersonFactory"
        factory-method="getPersonStatic">
    </bean>
实例工厂实例化对象
  • 该bean是通过工厂bean中的方法获取的
  • 因此该bean需要调用指定的工厂bean中的方法
  • 在配置中需要指定所使用的工厂bean与工厂方法
  • factory-bean

    • 表示当前所调用的工厂bean是谁
    • 其值是当前容器所管理的工厂bean的id值
  • factory-method

    • 生产当前bean的工厂bean所使用的方法名
    • 此处为实例方法
<!--配置工厂对象-->  
<bean id="factory" class="com.hy.entity.PersonFactory2"></bean>

<!--    factory-bean:工厂bean,填写工厂类的id  factory-method:使用哪个方法-->
    <bean id="p1" factory-bean="factory" factory-method="getPerson"></bean>

7-2 初始化销毁方法

实例化===》属性注入===》aware接口的方法执行===》init-method执行了

  • init-method

    • 将bean中的指定的方法设置为初始化方法
    • 当方法被设置为初始化方法之后,其执行时可以获取到DI的值
    • 该方法将会在DI之后,使用之前进行自动执行
  • destroy-method

    • 将bean中指定的方法设置为销毁方法
    • 将方法设置为销毁方法之后,Spring可以主动进行销毁
    • 此时的销毁是强制执行的,该方法是由ClassPathXmlApplicationContext实现的
    • ApplicationContext接口中没有提供销毁方法
    • 因此,必须直接定义实现类ClassPathXmlApplicationContext
    • 提供了两种销毁方法

      • destroy()

        • 调用方法的时候立刻销毁
        • 不关心当前的bean是否已经使用完毕
        • 因此,调用方法之后,如果仍然使用容器中的bean会报错
      • registerShutdownHook()

        • 调用方法的时候不会立刻销毁
        • 当虚拟机彻底停止之后才会进行销毁
        • 因此,调用方法之后,仍然可以使用容器中的bean
<bean id="someBean" class="ioc07.SomeBean" init-method="a" destroy-method="b">
    <property name="name" value="admin"/>
</bean>
public class Test {

    public static void main(String[] args) {
//        ApplicationContext ac = new ClassPathXmlApplicationContext("ioc07/spring.xml");
//        SomeBean someBean = (SomeBean) ac.getBean("someBean");
//        System.out.println(someBean);
//        someBean.a();

        // 执行销毁方法
        // 在java中,我们只能对销毁方法做建议
        // 无法强制其执行
//        Runtime.getRuntime().gc();

        ClassPathXmlApplicationContext ac = new ClassPathXmlApplicationContext("ioc07/spring.xml");

        // 销毁方式一
        ac.destroy();
        // 销毁方式二
//        ac.registerShutdownHook();
//        System.out.println("Test.main");
//        SomeBean someBean = (SomeBean) ac.getBean("someBean");
//        System.out.println(someBean);
        while (true){

        }
    }
}

7-3 组件作用域

默认情况下,IOC容器中所有的bean都是单例的

有些时候,我们需要多个不同的实例

可以通过scope属性更改组件作用域

使得bean不再是单例的

  • scope属性:组件作用域,值有两种

    • prototype:每一次调用都会生成一个新的实例
    • singleton:默认值,单例
<bean id="someBean" class="ioc09.SomeBean" scope="prototype"></bean>
<bean id="someBean2" class="ioc09.SomeBean"></bean>

单实例的bean在容器启动完成之前就已经创建好对象,保存在容器中。任何时候获取都是之前创建好的实例。

多实例情况下:容器启动不会创建多实例bean。在获取的时候创建。

8.aware相关接口

将当前的bean实现ApplicationContextAware接口

重写其中的setApplicationContext方法

将其作为一个注入操作

其容器的实例是由Spring帮我们传递的

传递到对应的setApplicationContext方法的参数中

将该实例传递到当前的全局变量中,则当前bean中可以获取到当前容器的实例

public class SomeBean implements ApplicationContextAware {
    private ApplicationContext ac;
    @Override
    public void setApplicationContext(ApplicationContext ac) throws BeansException {
        this.ac = ac;
    }
    public void doSome(){
//        ApplicationContext ac = new ClassPathXmlApplicationContext("ioc10/spring.xml");
        System.out.println("doSome-ac:"+ac);
    }
}

10.Resource

Resource属于简单值装配

j2ee的规范

11.FactoryBean

11-1 简介

FactoryBean是Spring规定的一个接口,只要是这个接口的实现类,Spring都认为是一个工厂。

该bean本质上是一个工厂

该bean并不是用于创建自己的

而是为了创建另一个bean

在某些时候,某些bean的实例化过程比较麻烦

但是对于使用者而言,只关心最终生成的bean

需要的仅仅只是最终的bean

其过程使用者并不关心

11-2 开发步骤

  • 创建一个Java类

    • 该类属于FactoryBean,用于生产一个对应的bean
    • 该类要求实现FactoryBean接口
    • 重写其中的三个方法

      • getObject():生产bean的过程
      • getObjectType():生产的bean的类型
      • isSingleton():生产的bean是否是单例的
public class DateFactoryBean implements FactoryBean {

    @Override
    public Object getObject() throws Exception {
        Calendar c = Calendar.getInstance();
        Date date = c.getTime();
        return date;
    }

    @Override
    public Class<?> getObjectType() {
        return Date.class;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}
  • 配置FactoryBean

    • 在IOC容器中配置你所需要的的FactoryBean
    • 该FactoryBean是用于产生某个具体的bean的
    • 因此,配置的FactroyBean就相当于最终所需要的的bean
    • 只是其class指向的是当前的FactoryBean
<bean id="date" class="ioc14.DateFactoryBean"></bean>

12.后处理bean

12-1 简介

对当前容器中的所有的bean做一个后处理

在初始化之前或者之后做一些额外的处理

实例化-->DI-->postProcessBeforeInitialization-->初始化

-->postProcessAfterInitialization-->使用-->销毁

后处理bean并不是针对某一个bean进行处理

而是针对整个IOC容器

后处理bean是对整个IOC容器中所有的bean都生效

12-2 开发步骤

  • 创建一个Java类

    • 该类实现BeanPostProcessor接口
    • 根据Spring版本不同,处理也不同

      • Spring5之前,必须重写接口中的两个方法
      • Spring5之后,可以选择性的重写某个方法
public class SomeBeanPostProcessor implements BeanPostProcessor {

    /**
     * 在初始化之前做后处理
     * @param bean  当前需要处理的bean
     * @param id    当前需要处理的bean的id
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessBeforeInitialization(Object bean, String id) throws BeansException {
        // 判断当前正在处理的bean是谁
        // 对SomeBean的name做大写处理
        if(bean instanceof SomeBean){
            SomeBean someBean = (SomeBean) bean;
            someBean.setName(someBean.getName().toUpperCase());
//            return someBean;
        }
        // 对OtherBean的name做小写处理
        if("otherBean".equals(id)){
            OtherBean otherBean = (OtherBean) bean;
            otherBean.setName(otherBean.getName().toLowerCase());
//            return otherBean;
        }
        return bean;
    }

    /**
     * 在初始化之后做后处理
     * @param bean
     * @param id
     * @return
     * @throws BeansException
     */
    @Override
    public Object postProcessAfterInitialization(Object bean, String id) throws BeansException {
        return bean;
    }
}
  • 配置后处理bean

    • 由于后处理bean并不是针对某一个bean,而是为所有的bean服务
    • 因此配置的后处理bean不需要id属性
    • 只需要指定谁是后处理bean即可
<bean id="someBean" class="ioc15.SomeBean">
    <property name="id" value="1"/>
    <property name="name" value="admin"/>
</bean>

<bean id="otherBean" class="ioc15.OtherBean">
    <property name="name" value="Jack"/>
</bean>

<!-- 配置后处理bean -->
<bean class="ioc15.SomeBeanPostProcessor"/>

13.访问properties文件

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://127.0.0.1:3306/mysql?useUnicode=true&characterEncoding=utf-8
username=gxh
password=123
public class SomeBean {
    private String driver;
    private String url;
    private String username;
    private String password;
<!-- 访问properties文件 -->
<!--
    方式一:使用Spring提供的后处理bean进行访问
        该方式已过时
-->
<!--<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">-->
    <!--<property name="location" value="classpath:ioc17/dataSource.properties"/>-->
<!--</bean>-->

<!-- 方式二:使用context命名空间提供的方法 -->
<context:property-placeholder location="classpath:ioc17/dataSource.properties"/>

<bean id="someBean" class="ioc17.SomeBean">
    <property name="driver" value="${driver}"/>
    <property name="url" value="${url}"/>
    <property name="username" value="${username}"/>
    <property name="password" value="${password}"/>
</bean>

数据库作为连接池是单例的是最合适的,所以可以让spring管理连接池。

14 bean的生命周期

![]()

三.AOP

1.简介

AOP:Aspect Oriented Programming:面向切面编程

OOP:Object Oriented Programming:面向对象编程

面向切面编程

使得应用易于扩展

开闭原则

将应用中所需要使用的交叉业务逻辑提取出来封装成切面

由AOP容器在适当的时机

将这些切面织入到具体的业务逻辑中

指在运行期间,将某段代码动态的切入到指定方法的指定位置。

为了让一些非核心业务的代码和核心代码分离。

应用场景:

比如说日志管理;

事务管理;

methodA(){

开启事务;

try{

​ commit();

}catch(){

​ rollback;

}finally{

​ close()

}

}

2.目的

  • 解耦合

    • 将具体的核心业务逻辑与交叉业务逻辑相分离
  • 切面的复用

    • 一个切面被多次使用
  • 独立模块化

    • 例如原本交叉业务逻辑做的打印操作
    • 将打印操作统一更换为日志操作
    • 只要切面改了,所有的业务均跟着改变
    • 独立模块化的前提是切面的复用
  • 在不改变原有功能的基础上,增加新的功能

3.动态代理

AOP是基于动态代理来实现的

动态代理是根据目标类的类加载器、代理的接口、交叉业务逻辑

生成对应的目标类的代理类

public class LogInvocationHandler implements InvocationHandler {

    private Object target;

    public LogInvocationHandler(Object target) {
        this.target = target;
    }

    /**
     * 交叉业务逻辑
     * @param proxy     代理对象,完全没用
     * @param method    需要代理的目标方法
     * @param args      目标方法的参数列表
     * @return      目标方法的返回值
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("在"+new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss").format(new Date())+"执行了"+method.getName()+"方法");
        return method.invoke(target,args);
    }
}
public static void main(String[] args) {
    System.getProperties().put("sun.misc.ProxyGenerator.saveGeneratedFiles","true");
    SomeService someService = (SomeService) Proxy.newProxyInstance(
            SomeServiceImpl.class.getClassLoader(),
            SomeServiceImpl.class.getInterfaces(),
            new LogInvocationHandler(new SomeServiceImpl())
    );
    someService.doSome();
    System.out.println("---------------------------");
    someService.doOther();

    System.out.println("----------------------------------------");

    OtherService otherService = (OtherService) Proxy.newProxyInstance(
            OtherServiceImpl.class.getClassLoader(),
            OtherServiceImpl.class.getInterfaces(),
            new LogInvocationHandler(new OtherServiceImpl())
    );
    otherService.doSome();
    System.out.println("----------------------");
    otherService.doOther();
}

4.AOP

4.1 术语

  • 连接点

    • 类里面哪些方法可以被增强
  • 切入点(真正关注的点)

    • 实际真正被增强的方法
  • 通知(增强)

    • 实际增强的逻辑部分称为通知(增强)
    • 通知有多种类型

      • 前置通知 before
      • 后置通知 afterRerurning
      • 环绕通知 round
      • 异常通知 afterThrowing
      • 最终通知 after
  • 切面

    • 是个动作,把通知应用到切入点的的过程就叫做切面

    连接点:可以切入通知方法的点

    切入点:真正去做切入的点

    从连接点中选出切入点的表达式:切入点表达式

image-20210616173433412

4-2 POM依赖

spring框架一般都是基于AspectJ实现AOP操作

AspectJ不是spring的组成部分,独立aop框架。一般把AspectJ和Spring框架一起使用,进行AOP操作。


<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aop</artifactId>
    <version>${spring.version}</version>
</dependency>

<dependency>
    <groupId>cglib</groupId>
    <artifactId>cglib</artifactId>
    <version>3.3.0</version>
</dependency>

<!-- https://mvnrepository.com/artifact/aopalliance/aopalliance -->
<dependency>
    <groupId>aopalliance</groupId>
    <artifactId>aopalliance</artifactId>
    <version>1.0</version>
</dependency>


<!-- https://mvnrepository.com/artifact/org.aspectj/aspectjweaver -->
<dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.5</version>
</dependency>

4-3 步骤

1.导包

2.创建一个类,在类里面定义方法(核心业务类和接口)

3.创建增强类,书写增强方法

4.进行通知的配置

作用:表明对哪个类里面的哪个方法进行增强

execution(权限修饰符 返回类型 类全路径 方法名 方法参数(参数列表))

注意点:

1.getBean返回的类型是什么类型?如果通过类型去获取,怎么获取?

2.切入点表达式的范围?

4-4 通知类型

  • 前置通知 before

    • 在执行核心业务逻辑之前执行
  • 后置通知

    • 正常返回通知 afterReturning

      • 当方法正常执行结束,没有遇到异常的时候执行
    • 异常通知 afterThrowing

      • 当方法运行出现异常的时候执行
    • (最终)后置通知 after

      • 不管方法是否遇到异常均会执行
  • 环绕通知 around

    • 包含以上所有

4-5 无参通知

public class LogAdvice {

    public void a(){
        System.out.println("前置通知");
    }

    public void b(){
        System.out.println("正常返回通知");
    }

    public void c(){
        System.out.println("异常通知");
    }

    public void d(){
        System.out.println("后置通知");
    }

}
<!-- 定义目标类 -->
<bean id="someService" class="aop04.SomeServiceImpl"></bean>
<bean id="otherService" class="aop04.OtherServiceImpl"></bean>

<!-- 定义通知 -->
<bean id="logAdvice" class="aop04.LogAdvice"></bean>

<!--
    引入了新的命名空间AOP,用于定义AOP的相关配置
    引入了切点表达式,用于表示当前切面应用于哪些业务逻辑
-->
<aop:config>
    <!--
        定义切入点
        表示需要使用对应交叉业务逻辑的核心业务逻辑有些哪些
        简单来讲,即哪些类中的哪些方法需要使用交叉业务逻辑
        id属性:当前切入点的唯一性标识符
        expression属性:指定切点表达式
    -->
    <!-- 匹配SomeServiceImpl中的所有方法 -->
    <aop:pointcut id="pc1" expression="within(aop04.SomeServiceImpl)"></aop:pointcut>
    <!--
        定义切面,即通知
        ref属性:定义当前通知所使用的通知类是谁
            其属性值对应的是bean的id属性值
        before子标签:配置前置通知
        after-returning子标签:正常返回通知
        after-throwing子标签:异常通知
        after:后置通知
        method属性:当前通知所对应方法是bean中哪一个
        pointcut-ref属性:当前切面所使用的切入点是谁
    -->
    <aop:aspect ref="logAdvice">
        <aop:before method="a" pointcut-ref="pc1"/>
        <aop:after-returning method="b" pointcut-ref="pc1"/>
        <aop:after-throwing method="c" pointcut-ref="pc1"/>
        <aop:after method="d" pointcut-ref="pc1"/>
    </aop:aspect>
</aop:config>

4-6 切点表达式

  • execution

    • 匹配你想要的一切
    • 可以是类,可以是方法
    • 语法:execution(返回值类型 包名.类名.方法名(参数列表))
    • 支持通配符用法

      • *

        • 用法一:匹配0或者多个字符
        • 用法二:匹配一个参数
      • ..

        • 表示匹配0或者多个参数
        • 匹配任意多个路径
  • 支持连接条件

    • and:且的意思,多个条件必须同时满足
    • or:或的意思,多个条件只要满足任意一个即可
    • not:非的意思,匹配不满足条件的方法

      • 使用not前面必须有空格

<!-- 匹配SomeServiceImpl中的所有无参无返回值的方法 -->
<aop:pointcut id="pc1" expression="execution(void aop04.SomeServiceImpl.doSome())"></aop:pointcut>
<aop:pointcut id="pc1" expression="execution(java.lang.String aop04.SomeServiceImpl.doSome(java.lang.String))"></aop:pointcut>
<aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.do*(*))"></aop:pointcut>
<aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.do*(..))"></aop:pointcut>
<aop:pointcut id="pc1" expression="execution(* aop04.*.*(..))"></aop:pointcut>
<aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.doSome(..))"></aop:pointcut>
<aop:pointcut id="pc2" expression="execution(* aop04.OtherServiceImpl.doOther(..))"></aop:pointcut>
<aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.doSome(..)) or execution(* aop04.OtherServiceImpl.doOther(..))"></aop:pointcut>
<aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.*(..)) or execution(* aop04.OtherServiceImpl.doOther(..))"></aop:pointcut>
<!-- 匹配SomeServiceImpl中的所有无参方法 -->
<aop:pointcut id="pc1" expression="execution(* aop04.SomeServiceImpl.*())"></aop:pointcut>
<!-- 匹配aop04包下所有除了doSome以外的方法 -->
<aop:pointcut id="pc1" expression="execution(* aop04.*.*(..)) and not execution(* aop04.*.doSome(..))"></aop:pointcut>

4-7 有参通知

如何获取与目标方法相关的信息

此时需要使用有参通知来实现

使用有参通知的时候,其参数必须是:JoinPoint

注意:

通知方法的参数不能随便乱写。通知方法是Spring利用反射调用的。每次调用都需要确认参数是什么?

异常类型写Exception。

public class LogAdvice {

    public void before(JoinPoint jp){
        // 获取目标类
        Object target = jp.getThis();
        // 获取目标方法签名
        Signature signature = jp.getSignature();
        //获取方法名
        String name = signature.getName();
        // 获取目标方法参数列表
        Object[] args = jp.getArgs();
        System.out.println("前置通知:"+target+"中的"+signature.getName()+"方法即将执行");
    }

    //Object:指定通知方法可以接收的结果类型
    public void afterReturning(JoinPoint jp, Object returnValue){
        System.out.println("正常返回通知:"+jp.getSignature().getName()+"方法执行完成,方法返回值为:"+returnValue);
    }

    //Exception:指定通知方法可以接收的异常
    public void afterThrowing(JoinPoint jp, Exception e){
        System.out.println("异常通知:"+jp.getSignature().getName()+"方法执行出现了异常,异常为:"+e);
    }

    public void after(JoinPoint jp){
        System.out.println("后置通知:"+jp.getSignature().getName()+"方法执行结束");
    }

}
<bean id="someService" class="aop05.SomeServiceImpl"></bean>

<bean id="logAdvice" class="aop05.LogAdvice"></bean>

<aop:config>
    <aop:pointcut id="pc1" expression="within(aop05.SomeServiceImpl)"/>

    <aop:aspect ref="logAdvice">
        <aop:before method="before" pointcut-ref="pc1"/>
        <!--
            returning属性:指定当前方法中的哪一个参数作为目标方法的返回值
        -->
        <aop:after-returning method="afterReturning" pointcut-ref="pc1" returning="returnValue"/>
        <!--
            throwing属性:指定当前方法中的哪一个参数作为接收目标方法异常的参数
        -->
        <aop:after-throwing method="afterThrowing" pointcut-ref="pc1" throwing="e"/>
        <aop:after method="after" pointcut-ref="pc1"/>
    </aop:aspect>
</aop:config>

4-8 环绕通知

spring中最强大的通知方法。

try{

//前置通知

method.invoke(obj,args);

//返回通知

}catch(){

//异常通知

}finally{

//后置通知

}

四合一通知就是环绕通知

环绕通知优先于其他通知执行。

环绕通知中有一个参数ProceedingJoinPoint pjp

4-9执行顺序:

环绕通知对方法存在三个要求
  • 方法必须有返回值

    • 返回值类型必须是Object
    • 表示的就是目标方法的返回值
  • 方法必须有参数

    • 参数为:proceedingJoinPoint
    • 该参数是JoinPoint的子类
    • 可以获取与目标方法相关的信息
    • 可以用于执行目标方法
  • 方法必须抛出Throwable
public class AroundAdvice {

    //ProceedingJoinPoint
    public Object around(ProceedingJoinPoint jp) throws Throwable{

        Object returnValue = null;
        System.out.println("环绕通知之前置通知");
        long begin = System.currentTimeMillis();

        try {
            // jp.proceed();利用反射调用目标方法的执行,就是method.invoke(obj,args)
            returnValue = jp.proceed();
            long end = System.currentTimeMillis();
            System.out.println("环绕通知之正常返回通知,执行方法共花费了"+(end-begin)+"毫秒");
        } catch (Throwable throwable) {
            System.out.println("环绕通知之异常通知");
        } finally {
            System.out.println("环绕通知之后置通知");
        }
        return returnValue;
    }
}

Spring要求对通知方法的参数不能乱写。通知方法是spring利用反射调用的,所以每次方法调用得确定这个方法参数表的值。不知道是什么的参数一定要告诉spring是什么。

5-8 优先级

默认情况下

根据配置的位置而定

谁的配置在前,谁的优先级高

优先级高的前置通知执行时机更前

优先级高的后置通知执行时机更后

在切面中进行配置,在aop:aspect标签中通过order属性定义优先级

值越小,优先级越高

<aop:aspect ref="logAdvice" order="2">
    <aop:before method="before" pointcut-ref="pc1"/>
    <aop:after-returning method="afterReturning" pointcut-ref="pc1" returning="returnValue"/>
    <aop:after-throwing method="afterThrowing" pointcut-ref="pc1" throwing="e"/>
    <aop:after method="after" pointcut-ref="pc1"/>
</aop:aspect>

<aop:aspect ref="aroundAdvice" order="1">
    <aop:around method="around" pointcut-ref="pc1"/>
</aop:aspect>

5-9 抽取可重用的切入点表达式

步骤:

1.随便声明一个没有实现的void方法

2.给方法上加@PointCut注解

3.在通知方法上加上@Before("方法名()")

四.Annotation

Spring注解是从2.5开始

在主流的SSM开发中,一般是配置文件+注解

在主流的SpringBoot开发中,一般是纯注解

1.IOC注解

  • 普通bean

    • @Component
    • 该注解标注在类上,表示该类是一个受Spring管理的bean
    • 该注解可以传递参数,也可以不传递参数
    • 当注解存在参数的时候

      • 该参数即为当前bean的id属性值
    • 当注解不存在参数的时候

      • 当前bean的id默认为当前类名,首字母小写
  • 持久层bean

    • @Repository
    • 用于与普通bean一致
  • 业务层bean

    • @Service
    • 用法与普通bean一致
  • 控制器bean

    • @Controller
    • 详情请期待SpringMVC
  • 注入值

    • @Value("值")
    • 可以访问properties文件
    • 当访问properties文件之后,需要使用配置文件+注解的方式实现
  • 自动装配

    • @Autowired

      • 自动根据byType或者byName查找对应的bean
      • 自动识别
    • @Autowired@Qualifier("ob")

      • 根据bean的id进行装配
      • 如果没有找到对应的id值的bean
      • 则会报错
      • 不建议使用
@Component
public class SomeBean {
//    @Autowired
    @Autowired@Qualifier("ob")
    private OtherBean otherBean;
@Component
public class OtherBean {
//    @Value("1")
    @Value("${someBean.id}")
    private Integer id;
//    private Integer id = 1;
//    @Value("admin")
    @Value("${someBean.name}")
    private String name;
//    private String name = "admin";
<context:property-placeholder location="classpath:bean.properties"/>
<!--
    配置扫描注解所在包的操作
    当配置了之后,Spring会自动扫描指定的包以及其子包中所有的注解
 -->
<context:component-scan base-package="bean"/>


<!--实例1:use-default-filters:表示不适用默认filter。自己配置filter,include-filter:设置扫描哪些内容-->
<context:component-scan base-package="com" use-default-filters="false">
    <!--type=assignable 按照类-->
    <context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
</context:component-scan>
<!--实例2:配置com下的所有包,exclude-filter:设置哪些内容不扫描-->
<context:component-scan base-package="com">
    <context:exclude-filter type="annotation" expression="org.springframework.stereotype.Service"/>
</context:component-scan>
someBean.id=1
someBean.name=admin

2.AOP注解

2-1 注解用法

  • 切入点

    • 在使用注解的时候,切点表达式也是一个方法
    • 通过@Pointcut(切点表达式)进行标注
    • 该方法可以没有方法体的内容,是一个空方法
    • 定义了切入点之后,当前切入点的id即为当前方法名
  • 切面

    • 在类上使用@Aspect进行标注
    • 表示当前类是一个切面的类
    • 由于切面的类也是由Spring所管理的bean
    • 因此,需要使用@Component进行标注
  • 通知

    • 前置通知

      • @Before(value="切入点")
      • value属性指定当前通知所使用的切入点是谁

        • 如果注解的属性中只有value="xxx"这种情况下

          • 此时关键字value=可以省略
        • 如果value属性的值不是一个字符串或者注解属性不止一个value

          • 此时关键字value=不可以省略
    • 正常返回通知

      • AfterReturning(..)
      • value属性:指定当前通知所使用的切入点是谁
      • returning属性:指定当前接收目标方法返回值的参数
    • 异常通知

      • @AfterThrowing(..)
      • value属性:指定当前通知所使用的切入点是谁
      • throwing属性:指定当前接收目标方法异常的参数
    • 后置通知

      • @After(..)
      • value属性:指定当前通知所使用的切入点是谁
    • 环绕通知

      • @Around(..)
      • value属性:指定当前通知所使用的切入点是谁
@Component
@Aspect
public class LogAdvice {

    //抽取可重复使用的切入点表达式
    //声明一个void的方法
    @Pointcut(value="execution(* bean.*.*(..))")
    public void pointcutName(){};

    @Before(value = "pointcutName()")
    public void before(JoinPoint jp){
        System.out.println("前置通知");
    }

    @AfterReturning(value = "pointcutName()",returning = "returnValue")
    public void afterReturning(JoinPoint jp, Object returnValue){
        System.out.println("正常返回通知,返回值为:"+returnValue);
    }

    @AfterThrowing(value = "pointcutName()",throwing = "e")
    public void afterThrowing(JoinPoint jp,Exception e){
        System.out.println("异常通知,异常为:"+e);
    }


    @After("pointcutName()")
    public void after(JoinPoint jp){
        System.out.println("后置通知");
    }

    @Around("pointcutName()")
    public Object around(ProceedingJoinPoint jp) throws Throwable{
        Object returnValue = null;

        try {
            System.out.println("环绕通知之前置通知");
            returnValue = jp.proceed();
            System.out.println("环绕通知之正常返回通知");
        } catch (Throwable throwable) {
            System.out.println("环绕通知之异常通知");
        } finally {
            System.out.println("环绕通知之后置通知");
        }
        return returnValue;
    }
}

2-2 配置

<!-- 使用注解AOP需要进行配置 -->
<!-- 有两种方式进行配置 -->
<!-- 方式一:使用Spring提供的后处理bean -->
<bean class="org.springframework.aop.aspectj.annotation.AnnotationAwareAspectJAutoProxyCreator"></bean>
<!-- 方式二:使用AOP命名空间来实现 -->
<aop:aspectj-autoproxy/>

2-3 Advice执行顺序

注解版AOP通知的执行顺序与配置文件版不一致

  • 正常返回时的执行顺序

    • 环绕通知前置==》@Before==》执行目标方法==》@AfterReturning==>@After==>环绕通知返回==》环绕通知结束
  • 出现异常时的执行顺序

    • 环绕通知前置==》@Before==》执行目标方法==》@AfterThrowing==>@After==>环绕通知异常==》环绕通知结束

多个切面执行顺序

2-4 AOP 使用场景

  • 日志管理
  • 权限管理
  • 安全检查
  • 事务控制

    • 前置通知 设置非自动提交
    • 返回通知 提交事务
    • 后置通知 关闭资源
    • 异常通知 回滚

五.通用支持

与其他组件的整合

1.数据源的配置

  • Spring内置数据源

    • DriverManagerDataSource
  • 第三方数据源

    • BasicDataSource

1-1 使用Spring内置的

  • POM配置
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>${spring.version}</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.38</version>
</dependency>
  • 数据源配置
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
   <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
   <property name="url" value="jdbc:mysql://127.0.0.1:3306/?useUnicode=true&amp;characterEncoding=utf-8"/>
   <property name="username" value="gxh"/>
   <property name="password" value="123"/>
</bean>

1-2 使用第三方的

  • POM配置
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>${spring.version}</version>
</dependency>

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.38</version>
</dependency>

<dependency>
    <groupId>commons-dbcp</groupId>
    <artifactId>commons-dbcp</artifactId>
    <version>1.4</version>
</dependency>
  • 数据源配置

    • 此处以properties文式为例
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/mysql?useUnicode=true&characterEncoding=utf-8
jdbc.username=root
jdbc.password=
jdbc.maxActive=1
jdbc.init=1
jdbc.maxWait=3000
<context:property-placeholder location="classpath:dataSource.properties"/>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    <!-- 最大连接数 -->
    <property name="maxActive" value="${jdbc.maxActive}"/>
    <!-- 初始化连接数 -->
    <property name="initialSize" value="${jdbc.init}"/>
    <!-- 最大等待时间 -->
    <property name="maxWait" value="${jdbc.maxWait}"/>
</bean>

2.JDBC支持

2-1 使用JdbcTemplate

使用Spring提供的模板类JdbcTemplate

将JdbcTemplate注入给对应的dao

将dataSource注入给JdbcTemplate

1.查询所有用户,返回list

2.查询某个用户,返回对象

3.插入一个用户

4.批量插入用户

5.返回用户数量

6.删除一个用户

public class UserDaoImpl implements UserDao {

    private JdbcTemplate jdbcTemplate;

    public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
        this.jdbcTemplate = jdbcTemplate;
    }

    @Override
    public void insertUser(User user) {
        String sql = new StringBuffer()
                .append(" insert into ")
                .append(" t_user ")
                .append("   (username,password,phone,address)")
                .append(" values ")
                .append("   (?,?,?,?)")
                .toString();
        jdbcTemplate.update(sql,user.getUsername(),user.getPassword(),user.getPhone(),user.getAddress());
    }

    @Override
    public void deleteById(Integer id) {

    }

    @Override
    public void updateUser(User user) {

    }

    @Override
    public User selectById(Integer id) {
        String sql = new StringBuffer()
                .append(" select id,username,password,phone,address ")
                .append(" from t_user ")
                .append(" where id = ? ")
                .toString();
        List<User> users = jdbcTemplate.query(sql,new UserRowMapper(),id);
        return users.isEmpty()?null:users.get(0);
    }

    @Override
    public List<User> selectAll() {
        return null;
    }

    @Override
    public Long insertReturnPrimaryKey(User user) {
        KeyHolder keyHolder = new GeneratedKeyHolder();
        jdbcTemplate.update(new PreparedStatementCreator() {
            @Override
            public PreparedStatement createPreparedStatement(Connection conn) throws SQLException {
                String sql = new StringBuffer()
                        .append(" insert into ")
                        .append(" t_user ")
                        .append("   (username,password,phone,address)")
                        .append(" values ")
                        .append("   (?,?,?,?)")
                        .toString();
                PreparedStatement ps = conn.prepareStatement(sql,PreparedStatement.RETURN_GENERATED_KEYS);
                ps.setString(1,user.getUsername());
                ps.setString(2,user.getPassword());
                ps.setString(3,user.getPhone());
                ps.setString(4,user.getAddress());
                return ps;
            }
        },keyHolder);
        return (Long) keyHolder.getKey();
    }
}
<context:property-placeholder location="classpath:dataSource.properties"/>
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
    <!-- 最大连接数 -->
    <property name="maxActive" value="${jdbc.maxActive}"/>
    <!-- 初始化连接数 -->
    <property name="initialSize" value="${jdbc.init}"/>
    <!-- 最大等待时间 -->
    <property name="maxWait" value="${jdbc.maxWait}"/>
</bean>

<!-- 配置JdbcTemplate,将dataSource注入给JdbcTemplate -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
    <property name="dataSource" ref="dataSource"/>
</bean>

<!-- 配置userDao,将JdbcTemplate注入给UserDao -->
<bean id="userDao" class="dao.impl.UserDaoImpl">
    <property name="jdbcTemplate" ref="jdbcTemplate"/>
</bean>

3.MyBatis 支持

3-1 POM配置

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.4.6</version>
</dependency>

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis-spring</artifactId>
    <version>2.0.3</version>
</dependency>

3-2 配置文件

<context:property-placeholder location="classpath:dataSource.properties"/>
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
    <property name="driverClassName" value="${jdbc.driver}"/>
    <property name="url" value="${jdbc.url}"/>
    <property name="username" value="${jdbc.username}"/>
    <property name="password" value="${jdbc.password}"/>
</bean>

<!-- 整合mybatis的配置 -->
   <!-- SqlSession 不用单独创建,每次做crud操作都需要Mapper接口的代理对象 而代理对象的创建又必须有 SqlSession对象创建 Spring在通过MyBatis创建Mapper接口代理对象的时候,底层自动把SqlSession会话对象创建出来 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
    <!-- 配置数据源 -->
    <property name="dataSource" ref="dataSource"/>
    <!-- 配置别名 -->
    <property name="typeAliasesPackage" value="entity"/>
    <!-- 注册mapper -->
    <!--<property name="mapperLocations">-->
        <!--<list>-->
            <!--<value>classpath:mapper/UserMapper.xml</value>-->
            <!-- 支持通配符用法 -->
            <!--<value>classpath:mapper/*.xml</value>-->
        <!--</list>-->
    <!--</property>-->
    <property name="mapperLocations" value="classpath:mapper/*.xml"></property>
    <!-- 访问MyBatis的configuration配置 可以保留mybatis的一些个性化配置,比如缓存,日志等-->
    <!--<property name="configLocation" value="classpath:mybatis-config.xml"/>-->
</bean>

<!-- 此时可以通过MyBatis的方式进行实现了,但是获取对应的dao实例相对麻烦 -->
<!-- Spring提供了获取对应的dao实例的方式 -->
<!-- 方式一:获取某一个dao实例,通过FactoryBean进行实现 (数据映射器MapperFactoryBean) -->
<bean id="userDao" class="org.mybatis.spring.mapper.MapperFactoryBean">
    <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
    <property name="mapperInterface" value="dao.UserMapper"/>
</bean>

<!--
    方式三:使用后处理bean对当前的工程进行整体操作
    指定dao接口所在包,对该包进行扫包操作
    通过扫包找到对应包下的所有的接口
    自动生成对应的实例
    此时bean的id为当前的类名,首字母小写
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
    <property name="basePackage" value="dao"/>
</bean>

3-3 测试

ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");

// 方式一:使用源生的MyBatis实现方式
SqlSessionFactory factory = (SqlSessionFactory) ac.getBean("sqlSessionFactory");
SqlSession session = factory.openSession();
UserMapper userMapper = session.getMapper(UserMapper.class);
User user = userMapper.selectByPrimaryKey(1);
System.out.println(user);


// 方式二:使用MapperFactoryBean生成对应的dao的实例
// 此时的dao实例由Spring管理
UserMapper userMapper2 = (UserMapper) ac.getBean("userDao");
User user2 = userMapper.selectByPrimaryKey(1);
System.out.println(user2);

// 方式三:使用自动扫包操作
UserMapper userMapper3 = (UserMapper) ac.getBean("userMapper");
User user3 = userMapper3.selectByPrimaryKey(1);
System.out.println(user3);

4.事务支持

4-1 什么是事务

编程式事务:

​ try{

​ 开启事务;

​ 业务方法执行;

​ 提交;

​ }catch(){

​ 回滚;

​ }finally{

​ 关闭;

​ }

声明式事务(Spring):

这是一个事务:

serviceA;

A C I D

所谓的事务,表示一次不可再分的操作序列

这些操作序列中的所有操作

要么都执行,要么都不执行

它是一个不可分割的完整的工作单元

声明式事务:以前通过复杂的编程来编写一个事务,替换为只需要告诉Spring哪个方法是事务方法即可,Spring自动控制事务。

//获取连接

//设置非自动提交

目标代码运行

//正常提交

//异常回滚

//最终关闭

事务管理代码的固定模式作为一种横切关注点,可以通过AOP方法模块化,进而借助Spring AOP框架实现声明式事务管理。

编程式事务:

4-2 传统数据库的事务特性

ACID
  • A

    • Atomicity
    • 原子性
  • C

    • Consistency
    • 一致性
  • I

    • Isolation
    • 隔离性
  • D

    • Durability
    • 持久性

4-3 事务隔离级别

从小到大
  • static int TRANSACTION_NONE

    • 指示事务不受支持的常量。
    • 即:没有事务
  • static int TRANSACTION_READ_UNCOMMITTED

    • 指示可以发生脏读 (dirty read)、不可重复读和幻读 (phantom read) 的常量。
    • 即:读未提交数据
    • 有事务,但是这个事务啥都不管
  • static int TRANSACTION_READ_COMMITTED

    • 指示不可以发生脏读的常量;不可重复读和幻读可以发生。
    • 即:读已提交数据
  • static int TRANSACTION_REPEATABLE_READ

    • 指示不可以发生脏读和不可重复读的常量;幻读可以发生。
  • static int TRANSACTION_SERIALIZABLE 用不到

    • 指示不可以发生脏读、不可重复读和幻读的常量。
    • 在执行期间,禁止其他事物对这个表进行增删改的操作
    • 解决并发问题,性能十分低下。
    • 此处存在悲观锁的问题

Spring建议的是使用DEFAULT,就是数据库本身的隔离级别,配置好数据库本身的隔离级别,无论在哪个框架中读写数据都不用操心

4-4 数据库并发产生的问题

  • 脏读

    • 一个事务读取到另一个尚未提交的事务
  • 不可重复读 update
  • 可重复读:在同一个事务内,读取一条数据读到的能保证是一样的

    • 一个事务进行多次操作的时候,读取到的数据不一致
  • 虚读(幻读) insert delete

    • 一个事务进行多次操作的时候,读取到的数据量不一致(insert)

脏读是不允许发生的

脏读 不可重复读 幻读
READ_UNCOMMITTED:读未提交
READ_COMMITTED:读已提交
REPEATABLE_READ:可重复读
SERIALIZABLE:

数据库对事物隔离级别的支持程度

oracle mysql
READ_UNCOMMITTED 不支持 支持
READ_COMMITTED 支持 支持
REPEATABLE_READ 不支持 支持(默认)
SERIALIZABLE 支持 支持

根据业务特性做调整。

查询mysql的隔离级别:

select @@global.tx_isolation //查询全局隔离级别

select @@session.tx_isolation //查询当前会话的隔离级别

start TRANSACTION 开启事务

修改隔离级别sql:set session TRANSACTION ISOLATION LEVEL REPEATABLE READ

set session TRANSACTION ISOLATION LEVEL READ_COMMITTED

4-5 Spring事务特性

传播行为

事物的传播+事物的行为

​ 如果 有多个事物进行嵌套运行,子事物是否要和大事物共用一个事物。

比如:

void service1{

​ method1(){

​ }

}

  • 在Spring中,传播的规则有很多,一般需要熟练掌握其中两种
  • REQUIRED(默认)

    • 表示当前的方法必须运行在一个存在事务管理的环境中
    • 在运行时会判断当前的环境中是否存在事务管理

      • 如果存在,则会自动加入到当前的事务管理中
      • 如果不存在,则会开启一个新的事务环境用于执行当前的业务
    • 一般用于增删改操作
    • 如果是REQUIRED,事物里面的属性配置都用大事物中的属性。
    @Transactional(propagation=Propagation.REQUIRED)
    
    //如果方法1有事务,方法1调用方法2之后,方法2使用方法1的事务
    //如果方法1没有事务,方法1调用方法2之后,则创建一个新事物
    REQUIRED 
    
    //方法2调用方法2,不管1有没有事务,都会创建一个新的事务运行.并把之前的事务挂起(暂停)
    REQUIRES_NEW

    REQUIRED: 和REQUIRES_NEW是最常用的。

    REQUIRED:将之前的connection传过来

    REQUIRES_NEW:这个方法直接使用新的connection

    PROPAGATION_SUPPORTS--支持当前事务,如果当前没有事务,就以非事务方式执行。
    PROPAGATION_MANDATORY--支持当前事务,如果当前没有事务,就抛出异常。
    PROPAGATION_NOT_SUPPORTED--以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
    PROPAGATION_NEVER--以非事务方式执行,如果当前存在事务,则抛出异常。

  • SUPPORTS

    • 表示当前的方法不是必须运行在事务管理的环境中
    • 有事务可以运行,没有事务也可以运行
    • 在运行时会判断当前的环境中是否存在事务管理

      • 如果存在,则会自动加入到当前的事务管理中
      • 如果不存在,则拉倒
    • 一般用于查询操作
    • 一般不会单独使用,会与其他属性联合使用
  • 回滚条件

    • 默认情况下,当遇到RuntimeException以及其子类的时候,会自动回滚
    • 可以进行手动设置

      • rollbackFor="异常的class类型"

        • 表示遇到指定的异常会进行回滚
      • noRollbackFor="异常的class类型"

        • 表示遇到指定的异常不进行回滚
  • 只读优化

    • 当你确定你的业务中有且仅有查询操作时才能使用
    • readOnly=true
    • 一般与传播规则的SUPPORTS联合使用
  • 超时

    • Timeout
  • 隔离级别

    • 详见4-3

4-6 POM配置

<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-tx</artifactId>
    <version>${spring.version}</version>
</dependency>

4-7 Spring配置

以注解事务为例

事务切面==事务管理器

这个事务管理器就可以在目标方法运行前后进行事务控制。

PlatformTransactionManager

Spring整合JDBC与MyBatis使用的是相同的事务管理器

DataSourceTransactionManager

<context:component-scan base-package="service"/>
<!-- 事务配置 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="dataSource"/>
</bean>
<!-- 配置AOP2.X注解事务 需要导入面向切面编程需要的依赖包 -->
<!--开启基于注解的事务控制模式-->
<tx:annotation-driven transaction-manager="transactionManager"/>

dao里定义基本的增删改查的方法

一个dao方法就对应一次sql操作

service定义的是一个业务操作

transAcoount(){

​ 扣钱;

​ 加钱;

}

4-8 注解使用

  • @Transactional

    • 表示配置事务的相关操作
    • 该注解可以标注在类上,也可以标注在方法上
    • 该注解标注在类上表示当前类均使用此时进行的事务配置
    • 该注解标注在方法上表示当前方法使用此时进行的实物配置
    • 如果类上与方法上均有该注解,优先使用方法上的事务配置
  • propagation属性

    • 配置传播规则的事务属性
    • 其值是一个枚举类型,使用Propagation中提供的值
    • 其值一般使用REQUIRED或者SUPPOTRS
  • rollbackFor属性

    • 配置回滚条件
    • 当遇到指定的异常时进行回滚
  • noRollbackFor属性

    • 配置回滚条件
    • 当遇到指定的异常时不进行回滚,可以让原来回滚的异常不回滚
  • readOnly=true

    • 配置是否只读
    • 当操作中有且仅有查询操作时使用,可以加快查询速度
  • isolation 隔离级别
  • noRollbackForClassName :基本不用,用noRollbackFor
  • timeout:超时,事务超出指定时间执行时长后自动终止并回滚 以秒为单位.指的是2次sql完成时间不超过设置的时间。

    注意:默认 编译时异常不回滚。运行时异常回滚。

@Service
@Transactional(propagation = Propagation.REQUIRED,rollbackFor = Exception.class,noRollbackFor = ArithmeticException.class)
public class UserServiceImpl implements UserService {

    @Autowired
    private UserMapper userMapper;

    @Override
    public void regist(User user) throws UserExistException {
        UserExample example = new UserExample();
        example.or()
                .andUsernameEqualTo(user.getUsername());
        List<User> users = userMapper.selectByExample(example);
        if(!users.isEmpty()){
            throw new UserExistException("该用户已经被注册");
        }
        userMapper.insertSelective(user);
        int i = 1/0;
    }

    @Transactional(propagation = Propagation.SUPPORTS,readOnly = true)
    @Override
    public User login(String username, String password) throws UserNotExistException {
        UserExample example = new UserExample();
        example.or()
                .andUsernameEqualTo(username)
                .andPasswordEqualTo(password);
        List<User> users = userMapper.selectByExample(example);
        if(users.isEmpty()){
            throw new UserNotExistException("用户名或密码错误");
        }
        return users.get(0);
    }
}

4-9 基于xml的事务配置(仅了解)

<!--1.Spring中提供事务管理器,配置这个事务管理器
    2.配置出事务方法-->
<aop:config>
    <aop:pointcut id="txpoint" expression="execution(* com.hy.spring.service.*.*(..))"/>
    <!--aop:advisor:事务增强(建议)-->
    <aop:advisor advice-ref="txadvice" pointcut-ref="txpoint" />
</aop:config>

<!--配置事务管理器-->
<tx:advice id="txadvice" transaction-manager="tm">
    <!--事务属性-->
    <tx:attributes>
        <!--tx:method告诉Spring哪个方法是事务方法。
        切入点表达式只是说明事务管理器切入这些方法-->
        <tx:method name="transAccount" propagation="REQUIRED" />
    </tx:attributes>
</tx:advice>

工厂bean(实现FactoryBean接口)和普通bean的区别:

BeanFactoryPostProcessor;在所有bean实例化之前,对整个bean定义的内容做修改
BeanPostProcessor:bean实例化完成了,在bean初始化前后对bean做处理
/** Cache of singleton objects: bean name to bean instance. 
一级缓存:完整的javabean对象
*/
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);

/** Cache of singleton factories: bean name to ObjectFactory. 
三级缓存 :bean实例化出来的空盒子
*/
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);

/** Cache of early singleton objects: bean name to bean instance. 
二级缓存 :循环依赖() 
a <--> b
getBean() 加入三级缓存  populateBean()注入属性b ====>b:getBean() 加入三级缓存 注入属性a 从三级缓存中取到a,先注入空盒子 继续初始化b  继续a的初始化
*/
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
相关实践学习
基于CentOS快速搭建LAMP环境
本教程介绍如何搭建LAMP环境,其中LAMP分别代表Linux、Apache、MySQL和PHP。
全面了解阿里云能为你做什么
阿里云在全球各地部署高效节能的绿色数据中心,利用清洁计算为万物互联的新世界提供源源不断的能源动力,目前开服的区域包括中国(华北、华东、华南、香港)、新加坡、美国(美东、美西)、欧洲、中东、澳大利亚、日本。目前阿里云的产品涵盖弹性计算、数据库、存储与CDN、分析与搜索、云通信、网络、管理与监控、应用服务、互联网中间件、移动服务、视频服务等。通过本课程,来了解阿里云能够为你的业务带来哪些帮助 &nbsp; &nbsp; 相关的阿里云产品:云服务器ECS 云服务器 ECS(Elastic Compute Service)是一种弹性可伸缩的计算服务,助您降低 IT 成本,提升运维效率,使您更专注于核心业务创新。产品详情: https://www.aliyun.com/product/ecs
目录
相关文章
|
1天前
|
Java 数据安全/隐私保护 Sentinel
微服务学习 | Spring Cloud 中使用 Sentinel 实现服务限流
微服务学习 | Spring Cloud 中使用 Sentinel 实现服务限流
|
2天前
|
设计模式 算法 Java
[设计模式Java实现附plantuml源码~行为型]定义算法的框架——模板方法模式
[设计模式Java实现附plantuml源码~行为型]定义算法的框架——模板方法模式
|
2天前
|
安全 Java 数据库连接
[AIGC] Spring框架的基本概念和优势
[AIGC] Spring框架的基本概念和优势
|
2天前
|
Java Nacos 开发者
Java从入门到精通:4.2.1学习新技术与框架——以Spring Boot和Spring Cloud Alibaba为例
Java从入门到精通:4.2.1学习新技术与框架——以Spring Boot和Spring Cloud Alibaba为例
|
2天前
|
Dubbo Java 应用服务中间件
Java从入门到精通:3.2.2分布式与并发编程——了解分布式系统的基本概念,学习使用Dubbo、Spring Cloud等分布式框架
Java从入门到精通:3.2.2分布式与并发编程——了解分布式系统的基本概念,学习使用Dubbo、Spring Cloud等分布式框架
|
7天前
|
Java Maven 开发工具
《Java 简易速速上手小册》第5章:Java 开发工具和框架(2024 最新版)
《Java 简易速速上手小册》第5章:Java 开发工具和框架(2024 最新版)
27 1
|
8天前
|
Java 关系型数据库 MySQL
一套java+ spring boot与vue+ mysql技术开发的UWB高精度工厂人员定位全套系统源码有应用案例
UWB (ULTRA WIDE BAND, UWB) 技术是一种无线载波通讯技术,它不采用正弦载波,而是利用纳秒级的非正弦波窄脉冲传输数据,因此其所占的频谱范围很宽。一套UWB精确定位系统,最高定位精度可达10cm,具有高精度,高动态,高容量,低功耗的应用。
一套java+ spring boot与vue+ mysql技术开发的UWB高精度工厂人员定位全套系统源码有应用案例
|
9天前
|
负载均衡 Java 开发者
细解微服务架构实践:如何使用Spring Cloud进行Java微服务治理
【4月更文挑战第17天】Spring Cloud是Java微服务治理的首选框架,整合了Eureka(服务发现)、Ribbon(客户端负载均衡)、Hystrix(熔断器)、Zuul(API网关)和Config Server(配置中心)。通过Eureka实现服务注册与发现,Ribbon提供负载均衡,Hystrix实现熔断保护,Zuul作为API网关,Config Server集中管理配置。理解并运用Spring Cloud进行微服务治理是现代Java开发者的关键技能。
|
10天前
|
安全 Java 数据安全/隐私保护
使用Spring Security进行Java身份验证与授权
【4月更文挑战第16天】Spring Security是Java应用的安全框架,提供认证和授权解决方案。通过添加相关依赖到`pom.xml`,然后配置`SecurityConfig`,如设置用户认证信息和URL访问规则,可以实现应用的安全保护。认证流程包括请求拦截、身份验证、响应生成和访问控制。授权则涉及访问决策管理器,如基于角色的投票。Spring Security为开发者构建安全应用提供了全面且灵活的工具,涵盖OAuth2、CSRF保护等功能。
|
11天前
|
Java 大数据 云计算
Spring框架:Java后台开发的核心
【4月更文挑战第15天】Spring框架在Java后台开发中占据核心位置,因其控制反转(IoC)、面向切面编程(AOP)、事务管理等特性提升效率和质量。Spring提供数据访问集成、RESTful Web服务和WebSocket支持。优势包括高效开发、灵活扩展、强大生态圈和广泛应用。应用于企业级应用、微服务架构及云计算大数据场景。掌握Spring对Java开发者至关重要。