Spring学习笔记

108 阅读14分钟

Spring

1. Spring基础框架 Spring Framework

1.1 Spring特性

  • 非侵入式:使用 Spring Framework 开发应用程序时,Spring 对应用程序本身的结构影响非常 小。对领域模型可以做到零污染;对功能性组件也只需要使用几个简单的注解进行标记,完全不会 破坏原有结构,反而能将组件结构进一步简化。这就使得基于 Spring Framework 开发应用程序 时结构清晰、简洁优雅。
  • 控制反转:IOC——Inversion of Control,翻转资源获取方向。把自己创建资源、向环境索取资源 变成环境将资源准备好,我们享受资源注入。
  • 面向切面编程:AOP——Aspect Oriented Programming,在不修改源代码的基础上增强代码功 能。
  • 容器:Spring IOC 是一个容器,因为它包含并且管理组件对象的生命周期。组件享受到了容器化 的管理,替程序员屏蔽了组件创建过程中的大量细节,极大的降低了使用门槛,大幅度提高了开发 效率。
  • 组件化:Spring 实现了使用简单的组件配置组合成一个复杂的应用。在 Spring 中可以使用 XML 和 Java 注解组合这些对象。这使得我们可以基于一个个功能明确、边界清晰的组件有条不紊的搭 建超大型复杂应用系统。
  • 声明式:很多以前需要编写代码才能实现的功能,现在只需要声明需求即可由框架代为实现。
  • 一站式:在 IOC 和 AOP 的基础上可以整合各种企业应用的开源框架和优秀的第三方类库。而且 Spring 旗下的项目已经覆盖了广泛领域,很多方面的功能性需求可以在 Spring Framework 的基 础上全部使用 Spring 来实现。

1.2 Spring Framework五大功能模块

功能模块功能介绍
Core Container核心容器,在 Spring 环境下使用任何功能都必须基于 IOC 容器。
AOP&Aspects面向切面编程
Testing提供了对 junit 或 TestNG 测试框架的整合。
Data Access/Integration提供了对数据访问/集成的功能。
Spring MVC提供了面向Web应用程序的集成功能。

2. IOC

2.1 IOC思想

IOC:Inversion of Control,翻译过来是反转控制

2.1.1 获取资源的传统方式

在应用程序中的组件需要获取资源时,传统的方式是组件主动的从容器中获取所需要的资源,在这样的 模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效 率。

2.1.2 反转控制方式获取资源

反转控制的思想完全颠覆了应用程序组件获取资源的传统方式:反转了资源的获取方向——改由容器主 动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源 的方式即可,极大的降低了学习成本,提高了开发的效率。这种行为也称为查找的被动形式。

2.1.3 DI

DI:Dependency Injection,翻译过来是依赖注入。 DI 是 IOC 的另一种表述方式:即组件以一些预先定义好的方式(例如:setter 方法)接受来自于容器的资源注入。相对于IOC而言,这种表述更直接。 所以结论是:IOC 就是一种反转控制的思想, 而 DI 是对 IOC 的一种具体实现。

2.2 IOC容器在Spring中的实现

Spring 的 IOC 容器就是 IOC 思想的一个落地的产品实现。IOC 容器中管理的组件也叫做 bean。在创建 bean 之前,首先需要创建 IOC 容器。Spring 提供了 IOC 容器的两种实现方式:

2.2.1 BeanFactory

这是 IOC 容器的基本实现,是 Spring 内部使用的接口。面向 Spring 本身,不提供给开发人员使用。

2.2.2 ApplicationContext

BeanFactory 的子接口,提供了更多高级特性。面向 Spring 的使用者,几乎所有场合都使用 ApplicationContext 而不是底层的 BeanFactory。

2.2.3 ApplicationContext的主要实现类

BeanFactory实现类.png

类型名简介
ClassPathXmlApplicationContext通过读取类路径下的 XML 格式的配置文件创建 IOC 容器对象
FileSystemXmlApplicationContext通过文件系统路径读取 XML 格式的配置文件创建 IOC 容器对象
ConfigurableApplicationContextApplicationContext 的子接口,包含一些扩展方法refresh()close(),让 ApplicationContext 具有启动,关闭和刷新上下文的能力。
WebApplicationContext专门为 Web 应用准备,基于 Web 环境创建 IOC 容器对象,并将对象引入存入 ServletContext 域中。

3. 基于XML管理bean

3.1 helloworld

3.1.1 引入依赖

	<dependencies>
		<!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>5.3.1</version>
		</dependency>
		<!-- junit测试 -->
		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.12</version>
			<scope>test</scope>
		</dependency>
	</dependencies>

Spring-context依赖项.png

3.1.2 创建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">

   <!--
      bean:配置一个bean对象,将对象交给IOC容器管理
      属性:
      id:bean的唯一标识,不能重复
      class:设置bean对象所对应的类型
   -->
   <bean id="helloworld" class="com.nick.spring.pojo.HelloWorld"></bean>

</beans>

3.1.3 测试

public class HelloWorldTest {

   @Test
   public void test() {
      // 获取IOC容器
      ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml");
      // 获取IOC容器中的bean
      HelloWorld helloworld = (HelloWorld) ioc.getBean("helloworld");
      helloworld.sayHello();
   }

}

3.1.4 注意

Spring 底层默认通过反射技术调用组件类的无参构造器来创建组件对象,这一点需要注意。如果在需要 无参构造器时,没有无参构造器,则会抛出下面的异常:

org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'helloworld' defined in class path resource [applicationContext.xml]: Instantiation of bean failed; nested exception is org.springframework.beans.BeanInstantiationException: Failed to instantiate [com.nick.spring.bean.HelloWorld]: No default constructor found; nested exception is java.lang.NoSuchMethodException: com.nick.spring.bean.HelloWorld. ()

3.2 获取Bean的三种方式

3.2.1 根据id获取

由于 id 属性指定了 bean 的唯一标识,所以根据 bean 标签的 id 属性可以精确获取到一个组件对象。

3.2.2 根据类型获取

注意:根据类型获取bean时,要求IOC容器中有且只有一个类型匹配的bean 若没有任何一个类型匹配的bean,此时抛出异常:NoSuchBeanDefinitionException 若有多个类型匹配的bean,此时抛出异常:NoUniqueBeanDefinitionException

@Test
public void testHelloWorld(){
    ApplicationContext ac = new
    ClassPathXmlApplicationContext("applicationContext.xml");
    HelloWorld bean = ac.getBean(HelloWorld.class);
    bean.sayHello();
}

3.2.3 根据id和类型

结论:

根据类型来获取bean时,在满足bean唯一性的前提下 其实只是看:『对象 instanceof 指定的类型』的返回结果 只要返回的是true就可以认定为和类型匹配,能够获取到。 即通过bean的类型、bean所继承的类的类型、bean所实现的接口的类型都可以获取bean

@Test
public void testHelloWorld(){
    ApplicationContext ac = new
    ClassPathXmlApplicationContext("applicationContext.xml");
    HelloWorld bean = ac.getBean("helloworld", HelloWorld.class);
    bean.sayHello();
}

3.3 依赖注入

3.3.1 setter注入

<bean id="studentTwo" class="com.nick.spring.pojo.Student">
   <!--
      property:通过成员变量的set方法进行赋值
      name:设置需要赋值的属性名(和set方法有关)
      value:设置为属性所赋的值
   -->
   <property name="sid" value="1001"></property>
   <property name="sname" value="张三"></property>
   <property name="age" value="23"></property>
   <property name="gender" value="男"></property>
</bean>

3.3.2 构造器注入

<bean id="studentThree" class="com.nick.spring.pojo.Student">
   <constructor-arg value="1002"></constructor-arg>
   <constructor-arg value="李四"></constructor-arg>
   <constructor-arg value="女"></constructor-arg>
   <constructor-arg value="24" name="age"></constructor-arg>
</bean>

注意: constructor-arg标签还有两个属性可以进一步描述构造器参数:

  • index属性:指定参数所在位置的索引(从0开始)
  • name属性:指定参数名

3.4 特殊值处理

3.4.1 字面量赋值

什么是字面量? int a = 10; 声明一个变量a,初始化为10,此时a就不代表字母a了,而是作为一个变量的名字。当我们引用a的时候,我们实际上拿到的值是10。 而如果a是带引号的:'a',那么它现在不是一个变量,它就是代表a这个字母本身,这就是字面量。所以字面量没有引申含义,就是我们看到的这个数据本身。

3.4.2 null值

<property name="name">
	<null />
</property>

注意:

<property name="name" value="null"></property>

以上写法,为name所赋的值是字符串null

3.4.3 xml实体与CDATA节

<!--
   <:&lt;
   >:&gt;
   CDATA节其中的内容会原样解析<![CDATA[...]]>
   CDATA节是xml中一个特殊的标签,因此不能写在一个属性中
-->
<!--<property name="sname" value="&lt;王五&gt;"></property>-->
<property name="sname">
   <value><![CDATA[<王五>]]></value>
</property>

3.5 为类类型属性赋值

3.5.1 引用外部已声明的bean

<bean id="clazzOne" class="com.nick.spring.bean.Clazz">
    <property name="clazzId" value="1111"></property>
    <property name="clazzName" value="财源滚滚班"></property>
</bean>
<bean id="studentFour" class="com.nick.spring.bean.Student">
    <property name="id" value="1004"></property>
    <property name="name" value="赵六"></property>
    <property name="age" value="26"></property>
    <property name="sex" value="女"></property>
    <!-- ref属性:引用IOC容器中某个bean的id,将所对应的bean为属性赋值 -->
    <property name="clazz" ref="clazzOne"></property>
</bean>

如果错把ref属性写成了value属性,会抛出异常: Caused by: java.lang.IllegalStateException: Cannot convert value of type 'java.lang.String' to required type 'com.nick.spring.bean.Clazz' for property 'clazz': no matching editors or conversion strategy found 意思是不能把String类型转换成我们要的Clazz类型,说明我们使用value属性时,Spring只把这个 属性看做一个普通的字符串,不会认为这是一个bean的id,更不会根据它去找到bean来赋值

3.5.2 内部bean

<bean id="studentFour" class="com.nick.spring.bean.Student">
    <property name="id" value="1004"></property>
    <property name="name" value="赵六"></property>
    <property name="age" value="26"></property>
    <property name="sex" value="女"></property>
    <property name="clazz">
    <!-- 在一个bean中再声明一个bean就是内部bean -->
    <!-- 内部bean只能用于给属性赋值,不能在外部通过IOC容器获取,因此可以省略id属性 -->
        <bean id="clazzInner" class="com.nick.spring.bean.Clazz">
            <property name="clazzId" value="2222"></property>
            <property name="clazzName" value="远大前程班"></property>
        </bean>
    </property>
</bean>

3.5.3 级联属性赋值

<bean id="studentFour" class="com.nick.spring.bean.Student">
    <property name="id" value="1004"></property>
    <property name="name" value="赵六"></property>
    <property name="age" value="26"></property>
    <property name="sex" value="女"></property>
    <!-- 一定先引用某个bean为属性赋值或者实例化,才可以使用级联方式更新属性 -->
    <property name="clazz" ref="clazzOne"></property>
    <property name="clazz.clazzId" value="3333"></property>
    <property name="clazz.clazzName" value="最强王者班"></property>
</bean>

3.6 为数组类型属性赋值

<property name="hobby">
    <array>
        <value>抽烟</value>
        <value>喝酒</value>
        <value>烫头</value>
    </array>
</property>

3.7 为集合类型属性赋值

  • 常规
<property name="students">
    <list>
        <ref bean="studentOne"></ref>
        <ref bean="studentTwo"></ref>
        <ref bean="studentThree"></ref>
    </list>
</property>
  • 使用util约束
<?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:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 		http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">

    <bean id="clazzOne" class="com.nick.spring.pojo.Clazz">
        <property name="students" ref="studentList"></property>
    </bean>

<!--配置一个集合类型的bean,需要使用util的约束-->
    <util:list id="studentList">
        <ref bean="studentOne"></ref>
        <ref bean="studentTwo"></ref>
        <ref bean="studentThree"></ref>
    </util:list>
</beans>

3.8 为Map集合类型属性赋值

  • 常规
<property name="teacherMap">
    <map>
        <entry key="10086" value-ref="teacherOne"></entry>
        <entry key="10010" value-ref="teacherTwo"></entry>
    </map>
</property>

<bean id="teacherOne" class="com.nick.spring.pojo.Teacher">
    <property name="tid" value="10086"></property>
    <property name="tname" value="大宝"></property>
</bean>

<bean id="teacherTwo" class="com.nick.spring.pojo.Teacher">
    <property name="tid" value="10010"></property>
    <property name="tname" value="小宝"></property>
</bean>
  • 使用util约束
<?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:util="http://www.springframework.org/schema/util"
       xsi:schemaLocation="http://www.springframework.org/schema/beans 		http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/util https://www.springframework.org/schema/util/spring-util.xsd">

<bean id="studentFive" class="com.nick.spring.pojo.Student">
    <property name="teacherMap" ref="teacherMap"></property>
</bean>
    
<util:map id="teacherMap">
    <entry key="10086" value-ref="teacherOne"></entry>
    <entry key="10010" value-ref="teacherTwo"></entry>
</util:map>

3.9 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 http://www.springframework.org/schema/beans/spring-beans.xsd">
       
    <bean id="studentSix" class="com.nick.spring.pojo.Student"
	      p:sid="1005" p:sname="小明" p:teacherMap-ref="teacherMap"></bean>
    
</beans>

3.10 引入外部属性文件

3.10.1 加入依赖

		<!-- MySQL驱动 -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>8.0.16</version>
		</dependency>
		<!-- 数据源 -->
		<dependency>
			<groupId>com.alibaba</groupId>
			<artifactId>druid</artifactId>
			<version>1.0.31</version>
		</dependency>

3.10.2 配置

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

	<!--引入jdbc.properties,之后可以通过${key}的方式访问value-->
	<context:property-placeholder location="jdbc.properties"></context:property-placeholder>
	<!--过时了,不建议使用-->
	<!--	<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"></bean>-->

	<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
		<property name="driverClassName" value="${jdbc.driver}"></property>
		<property name="url" value="${jdbc.url}"></property>
		<property name="username" value="${jdbc.username}"></property>
		<property name="password" value="${jdbc.password}"></property>
	</bean>

</beans>

3.11 bean的作用域

3.11.1 概念

在Spring中可以通过配置bean标签的scope属性来指定bean的作用域范围,各取值含义参加下表:

取值含义创建对象的时机
singleton(默认)在IOC容器中,这个bean的对象始终为单实例IOC容器初始化时
prototype这个bean在IOC容器中有多个实例获取bean时

如果是在WebApplicationContext环境下还会有另外两个作用域(但不常用):

取值含义
request在一个请求范围内有效
session在一个会话范围内有效

3.11.2 配置

<!--scope:设置bean的作用域
        scope="singleton|prototype"
        singleton(单例(默认值)):表示获取该bean所对应的对象都是同一个,IOC容器初始化时创建对象
        prototype(多例):表示获取该bean所对应的对象都不是同一个,getBean()时创建对象-->
<bean class="com.nick.bean.User" scope="prototype"></bean>

3.12 bean的生命周期

  1. 实例化(调用无参构造器,bean对象创建)

  2. 依赖注入(给bean对象设置属性)

  3. 后置处理器的postProcessBeforeInitialization(bean对象初始化之前操作)

  4. 初始化(需要通过bean的init-method属性指定初始化的方法)

  5. 后置处理器的postProcessAfterInitialization(bean对象初始化之后操作)

  6. bean对象就绪可以使用

  7. bean对象销毁(需要通过bean的destroy-method属性指定销毁的方法)

  8. IOC容器

    bean的后置处理器会在生命周期的初始化前后添加额外的操作 需要实现BeanPostProcessor接口且配置到IOC容器中 需要注意的是,bean后置处理器不是单独针对某一个bean生效,而是针对IOC容器中所有bean都会执行 注意: 若bean的作用域为单例时,生命周期的前三个步骤(1,2,4)会在获取IOC容器时执行 若bean的作用域为多例时,生命周期的前三个步骤(1,2,4)会在获取bean时执行

3.12.1 配置

<?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="user" class="com.nick.spring.pojo.User" init-method="initMethod" destroy-method="destroyMethod">
		<property name="id" value="1"></property>
		<property name="username" value="admin"></property>
		<property name="password" value="123456"></property>
		<property name="age" value="23"></property>
	</bean>

	<!--	IOC容器中bean的后置处理器要放入IOC容器才能生效-->
	<bean id="myBeanPostProcessor" class="com.nick.spring.process.MyBeanPostProcessor"></bean>

</beans>

3.12.2 bean的后置处理器

public class MyBeanPostProcessor implements BeanPostProcessor {
    @Override
    public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
        //此方法在bean的生命周期初始化之前执行
        System.out.println("MyBeanPostProcessor-->后置处理器postProcessBeforeInitialization");
        return bean;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        //此方法在bean的生命周期初始化之后执行
        System.out.println("MyBeanPostProcessor-->后置处理器postProcessAfterInitialization");
        return bean;
    }
}

3.13 FactoryBean

FactoryBean是Spring提供的一种整合第三方框架的常用机制。和普通的bean不同,配置一个 FactoryBean类型的bean,在获取bean的时候得到的并不是class属性中配置的这个类的对象,而是 getObject()方法的返回值。通过这种机制,Spring可以帮我们把复杂组件创建的详细过程和繁琐细节都 屏蔽起来,只把最简洁的使用界面展示给我们。

FactoryBean是一个接口,需要创建一个类实现该接口 其中有三个方法: getObject():通过一个对象交给IOC容器管理 getObjectType():设置所提供对象的类型 isSingleton():所提供的对象是否单例 当把FactoryBean的实现类配置为bean时,会将当前类中getObject()所返回的对象交给IOC容器管理

3.14 基于xml的自动装配

自动装配: 根据指定的策略,在IOC容器中匹配某一个bean,自动为指定的bean中所依赖的类类型或接口类 型属性赋值,可以通过bean标签中的autowire属性设置自动装配的策略

3.14.1 配置bean

自动装配的策略:

  1. no,default:表示不装配,即bean中的属性不会自动匹配某个bean为属性赋值,此时属性使用默认值

  2. byType:根据要赋值的属性的类型,在IOC容器中匹配某个bean,为属性赋值

    注意:

    ​ 若通过类型没有找到任何一个类型匹配的bean,此时不装配,属性使用默认值

    ​ 若通过类型找到了多个类型匹配的bean,此时会抛出异常NoUniqueBeanDefinitionException

    总结:当使用byType实现自动装配时,IOC容器中有且只有一个类型匹配的bean能够为属性赋值

  3. byName:将要赋值的属性的属性名作为bean的id在IOC容器中匹配某个bean,为属性赋值

    总结:当类型匹配的bean有多个时,此时可以使用byName实现自动装配

<?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="userController" class="com.nick.spring.controller.UserController" autowire="byName">
		<!--<property name="userService" ref="userService"></property>-->
	</bean>

	<bean id="userService" class="com.nick.spring.service.impl.UserServiceImpl" autowire="byName">
		<!--<property name="userDao" ref="userDao"></property>-->
	</bean>

	<bean id="service" class="com.nick.spring.service.impl.UserServiceImpl" autowire="byName">
		<!--<property name="userDao" ref="userDao"></property>-->
	</bean>

	<bean id="userDaoImpl" class="com.nick.spring.dao.impl.UserDaoImpl"></bean>

	<bean id="dao" class="com.nick.spring.dao.impl.UserDaoImpl"></bean>

</beans>

4. 基于注解和xml管理bean

4.1 标记与扫描

4.1.1 注解

和 XML 配置文件一样,注解本身并不能执行,注解本身仅仅只是做一个标记,具体的功能是框架检测 到注解标记的位置,然后针对这个位置按照注解标记的功能来执行具体操作。 本质上:所有一切的操作都是Java代码来完成的,XML和注解只是告诉框架中的Java代码如何执行。

4.1.2 扫描

Spring 为了知道程序员在哪些地方标记了什么注解,就需要通过扫描的方式,来进行检测。然后根据注 解进行后续操作。

4.1.3 导入依赖

<dependencies>
    <!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
    <dependency>
        <groupId>org.springframework</groupId>
        <artifactId>spring-context</artifactId>
        <version>5.3.1</version>
    </dependency>
    <!-- junit测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
</dependencies>

4.1.3 创建Spring配置文件

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

	<!--
		context:exclude-filter:排除扫描
		type:设置排除扫描的方式
		type="annotation|assignable"
		annotation:根据注解的类型进行排除,expression需要设置排除的注解的全类名
		assignable:根据类的类型进行排除,expression需要设置排除的类的全类名

		context:include-filter:包含扫描
		注意:需要在context:component-scan标签中设置use-default-filters="false"
		use-default-filters="true"(默认),所设置的包下所有的类都需要扫描,此时可以使用排除扫描
		use-default-filters="false",所设置的包下所有的类都不需要扫描,此时可以使用包含扫描
	-->

	<!--扫描组件-->
	<context:component-scan base-package="com.nick.spring"></context:component-scan>
	<!--<context:component-scan base-package="com.nick" use-default-filters="false">
		&lt;!&ndash;<context:exclude-filter type="annotation" expression="org.springframework.stereotype.Controller"/>&ndash;&gt;
		&lt;!&ndash;<context:exclude-filter type="assignable" expression="com.nick.controller.UserController"/>&ndash;&gt;
		<context:include-filter type="annotation" expression="org.springframework.stereotype.Controller"/>
	</context:component-scan>-->

	<!--<bean id="service" class="com.nick.service.impl.UserServiceImpl"></bean>

	<bean id="dao" class="com.nick.dao.impl.UserDaoImpl"></bean>-->

</beans>

4.1.4 标识组件的常用注解

  • @Component:将类标识为普通组件
  • @Controller:将类标识为控制层组件
  • @Service:将类标识为业务层组件
  • @Repository:将类标识为持久层组件

4.1.5 组件所对应的bean的id

默认情况 类名首字母小写就是bean的id。例如:UserController类对应的bean的id就是userController。 自定义bean的id,可通过标识组件的注解的value属性设置自定义的bean的id @Service("userService")//默认为userServiceImpl

public class UserServiceImpl implements UserService {}

4.2 基于注解的自动装配

4.2.1 场景模拟

参考基于xml的自动装配 在UserController中声明UserService对象 在UserServiceImpl中声明UserDao对象

4.2.2 @Autowired注解

在成员变量上直接标记@Autowired注解即可完成自动装配,不需要提供setXxx()方法。

@Controller
public class UserController {
    @Autowired
    private UserService userService;
    
    public void saveUser(){
        userService.saveUser();
    }
}

public interface UserService {
    void saveUser();
}

@Service
public class UserServiceImpl implements UserService {
    @Autowired
    private UserDao userDao;
    
    @Override
    public void saveUser() {
        userDao.saveUser();
    }
}

public interface UserDao {
    void saveUser();
}

@Repository
public class UserDaoImpl implements UserDao {
    @Override
    public void saveUser() {
    System.out.println("保存成功");
    }
}

4.2.3 @Autowired注解其他细节

@Autowired注解可以标记在标识在为当前成员变量赋值的有参构造和set方法上

@Controller
public class UserController {
    private UserService userService;
    
    @Autowired
    public UserController(UserService userService){
        this.userService = userService;
	}
    
    public void saveUser(){
        userService.saveUser();
    }
}

4.2.4 @Autowired注解原理

@Autowired注解的原理

  1. 默认通过byType的方式,在IOC容器中通过类型匹配某个bean为属性赋值
  2. 若有多个类型匹配的bean,此时会自动转换为byName的方式实现自动装配的效果 即将要赋值的属性的属性名作为bean的id匹配某个bean为属性赋值
  3. 若byType和byName的方式都无妨实现自动装配,即IOC容器中有多个类型匹配的bean 且这些bean的id和要赋值的属性的属性名都不一致,此时抛异常:NoUniqueBeanDefinitionException
  4. 此时可以在要赋值的属性上,添加一个注解@Qualifier 通过该注解的value属性值,指定某个bean的id,将这个bean为属性赋值

注意:若IOC容器中没有任何一个类型匹配的bean,此时抛出异常:NoSuchBeanDefinitionException 在@Autowired注解中有个属性required,默认值为true,要求必须完成自动装配 可以将required设置为false,此时能装配则装配,无法装配则使用属性的默认值

首先根据所需要的组件类型到IOC容器中查找

  1. 能够找到唯一的bean:直接执行装配

  2. 如果完全找不到匹配这个类型的bean:装配失败

  3. 和所需类型匹配的bean不止一个

    • 没有@Qualifier注解:根据@Autowired标记位置成员变量的变量名作为bean的id进行匹配

      • 能够找到:执行装配

      • 找不到:装配失败

    • 使用@Qualifier注解:根据@Qualifier注解中指定的名称作为bean的id进行匹配

      • 能够找到:执行装配

      • 找不到:装配失败

5. AOP

5.1 代理模式

二十三种设计模式中的一种,属于结构型模式。它的作用就是通过提供一个代理类,让我们在调用目标 方法的时候,不再是直接对目标方法进行调用,而是通过代理类间接调用。让不属于目标方法核心逻辑 的代码从目标方法中剥离出来——解耦。调用目标方法时先调用代理对象的方法,减少对目标方法的调 用和打扰,同时让附加功能能够集中在一起也有利于统一维护。

5.2 场景模拟

public interface Calculator {

    int add(int i, int j);

    int sub(int i, int j);

    int mul(int i, int j);

    int div(int i, int j);

}

public class CalculatorImpl implements Calculator {
    @Override
    public int add(int i, int j) {
        int result = i + j;
        System.out.println("方法内部,result:"+result);
        return result;
    }

    @Override
    public int sub(int i, int j) {
        int result = i - j;
        System.out.println("方法内部,result:"+result);
        return result;
    }

    @Override
    public int mul(int i, int j) {
        int result = i * j;
        System.out.println("方法内部,result:"+result);
        return result;
    }

    @Override
    public int div(int i, int j) {
        int result = i / j;
        System.out.println("方法内部,result:"+result);
        return result;
    }
}

5.3 静态代理

public class CalculatorStaticProxy implements Calculator {

    private CalculatorImpl target;

    public CalculatorStaticProxy(CalculatorImpl target) {
        this.target = target;
    }

    @Override
    public int add(int i, int j) {
        System.out.println("日志,方法:add,参数:"+i+","+j);
        int result = target.add(i, j);
        System.out.println("日志,方法:add,结果:"+result);
        return result;
    }

    @Override
    public int sub(int i, int j) {
        System.out.println("日志,方法:sub,参数:"+i+","+j);
        int result = target.sub(i, j);
        System.out.println("日志,方法:sub,结果:"+result);
        return result;
    }

    @Override
    public int mul(int i, int j) {
        System.out.println("日志,方法:mul,参数:"+i+","+j);
        int result = target.mul(i, j);
        System.out.println("日志,方法:mul,结果:"+result);
        return result;
    }

    @Override
    public int div(int i, int j) {
        System.out.println("日志,方法:div,参数:"+i+","+j);
        int result = target.div(i, j);
        System.out.println("日志,方法:div,结果:"+result);
        return result;
    }
}

5.4 动态代理

public class ProxyFactory {

    private Object target;

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

    public Object getProxy(){
        /**
         * ClassLoader loader:指定加载动态生成的代理类的类加载器
         * Class[] interfaces:获取目标对象实现的所有接口的class对象的数组
         * InvocationHandler h:设置代理类中的抽象方法如何重写
         */
        ClassLoader classLoader = this.getClass().getClassLoader();
        Class<?>[] interfaces = target.getClass().getInterfaces();
        InvocationHandler h = new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                Object result = null;
                try {
                    System.out.println("日志,方法:"+method.getName()+",参数:"+ Arrays.toString(args));
                    //proxy表示代理对象,method表示要执行的方法,args表示要执行的方法到的参数列表
                    result = method.invoke(target, args);
                    System.out.println("日志,方法:"+method.getName()+",结果:"+ result);
                } catch (Exception e) {
                    e.printStackTrace();
                    System.out.println("日志,方法:"+method.getName()+",异常:"+ e);
                } finally {
                    System.out.println("日志,方法:"+method.getName()+",方法执行完毕");
                }
                return result;
            }
        };
        return Proxy.newProxyInstance(classLoader, interfaces, h);
    }
}

5.5 两种动态代理

5.5.1 jdk动态代理

要求必须有接口,最终生成的代理类和目标类实现相同的接口,在com.sun.proxy包下,类名为$proxy+一个数字

5.5.2 cglib动态代理

最终生成的代理类会继承目标类,并且和目标类在相同的包下

5.6 AOP概念及相关术语

5.6.1 概述

AOP(Aspect Oriented Programming)是一种设计思想,是软件设计领域中的面向切面编程,它是面 向对象编程的一种补充和完善,它以通过预编译方式和运行期动态代理方式实现在不修改源代码的情况 下给程序动态统一添加额外功能的一种技术。

5.6.2 相关术语

  1. 横切关注点

    • 从每个方法中抽取出来的同一类非核心业务。在同一个项目中,我们可以使用多个横切关注点对相关方法进行多个不同方面的增强。 这个概念不是语法层面天然存在的,而是根据附加功能的逻辑上的需要:有十个附加功能,就有十个横切关注点。
  2. 通知

    每一个横切关注点上要做的事情都需要写一个方法来实现,这样的方法就叫通知方法。

    • 前置通知:在被代理的目标方法前执行
    • 返回通知:在被代理的目标方法成功结束后执行(寿终正寝)
    • 异常通知:在被代理的目标方法异常结束后执行(死于非命)
    • 后置通知:在被代理的目标方法最终结束后执行(盖棺定论)
    • 环绕通知:使用try...catch...finally结构围绕整个被代理的目标方法,包括上面四种通知对应的所有位置
  3. 切面

    封装通知方法的类。

  4. 目标

    被代理的目标对象。

  5. 代理

    向目标对象应用通知之后创建的代理对象。

  6. 连接点

    这也是一个纯逻辑概念,不是语法定义的。 把方法排成一排,每一个横切位置看成x轴方向,把方法从上到下执行的顺序看成y轴,x轴和y轴的交叉 点就是连接点。

连接点.png 7. 切入点

定位连接点的方式。 每个类的方法中都包含多个连接点,所以连接点是类中客观存在的事物(从逻辑上来说)。 如果把连接点看作数据库中的记录,那么切入点就是查询记录的 SQL 语句。 Spring 的 AOP 技术可以通过切入点定位到特定的连接点。 切点通过 org.springframework.aop.Pointcut 接口进行描述,它使用类和方法作为连接点的查询条 件。

5.6.3 作用

  • 简化代码:把方法中固定位置的重复的代码抽取出来,让被抽取的方法更专注于自己的核心功能,提高内聚性。
  • 代码增强:把特定的功能封装到切面类中,看哪里有需要,就往上套,被套用了切面逻辑的方法就被切面给增强了。

5.6.4 各种通知与切入点表达式

5.6.4.1 添加依赖

<!-- spring-aspects会帮我们传递过来aspectjweaver -->
<dependency>
   <groupId>org.springframework</groupId>
   <artifactId>spring-aspects</artifactId>
   <version>5.3.1</version>
</dependency>

5.6.4.2 开启包扫描与基于注解的AOP

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

   <!--
      AOP的注意事项:
      切面类和目标类都需要交给IOC容器管理
      切面类必须通过@Aspect注解标识为一个切面
      在Spring的配置文件中设置<aop:aspectj-autoproxy />开启基于注解的AOP
   -->
   <context:component-scan base-package="com.nick.spring.aop.annotation"></context:component-scan>

   <!--开启基于注解的AOP-->
   <aop:aspectj-autoproxy/>

</beans>

5.6.4.3 准备切面类

// @Component注解保证这个切面类能够放入IOC容器
@Component
@Aspect // 将当前组件标识为切面
@Order(1)
public class LoggerAspect {

   @Pointcut("execution(* com.nick.spring.aop.annotation.CalculatorImpl.*(..))")
   public void pointCut() {
   }

   //@Before("execution(public int com.nick.spring.aop.annotation.CalculatorImpl.add(int, int))")
   //@Before("execution(* com.nick.spring.aop.annotation.CalculatorImpl.*(..))")
   @Before("pointCut()")
   public void beforeAdviceMethod(JoinPoint joinPoint) {
      // 获取连接点所对应方法的签名信息
      Signature signature = joinPoint.getSignature();
      // 获取连接点所对应方法的参数
      Object[] args = joinPoint.getArgs();
      System.out.println("LoggerAspect,方法:" + signature.getName() + ",参数:" + Arrays.toString(args));
   }

   @After("pointCut()")
   public void afterAdviceMethod(JoinPoint joinPoint) {
      // 获取连接点所对应方法的签名信息
      Signature signature = joinPoint.getSignature();
      System.out.println("LoggerAspect,方法:" + signature.getName() + ",执行完毕");
   }

   /**
    * 在返回通知中若要获取目标对象方法的返回值
    * 只需要通过@AfterReturning注解的returning属性
    * 就可以将通知方法的某个参数指定为接收目标对象方法的返回值的参数
    */
   @AfterReturning(value = "pointCut()", returning = "result")
   public void afterReturningAdviceMethod(JoinPoint joinPoint, Object result) {
      // 获取连接点所对应方法的签名信息
      Signature signature = joinPoint.getSignature();
      System.out.println("LoggerAspect,方法:" + signature.getName() + ",结果:" + result);
   }

   /**
    * 在异常通知中若要获取目标对象方法的异常
    * 只需要通过AfterThrowing注解的throwing属性
    * 就可以将通知方法的某个参数指定为接收目标对象方法出现的异常的参数
    */
   @AfterThrowing(value = "pointCut()", throwing = "ex")
   public void afterThrowingAdviceMethod(JoinPoint joinPoint, Throwable ex) {
      // 获取连接点所对应方法的签名信息
      Signature signature = joinPoint.getSignature();
      System.out.println("LoggerAspect,方法:" + signature.getName() + ",异常:" + ex);
   }

   @Around("pointCut()")
   // 环绕通知的方法的返回值一定要和目标对象方法的返回值一致
   public Object aroundAdviceMethod(ProceedingJoinPoint joinPoint) {
      Object result = null;
      try {
         System.out.println("环绕通知-->前置通知");
         // 表示目标对象方法的执行
         result = joinPoint.proceed();
         System.out.println("环绕通知-->返回通知");
      } catch (Throwable throwable) {
         throwable.printStackTrace();
         System.out.println("环绕通知-->异常通知");
      } finally {
         System.out.println("环绕通知-->后置通知");
      }
      return result;
   }

}

5.6.4.4 通知

  1. 在切面中,需要通过指定的注解将方法标识为通知方法

    • @Before:前置通知,在目标对象方法执行之前执行
    • @After:后置通知,在目标对象方法的finally字句中执行
    • @AfterReturning:返回通知,在目标对象方法返回值之后执行
    • @AfterThrowing:异常通知,在目标对象方法的catch字句中执行
  2. 切入点表达式:设置在标识通知的注解的value属性中

    execution(public int com.nick.spring.aop.annotation.CalculatorImpl.add(int, int) execution(* com.nick.spring.aop.annotation.CalculatorImpl.(..) 第一个表示任意的访问修饰符和返回值类型 第二个表示类中任意的方法 ..表示任意的参数列表 类的地方也可以使用,表示包下所有的类

  3. 重用切入点表达式

    //@Pointcut声明一个公共的切入点表达式 @Pointcut("execution(* com.nick.spring.aop.annotation.CalculatorImpl.*(..))") public void pointCut(){} 使用方式:@Before("pointCut()")

  4. 获取连接点的信息

    在通知方法的参数位置,设置JoinPoint类型的参数,就可以获取连接点所对应方法的信息 //获取连接点所对应方法的签名信息 Signature signature = joinPoint.getSignature(); //获取连接点所对应方法的参数 Object[] args = joinPoint.getArgs();

  5. 切面的优先级

    可以通过@Order注解的value属性设置优先级,默认值Integer的最大值 @Order注解的value属性值越小,优先级越高

5.6.5 基于xml的AOP

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

   <!--扫描组件-->
   <context:component-scan base-package="com.nick.spring.aop.xml"></context:component-scan>

   <aop:config>
      <!--设置一个公共的切入点表达式-->
      <aop:pointcut id="pointCut" expression="execution(* com.nick.spring.aop.xml.CalculatorImpl.*(..))"/>
      <!--将IOC容器中的某个bean设置为切面-->
      <aop:aspect ref="loggerAspect">
         <aop:before method="beforeAdviceMethod" pointcut-ref="pointCut"></aop:before>
         <aop:after method="afterAdviceMethod" pointcut-ref="pointCut"></aop:after>
         <aop:after-returning method="afterReturningAdviceMethod" returning="result"
                              pointcut-ref="pointCut"></aop:after-returning>
         <aop:after-throwing method="afterThrowingAdviceMethod" throwing="ex"
                             pointcut-ref="pointCut"></aop:after-throwing>
         <aop:around method="aroundAdviceMethod" pointcut-ref="pointCut"></aop:around>
      </aop:aspect>
      <aop:aspect ref="validateAspect" order="1">
         <aop:before method="beforeMethod" pointcut-ref="pointCut"></aop:before>
      </aop:aspect>
   </aop:config>

</beans>

6. 声明式事务

6.1 简介

Spring 框架对 JDBC 进行封装,使用 JdbcTemplate 方便实现对数据库操作

6.2 准备工作

  1. 加入依赖

    <dependencies>
    
       <!-- 基于Maven依赖传递性,导入spring-context依赖即可导入当前所需所有jar包 -->
       <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-context</artifactId>
          <version>5.3.1</version>
       </dependency>
    
       <!-- Spring 持久化层支持jar包 -->
       <!-- Spring 在执行持久化层操作、与持久化层技术进行整合过程中,需要使用orm、jdbc、tx三个jar包 -->
       <!-- 导入 orm 包就可以通过 Maven 的依赖传递性把其他两个也导入 -->
       <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-orm</artifactId>
          <version>5.3.1</version>
       </dependency>
    
       <!-- Spring 测试相关 -->
       <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-test</artifactId>
          <version>5.3.1</version>
       </dependency>
    
       <!-- junit测试 -->
       <dependency>
          <groupId>junit</groupId>
          <artifactId>junit</artifactId>
          <version>4.12</version>
          <scope>test</scope>
       </dependency>
    
       <!-- MySQL驱动 -->
       <dependency>
          <groupId>mysql</groupId>
          <artifactId>mysql-connector-java</artifactId>
          <version>8.0.16</version>
       </dependency>
       <!-- 数据源 -->
       <dependency>
          <groupId>com.alibaba</groupId>
          <artifactId>druid</artifactId>
          <version>1.0.31</version>
       </dependency>
       <dependency>
          <groupId>org.springframework</groupId>
          <artifactId>spring-aspects</artifactId>
          <version>5.3.1</version>
       </dependency>
    </dependencies>
    
  2. 创建jdbc.properties

    jdbc.driver=com.mysql.cj.jdbc.Driver
    jdbc.url=jdbc:mysql://localhost:3308/ssm?serverTimezone=UTC
    jdbc.username=root
    jdbc.password=1234
    
  3. 创建spring配置文件

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

   <!--引入jdbc.properties-->
   <context:property-placeholder location="classpath:jdbc.properties"></context:property-placeholder>

   <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
      <property name="driverClassName" value="${jdbc.driver}"></property>
      <property name="url" value="${jdbc.url}"></property>
      <property name="username" value="${jdbc.username}"></property>
      <property name="password" value="${jdbc.password}"></property>
   </bean>

   <bean class="org.springframework.jdbc.core.JdbcTemplate">
      <property name="dataSource" ref="dataSource"></property>
   </bean>

</beans>

6.3 测试

//spring整合junit5注解
// @SpringJUnitConfig(locations="classpath:spring-jdbc.xml")

//指定当前测试类在Spring的测试环境中执行,此时就可以通过注入的方式直接获取IOC容器中bean
@RunWith(SpringJUnit4ClassRunner.class)
//设置Spring测试环境的配置文件
@ContextConfiguration("classpath:spring-jdbc.xml")
public class JdbcTemplateTest {

    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Test
    public void testInsert(){
        String sql = "insert into t_user values(null,?,?,?,?,?)";
        jdbcTemplate.update(sql, "root", "123", 23, "女", "123@qq.com");
    }

    @Test
    public void testGetUserById(){
        String sql = "select * from t_user where id = ?";
        User user = jdbcTemplate.queryForObject(sql, new BeanPropertyRowMapper<>(User.class), 1);
        System.out.println(user);
    }

    @Test
    public void testGetAllUser(){
        String sql = "select * from t_user";
        List<User> list = jdbcTemplate.query(sql, new BeanPropertyRowMapper<>(User.class));
        list.forEach(System.out::println);
    }

    @Test
    public void testGetCount(){
        String sql = "select count(*) from t_user";
        Integer count = jdbcTemplate.queryForObject(sql, Integer.class);
        System.out.println(count);
    }

}

6.4 基于注解的声明式事务

6.4.1 编程式事务

Connection conn = ...;
	try{
		// 开启事务:关闭事务的自动提交
		conn.setAutoCommit(false);
		// 核心操作
		// 提交事务
		conn.commit();
	}catch(Exception e){
		// 回滚事务
		conn.rollBack();
	}finally{
		// 释放数据库连接
		conn.close();
	}

6.4.2 配置

在Spring的配置文件中添加配置:

<!--配置事务管理器-->
	<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
		<property name="dataSource" ref="dataSource"></property>
	</bean>

	<!--
		开启事务的注解驱动
		将使用@Transactional注解所标识的方法或类中所有的方法使用事务进行管理
		transaction-manager属性设置事务管理器的id
		若事务管理器的bean的id默认为transactionManager,则该属性可以不写
	-->
	<tx:annotation-driven transaction-manager="transactionManager"/>

基于注解的声明式事务.png

6.4.3 步骤

  1. 在Spring的配置文件中配置事务管理器
  2. 开启事务的注解驱动
  3. 在需要被事务管理的方法上,添加@Transactional注解,该方法就会被事务管理

@Transactional注解标识的位置:

  • 标识在方法上
  • 标识在类上,则类中所有的方法都会被事务管理

6.4.4 事务的属性

  1. 只读

    @Transactional(readOnly = true)

  2. 超时

    超时回滚,释放资源。

  3. 回滚策略

    声明式事务默认只针对运行时异常回滚,编译时异常不回滚。

    可以通过@Transactional中相关属性设置回滚策略

    • rollbackFor属性:需要设置一个Class类型的对象
    • rollbackForClassName属性:需要设置一个字符串类型的全类名
    • noRollbackFor属性:需要设置一个Class类型的对象
    • rollbackFor属性:需要设置一个字符串类型的全类名
  4. 隔离级别

    数据库系统必须具有隔离并发运行各个事务的能力,使它们不会相互影响,避免各种并发问题。一个事 务与其他事务隔离的程度称为隔离级别。SQL标准中规定了多种事务隔离级别,不同隔离级别对应不同 的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱。

    • @Transactional(isolation = Isolation.DEFAULT)//使用数据库默认的隔离级别
    • @Transactional(isolation = Isolation.READ_UNCOMMITTED)//读未提交
    • @Transactional(isolation = Isolation.READ_COMMITTED)//读已提交
    • @Transactional(isolation = Isolation.REPEATABLE_READ)//可重复读
    • @Transactional(isolation = Isolation.SERIALIZABLE)//串行化
    隔离级别脏读不可重复读幻读OracleMySQL
    READ UNCOMMITTED×
    READ COMMITTED√(默认)
    REPEATABLE READ×√(默认)
    SERIALIZABLE
  5. 传播行为

    当事务方法被另一个事务方法调用时,必须指定事务应该如何传播。例如:方法可能继续在现有事务中 运行,也可能开启一个新事务,并在自己的事务中运行。

6.4.5 基于xml

<aop:config>
    <!-- 配置事务通知和切入点表达式 -->
    <aop:advisor advice-ref="txAdvice" pointcut="execution(*
    com.nick.spring.tx.xml.service.impl.*.*(..))"></aop:advisor>
</aop:config>
<!-- tx:advice标签:配置事务通知 -->
<!-- id属性:给事务通知标签设置唯一标识,便于引用 -->
<!-- transaction-manager属性:关联事务管理器 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
    <tx:attributes>
        <!-- tx:method标签:配置具体的事务方法 -->
        <!-- name属性:指定方法名,可以使用星号代表多个字符 -->
        <tx:method name="get*" read-only="true"/>
        <tx:method name="query*" read-only="true"/>
        <tx:method name="find*" read-only="true"/>
        <!-- read-only属性:设置只读属性 -->
        <!-- rollback-for属性:设置回滚的异常 -->
        <!-- no-rollback-for属性:设置不回滚的异常 -->
        <!-- isolation属性:设置事务的隔离级别 -->
        <!-- timeout属性:设置事务的超时属性 -->
        <!-- propagation属性:设置事务的传播行为 -->
        <tx:method name="save*" read-only="false" rollback-
        for="java.lang.Exception" propagation="REQUIRES_NEW"/>
        <tx:method name="update*" read-only="false" rollback-
        for="java.lang.Exception" propagation="REQUIRES_NEW"/>
        <tx:method name="delete*" read-only="false" rollback-
        for="java.lang.Exception" propagation="REQUIRES_NEW"/>
    </tx:attributes>
</tx:advice>