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的官网
- 地址:https://spring.io
1.3 Spring优点
- Spring是一个框架,是一个半成品的软件,有20个模块组成。它是一个容器管理对象,容器是装东西的,Spring容器不装文本、数字,装的是对象。Spring是存储对象的容器
- 轻量
- Spring框架使用的jar都比较小,一般在1M以下或者几百kb。Spring核心功能的所需的jar总共在3M左右。Spring框架运行占用的资源少,运行效率高,不依赖其他jar
2. 针对接口编程,解耦合 - Spring提供了ioc控制反转,由容器管理对象,对象的依赖关系。原来在程序代码中的对象创建方式,现在由容器完成。对象之间的依赖解耦合
3. AOP编程的支持 - 通过Spring提供的AOP功能,方便进行面向切面的编程,许多不容易用传统OOP实现的功能可以通过AOP轻松付在Spring中,开发人员可以从繁杂的事务管理代码中解脱出来,通过声明式方式灵活地进行事务的管理,提高开发效率和质量
4. 方便集成各种优秀框架 - Spring不排斥各种优秀的开源框架,相反Spring可以降低各种框架的使用难度,Spring提供了对各种优秀框架(如Struts、Hibernate、MyBatis)等的直接支持,简化框架的使用。Spring像插线板一样,其他框架是插头,可以容易的组合到一起。需要使用哪个框架,就把这个插头放入插线板,不需要可以轻易的移除
第二章 IoC控制反转
2.1 概念
- IoC(Inversion of Control):控制反转,是一个理论、一个指导思想,指导开发人员如何使用对象,管理对象的。把对象的创建,属性赋值,对象的生命周期都交给代码之外的容器管理。
-
IoC分为控制和反转:
- 控制:对象创建,属性赋值,对象生命周期管理
- 反转:把开发人员管理对象的权限转移给了代码之外的容器实现,由容器完成对象的管理
- 正转:开发人员在代码中,使用new构造方法创建对象。开发人员掌握了对象的创建,属性赋值,对象从开始到销毁的全部过程。开发人员有对对象全部控制
- 通过容器,可以使用容器中的对象(容器已经创建了对象,对象属性赋值了,对象也组装好了)。
- Spring就是一个容器,可以管理对象,创建对象,给属性赋值。
-
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();
- Spring框架使用的DI实现IoC
- 通过Spring框架,只需要提供要使用的对象名称就可以了 ,从容器中获取名称对应的对象
- spring底层使用的反射机制,通过反射创建对象,给属性赋值
2.2 Spring的配置文件
- spring标准的配置文件:
- 跟标签是beans
- beans后面的是约束文件说明
- beans里面是bean的声明
- 声明是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容器创建对象的特点
- 容器对象ApplicationContext:接口
- 通过ApplicationContext对象,获取要使用的其他java对象,执行getBean(“bean标签的id”)
- spring默认是调用类的无参数构造方法,创建对象
- spring读取配置文件,一次创建好所有的java对象,都放在map中
2.4 DI:给属性赋值
- spring调用类的无参数构造方法,创建对象,对象创建后给属性赋值
- 给属性赋值可以使用:
- xml配置文件中的标签和属性
- 使用注解
- DI分类:
- set注入,也叫做设值注入
- 构造注入
- p命名空间注入
2.4.1 基于xml的DI
- 在xml配置文件中使用标签和属性,完成对象创建,属性赋值
- 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>
- 构造注入
- 构造注入: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>
- 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"/>
- 引用类型的自动注入
- 概念:spring可以根据某些规则给引用类型完成赋值,只对引用类型有效,规则byName、byType
- byName(按名称注入):java类中引用类型属性名称和spring容器中bean的id名称一样的,且数据类型也是一样的,这些bean能够赋值给引用类型
- byType(按类型注入):java类中引用类型的数据类型和spring容器中bean的class值是同源关系的,这样的bean能赋值给引用类型
- 概念:spring可以根据某些规则给引用类型完成赋值,只对引用类型有效,规则byName、byType
<!--
引用类型自动注入: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>
-
项目中使用多个spring配置文件
- 分多个配置文件的方式:
- 按功能模块分,一个模块一个配置文件
- 按类的功能分,数据库操作相关的类在一个文件,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对象创建,属性赋值
-
注解使用的核心步骤:
- 在源代码加入注解,例如@Component
- 在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使用外部属性配置文件
-
步骤:
- 创建配置文件:myconf.properties
myname=李四myage=20
- 读取外部属性配置文件
<!--读取外部的属性配置文件 property-placeholder:读取properties这样的文件 --><context:property-placeholder location="classpath:myconf.properties"/>
- 使用配置文件
@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作为容器适合管理什么对象?
- service对象,dao对象
- 工具类对象
- 不适合交给spring的对象?
- 实体类
- servlet、listener、filter等web中的对象。它们是tomcat创建和管理的
第三章 AOP 面向切面编程
3.1 增加功能,导致的问题
- 在源代码中,业务方法中增加的功能
- 源代码可能改动的比较多
- 重复的代码比较多
- 代码难于维护
3.2 AOP概念
3.2.1 什么是AOP
-
AOP(Aspect Orient Programming):面向切面编程
-
Aspect :表示切面,给业务方法增加的功能,叫做切面。切面一般都是非业务功能,而且切面功能一般都是可以复用的。例如:日志功能、事务功能、权限检查、参数检查、统计信息等等
-
Orient :面向,对着
-
Programming:编程
-
怎么理解面向切面编程?(以切面为核心设计开发你的应用)
- 设计项目时,找出切面的功能
- 安排切面的执行时间,执行的位置
3.2.2 AOP的作用
- 让切面功能复用
- 让开发人员专注业务逻辑,通过开发效率
- 实现业务功能和其他非业务功能解耦合
- 给存在的业务方法,增加功能,不用修改原来的代码
3.2.3 AOP中术语
- Aspect :切面,给业务方法增加的功能
- JoinPoint:连接点,连接切面的业务方法。在这个业务方法执行时,会同时执行切面的功能
- Pointcut:切入点,是一个或多个连接点集合,表示这些方法执行时,都能增加切面的功能。表示切面执行的位置
- target:目标对象,给哪个对象增加切面的功能,这个对象就是目标对象
- Advice:通知(增强),表示切面的执行时间,在目标方法之前执行切面,函数目标方法之后执行切面
- AOP中重要的三个要素:Aspect 、Pointcut、Advice。三个概念的理解是:我们在Advice的时间,在Pointcut的位置,执行Aspect
- AOP是这个动态的思想。在程序运行期间,创建代理(ServiceProxy),使用代理执行方法时,增加切面的功能。这个代理对象是存在内存中的
3.2.4 什么时候用AOP
- 你要给某些方法增加相同的一些功能,源代码不能改,给业务方法增加非业务功能,也可以使用AOP
3.2.5 AOP技术思想的实现
- 使用框架实现AOP。实现AOP的框架有很多,有名的有两个:
- Spring:Spring框架实现AOP思想中的部分功能。Spring框架实现AOP的操作比较繁琐,笨重
- Aspectj:独立的框架,专门做AOP。属于Eclipse
3.3 使用Aspectj框架实现AOP
- Aspectj框架可以使用注解和xml配置文件两种方式实现AOP
3.3.1 通知
- Aspectj表示切面执行时间,用到通知(Advice)。这个通知可以使用注解表示
- 主要五个注解,表示切面的5个执行时间,这些注解叫做通知注解
- Before:前置通知
- AfterRetunring:后置通知
- Around:环绕通知
- AfterThrowing:异常通知
- After:最终通知
3.3.2 Pointcut(位置)
- Pointcut用来表示切面执行的位置,使用Aspectj中切入点表达式
- 切入点表达式语法:execution(访问权限 方法返回值 方法声明(参数) 异常类型)
- 注:方法返回值和方法声明(参数)必须有
3.3.3 代理生成器
<!--声明自动代理生成器:目的是创建目标对象的代理 调用aspectj框架中的功能,寻找spring容器中的所有目标对象, 把每个目标对象加入切面类中的功能,生成代理 这个代理对象是修改的内存中的目标对象,这个目标对象就是代理对象(ServiceProxy)--><aop:aspectj-autoproxy />
3.3.4 @Before前置通知
- @Before:在目标方法之前执行的
/** *@Aspect: 切面类的注解 * 位置:放在某个类的上面 * 作用:表示当前类是切面类 * 切面类:表示切面功能的类 */@Aspectpublic 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:表示正在执行的业务方法
@Aspectpublic 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:在目标方法之后执行的
@Aspectpublic 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=“切入点表达式”)
- 使用环绕通知:就是调用切面类中的通知方法
@Aspectpublic 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=“自定义变量”)
@Aspectpublic 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=“切入点表达式”)
@Aspectpublic 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=“切入点表达式”)
@Aspectpublic 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语句,需要使用那些对象
- 需要有dao接口的代理对象,例如StudentDao接口,需要一个它的代理对象。使用SqlSession.getMapper(StudentDao.class),得到dao代理对象
- 需要有SqlSessionFactory,创建SqlSessionFactory对象,才能使用openSession()得到SqlSession对象
- 数据源DataSource对象,使用一个更强大,功能更多的连接池对象代替MyBatis自己的PooledDataSource
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Q4P0WH5K-1627891130751)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20210731162426306.png)]
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项目
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-bw3IvVsu-1627891130755)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20210728204208945.png)]
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();}
/** * 学生业务逻辑层实现类 */@Servicepublic 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的配置文件
- 声明数据源DataSource,使用阿里的Druid连接池
- 声明SqlSessionFactoryBean类,在这个类内部创建的是SqlSessionFactory对象
- 声明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 事务管理器
不同的数据库访问技术,处理事务是不同的
- 使用jdbc访问数据库,事务处理
public void updateAccount(){ Connection con = ... con.setAutoCommit(false); stat.insert(); stat.update(); con.commit(); con.setAutoCommit(true)}
- 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
- 事务管理器工作方式:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fX3pW71M-1627891130759)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20210801140542903.png)]
5.3.2 事务的提交和回顾的时机
- 声明时候提交事务,回顾事务?
- 当你的业务方法正常执行时,没有异常,事务是正常提交的。如果你的业务方法抛出运行时异常,事务是回顾的。
- 异常的分类:
- Error:严重错误,回顾事务
- Exception:异常类,可以处理的异常情况
- 运行时异常:RuntimeException和他的子类都是运行时异常,在程序执行过程中抛出的异常。常见的运行时异常:空指针异常NullPoinerException、数字格式异常NumberFormatException、算术异常ArithmeticException等
- 受查异常:编写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接口,定义了三类常量,常量定义了有关事务控制的属性。
- 事务的属性:
- 隔离级别
- 传播行为
- 事务的超时
- 给业务方法说明事务属性,和ACID不一样。
5.4.1 隔离级别
- 隔离级别:控制事务之间影响的程度。有5个值,只有4个隔离级别
- DEFAULT:采用DB默认的事务隔离级别。mysql默认为REPEATABLE_READ;oracle默认为READ_COMMITTED
- READ_UNCOMMITTED:读未提交。未解决任何并发问题
- READ_COMMITTED:读已提交。解决脏读,存在不可重复读和幻读
- REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
- SERIALIZABLE:串行化。不存在任何并发问题
5.4.2 超时时间
- 超时时间:表示一个业务方法最长的执行时间,如果到达时间没有执行完毕,spring会回顾事务。
- 超时时间,以秒为单位,整数值,默认是-1。
5.4.3 传播行为
-
传播行为:业务方法在调用时,事务在方法之间的传递和使用
-
使用传播行为,标识方法有无事务
-
传播行为有7个值:
- PROPAGATION_REQUIRED
- PROPAGATION_SUPPORTS
- PROPAGATION_REQUIRES_NEW
- PROPAGATION_MANDATORY
- PROPAGATION_NOT_SUPPORTED
- PROPAGATION_NEVER
- PROPAGATION_NESTED
-
前三个需要掌握:
- REQUIRED:spring默认传播方式,方法在调用的时候,如果存在事务就使用当前的事务,如果没有事务则新建事务,方法在新事务中执行
- SUPPORTS:支持,方法有事务可以正常执行,没有事务也可以正常执行
- 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 goodsVALUES(1001,'笔记本电脑',100,6000),(1002,'手机',50,3000);
2. 创建Maven项目
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-RfkkPUR4-1627891130763)(C:\Users\naizhi\AppData\Roaming\Typora\typora-user-images\image-20210728204208945.png)]
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注解,使用注解的属性控制事务(隔离级别、传播行为、超时)
- 属性:
- propagation:事务的传播行为,他使用的Propagation类的枚举值。例如:Propagation.REQUIRED
- isolation:使用isolation类的枚举值,表示隔离级别。默认isolation.DEFAULT
- readOnly:boolean类型的值,表示数据库操作是不是只读的。默认是false
- timeout:事务超时,默认为1,整数值,单位是秒。例如:timeout=20
- rollbackFor:表示回顾的异常类列表,他的值是一个数组,每个值是异常类型的class
- rollbackForClassName:表示回顾的异常类列表,他的值是异常类的名称,是String类型的值
- noRollbackFor:表示不需要回顾的异常类列表,是class类型的
- noRollbackForClassName:表示不需要回顾的异常类列表,是String类型的值
- 位置:
- 在业务方法的上面,是在public方法的上面
- 在类的上面
- 注解的使用步骤:
- 在spring的配置文件,声明事务的内容
- 声明事务管理器,说明使用哪个事务管理器对象
- 声明使用注解管理事务,开启是注解驱动
- 在类的源代码中,加入@Transactional
- 在spring的配置文件,声明事务的内容
- 事务的控制模式:
- 编码式,在代码中编程控制事务
- 声明式事务,不用编码
声明事务管理器
<!--声明事务的控制-->
<!--声明事务管理器-->
<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使用的特点:
- spring框架自己提供的事务控制
- 适合中小型项目
- 使用方便,效率高
5.7 使用Aspectj框架在spring的配置文件中,声明事务控制
-
使用Aspectj的aop声明事务控制,叫做声明式事务
-
使用步骤:
- pom.xml加入spring-aspects的依赖
- 在spring的配置文件中声明事务的内容
- 声明事务管理器
- 事务业务方法需要的事务属性
- 声明切入点表达式
-
声明式事务:
<!--声明式事务:不用写代码-->
<!--声明事务管理器-->
<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>
- 声明式事务优缺点:
- 缺点:理解难,配置复杂
- 优点:代码和事务配置是分开的,控制事务源代码不用修改;能快速的了解和掌握项目的全部事务;适合大型项目
第六章 Spring与Web
-
现在使用容器对象的问题
- 在web应用中创建容器对象次数多
- 在多个servlet中,分别创建容器对象
-
需要一个什么样的容器对象
- 容器对象只有一个,创建一个就可以了
- 容器对象应该在整个项目中共享使用,多个servlet都能使用同一个容器对象
-
解决问题使用监听器:ServletContextListener(两个方法:初始时执行的,销毁时执行的)
-
在监听器中,创建好的容器对象,应该放在web应用中的ServletContext作用域中
6.1 ContextLoaderListener
- ContextLoaderListener是一个监听器对象,是spring框架提供的。
- 使用这个监听器作用:
- 创建容器对象,只有一次
- 把容器对象放入到ServletContext作用域
- 使用步骤:
- 在pom.xml加入依赖spring-web
- 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);
}