参考视频

SpringIOC控制反转(Inverse of Control)

  1. IOC的概念:控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现对象的创建,属性赋值,依赖的管理。
  2. IOC的实现方式:IOC的实现方式多种多样,当前比较流行的实现方式是依赖 注入(DI)。
  3. DI(dependency injection)的概念:指程序运行过程中,若需要调用另一个对象协助时,无须在代码中创建被调用者,而是依赖于外部容器,由外部容器创建后传递给程序

Spring装载Bean

1.默认装载方式

代码通过getBean()方式从容器获取指定的 Bean实例,容器首先会调用Bean类的无参构造器,创建空值的实例对象。(Bean的定义:Spring容器是一个超级大工厂,负责创建、管理所有的Java对象,这些 Java 对象被称为 Bean。

2.容器中Bean的作用域(默认singleton)

当通过 Spring 容器创建一个 Bean 实例时,不仅可以完成 Bean 的实例化,还可以通过 scope 属性,为 Bean 指定特定的作用域。Spring 支持 5 种作用域。

● singleton:单态模式。即在整个 Spring 容器中,
使用 singleton 定义的 Bean 将是单例的, 只有一个实例。默认为单态的。
当spring核心配置文件被加载时,实例化配置的Bean实例。
对象创建:当应用加载,创建容器时对象就被创建。

● prototype:原型模式。即每次使用 getBean 方法获取的同一个的实例都是一个 新的实例。
在代码中使用该Bean实例时才进行装配
对象创建:当使用对象时,创建新的对象实例

● request:对于每次 HTTP 请求,都将会产生一个不同的 Bean 实例。

● session:对于每个不同的 HTTP session,都将产生一个不同的 Bean 实例。
3.Bean生命周期配置
  1. init-method:指定类中的初始化方法名称,对象创建之后调用
  2. destroy-method:指定类中的销毁方法名称

Bean实例化的三种方式

1.无参构造方法实例化
package com.nrvcer;

public interface UserDao {
    public abstract void save();
}

package com.nrvcer.impl;

import com.nrvcer.UserDao;

public class UserDaoImpl implements UserDao {
    public void save() {
        System.out.println("save success!");
    }
}

// applicationContext.xml如下
<bean id="userDao" class="com.nrvcer.impl.UserDaoImpl"></bean>
2.工厂静态方法实例化
package com.nrvcer.factory;

import com.nrvcer.UserDao;
import com.nrvcer.impl.UserDaoImpl;

public class StaticFactory {
    public static UserDao getUserDao() {
        return new UserDaoImpl();
    }
}

// applicationContext.xml如下
<!--静态工厂可以直接调用方法,无需创建工厂类对象 -->
<bean id="userDao" class="com.nrvcer.factory.StaticFactory" factory-method="getUserDao"></bean>
3.工厂实例方法实例化
package com.nrvcer.factory;

import com.nrvcer.UserDao;
import com.nrvcer.impl.UserDaoImpl;

public class DynamicFactory {
    public UserDao getUserDao() {
        return new UserDaoImpl();
    }
}

// applicationContext.xml如下
<!-- 动态工厂需要先有工厂类对象-->
    <bean id="factory" class="com.nrvcer.factory.DynamicFactory"></bean>
    <bean id="userDao" factory-bean="factory" factory-method="getUserDao"></bean>

Spring中基于XML的注入

1.xml注入的概念
  1. bean实例在调用无参构造器创建了空值对象后,就要对 bean对象的属性进行初始化。初始化是由容器自动完成的,称为注入。
  2. 根据注入方式的不同,常用的有两类:设值注入(set注入)、构造注入。
  3. 注入数据的三种数据类型:普通数据类型、引用数据类型、集合数据类型。
2.设值注入

设值注入是指,通过 setter 方法传入被调用者的实例,这样就不用通过getBean方法从容器中获取被调用者的实例。要求被调用者所在类需要有setter方法。

  1. 简单数据类型示例
<!-- Student类中必须有setter方法-- >
<!-- id属性值表示Bean实例在spring容器中的唯一标识-->
<!-- class属性值表示类的全限定名-->
<bean id="student" class="impl.Student">
    <property name="name" value="张三"></property>
    <property name="age" value="23"></property>
</bean>
  1. 引用数据类型示例,需要使用bean标签的ref属性。ref的使用场景:指定 bean 的某属性值为另一 bean 的实例时,通过 ref 指定它们间的引用关系。ref 的值必须为某 bean 的 id 值。
<!-- Student类中的school属性是一个引用数据类型-->
<bean id="school" class="nrv.School">
    <property name="addr" value="浙江大学"></property>
</bean>
<bean id="stu" class="nrv.Student">
    <property name="name" value="李四"></property>
    <property name="age" value="24"></property>
    <property name="school" ref="school"></property>
</bean>
3.构造注入
  1. 标签中用于指定参数的属性有:index和name
<!-- 要求需要有有参构造函数-->
<!-- 1. name:指定参数名称。-->
<bean id="school" class="nrv.School">
    <constructor-arg name="addr" value="南昌大学"/>
</bean>
<bean id="stu" class="nrv.Student">
    <constructor-arg name="name" value="王五"></constructor-arg>
    <constructor-arg name="age" value="34"></constructor-arg>
    <constructor-arg name="school" ref="school"></constructor-arg>
</bean>

<!-- index  指明该参数对应着构造器的第几个参数,从 0 开始-->
<bean id="school" class="nrv.School">
    <constructor-arg index="0" value="南昌大学"/>
</bean>
<bean id="stu" class="nrv.Student">
    <constructor-arg index="0" value="王五"></constructor-arg>
    <constructor-arg index="1" value="34"></constructor-arg>
    <constructor-arg index="2" ref="school"></constructor-arg>
</bean>
4.具有集合性质的属性注入
  1. MyCollections类中的属性如下:
private String[] strs;
private List<Student> students;
private Set<String> mySet;
private Map<String,Integer> myMap;
private Properties myPro;
  1. applicationContext.xml(spring配置文件名任意,推荐使用applicationContext)文件如下:
<bean id="myStudent1" class="impl.Student">
    <property name="name" value="张三"></property>
    <property name="age" value="23"></property>
</bean>
<bean id="myStudent2" class="impl.Student">
    <property name="name" value="李四"></property>
    <property name="age" value="25"></property>
</bean>
<bean id="myCollections" class="impl.MyCollections">
    <!-- 为数组注入值-->
    <property name="strs">
        <array>
            <value>Hello</value>
            <value>World</value>
        </array>
    </property>
    <!--为list注入值-->
    <property name="students">
        <list>
            <ref bean="myStudent1"></ref>
            <ref bean="myStudent2"></ref>
        </list>
    </property>
    <!-- 为Set注入值-->
    <property name="mySet">
        <set>
            <value>大叔</value>
            <value>萝莉</value>
        </set>
    </property>
    <!-- 为Map注入值-->
    <property name="myMap">
        <map>
            <entry key="height" value="175"></entry>
            <entry key="weight" value="138"></entry>
        </map>
    </property>
    <!--为Propertied注入值 -->
    <property name="myPro">
        <props>
            <prop key="user">NrvCer</prop>
            <prop key="password">123456</prop>
        </props>
    </property>
    <!-- 为复合集合属性注入-->
    <property name="list">
        <list>
            <map>
                <entry key="1" value="a"></entry>
                <entry key="2" value="b"></entry>
            </map>
        </list>
    </property>
</bean>
5.对于引用类型属性的自动注入(可以取代ref)

对于引用类型属性的注入,也可不在配置文件中显示的注入。可以通过为标签设置autowire属性,为引用类型属性进行隐式自动注入(默认是不自动注入引用类型属性)。autowire的属性值有两种:byName,byType.

根据自动注入判断标准的不同,可以分为两种:
1. byName:根据名称自动注入
2. byType:根据类型自动注入
  1. byName方式自动注入的要求:配置文件中被调用者 bean 的 id 值与代码中调用者bean类的属性名相同。byName方式自动注入举例:
<!-- idschool即为Student的一个属性名-->
<bean id="school" class="nrv.School">
    <property name="addr" value="浙江大学"></property>
</bean>
<bean id="stu" class="nrv.Student" autowire="byName">
    <property name="name" value="李四"></property>
    <property name="age" value="24"></property>
</bean>
  1. byType方式自动注入举例
<bean id="mySchool" class="nrv.School">
    <property name="addr" value="浙江大学"></property>
</bean>
<bean id="stu" class="nrv.Student" autowire="byType">
    <property name="name" value="李四"></property>
    <property name="age" value="24"></property>
</bean>

为应用指定多个配置文件

在实际应用里,随着应用规模的增加,系统中Bean数量也大量增加,导致配置文件变得非常庞大、臃肿。为了避免这种情况的产生,提高配置文件的可读性与可维护性,可以将Spring 配置文件分解成多个配置文件。

1.平等关系的配置文件
// 各个配置文件间为并列关系,不分主次
String[] resource = {"xxx1.xml","xxx2.xml"};
// ClassPathXmlApplicationContext实现类表示从类的根路径下即resources目录下加载配置文件
ApplicationContext ac = new ClassPathXmlApplicationContext(resource);
2.包含关系的配置文件
<!-- spring主配置文件中通过import标签进行加载其他子文件
使用主配置文件对容器进行初始化即可。
-->
<beans>
<!--    <import resource="classpath:impl/serviceImpl.xml"></import>-->
    <import resource="classpath:impl/*.xml"/>
</beans>

Spring中基于注解的依赖注入(掌握)

对于 DI 使用注解,将不再需要在Spring配置文件中声明 bean 实例。

1.spring中的原始注解

1.实现DI的步骤
  1. 在pom.xml文件中添加依赖
<!-- 如果在pom.xml中已经添加了spring-context,那此依赖自动包含AOP,无需再次添加。-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aop</artifactId>
   <version>4.3.16.RELEASE</version>
</dependency> 

  1. 需要更换配置文件头,加入 spring-context.xsd 约束
<!-- 如果使用IDEA进行编辑,这个文件头在声明组件
扫描器时会自动更换-->
  1. 在类上使用注解
  2. Spring主配置文件中配置组件扫描器,用于在指定的基本包中或者其子包中扫描注解
<!-- 位于beans标签下,将会扫描service包下的注解-->
<context:component-scan base-package="service"></context:component-scan>

<!-- 指定不同的包路径的两种方式:-->
<!-- 1.方式1:使用多个context:component-scan指定不同的包路径 -->
<!-- 2.方式2:多个base-package 的值之间使用逗号或分号分隔符-->
<!--方式3:base-package的值指定到父报名-->
2.定义 Bean(用于创建对象)的注解@Component

在类上使用注解@Component。@Component注解的value属性用于指定创建出来的Bean的id,如果省略该属性,则Bean的id为类名的首字母小写。 Spring 还提供了 3 个功能基本和@Component 等效的注解:

● @Repository 用于对 DAO 实现类进行注解
● @Service 用于对 Service 实现类进行注解
● @Controller 用于对 Controller 实现类进行注解
  1. 简单例子
// value可以省略,该属性用于指定Bean的id
@Component(value="student")
public class Student {
3.简单类型的注入,使用@Value注解

在属性上使用注解@Value,该注解的 value 属性用于指定要注入的值。使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加到 setter 上。

 @Value("张三")
private String name;
@Value(value="23")
private String age;
4.byType自动注入使用@Autowired(按照数据类型从spring容器中进行匹配的)

在引用属性上使用注解@Autowired,该注解默认使用按类型自动装配Bean的方式。 使用该注解完成属性注入时,类中无需 setter。当然,若属性有setter,则也可将其加到 setter 上。

// Student类中
@Value("张三")
private String name;
@Value(value="23")
private String age;
@Autowired
private School school;
5.byName 自动注入使用@Autowired 与@Qualifier(按照id值从容器中进行匹配)

需要在引用属性上联合使用注解@Autowired 与@Qualifier。@Qualifier 的 value 属性用于指定要匹配的 Bean 的 id 值。同样类中无需 setter,也可加到 setter 上。
举例:

@Autowired
@Qualifier(value="school")
private School school;

@Autowired 还有一个属性 required,默认值为 true,表示当匹配失败后,会终止程序运行。若将其值设置为 false,则匹配失败,将被忽略,未匹配的属性值为 null。

6.JDK 注解@Resource 自动注入

Spring提供了对 jdk中@Resource注解的支持。@Resource 注解既可以按名称匹配Bean,也可以按类型匹配 Bean.默认是按名称注入

1、byType 注入引用类型属性
@Resource 注解若不带任何参数,采用默认按名称的方式注入,按名称不能注入 bean,则会按照类型进行 Bean 的匹配注入。

// 先按照名称注入,如果名称注入不行,则按照类型
 @Resource
private School school;

2、byName 注入引用类型属性
@Resource 注解指定其 name 属性,则 name 的值即为按照名称进行匹配的 Bean 的 id。

// 指定按照名称注入,我就需要Bean id为school的对象
// 相当于@Autowired + @Qualifier(value="school")
@Resource(name = "school")
private School school;
7.@Scope

@Scope注解标注Bean的作用范围

8.@PostConstruct

@PostConstruct注解使用在方法上标注该方法是Bean的初始化方法

9.@PreDestroy

@PreDestroy注解使用在方法上标注该方法是Bean的销毁方法

2.spring中的新注解

使用spring原始注解不能够全部替代xml配置文件,比如说还能够使用注解替代的配置如下:

  1. 非自定义的Bean的配置:<bean></bean>
  2. 加载properties文件的配置:context:property-placeholder
  3. 组件扫描器的配置:context:component-scan
  4. 引入其他文件:
1.@Configuration

该注解用于指定当前类是一个spring配置类,当创建容器时会从该类上加载注解

2.@ComponentScan

该注解用于指定spring在初始化容器时要扫描的包,作用和在springxml配置文件中配置组件扫描器context:component-scan base-package="com.nrvcer"/>一样

3.@Bean

该注解使用在方法上,标注将该方法的返回值(一般为对象)存储到spring容器中

4.@PropertySource

该注解用于加载.properties文件中的配置

5.@Import

该注解用于导入其他配置类

AOP(aspect oriented programming)的三要素

面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、事务、日志、缓存等。

  1. 切面(aspect):表示给业务方法增加的功能(泛指交叉业务逻辑)。常见的切面有日志、事务、统计信息、权限验证等
    1. 一个切面有三个关键的要素:
      1. 切面的功能代码,表示切面要干什么
      2. 切面的执行位置,使用Pointcut表示切面执行的位置
      3. 切面的执行时间,使用Advice表示执行时间
  2. 切入点(pointcut):多个连接点的集合,多个业务接口中的方法。表示切面功能执行的位置。标记为final的方法是不能作为连接点与切入点的(不能被增强)
  3. 通知(advice,也叫做增强):表示切面执行的时间,目标方法前,目标方法后等。通知定义了增强代码切入到目标代码的时间点。这三个为AOP的三要素。
  4. 连接点(joinpoint):连接点指的是可以被切面织入的具体方法,通常业务接口中的方法均为连接点。
  5. 目标对象(target):指的是要被增强的对象,即包含主业务逻辑的类的对象

AspectJ

1.概述

Spring 又将 AspectJ的对于AOP的实现也引入到了自己的框架中。在 Spring 中使用 AOP开发时,一般使用AspectJ 的实现方式。

2.AspectJ的切入点表达式:表示切面执行的位置

切入点表达式用于指定切入点的。 切入点表达式共有四个部分:

// []表示可省略
// 参数中只需要参数的类型,参数名不需要
execution([访问权限] 方法返回值类型 [包名类名]方法声明(参数) [异常类型])

在切入点表达式中可以使用以下符号:

*:0至多个任意字符
..: 用在方法参数中,表示任意多个参数;用在包名后,表示当前包及其子包
+:用在类名后,表示当前类及其子类;用在接口名后,表示当前接口及其实现类

举例:

  1. 指定切入点为:任意公共方法
execution(public * *(..))
  1. 指定切入点为:任何一个以set开始的方法,任意方法返回值,任意方法参数
execution(* set*(..))
  1. 指定切入点为:定义在service包里的任意类的任意方法
execution(* com.xyz.service.*.*(..))
  1. 指定切入点为定义在service包或者子包中的任意类的任意方法
execution(* com.xyz.service..*.*(..))
  1. 指定切入点为:指定所有包下的service子包下的所有类(接口)中所有方法
execute(* *..service.*.*(..))
3.AspectJ 的开发环境
// 需要在pom.xml文件中引入AspectJ的依赖
<!--aspectj的依赖-->
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-aspects</artifactId>
  <version>4.3.16.RELEASE</version>
</dependency>
4.AspectJ通知注解:表示切面的执行时间

AspectJ 对于 AOP的实现有注解和XML配置文件两种方式,常用是注解方式。

  1. 通知注解:在切面类中修饰方法的注解,这些注解体现了通知类型。使用了通知注解修饰的方法称之为通知方法。 一共有五种通知类型,就对应了五种通知注解(23456)
1. 前置通知:@Before
2. 后置通知:@AfterReturning
3. 环绕通知:@Around
4. 异常通知:@AfterThrowing
5. 最终通知:@After
  1. @Before前置通知-方法有JoinPoint参数.所有的通知方法都可以包含该参数,并且该参数必须是第一个位置。
// 所有通知方法都可以包含JoinPoint参数
// JoinPoint参数表示要加入切面功能的业务方法
@Before(value="execution(* *..*.*.*(..))")
public void twoAspect(JoinPoint joinPoint) {
    // JoinPoint参数能够获取到方法的定义,方法的参数等信息

    // 连接点方法的定义:void com.nrvcer.service.SomeService.dosome(String,Integer)
    System.out.println("连接点方法的定义:" + joinPoint.getSignature());
    System.out.println("连接点方法的名称:" + joinPoint.getSignature().getName());
    // 连接点方法的参数个数:2
    System.out.println("连接点方法的参数个数:" + joinPoint.getArgs().length);
    // 方法参数的信息
    Object[] args = joinPoint.getArgs();
    for (Object arg : args) {
        System.out.println(arg);
    }
    System.out.println("前置通知,在目标方法之前执行输出日志");
}
  1. @AfterReturning 后置通知-注解有 returning 属性,用于指定接收目标方法返回值的变量名。被该注解修饰的方法除了包含JointPoint参数,还能包含用于接收返回值的变量
@AfterReturning(value ="execution(String *..*.*.*(..))"
        ,returning = "result")
public void threeAspect(Object result) {
    // 修改目标方法的执行结果
    // result是目标方法的返回值
    if (result != null) {
        String str = (String)result;
        result = str.toUpperCase(Locale.ROOT);
    }
    System.out.println("后置通知,在目标方法之后执行的功能增强,例如执行事务处理," + result);
}
  1. @Around 环绕通知-增强方法中有 ProceedingJoinPoint参数。在目标方法执行之前之后执行(增强功能)。被注解为环绕增强的方法要有返回值,Object 类型。并且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口ProceedingJoinPoint其有一个proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。环绕通知经常用于做事务,在目标方法之前开启事务,执行目标方法,在目标方法之后提交事务。
@Around(value = "execution(String *..*.*.*First(..))")
public Object fourAspect(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
    Object obj = null;
    // 增强功能
    System.out.println("环绕通知:在目标方法之前执行的,例如输出日志");
    // 执行目标方法的调用。等同于method.invoke(target, args)
    // 可以控制目标方法是否执行
    obj = proceedingJoinPoint.proceed();
    // 增强功能
    System.out.println("环绕通知:在目标方法之后执行的,例如处理事务");
    // 返回目标方法的执行结果
    return obj;
}
  1. @AfterThrowing 异常通知-注解中有 throwing 属性
  2. @After最终通知
  3. @Pointcut 定义切入点(辅助)
  4. @Aspect注解:该注解放在类上,表示当前类是切面类
5.设置AspectJ 实现 AOP的方式

在Spring配置文件中,通过aop:aspectj-autoproxy/的proxy-target-class属性设置选择通过JDK动态代理还是cglib动态代理实现AOP。

<!--声明自动代理生成器:使用aspectj把spring容器中目标类对象生成代理
        proxy-target-class="true"表示使用cglib动态代理

        目标类有接口,默认使用jdk动态代理。
        目标类没有接口,默认时候cglib动态代理
        目标类有接口,也可以使用cglib动态代理,需要设置proxy-target-class="true"
-->
<aop:aspectj-autoproxy proxy-target-class="true" />
<!-- <aop:aspectj-autoproxy/> -->
6.示例:给已经存在的业务方法增加额外的功能
  1. 添加依赖
  2. 创建目标类:接口和实现类
  3. 创建切面类:就是一个普通类
    1. 在类的上面加上@Aspect注解
    2. 类中定义方法,方法就是切面要执行的功能代码,在方法的上面加入AspectJ中的通知注解,比如说@Before,通知注解的value属性值指定切入点表达式execution()
  4. 创建spring配置文件:声明对象,将对象交给容器统一管理。声明对象可以使用注解方式或者XML中的Bean标签形式。
    1. 声明目标类对象
    2. 声明切面类对象
    3. 声明AspectJ中的自动代理生成器
  5. 创建测试类

Spring集成mybatis

1.概述

Spring集成myBatis,其本质工作就是:将使用mybatis框架时用到的一些需要自己创建的对象,交由Spring统一管理。 主要有一下对象:

  1. 数据源dataSource。就是保存数据库连接信息的对象。在实际业务开发中,我们放弃使用Mybatis自带的数据库连接池,而采用阿里的Druid,更加高效;
  2. 生成sqlSession对象的sqlSessionFactory;
  3. Dao接口的实现类对象。
2.Spring集成myBatis创建项目的流程
  1. 在pom.xml文件中添加依赖
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>spring_mybatis</artifactId>
    <version>1.0-SNAPSHOT</version>
    <dependencies>
        <dependency>
<!--            junit依赖-->
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.1</version>
            <scope>test</scope>
        </dependency>
<!--        spring依赖-->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.3.10</version>
        </dependency>
<!--        数据库连接池Druid-->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.12</version>
        </dependency>
<!--        mysql驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.26</version>
        </dependency>
<!--        mybatis整合spring的依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>1.3.1</version>
        </dependency>
<!--        mybatis的依赖-->
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.7</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-jdbc</artifactId>
            <version>5.3.11</version>
        </dependency>

    </dependencies>
    <build>
        <resources>
            <!-- 资源插件-->
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                    <include>**/*.properties</include>
                </includes>
                <filtering>false</filtering>
            </resource>

            <!--原有资源路径-->
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.xml</include>
                    <include>**/*.properties</include>
                </includes>
                <filtering>false</filtering>
            </resource>

        </resources>

    </build>
</project>
  1. mybatis主配置文件
<?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>
    <!-- mapper映射文件的注册:有两种方式-->
    <!--1. 使用package标签-->
    <!--2. 使用mapper标签-->
    <mappers>
         <!--加载dao包下的所有mapper文件-->
         <package name="dao"/>
    </mappers>
</configuration>

  1. 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"
       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">
    <!--读取配置文件
   location:指定属性配置文件的路径
   "classpath:":关键字表示类路径,也就是class文件所在的目录
-->
    <context:property-placeholder location="classpath:jdbc.properties" />

    <!--配置数据源DataSource-->
    <bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <!--读取属性配置文件jdbc.properties的key的值,使用 ${key}-->
        <!--数据库的uri-->
        <property name="url" value="${jdbc.url}"/> <!--setUrl()-->
        <!--数据库的用户名-->
        <property name="username" value="${jdbc.username}"/> <!--setUsername()-->
        <!--访问密码-->
        <property name="password" value="${jdbc.password}" /><!--setPassoword()-->
    </bean>
    <!--
        DruidDataSource myDataSource = new DruidDataSource();
        myDataSource.setUrl();
        myDataSource.setUsername();
        myDataSource.setPassword();
        myDataSource.init();
    -->

    <!--声明SqlSessionFactoryBean,创建SqlSessionFactory对象-->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--数据源-->
        <property name="dataSource" ref="myDataSource" />
        <!--指定mybatis的主配置文件-->
        <property name="configLocation" value="classpath:mybatis.xml" />
    </bean>

    <!--声明MyBatis的扫描器,创建Dao接口的实现类对象-->
    <bean  class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!--指定SqlSessionFactory对象,能获取SqlSession-->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
        <!--指定Dao接口的包名,框架会把这个包中的所有接口一次创建出Dao对象-->
        <property name="basePackage" value="dao" />
    </bean>
    <!--
        从spring中获取SqlSessionFacotory,因为spring是一个容器(Map)
        SqlSessionFactory factory  = map.get("sqlSessionFactory");
        SqlSession session = factory.openSession();
        遍历dao包下的所有接口
        for(接口:dao)
        {
            Dao对象 =  session.getMapper(接口)
            //把创建好的对象放入到spring容器中
            spring的Map.put( 接口名的首字母小写, Dao对象 )

        }

    -->
    <!--声明Service-->
    <bean id="someService" class="service.impl.SomeServiceImpl">
        <constructor-arg ref="studentDao"/>
    </bean>

</beans>
  1. jdbc.properties
jdbc.url=jdbc:mysql://localhost:3306/text?serverTimezone=UTC
jdbc.username=root
jdbc.password=123456
  1. test
ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
SomeService service = (SomeService) ac.getBean("someService");
List<Student> students = service.queryStudents();
for (Student s : students) {
    System.out.println(s);
}

Spring的事务

多种数据库的访问技术,有不同的事务处理机制、对象、方法。spring提供一种处理事务的统一模型,能使用统一步骤,方式完成多种数据库访问技术的事务处理。 在Spring中通常可以通过以下两种方式来实现对事务的管理:

  1. 使用Spring的事务注解管理事务
  2. 使用AspectJ的AOP配置管理事务
1.Spring事务管理API
  1. 事务管理器接口:事务管理器是PlatformTransactionManager接口实现类对象。其主要用于完成事务的提交、回滚,及获取事务的状态信息。
    1. 常用的两个实现类:DataSourceTransactionManager:使用 JDBC 或MyBatis进行数据库操作时使用。
      HibernateTransactionManager:使用 Hibernate 进行持久化数据时使用。
    2. spring的回滚方式:Spring 事务的默认回滚方式是:发生运行时异常和 error 时回滚,发生受查(编译)异常时提交,没有异常抛出则在方法执行后提交事务。对于受查时异常,程序员可以手动设置其回滚方式
<!--告诉spring使用的是哪种数据库的访问技术,比如说MyBatis-->
<!--或者Hibernate访问数据库-->
<!--声明事务管理器-->
  1. 事务定义接口:事务定义接口TransactionDefinition 中定义了事务描述相关的三类常量:事务隔离级别、
    事务传播行为、事务默认超时时限,及对它们的操作。
五个事务隔离级别常量
1. ISOLATION_DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为
REPEATABLE_READ; Oracle默认为 READ_COMMITTED。
2. READ_UNCOMMITTED:读未提交。未解决任何并发问题。
3. EAD_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
4. EPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
5. ERIALIZABLE:串行化。不存在并发问题。

七个事务传播行为常量:控制业务方法是不是有事务,是什么样的事务
1. PROPAGATION_REQUIRED
    指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;
    若当前没有事务,则创建一个新事务。Spring 默认的事务传播行为
2. PROPAGATION_REQUIRES_NEW
    总是新建一个事务,若当前存在事务,就将当前事务挂起,
    直到新事务执行完毕
3. PROPAGATION_SUPPORTS
    指定的方法支持当前事务,但若当前没有事务,
    也可以以非事务方式执行。
4. PROPAGATION_MANDATORY 
5. PROPAGATION_NESTED
6. PROPAGATION_NEVER
7. PROPAGATION_NOT_SUPPORTED

事务的超时时间
常量 TIMEOUT_DEFAULT定义了事务底层默认的超时时限,
sql 语句的执行时长。
表示一个方法最长的执行时间,如果方法执行时超过了时间,事务就回滚
单位是秒,整数值,默认是-1(表示不设置超时时间)

2.总结spring的事务
  1. 管理事务的是事务管理器和它的实现类
  2. spring的事务是一个统一模型
    1. 指定要使用的事务管理器实现类,使用
    2. 指定哪些类、哪些方法需要加入事务的功能
    3. 指定方法需要的隔离级别,传播行为,超时时间
3.使用Spring的事务注解管理事务

通过@Transactional 注解方式,可将事务织入到相应 public 方法中,实现事务管理。

1.@Transactional的所有可选属性如下:
image.png
image.png

  1. @Transactional注解的位置
1. 在方法上:只能用于public方法上,对于其他加上了该注解的非公有方法,
不会将指定事务织入到该方法中
2. 在类上:表示该类上所有的方法均将在执行时织入事务
  1. 实现注解的事务步骤:
1. 声明事务管理器
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
    <property name="dataSource" ref="druidDataSource"/>
</bean>
2. 开启注解驱动:其中transaction-manager属性的值为事务管理器bean的id
<tx:annotation-driven transaction-manager="transactionManager"/>
3. 业务层public方法加入@Transactional注解
    比如:@Transactional(
            propagation = Propagation.REQUIRED,
            isolation = Isolation.DEFAULT,
            readOnly = false,
            rollbackFor = {
                    NullPointerException.class,NotEnoughException.class
            }
    )
    或者:
    // 表示使用的是事务控制的默认值,默认的传播行为是REQUIRED,默认的隔离级别是DEFAULT
    // 默认抛出运行时异常,回滚事务
    // @Transactional
4.使用AspectJ的AOP配置管理事务(推荐使用)

使用AspectJ框架功能,在spring配置文件中声明类、方法需要的事务。这种方式业务方法和事务配置完全分离。

  1. 使用AspectJ框架,需要在POM文件中加入AspectJ依赖
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-aspects</artifactId>
    <version>5.2.5.RELEASE</version>
</dependency>
  1. 声明事务管理器对象
<bean id="druidDataSource" class="com.alibaba.druid.pool.DruidDataSource">
    <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>
<!-- 1.声明事务管理器对象-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
    <property name="dataSource" ref="druidDataSource"/>
</bean>
  1. 声明方法需要的事务类型(配置方法的事务属性,隔离级别、传播行为、超时)
<!-- 2.声明业务方法的事务属性(隔离级别,传播行为,超时时间)
     transaction-manager:表示事务管理器对象的ID
-->
<td:advice id= "myadvice" transaction-manager="transactionManager">
    <!--tx:attributes:配置事务的属性 -->
    <tx:attributes>
        <!-- tx:method:给具体的方法配置事务属性,method可以有多个,分别给不同的方法设置事务属性
            name:方法名称
                1. 完整的方法名称,不带有包和类
                2. 方法可以使用通配符,*表示任意字符
            propagation:传播行为
            isolation:隔离级别
            rollback-for:指定的异常类名,全限定类名。表示发生异常一定回滚
        -->

        <tx:method name="buy*"
                   propagation="REQUIRED"
                   isolation="DEFAULT"
                   rollback-for="java.lang.NullPointerException,com.nrvcer.exces.NotEnoughException"/>
    </tx:attributes>
</td:advice>
  1. 配置AOP:指定哪些类需要创建代理
<!--配置AOP -->
<aop:config>
    <!-- 配置切入点表达式:指定哪些包中的类需要使用事务
        id:切入点表达式的名称
        expression:切入点表达式,指定哪些类需要使用事务
    -->

<!--        所有service包及其子包下的所有方法-->
    <aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>
    <!--配置增强器:关联advice和pointcut -->
    <aop:advisor advice-ref="myadvice" pointcut-ref="servicePt"/>
</aop:config>

Spring与Web

在web项目中使用Spring框架,首先要在web层中获取到spring容器,在web层中获取到了Spring容器,才能从容器中获取到service对象。

1.创建一个Spring容器对象
  1. 解决不同Servlet或者同一个Servlet中重复创建ApplicationContext对象(即spring容器),造成内存浪费的问题
  2. 解决:将spring容器的创建时机放在ServletContext初始化时,然后将容器对象放入全局作用域ServletContext中(spring容器具有全局性)。
  3. 创建一个容器对象的解决方案:使用Spring的监听器ContextLoaderListener。若要在 ServletContext 初 始 化 时 创 建 Spring 容 器 , 就 需 要 使 用 监 听 器 接 口ServletContextListener 对 ServletContext 进行监听

监听器的作用:

1. 创建容器对象,类似于执行ApplicationContext ac = new ClassPathXmlApplicationContext("applicationContext.xml");
2. 将容器对象放入到ServletContext中,即servletContext.setAttribute(key, ac);

步骤:

1. 为了使用spring的那个监听器,需要在pom.xml中加入依赖
<dependency>
  <groupId>org.springframework</groupId>
  <artifactId>spring-web</artifactId>
  <version>5.2.5.RELEASE</version>
</dependency>

2. 在web.xml中注册监听器ContextLoaderListener
<listener>
    <listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

3. 使用context-param标签在web.xml中指定Spring配置文件的位置
<context-param>
    <param-name>contextConfigLocation</param-name>
    <param-value>classpath:applicationContext.xml</param-value>
</context-param>

2.Spring容器对象的获取
  1. 方式1:直接从ServletContext中获取,调用ServletContext的getAttribute方法
WebApplicationContext app = null;
// 获取spring容器对象
ServletContext servletContext = this.getServletContext();
String key = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
Object  attribute= servletContext.getAttribute(key);
if (attribute != null) {
    app = (WebApplicationContext) attribute;
}
  1. 方式2:通过WebApplicationContextUtils工具类获取
ServletContext servletContext = this.getServletContext();
WebApplicationContext context = WebApplicationContextUtils.getRequiredWebApplicationContext(servletContext);

创建一个ServletContextListener(ContextLoaderListener是其实现类)。在ServletContext初始化的时候创建ApplicationContext对象(容器对象),并将它保存在ServletContext中。
这样,在每个servlet中,只要调用当前servlet的ServletContext对象的getAttribute方法就可以获取这个webapp中共享的一个ApplicationContext对象。spring-web框架已经帮我们创建好了这样一个监听器。我们只需要在web.xml注册这个监听器就可以使用了。