Spring

Spring

第一章 Spring概述

1.1 什么是spring

  • spring就是一个java框架,使用java语言开发的,轻量级、开源的框架。可以在j2se、j2ee项目中都可以使用

  • spring核心技术:ioc、aop

  • spring又叫做容器,spring作为容器,装的是java对象。可以让spring创建java对象,给属性赋值

  • spring作用:实现解耦合,解决java对象之间的耦合,解决模块之间的耦合

  • tomcat也是容器:管理的是servlet、listener、filter等

    • 创建HelloServlet类,写web.xml文件

    • spring:创建SomeServiceImpl类,写spring的配置文件

1.2 spring的官网

1.3 Spring优点

  • Spring是一个框架,是一个半成品的软件,有20个模块组成。它是一个容器管理对象,容器是装东西的,Spring容器不装文本、数字,装的是对象。Spring是存储对象的容器

    1. 轻量

  • Spring框架使用的jar都比较小,一般在1M以下或者几百kb。Spring核心功能的所需的jar总共在3M左右。Spring框架运行占用的资源少,运行效率高,不依赖其他jar

    1. 针对接口编程,解耦合

  • Spring提供了ioc控制反转,由容器管理对象,对象的依赖关系。原来在程序代码中的对象创建方式,现在由容器完成。对象之间的依赖解耦合

    1. AOP编程的支持

  • 通过Spring提供的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松付在Spring中,开发人员可以从繁杂的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量

    1. 方便集成各种优秀框架

  • Spring不排斥各种优秀的开源框架,相反Spring可以降低各种框架的使用难度,Spring提供了对各种优秀框架(如Struts、Hibernate、MyBatis)等的直接支持,简化框架的使用。Spring像插线板一样,其他框架是插头,可以容易的组合到一起。需要使用哪个框架,就把这个插头放入插线板,不需要可以轻易的移除

第二章 IoC控制反转

2.1 概念

  • IoC(Inversion of Control):控制反转,是一个理论、一个指导思想,指导开发人员如何使用对象,管理对象的。把对象的创建,属性赋值,对象的生命周期都交给代码之外的容器管理。

  1. IoC分为控制和反转:

    • 控制:对象创建,属性赋值,对象生命周期管理

    • 反转:把开发人员管理对象的权限转移给了代码之外的容器实现,由容器完成对象的管理

    • 正转:开发人员在代码中,使用new构造方法创建对象。开发人员掌握了对象的创建,属性赋值,对象从开始到销毁的全部过程。开发人员有对对象全部控制

    • 通过容器,可以使用容器中的对象(容器已经创建了对象,对象属性赋值了,对象也组装好了)。

    • Spring就是一个容器,可以管理对象,创建对象,给属性赋值。

  2. IoC的技术实现

    • DI(依赖注入):Dependency Injection,缩写是DI,是IoC的一种技术实现。程序只需要提供要使用的对象名称就可以了,对象如何创建,如何从容器中查找,获取都由容器内部自己实现。

    • 依赖名词:比如说A类使用了B的属性或者方法,叫做A依赖B

public class B{
   //方法
   public void Order(){}
}
​
public class A{
   //属性
   private B b = new B();
   //方法
   public void buy(){
       b.Order();
  }
}
//执行了A的buy方法
A a = new A();
a.buy();
  1. Spring框架使用的DI实现IoC

    • 通过Spring框架,只需要提供要使用的对象名称就可以了 ,从容器中获取名称对应的对象

    • spring底层使用的反射机制,通过反射创建对象,给属性赋值

2.2 Spring的配置文件

  • spring标准的配置文件:

    1. 跟标签是beans

    2. beans后面的是约束文件说明

    3. beans里面是bean的声明

    4. 声明是bean:bean就是java对象,spring容器管理的java对象,叫做bean

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

2.3 spring容器创建对象的特点

  1. 容器对象ApplicationContext:接口

    • 通过ApplicationContext对象,获取要使用的其他java对象,执行getBean("bean标签的id")

  2. spring默认是调用类的无参数构造方法,创建对象

  3. spring读取配置文件,一次创建好所有的java对象,都放在map中

2.4 DI:给属性赋值

  • spring调用类的无参数构造方法,创建对象,对象创建后给属性赋值

  • 给属性赋值可以使用:

    1. xml配置文件中的标签和属性

    2. 使用注解

  • DI分类:

    1. set注入,也叫做设值注入

    2. 构造注入

    3. p命名空间注入

2.4.1 基于xml的DI

  • 在xml配置文件中使用标签和属性,完成对象创建,属性赋值

  1. set注入

    • 概念:spring调用类中的set方法,在set方法中可以完成属性赋值。推荐使用

<!--
   DI:给属性赋值
   简单类型:java中的基本数据类型和String
   1.set注入:spring调用类的set方法,通过set方法完成属性赋值
       简单类型的set注入:
       语法:<bean id="xxx" class="yyy">
               <property name="属性名" value="简单类型的数据值"/>
               ...
            </bean>
   2.引用类型set注入:
        语法:<bean id="xxx" class="yyy">
               <property name="属性名" ref="bean的id"/>
               ...
             </bean>
-->
<!--简单类型set注入-->
<bean id="myStudent" class="com.kgc.ba01.Student">
   <property name="name" value="张三"/><!--setName("张三")-->
   <property name="age" value="20"/><!--setAge(20)-->
</bean>
<!--声明日期类-->
<bean id="myDate" class="java.util.Date">
   <property name="time" value="29595295295925"/><!--setTime()-->
</bean>
<!--声明school-->
<bean id="mySchool" class="com.kgc.ba01.School">
   <property name="name" value="北京大学"/>
   <property name="address" value="北京海淀区"/>
</bean>
<!--引用类型set注入-->
<bean id="myStudent" class="com.kgc.ba01.Student">
   <property name="name" value="张三"/><!--setName("张三")-->
   <property name="age" value="20"/><!--setAge(20)-->
   <!--引用类型的赋值-->
   <property name="school" ref="mySchool"/>
</bean>
  1. 构造注入

    • 构造注入:spring调用类中的有参构造方法,在创建对象的同时,给属性赋值

<!--
   构造注入:spring调用类的有参构造方法,创建对象同时给属性赋值
   语法:
       <bean id="xxx" class="yyy">
          <constructor-arg>:表示一个构造方法的形参
          标签里属性:   name:构造方法形参名
                      index:构造方法的参数位置
                      value:简单类型的形参值
                      ref :引用类型的形参值
       </bean>
-->
<!--声明school-->
<bean id="mySchool" class="com.kgc.ba01.School">
   <property name="name" value="北京大学"/>
   <property name="address" value="北京海淀区"/>
</bean>
<!--构造注入,使用name属性-->
<bean id="myStudent" class="com.kgc.ba01.Student">
   <constructor-arg name="name" value="李四"/>
   <constructor-arg name="age" value="22"/>
   <constructor-arg name="school" ref="mySchool"/>
</bean>
<!--构造注入,使用index,参数的位置,构造方法参数从左往右,位置是0、1、2-->
<bean id="myStudent2" class="com.kgc.ba01.Student">
   <constructor-arg index="1" value="22"/>
   <constructor-arg index="0" value="李四"/>
   <constructor-arg index="2" ref="mySchool"/>
</bean>
<!--构造注入,省略index属性-->
<bean id="myStudent3" class="com.kgc.ba01.Student">
   <constructor-arg  value="李四"/>
   <constructor-arg  value="22"/>
   <constructor-arg  ref="mySchool"/>
</bean>
<!--声明file对象-->
<bean id="myFile" class="java.io.File">
   <constructor-arg name="parent" value="D:\\course"/>
   <constructor-arg name="child" value="xxx.txt"/>
</bean>
  1. p命名空间注入

    • p命名的一种注入方式,属性直接写在bean标签里面,以p:属性=“属性值”命名

<!--
   p标签命名方式:p命名的一种注入方式,属性直接写在bean标签里面,以p:属性=“属性值”命名
   步骤:
       1.先在beans标签声明p命名:
       xmlns:p="http://www.springframework.org/schema/p"
       2.
       语法:
           简单类型
           <bean id="xxx" class="yyy" p:属性="属性值".../>
           引用类型
           <bean id="xxx" class="yyy" p:属性-ref="bean标签id".../>
-->
<!--声明school-->
<bean id="mySchool" class="com.kgc.ba01.School">
   <property name="name" value="北京大学"/>
   <property name="address" value="北京海淀区"/>
</bean>
<!--p标签命名方式-->
<bean id="myStudent" class="com.kgc.ba01.Student" p:name="张三" p:age="20" p:school-ref="mySchool"/>
  1. 引用类型的自动注入

    • 概念:spring可以根据某些规则给引用类型完成赋值,只对引用类型有效,规则byName、byType

      1. byName(按名称注入):java类中引用类型属性名称和spring容器中bean的id名称一样的,且数据类型也是一样的,这些bean能够赋值给引用类型

      2. byType(按类型注入):java类中引用类型的数据类型和spring容器中bean的class值是同源关系的,这样的bean能赋值给引用类型

<!--
   引用类型自动注入:spring根据byName,byType规则给引用类型赋值
   1.byName语法:
           <bean id="xxx" class="yyy" autowire="byName">
               简单类型属性赋值
           </bean>
   2.byType:java类中引用类型的数据类型和spring容器中bean的class值是同源关系的,
             这样的bean能赋值给引用类型
     同源关系:
        1.java中引用类型的数据类型 和bean的class值是一样的
        2.java中引用类型的数据类型 和bean的class值是父子类关系的
        3.java中引用类型的数据类型 和bean的class值是接口和实现类关系的
        语法:
            <bean id="xxx" class="yyy" autowire="byType">
                简单类型属性赋值
            </bean>
        注意:在xml配置文件中,符合条件的对象,只能有一个
             多余一个是报错的
-->
<!--声明school-->
<bean id="school" class="com.kgc.ba01.School">
   <property name="name" value="北京大学"/>
   <property name="address" value="北京海淀区"/>
</bean>
<!--byName自动注入-->
<bean id="myStudent" class="com.kgc.ba01.Student" autowire="byName">
   <property name="name" value="张三"/>
   <property name="age" value="20"/>
</bean>
<!--byType自动注入-->
<bean id="myStudent2" class="com.kgc.ba01.Student" autowire="byType">
   <property name="name" value="张三"/>
   <property name="age" value="20"/>
</bean>
  1. 项目中使用多个spring配置文件

    • 分多个配置文件的方式:

      1. 按功能模块分,一个模块一个配置文件

      2. 按类的功能分,数据库操作相关的类在一个文件,service类在一个配置文件,配置redis,事务等等的一个配置文件

    • spring管理多个配置文件:常用的是包含关系的配置文件,项目中有一个总的文件,里面是有import标签包含其他的多个配置文件

    • 语法:

    总的文件(xml)
    <import resource="其他文件的路径1"/>
    <import resource="其他文件的路径2"/>
    关键字"classpath":表示类路径,也就是类文件(class文件)所在的目录。spring到类路径中加载文件
    什么时候使用classpath:在一个文件中要使用其他的文件,需要使用classpath
    <!--当前是总的文件,目的是包含其他的多个配置文件,一般不声明bean
       语法:
       <import resource="classpath:其他文件的路径"/>
       classpath:表示类路径,类文件所在的目录,spring通过类路径加载配置文件
    -->
    <import resource="classpath:ba01/spring-student.xml"/>
    <import resource="classpath:ba01/spring-school.xml"/>
    <!--包含关系的配置文件,可使用通配符(*:表示任意字符)
       注意:总的文件名称,不能包含在通配符范围内(applicationContext.xml不能叫做
       spring-applicationContext.xml)
    -->
    <import resource="classpath:ba01/spring-*.xml"/>

     

2.4.2 基于注解的DI

  • 基于注解的DI:使用spring提供的注解,完成java对象创建,属性赋值

  • 注解使用的核心步骤:

    1. 在源代码加入注解,例如@Component

    2. 在spring的配置文件中,加入组件扫描器的标签

    <!--声明组件扫描器:使用注解必须加入这个语句
    component-scan:翻译过来是组件扫描器,组件是java对象
    属性:base-package注解在你的项目中的包名
    框架会扫描这个包和子包中的所有类,找类中的所有注解
    遇到注解后,按照注解表示的功能,去创建对象,给属性赋值
    -->
    <context:component-scan base-package="注解所在的包名"/>

扫描多个包的三种方式:

<!--扫描多个包的三种方式-->
<!--第一种:使用多次组件扫描器-->
<context:component-scan base-package="com.kgc.ba01"/>
<context:component-scan base-package="com.kgc.ba02"/>
<!--第二种:使用分隔符(;或,),指定多个包-->
<context:component-scan base-package="com.kgc.ba01;com.kgc.ba02"/>
<!--第三种:指定父包-->
<context:component-scan base-package="com.kgc"/>

创建对象的四个注解:

/**
* @Component: 表示创建对象,对象放到容器中,作用是<bean>
* 属性:value,表示对象名称,也就是bean的id属性值
* 位置:在类的上面,表示创建此类的对象
* @Component(value = "myStudent") 等同于
* <bean id="myStudent" class="com.kgc.ba01.Student"/>
* 和@Component功能相同的创建对象的注解
* 1.@Repository:放在dao接口的实现类上面,表示创建dao对象,持久层对象,能访问数据库
* 2.@Service:放在业务层接口的实现类上面,表示创建业务层对象,业务层对象有事务的功能
* 3.@Controller:放在控制器类的上面,表示创建控制器对象,属于表示层对象
* 控制器对象能接受请求,把请求的处理结果显示给用户
* 以上四个注解都能创建对象,但是@Repository、@Service、@Controller它们有角色说明,
* 表示对象是分层的,对象是属于不同层,具有额外的功能
*
*/
//使用value指定对象的名称
//@Component(value = "myStudent")
//省略value
//@Component("myStudent2")
//没有提供自定义的对象名称,所有框架的默认对象名称:类名首字母小写
@Component
public class Student {
private String name;
private int age;

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

简单类型@Value

@Component("myStudent")
public class Student {
/**
* 简单类型属性赋值:@Value
* 属性:value 简单类型属性值
* 位置:1.在属性定义的上面,无需set方法,推荐使用
* 2.在set方法的上面
*/
@Value(value = "李四")
private String name;
private int age;

public void setName(String name) {
this.name = name;
}
@Value("40")
public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

@Value使用外部属性配置文件

  • 步骤:

    1. 创建配置文件:myconf.properties

    myname=李四
    myage=20
    1. 读取外部属性配置文件

    <!--读取外部的属性配置文件
    property-placeholder:读取properties这样的文件
    -->
    <context:property-placeholder location="classpath:myconf.properties"/>
    1. 使用配置文件

    @Component("myStudent")
    public class Student {
    //使用外部属性配置文件中的数据,语法@Value(${key})
    @Value("${myname}")
    private String name;
    private int age;

    public void setName(String name) {
    this.name = name;
    }
    @Value("${myage}")
    public void setAge(int age) {
    this.age = age;
    }

    @Override
    public String toString() {
    return "Student{" +
    "name='" + name + '\'' +
    ", age=" + age +
    '}';
    }
    }

@Autowired:自动注入byType

@Component("myStudent")
public class Student {
@Value("${myname}")
private String name;
private int age;

/**
* 引用类型
*@Autowired: spring框架提供的,给引用类型赋值的,使用自动注入原理
* 支持byName、byType。默认是byType
* 属性:required:boolean类型的属性,默认true
* true:spring在启动的时候,创建容器对象时候,会检查引用类型是否赋值成功。
* 如果赋值失败,终止程序执行,并报错
* flase:引用类型赋值失败,程序正常执行,不报错,引用类型的值是null
* 位置:1.在属性定义的上面,无需set方法,推荐使用
* 2.在set方法的上面
*/
//默认使用byType
@Autowired
private School school;
public void setName(String name) {
this.name = name;
}
@Value("${myage}")
public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

@Autowired:自动注入byName

@Component("myStudent")
public class Student {
@Value("${myname}")
private String name;
private int age;

/**
* byName自动注入:
* 1.@Autowired:给引用类型赋值
* 2.@Qualifier(value=“bean的id”):从容器中找到指定名称的对象,
* 把这个对象赋值给引用类型
*/
//byName
@Autowired
@Qualifier("mySchool")
private School school;
public void setName(String name) {
this.name = name;
}
@Value("${myage}")
public void setAge(int age) {
this.age = age;
}

@Override
public String toString() {
return "Student{" +
"name='" + name + '\'' +
", age=" + age +
'}';
}
}

@Resource默认是byName

@Component("myStudent")
public class Student {
@Value("${myname}")
private String name;
private int age;

/**
* @Resource: 来自jdk中,给引用类型赋值的,支持byName、byType,默认是byName
* spring支持这个注解的使用
* 位置:1.在属性定义的上面,无需set方法,推荐使用
* 2.在set方法的上面
* 说明:使用jdk1.8带有@Resource注解,高于1.8没有这个@Resource
* 需要加入一个依赖
* <dependency>
* <groupId>javax.annotation</groupId>
* <artifactId>javax.annotation-api</artifactId>
* <version>1.3.2</version>
* </dependency>
*/
//默认使用byName自动注入
//先使用byName赋值,如果赋值失败,再使用byType
//本例是byType赋值成功
@Resource
private School school;
}

@Resource只使用byName注入

@Component("myStudent")
public class Student {
@Value("${myname}")
private String name;
private int age;

/**
* @Resource 只使用byName赋值
* 使用注解属性name="bean的id"
*/
//只使用byName自动注入
@Resource(name = "mySchool")
private School school;
}

2.5 IoC总结

  • IoC:管理对象的,把对象放在容器中,创建,赋值,管理依赖关系

  • IoC通过管理对象,实现解耦合。IoC解决处理业务逻辑对象之间的耦合关系,也就是service和dao之间的解耦合

  • spring作为容器适合管理什么对象?

    1. service对象,dao对象

    2. 工具类对象

  • 不适合交给spring的对象?

    1. 实体类

    2. servlet、listener、filter等web中的对象。它们是tomcat创建和管理的

第三章 AOP 面向切面编程

3.1 增加功能,导致的问题

  • 在源代码中,业务方法中增加的功能

    1. 源代码可能改动的比较多

    2. 重复的代码比较多

    3. 代码难于维护

3.2 AOP概念

3.2.1 什么是AOP

  • AOP(Aspect Orient Programming):面向切面编程

  • Aspect :表示切面,给业务方法增加的功能,叫做切面。切面一般都是非业务功能,而且切面功能一般都是可以复用的。例如:日志功能、事务功能、权限检查、参数检查、统计信息等等

  • Orient :面向,对着

  • Programming:编程

 

  • 怎么理解面向切面编程?(以切面为核心设计开发你的应用)

    1. 设计项目时,找出切面的功能

    2. 安排切面的执行时间,执行的位置

3.2.2 AOP的作用

  1. 让切面功能复用

  2. 让开发人员专注业务逻辑,通过开发效率

  3. 实现业务功能和其他非业务功能解耦合

  4. 给存在的业务方法,增加功能,不用修改原来的代码

3.2.3 AOP中术语

  1. Aspect :切面,给业务方法增加的功能

  2. JoinPoint:连接点,连接切面的业务方法。在这个业务方法执行时,会同时执行切面的功能

  3. Pointcut:切入点,是一个或多个连接点集合,表示这些方法执行时,都能增加切面的功能。表示切面执行的位置

  4. target:目标对象,给哪个对象增加切面的功能,这个对象就是目标对象

  5. Advice:通知(增强),表示切面的执行时间,在目标方法之前执行切面,函数目标方法之后执行切面

  • AOP中重要的三个要素:Aspect 、Pointcut、Advice。三个概念的理解是:我们在Advice的时间,在Pointcut的位置,执行Aspect

  • AOP是这个动态的思想。在程序运行期间,创建代理(ServiceProxy),使用代理执行方法时,增加切面的功能。这个代理对象是存在内存中的

3.2.4 什么时候用AOP

  • 你要给某些方法增加相同的一些功能,源代码不能改,给业务方法增加非业务功能,也可以使用AOP

3.2.5 AOP技术思想的实现

  • 使用框架实现AOP。实现AOP的框架有很多,有名的有两个:

    1. Spring:Spring框架实现AOP思想中的部分功能。Spring框架实现AOP的操作比较繁琐,笨重

    2. Aspectj:独立的框架,专门做AOP。属于Eclipse

3.3 使用Aspectj框架实现AOP

  • Aspectj框架可以使用注解和xml配置文件两种方式实现AOP

3.3.1 通知

  • Aspectj表示切面执行时间,用到通知(Advice)。这个通知可以使用注解表示

  • 主要五个注解,表示切面的5个执行时间,这些注解叫做通知注解

  1. Before:前置通知

  2. AfterRetunring:后置通知

  3. Around:环绕通知

  4. AfterThrowing:异常通知

  5. After:最终通知

3.3.2 Pointcut(位置)

  • Pointcut用来表示切面执行的位置,使用Aspectj中切入点表达式

  • 切入点表达式语法:execution(访问权限 方法返回值 方法声明(参数) 异常类型)

    • 注:方法返回值和方法声明(参数)必须有

3.3.3 代理生成器

<!--声明自动代理生成器:目的是创建目标对象的代理
调用aspectj框架中的功能,寻找spring容器中的所有目标对象,
把每个目标对象加入切面类中的功能,生成代理
这个代理对象是修改的内存中的目标对象,这个目标对象就是代理对象(ServiceProxy)
-->
<aop:aspectj-autoproxy />

3.3.4 @Before前置通知

  • @Before:在目标方法之前执行的

/**
*@Aspect: 切面类的注解
* 位置:放在某个类的上面
* 作用:表示当前类是切面类
* 切面类:表示切面功能的类
*/
@Aspect
public class MyAspect {

/**
* 前置通知方法的定义
* 1.方法是public
* 2.方法是void
* 3.方法名称自定义
* 4.方法可以有参数,如果有是JoinPoint,也可以没有
*/
/**
* @Before: 前置通知
* 属性:value切入点表达式,表示切面的执行位置,
* 在这个方法执行时,会同时执行切面的功能
* 位置:方法的上面
* 特点:
* 1.执行时间:在目标方法之前先执行的
* 2.不会影响目标方法的执行
* 3.不会修改目标方法的执行结果
*/
//定义方法,表示切面的具体功能
@Before("execution(* com.kgc.service..*.*(..))")
public void myBefore(){
//切面的代码
System.out.println("前置通知,切面的功能,在目标方法之前先执行:"+new Date());
}
}

3.3.5 Joinpoint

  • JoinPoint:表示正在执行的业务方法

@Aspect
public class MyAspect {

/**
* 切面类中的通知方法,可以有参数
* JoinPoint必须是他
* JoinPoint:表示正在执行的业务方法。相当于反射中Method
* 使用要求:必须是参数列表的第一个
* 作用:获取方法执行时的信息,例如方法名称,方法的参数集合
*/
@Before("execution(* com.kgc.service..*.*(..))")
public void myBefore(JoinPoint js){
//获取方法的定义
System.out.println("前置通知中,获取目标方法的定义:"+js.getSignature());
System.out.println("前置通知中,获取方法名称;"+js.getSignature().getName());
//获取方法执行时的参数
Object[] args = js.getArgs();//数组中存放的是方法的所有参数
for (Object arg : args) {
System.out.println("前置通知,获取方法的参数:"+arg);
}
}
}

3.3.6 @AfterReturning后置通知

  • @AtferReturning:在目标方法之后执行的

@Aspect
public class MyAspect {
/**
* 后置通知方法的定义
* 1.方法是public
* 2.方法是void
* 3.方法名称自定义
* 4.方法有参数,推荐使用Object类型
*/
/**
* @AfterReturning: 后置通知
* 属性:value切入点表达式
* returning:自定义表达式,表示方法的返回值的
* 自定义变量名称必须和通知方法的形参名一样
* 位置:在方法的上面
* 特点:
* 1.在目标方法之后,执行的
* 2.能获取到目标方法的执行结果
* 3.不会影响目标方法的执行
* 方法的参数:
* Object res :表示目标方法的返回值,使用res接收方法的调用结果
* Object res = 方法名();
* 后置通知的执行顺序
* Object res = 方法名(..);
* myAfterReturning(res);
*/
@AfterReturning(value = "execution(* com.kgc.service..*.*(..))",returning = "res")
public void myAfterReturning(JoinPoint js,Object res){
System.out.println("后置通知,在目标方法之后,执行的。能拿到执行结果:"+res);
}
}

3.3.7 @Around环绕通知

  • @Around(value="切入点表达式")

  • 使用环绕通知:就是调用切面类中的通知方法

@Aspect
public class MyAspect {
/**
* 环绕通知方法的定义
* 1.方法是public
* 2.方法必须有返回值,推荐使用Object类型
* 3.方法名称自定义
* 4.方法必须有ProceedingJoinPoint参数
*/
/**
* @Around: 环绕通知
* 属性:value切入点表达式
* 位置:在方法定义的上面
* 返回值:Object,表示调用目标方法希望得到执行结果(不一定是目标方法自己的返回值)
* 参数:ProceedingJoinPoint,相当于反射中Method
* 作用:执行目标方法,等于Method.invoke()
* 特点:
* 1.在目标方法的前和后都能增强功能
* 2.控制目标方法是否执行
* 3.修改目标方法的执行结果
*/
@Around(value = "execution(* com.kgc.service..*.*(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
//获取方法执行时的参数值
String name ="";
Object[] args = pjp.getArgs();
if (args!=null && args.length>0){
Object arg = args[0];
if (arg!=null){
name = (String) arg;
}
}
Object methodReturn = null;
System.out.println("执行了环绕通知,在目标方法之前,输出日志时间="+new Date());
//执行目标方法
if ("lisi".equals(name)){
methodReturn = pjp.proceed();//method.invoke();表示执行业务方法本身
}
System.out.println("环绕通知,在目标方法之后,增加了事务提交功能");
//return "HelloAround";
//返回目标方法执行结果
return methodReturn;
}
}

3.3.8 @AfterThrowing异常通知

  • 语法:@AfterThrowing(value ="切入点表达式",throwing="自定义变量")

@Aspect
public class MyAspect {
/**
* 异常通知方法的定义
* 1.方法是public
* 2.方法没有返回值,是void
* 3.方法名称自定义
* 4.方法有参数是Exception
*/
/**
* @AfterThrowing: 异常通知
* 属性:value切入点表达式
* throwing 自定义变量,表示目标方法抛出的异常
* 变量名必须和通知方法的形参名一样
* 位置:在方法的上面
* 特点:
* 1.在目标方法抛出异常后执行的,没有异常不执行
* 2.能够获取到目标方法的异常信息
* 3.不是异常处理程序,可以得到发生异常的通知,可以发送邮件、短信通知开发人员
* 看做是目标方法的监控程序
* 异常通知的执行
* try{
* someServiceImpl.doSome(..)
* }catch(Exception ex){
* myAfterThrowing(ex);
* }
*/
@AfterThrowing(value = "execution(* com.kgc.service..*.*(..))",throwing = "ex")
public void myAfterThrowing(Exception ex){
System.out.println("异常通知,在目标方法抛出异常时执行的,异常原因是:"+ex.getMessage());
/**
* 异常发生可以做:
* 1.记录异常的时间、位置等信息
* 2.发生邮件,短信通知开发人员
*/
}
}

3.3.9@After最终通知

  • 语法:@After(value="切入点表达式")

@Aspect
public class MyAspect {
/**
* 最终通知方法的定义
* 1.方法是public
* 2.方法没有返回值,是void
* 3.方法名称自定义
* 4.方法没有参数
*/
/**
* @After: 最终通知
* 属性:value 切入点表达式
* 位置:在方法的上面
* 特点:
* 1.在目标方法之后执行的
* 2.总是会被执行
* 3.可以用来做程序最后的收尾工作,例如:清除临时数据,变量,清理内存
* 最终通知:
* try{
* someServiceImpl.doSome(..)
* }finally{
* myAfter()
* }
*/
@After("execution(* com.kgc.service..*.*(..))")
public void myAfter(){
System.out.println("最终通知,总是会被执行的");
}
}

3.3.10 @Pointcut定义和管理切入点注解

  • 语法:@Pointcut(value="切入点表达式")

@Aspect
public class MyAspect {
@Before("mypt()")
public void myBefore(){
System.out.println("前置通知,在目标方法之前先执行的");
}
@After("mypt()")
public void myAfter(){
System.out.println("最终通知,总是会被执行的");
}
/**
* @Pointcut: 定义和管理切入点,表示通知注解
* 属性:value 切入点表达式
* 位置:在一个自定义表达式方法的上面,这个方法看做是切入点表达式的别名
* 在其他的通知注解中,可以使用方法名称,就表示使用这个切入点表达式了
*/
@Pointcut("execution(* com.kgc.service..*.*(..))")
private void mypt(){
//无需代码
}
}

3.3.11 配置文件使用通知

<!--自动代理生成器-->
<aop:aspectj-autoproxy/>
<!--声明切面类对象-->
<bean id="loggerAop" class="com.kgc.aop.LoggerAop"/>
<!--aop-->
<aop:config>
<!--切入点表达式-->
<aop:pointcut id="pointcut" expression="execution(* com.kgc.service..*.*(..))"/>
<aop:aspect ref="loggerAop">
<!--环绕通知-->
<aop:around method="around" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>

3.4 AOP总结

  • AOP是一种动态的技术思想,目的是实现业务功能和非业务功能的解耦合。业务功能是独立的模块,其他功能也是独立的模块,例如:事务功能、日志等等。让这些事务,日志功能是可以被复用的。

  • 当目标方法需要一些功能时,可以在不修改,不能修改源代码的情况下,使用AOP技术在程序执行期间,生成代理对象,通过代理执行业务方法,同时增加功能

第四章 Spring集成MyBatis

4.1 集成思路

  • Spring能集成很多的框架,是Spring一个优势功能。通过集成功能,让开发人员使用其他框架更方便。

  • 集成使用的是Spring IoC核心技术

4.2 如何使用MyBatis

  • 使用MyBatis,需要创建MyBatis框架中的某些对象,使用这些对象,就能使用MyBatis提供的功能了

  • 分析:MyBatis执行sql语句,需要使用那些对象

    1. 需要有dao接口的代理对象,例如StudentDao接口,需要一个它的代理对象。使用SqlSession.getMapper(StudentDao.class),得到dao代理对象

    2. 需要有SqlSessionFactory,创建SqlSessionFactory对象,才能使用openSession()得到SqlSession对象

    3. 数据源DataSource对象,使用一个更强大,功能更多的连接池对象代替MyBatis自己的PooledDataSource

image-20210731162426306

4.3 集成MyBatis使用

1. 使用mysql库,创建学生表

CREATE TABLE student(
id INT PRIMARY KEY AUTO_INCREMENT,#编号
`name` VARCHAR(80) NOT NULL,#姓名
age INT NOT NULL#年龄
);#学生表

2. 创建Maven项目

3. 加入依赖

  • spring依赖、mybatis依赖、mysql驱动、junit依赖、mybatis-spring依赖(mybatis网站上提供的,用来在spring项目中,创建mybatis对象)、spring有关事务的依赖。

    • mybatis和spring整合的时候,事务是自动提交的

<?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>com.kgc</groupId>
<artifactId>ch03-mybatis</artifactId>
<version>1.0.0</version>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>

<dependencies>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--spring依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--spring事务依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<!--mybatis和spring集成-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.9</version>
</dependency>
<!--阿里的连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
</dependencies>

<build>
<!--资源插件:处理src/main/java目录下的xml-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>

4. 创建实体类Student

/**
* 学生类
*/
public class Student {
private Integer id;//编号
private String name;//姓名
private Integer age;//年龄
//get|set
public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAge() {
return age;
}

public void setAge(Integer age) {
this.age = age;
}

@Override
public String toString() {
return "Student{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
}

5. 创建Dao接口和mapper文件写sql语句

/**
* 学生数据访问层接口
*/
public interface StudentDao {
//添加学生
int insertStudent(Student student);
//查询学生
List<Student> selectStudent();
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kgc.dao.StudentDao">
<!--添加学生-->
<insert id="insertStudent">
insert into student values(null,#{name},#{age})
</insert>
<!--查询学生-->
<select id="selectStudent" resultType="com.kgc.pojo.Student">
select * from student
</select>
</mapper>

6. 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>
<!--设置日志-->
<settings>
<setting name="autoMappingBehavior" value="FULL"/>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<mappers>
<package name="com/kgc/dao"/>
</mappers>
</configuration>

7. 创建service接口和它的实现类

/**
* 学生业务逻辑层接口
*/
public interface StudentService {
//添加学生
int addStudent(Student student);
//查询学生
List<Student> queryStudent();
}
/**
* 学生业务逻辑层实现类
*/
@Service
public class StudentServiceImpl implements StudentService {
@Autowired
private StudentDao studentDao;
//实现添加学生
@Override
public int addStudent(Student student) {
return studentDao.insertStudent(student);
}
//实现查询学生
@Override
public List<Student> queryStudent() {
return studentDao.selectStudent();
}
}

8. 创建spring的配置文件

  1. 声明数据源DataSource,使用阿里的Druid连接池

  2. 声明SqlSessionFactoryBean类,在这个类内部创建的是SqlSessionFactory对象

  3. 声明MapperScannerConfigurer类,在内部创建dao代理对象,创建的对象都放在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:component-scan base-package="com.kgc"/>
<!--声明数据源DataSource-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/springDb"/>
<property name="username" value="root"/>
<property name="password" value="ok"/>
</bean>
<!--声明SqlSessionFactoryBean:在这个类的内部,创建SqlSessionFactory-->
<bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--指定数据源-->
<property name="dataSource" ref="dataSource"/>
<!--指定mybatis主配置文件
Resource可以直接使用 value属性赋值
-->
<property name="configLocation" value="classpath:mybatis.xml"/>
</bean>
<!--声明MapperScannerConfigurer
作用:循环basePackage所表示的包,把包中的每个接口都找到,调用SqlSession.getMapper
把每个dao接口都创建出dao代理对象,dao代理对象放在容器中
相当于:
ApplicationContext ctx =...
SqlSessionFactory factory = ctx.getBean("factory");
SqlSession session = factory.openSession();
for(接口:com.kgc.dao){
接口 对象 = session.getMapper(接口)
springMap.put(接口名(首字母小写),对象)
}
-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指定SqlSessionFactory对象的名称-->
<property name="sqlSessionFactoryBeanName" value="factory"/>
<!--指定基本包,dao接口所在的包名-->
<property name="basePackage" value="com.kgc.dao"/>
</bean>
</beans>

第五章 Spring事务

5.1 事务的概念

  • 什么是事务?事务是一些sql序列的集合,是多条sql,作为一个整体执行

mysql执行事务
beginTransaction 开启事务
insert into ...
update ...
delete from ...
endTransaction 事务结束
  • 声明情况下需要使用事务?

    • 一个操作需要多条(2条或2条以上)的sql语句一起完成,操作才能成功

5.2 程序中的事务使用

  • 事务:加在业务类的方法上面(public方法上面),表示业务方法执行时,需要事务的支持

public interface AccountDao{
public void updateA(){}
public void updateB(){}
}
public class AccountService{
private AccoutDao dao;
//在service(业务类)的public方法上面,需要说明事务
public void trans(String a,String b,Integer money){
dao.updateA();
dao.updateB();
}
}

5.3 事务管理器

不同的数据库访问技术,处理事务是不同的

  1. 使用jdbc访问数据库,事务处理

public void updateAccount(){
Connection con = ...
con.setAutoCommit(false);
stat.insert();
stat.update();
con.commit();
con.setAutoCommit(true)
}
  1. mybatis执行数据库,处理事务

public void updateAccount(){
SqlSession session = SqlSession.openSession(true);
try{
session.insert("insert into ...");
session.insert("update ...");
session.commit();
}catch(Exception ex){
session.rollback();
}
}

Spring统一管理事务,把不同的数据库访问技术事务处理统一起来

  • 使用spring的事务管理器,管理不同数据库服务技术的事务处理。开发人员只需要掌握spring的事务处理一个方案,就可以实现使用不同数据库访问技术的事务管理。

  • 管理事务面向的是spring,由spring管理事务,做事务提交,事务回顾。

5.3.1 Spring事务管理器

  • spring框架使用事务管理器对象,管理所有的事务。

  • 事务管理器接口:PlatformTransactionManager

    • 作用:定义了事务的操作方法,主要是commit(),rollback()

  • 事务管理器有很多实现类:一种数据库的访问技术有一个实现类。由实现类具体完成事务的提交、回顾。

    • 意味着:jdbc或者mybatis访问数据库有自己的事务管理器实现类:DataSourceTransactionManager

    • hibernate框架,它的事务管理器实现类:HibernateTransactionManager

  • 事务管理器工作方式:

image-20210801140542903

5.3.2 事务的提交和回顾的时机

  • 声明时候提交事务,回顾事务?

    • 当你的业务方法正常执行时,没有异常,事务是正常提交的。如果你的业务方法抛出运行时异常,事务是回顾的。

  • 异常的分类:

    • Error:严重错误,回顾事务

    • Exception:异常类,可以处理的异常情况

      1. 运行时异常:RuntimeException和他的子类都是运行时异常,在程序执行过程中抛出的异常。常见的运行时异常:空指针异常NullPoinerException、数字格式异常NumberFormatException、算术异常ArithmeticException等

      2. 受查异常:编写java代码的时候,必须处理的异常。例如:IOException、SQLException、FileNotFoundException

  • 区分方法:

    • 方法中抛出运行时异常,事务回顾,其他情况(正常执行方法、受查异常)介绍提交事务

5.3.3 事务使用的是AOP的环绕通知

  • 环绕通知:可以在目标方法的前和后都能增强功能,不需要修改原代码

spring给业务方法在执行时,增加上事务的切面功能
@Around("execution(* 所有业务类中的方法)")
public Object myAround(ProceedingJoinPoint pjp){
try{
PlatformTransactionManager.beginTransaction();//使用spring的事务管理器,开启事务
pjp.proceed();//执行目标方法,doSome()
PlatformTransactionManager.commit();//业务方法正常执行,提交事务
}catch(Exception e){
PlatformTransactionManager.rollback();//业务方法异常,回顾事务
}
}

5.4 事务定义接口TransactionDefinition

  • TransactionDefinition接口,定义了三类常量,常量定义了有关事务控制的属性。

  • 事务的属性:

    1. 隔离级别

    2. 传播行为

    3. 事务的超时

  • 给业务方法说明事务属性,和ACID不一样。

5.4.1 隔离级别

  • 隔离级别:控制事务之间影响的程度。有5个值,只有4个隔离级别

    1. DEFAULT:采用DB默认的事务隔离级别。mysql默认为REPEATABLE_READ;oracle默认为READ_COMMITTED

    2. READ_UNCOMMITTED:读未提交。未解决任何并发问题

    3. READ_COMMITTED:读已提交。解决脏读,存在不可重复读和幻读

    4. REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读

    5. SERIALIZABLE:串行化。不存在任何并发问题

5.4.2 超时时间

  • 超时时间:表示一个业务方法最长的执行时间,如果到达时间没有执行完毕,spring会回顾事务。

  • 超时时间,以秒为单位,整数值,默认是-1。

5.4.3 传播行为

  • 传播行为:业务方法在调用时,事务在方法之间的传递和使用

  • 使用传播行为,标识方法有无事务

  • 传播行为有7个值:

    1. PROPAGATION_REQUIRED

    2. PROPAGATION_SUPPORTS

    3. PROPAGATION_REQUIRES_NEW

    4. PROPAGATION_MANDATORY

    5. PROPAGATION_NOT_SUPPORTED

    6. PROPAGATION_NEVER

    7. PROPAGATION_NESTED

  • 前三个需要掌握:

  1. REQUIRED:spring默认传播方式,方法在调用的时候,如果存在事务就使用当前的事务,如果没有事务则新建事务,方法在新事务中执行

  2. SUPPORTS:支持,方法有事务可以正常执行,没有事务也可以正常执行

  3. REQUIRES_NEW:方法需要一个新事务。如果调用方法时,存在一个事务,则原来的方法暂停,直到新事务执行完毕。如果方法调用时,没有事务,则新建一个事务,在新事务执行代码

5.5 举例环境搭建

  • 模拟用户下订单,向订单表添加销售记录,从商品表减少库存

1. 创建数据库表

CREATE TABLE goods(
id INT PRIMARY KEY,#商品编号
`name` VARCHAR(100) NOT NULL,#商品名称
amount INT NOT NULL,#库存
price FLOAT NOT NULL#价格
);#商品表
CREATE TABLE sale(
id INT PRIMARY KEY AUTO_INCREMENT,#销售编号
gid INT NOT NULL,#商品编号
nums INT NOT NULL,#数量
FOREIGN KEY(gid) REFERENCES goods(id)
);#销售表
#添加商品数据
INSERT INTO goods
VALUES(1001,'笔记本电脑',100,6000),
(1002,'手机',50,3000);

2. 创建Maven项目

3. 加入依赖

  • spring依赖、mybatis依赖、mysql驱动、junit依赖、mybatis-spring依赖(mybatis网站上提供的,用来在spring项目中,创建mybatis对象)、spring有关事务的依赖。

<?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>com.kgc</groupId>
<artifactId>ch03-mybatis</artifactId>
<version>1.0.0</version>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
</properties>

<dependencies>
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!--spring依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--spring事务依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<!--mybatis依赖-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<!--mybatis和spring集成-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<!--mysql驱动-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.9</version>
</dependency>
<!--阿里的连接池-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.12</version>
</dependency>
</dependencies>

<build>
<!--资源插件:处理src/main/java目录下的xml-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
</build>
</project>

4. 创建实体类

/**
* 商品类
*/
public class Goods {
private Integer id;//商品编号
private String name;//商品名称
private Integer amount;//库存
private double price;//价格

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public String getName() {
return name;
}

public void setName(String name) {
this.name = name;
}

public Integer getAmount() {
return amount;
}

public void setAmount(Integer amount) {
this.amount = amount;
}

public double getPrice() {
return price;
}

public void setPrice(double price) {
this.price = price;
}

@Override
public String toString() {
return "Goods{" +
"id=" + id +
", name='" + name + '\'' +
", amount=" + amount +
", price=" + price +
'}';
}
}
/**
* 销售类
*/
public class Sale {
private Integer id;//销售编号
private Integer gid;//商品编号
private Integer nums;//数量

public Integer getId() {
return id;
}

public void setId(Integer id) {
this.id = id;
}

public Integer getGid() {
return gid;
}

public void setGid(Integer gid) {
this.gid = gid;
}

public Integer getNums() {
return nums;
}

public void setNums(Integer nums) {
this.nums = nums;
}

@Override
public String toString() {
return "Sale{" +
"id=" + id +
", gid=" + gid +
", nums=" + nums +
'}';
}
}

5. 创建dao接口和mapper文件

/**
* 销售数据访问层接口
*/
public interface SaleDao {
//添加销售记录
int insertSale(Sale sale);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kgc.dao.SaleDao">
<!--添加销售记录-->
<insert id="insertSale">
insert into sale values(null,#{gid},#{nums})
</insert>
</mapper>
/**
* 商品数据访问层接口
*/
public interface GoodsDao {
//查询指定商品
Goods selectGoods(Integer id);
//修改库存
int updateGoods(Goods goods);
}
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.kgc.dao.GoodsDao">
<!--查询指定商品-->
<select id="selectGoods" resultType="com.kgc.pojo.Goods">
select * from goods where id = #{id}
</select>
<!--修改库存-->
<update id="updateGoods">
update goods set amount = amount - #{amount} where id = #{id}
</update>
</mapper>

6. 创建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>
<!--设置日志-->
<settings>
<setting name="autoMappingBehavior" value="FULL"/>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<mappers>
<package name="com/kgc/mapper"/>
</mappers>
</configuration>

7. 创建service接口和实现类

  • 实现buy方法

/**
* 商品业务逻辑层接口
*/
public interface BuyGoodsService {
//购买商品
void buy(Integer gid,Integer num);
}
/**
* 商品业务逻辑层实现类
*/
@Service
public class BuyGoodsServiceImpl implements BuyGoodsService {
@Autowired
private SaleDao saleDao;
@Autowired
private GoodsDao goodsDao;
//实现购买商品
@Override
public void buy(Integer gid, Integer num) {
System.out.println("buy方法的开始");
//生成销售记录
Sale sale = new Sale();
sale.setGid(gid);
sale.setNums(num);
saleDao.insertSale(sale);
//查询商品
Goods goods = goodsDao.selectGoods(gid);
if (goods==null){
throw new NullPointerException(gid+"商品不存在");
}else if (goods.getAmount()<num){
throw new NotEnougthException(gid+"库存不足");
}
//更新库存
Goods goods1 = new Goods();
goods1.setAmount(num);
goods1.setId(gid);
goodsDao.updateGoods(goods1);
System.out.println("buy方法的结束");
}
}
/**
* 运行时异常
*/
public class NotEnougthException extends RuntimeException{
public NotEnougthException() {
super();
}

public NotEnougthException(String message) {
super(message);
}
}

8. 创建spring的配置文件

  • 声明数据源、SqlSessionFactoryBean、dao代理对象、组件扫描器

<?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:component-scan base-package="com.kgc"/>
<!--声明数据源DataSource-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/springDb"/>
<property name="username" value="root"/>
<property name="password" value="ok"/>
</bean>
<!--声明SqlSessionFactoryBean-->
<bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!--指定数据源-->
<property name="dataSource" ref="dataSource"/>
<!--指定mybatis主配置文件-->
<property name="configLocation" value="classpath:mybatis.xml"/>
</bean>
<!--声明MapperScannerConfigurer-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!--指定SqlSessionFactory对象的名称-->
<property name="sqlSessionFactoryBeanName" value="factory"/>
<!--指定基本包,dao接口所在的包名-->
<property name="basePackage" value="com.kgc.dao"/>
</bean>
</beans>

5.6 Spring框架使用自己的注解@Transactional控制事务

  • @Transactional注解,使用注解的属性控制事务(隔离级别、传播行为、超时)

  • 属性:

    1. propagation:事务的传播行为,他使用的Propagation类的枚举值。例如:Propagation.REQUIRED

    2. isolation:使用isolation类的枚举值,表示隔离级别。默认isolation.DEFAULT

    3. readOnly:boolean类型的值,表示数据库操作是不是只读的。默认是false

    4. timeout:事务超时,默认为1,整数值,单位是秒。例如:timeout=20

    5. rollbackFor:表示回顾的异常类列表,他的值是一个数组,每个值是异常类型的class

    6. rollbackForClassName:表示回顾的异常类列表,他的值是异常类的名称,是String类型的值

    7. noRollbackFor:表示不需要回顾的异常类列表,是class类型的

    8. noRollbackForClassName:表示不需要回顾的异常类列表,是String类型的值

  • 位置:

    1. 在业务方法的上面,是在public方法的上面

    2. 在类的上面

  • 注解的使用步骤:

    1. 在spring的配置文件,声明事务的内容

      • 声明事务管理器,说明使用哪个事务管理器对象

      • 声明使用注解管理事务,开启是注解驱动

    2. 在类的源代码中,加入@Transactional

  • 事务的控制模式:

    1. 编码式,在代码中编程控制事务

    2. 声明式事务,不用编码

声明事务管理器

<!--声明事务的控制-->
<!--声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--指定数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--开启事务注解驱动:告诉框架使用注解管理事务
transaction-manager:指定事务管理器的id
-->
<tx:annotation-driven transaction-manager="transactionManager"/>

使用事务修改5.5中的代码

/**
* 商品业务逻辑层实现类
*/
@Service
public class BuyGoodsServiceImpl implements BuyGoodsService {
@Autowired
private SaleDao saleDao;
@Autowired
private GoodsDao goodsDao;
/**
*rollbackFor的使用:
* 1.框架首先检查方法抛出的异常是不是在rollbackFor的数组中,如果在一定回顾
* 2.如果方法抛出的异常不在rollbackFor数组,框架会继续检查抛出的异常是不是RuntimeException
* 如果是RuntimeException,一定回顾。
*/
//@Transactional 放在public方法的上面,表示方法有事务功能
@Transactional(propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,
readOnly = false,timeout = 20,
rollbackFor = {NullPointerException.class,NotEnougthException.class})
@Override
public void buy(Integer gid, Integer num) {
System.out.println("buy方法的开始");
//生成销售记录
Sale sale = new Sale();
sale.setGid(gid);
sale.setNums(num);
saleDao.insertSale(sale);
//查询商品
Goods goods = goodsDao.selectGoods(gid);
if (goods==null){
throw new NullPointerException(gid+"商品不存在");
}else if (goods.getAmount()<num){
throw new NotEnougthException(gid+"库存不足");
}
//更新库存
Goods goods1 = new Goods();
goods1.setAmount(num);
goods1.setId(gid);
goodsDao.updateGoods(goods1);
System.out.println("buy方法的结束");
}
}
  • @Transactional使用的特点:

    1. spring框架自己提供的事务控制

    2. 适合中小型项目

    3. 使用方便,效率高

5.7 使用Aspectj框架在spring的配置文件中,声明事务控制

  • 使用Aspectj的aop声明事务控制,叫做声明式事务

  • 使用步骤:

    1. pom.xml加入spring-aspects的依赖

    2. 在spring的配置文件中声明事务的内容

      1. 声明事务管理器

      2. 事务业务方法需要的事务属性

      3. 声明切入点表达式

  • 声明式事务:

<!--声明式事务:不用写代码-->
<!--声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--指定数据源-->
<property name="dataSource" ref="dataSource"/>
</bean>
<!--声明业务方法的事务属性(隔离级别、传播行为、超时)
id:给业务方法配置事务段代码起个名称,唯一值
transaction-manager:事务管理器的id
-->
<tx:advice id="serviceAdvice" transaction-manager="transactionManager">
<!--给具体的业务方法增加事务的说明-->
<tx:attributes>
<!--
给具体的业务方法,说明他需要的事务属性
name:业务方法的名称,配置name的值:1.业务方法的名称 2.带有部分通配符的方法名称 3. 使用*
propagation:传播行为
isolation:管理级别
timeout:超时时间
read-only:是否只读,默认是false
rollback-for:指定回顾的异常类列表,使用的异常全限定名称
-->
<tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT" timeout="20" read-only="false" rollback-for=""/>
<!--在业务方法有命名规则后,可以对一些方法使用事务-->
<tx:method name="add*" propagation="REQUIRES_NEW"/>
<tx:method name="modify*" propagation="REQUIRED"/>
<tx:method name="remove*" propagation="REQUIRED"/>
</tx:attributes>
</tx:advice>
<!--声明切入点表达式:表示哪些包中的类,类中的方法参与事务-->
<aop:config>
<!--声明切入点表达式
expression:切入点表达式,表示哪些类和类中的方法要参与事务
id:切入点表达式的名称,唯一值
-->
<aop:pointcut id="pointcut" expression="execution(* com.kgc.service..*.*(..))"/>
<!--关联切入点表达式和事务通知-->
<aop:advisor advice-ref="serviceAdvice" pointcut-ref="pointcut"/>
</aop:config>
  • 声明式事务优缺点:

    1. 缺点:理解难,配置复杂

    2. 优点:代码和事务配置是分开的,控制事务源代码不用修改;能快速的了解和掌握项目的全部事务;适合大型项目

第六章 Spring与Web

  • 现在使用容器对象的问题

    1. 在web应用中创建容器对象次数多

    2. 在多个servlet中,分别创建容器对象

  • 需要一个什么样的容器对象

    1. 容器对象只有一个,创建一个就可以了

    2. 容器对象应该在整个项目中共享使用,多个servlet都能使用同一个容器对象

  • 解决问题使用监听器:ServletContextListener(两个方法:初始时执行的,销毁时执行的)

  • 在监听器中,创建好的容器对象,应该放在web应用中的ServletContext作用域中

6.1 ContextLoaderListener

  • ContextLoaderListener是一个监听器对象,是spring框架提供的。

  • 使用这个监听器作用:

    1. 创建容器对象,只有一次

    2. 把容器对象放入到ServletContext作用域

  • 使用步骤:

    1. 在pom.xml加入依赖spring-web

    2. web.xml声明监听器

添加依赖

<!--监听器依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>

声明监听器

<!--声明监听器
默认监听器:创建容器对象时,读取的配置文件:/WEB-INF/applicationContext.xml
-->
<!--自定义容器使用的配置文件路径
context-param:叫做上下文参数,给监听器提供参数的
contextConfigLocation:名称是固定的,表示自定义spring配置文件的路径
-->
<context-param>
<param-name>contextConfigLocation</param-name>
<!--自定义配置文件的路径-->
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>

6.2 ContextLoaderListener源代码

public class ContextLoaderListener extends ContextLoader implements ServletContextListener {
//监听器的初始方法
public void contextInitialized(ServletContextEvent event) {
this.initWebApplicationContext(event.getServletContext());
}
}

private WebApplicationContext context;

public WebApplicationContext initWebApplicationContext(ServletContext servletContext) {
long startTime = System.currentTimeMillis();

try {
if (this.context == null) {
//创建spring容器对象
this.context = this.createWebApplicationContext(servletContext);
}
//把容器对象放到servletContext作用域
//key=WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE
//value=容器对象
servletContext.setAttribute
(WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE, this.context);

} catch (Error | RuntimeException var8) {

}
}
}
//WebApplicationContext是web项目中使用的容器对象
public interface WebApplicationContext extends ApplicationContext {}
  • 所有监听器获取容器对象

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String name = request.getParameter("name");
String age = request.getParameter("age");
//创建容器对象
//ApplicationContext ctx = new ClassPathXmlApplicationContext("WEB-INF/applicationContext.xml");
//使用监听器已经创建好了容器对象,从ServletContext获取容器对象
WebApplicationContext ctx = null;
String key = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
ServletContext sc = getServletContext();//getServletContext,servlet中的方法
//ServletContext sc = request.getServletContext();//HttpServletRequest对象的方法
Object attr = sc.getAttribute(key);
if (attr!=null){
ctx = (WebApplicationContext) attr;
}
StudentService studentService = ctx.getBean(StudentService.class);
Student student = new Student();
student.setName(name);
student.setAge(Integer.valueOf(age));
studentService.addStudent(student);
//给用户显示请求的处理结果
request.getRequestDispatcher("/show.jsp").forward(request,response);
}
  • 使用spring工具类获取容器对象

protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
String name = request.getParameter("name");
String age = request.getParameter("age");
//使用spring提供的工具方法,获取容器对象
WebApplicationContext ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
StudentService studentService = ctx.getBean(StudentService.class);
Student student = new Student();
student.setName(name);
student.setAge(Integer.valueOf(age));
studentService.addStudent(student);
//给用户显示请求的处理结果
request.getRequestDispatcher("/show.jsp").forward(request,response);
}

 

posted @ 2021-08-02 16:04  乃智  阅读(85)  评论(0)    收藏  举报