第一章 Spring概述
1.1Spring概述
1)、Spring是一个开源框架
2)、Spring为简化企业级开发而生,使用Spring,javaBean就可以实现很多以前要EJB才能实现的功能。同样的功能,在EJB中通过繁琐的配置和复杂的代码才能够实现,而在Spring中却是非常的优雅和简洁
3)、Spring是一个IOC(DI)和AOP容器框架
4)、Spring的优良特性
非侵入性:基于Spring开发的应用中的对象可以不依赖Spring的API
依赖注入:DI——Dependency Injection,反转控制(IOC)最经典的实现
面向切面编程:Aspect Oriented Programming——AOP
容器:Spring是一个容器,因为它包含并且管理应用对象的生命周期
组件化:Spring实现了使用简单的组件配置组合成一个复杂的应用。在Spring中可以是使用XML和Java注解组合这些对象
一站式:在IOC和AOP的基础上可以整合各种企业应用的开源框架和优秀的第三方库(实际上Spring自身也提供了表述层的SpringMVC和持久层Spring JDBC)
5)、Spring模板
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-i1NnaLC0-1617112808701)(D:\笔记整合\img\spring模板.png)]
1.2 搭建Spring运行时环境
加入jar包
Spring自身jar包:spring-framework-5.2.9.RELEASE\libs目录下
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<!--日志jar包-->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
</dependencies>
1.3 HelloWorld
1)、目标:使用Spring创建对象,为属性赋值
2)、创建Student类
private Integer studentId;
private String studentName;
private Integer studentAge;
//get/set属性
3)、创建spring配置文件
<!--
使用bean元素定义一个由IOC容器创建的对象
class属性指定用于创建bean的全类名
id属性指定用于引用bean实例的标识
-->
<bean class="pojo.Student" id="student">
<property name="studentId" value="1001"/>
<property name="studentName" value="邓大佬"/>
<property name="studentAge" value="20"/>
</bean>
4)、测试:通过Spring的IOC容器创建Student类实例
//读取spring中的Bean.xml文件
ApplicationContext iocContext = new ClassPathXmlApplicationContext("spring/Bean.xml");
//读取Bean.xml文件bean id属性为student
Student student = (Student) iocContext.getBean("student");
System.out.println(student);
第二章 IOC容器和Bean的配置
2.1 IOC和DI
2.1.1 IOC(Inversion of Control):反转控制
在应用程序中的组件需要获取资源时,传统的方式是组件主动的从容器中获取所需要的资源,在这样的模式下开发人员往往需要知道在具体容器中特定资源的获取方式,增加了学习成本,同时降低了开发效率
反转控制的思想完全颠覆了应用程序组件获取资源的传统方式;反转了资源获取方向——改由容器主动的将资源推送给需要的组件,开发人员不需要知道容器是如何创建资源对象的,只需要提供接收资源的方式即可,极大的降低了学习成本,提高了开发的效率。这种行为也称为查找的被动形式
2.1.2 DI(Dependency Injection):依赖注入
IOC的另一种表述方式,即组件以一些预先定义好的方式(例如:setter 方法)接受来自于容器的资源注入。相对于IOC而言,这种表述更直接
2.1.3 IOC容器在Spring中的实现
1)、在通过IOC容器读取Bean的实例之前,需要先将IOC容器本身实例化
2)、Spring提供了IOC容器的两种实现方式
BeanFactory:IOC容器的基本实现,是Spring内部的基础设施,是面向对象Spring本身的,不是提供给开发人员使用的
ApplicationContext:BenFactory的子接口,提供了更多高级特性。面向Spring的使用者,几乎所有场合都使用ApplicationContext而不是底层的BeanFactory
2.1.4 ApplicationContext的主要实现类
1)、ClassPathXmlApplicationContext:对应路径下的XML格式的配置文件
2)、FileSystemXmlApplicationContext:对应文件系统中的XML格式的配置文件
3)、在初始化时就创建单例的bean,也可以通过配置的方式指定创建的Bean是多例的
2.1.5 ConfigurationApplicationContext
1)、是ApplicationContext的子接口,包含一些扩展
2)、refresh()和close()让ApplicationContext具有启动、关闭和刷新上下文的能力
2.1.6 WebApplicationContext
专门为WEB应用而准备的,它允许从相对于WEB根目录的路径中完成初始化工作
2.2 通过类型获取bean
1)、从IOC容器中获取bean时,除了通过id值获取,还可以通过bean的类型获取。同一个类型的bean在XML文件中配置了多个,则获取时会抛出异常,所以同一类型的bean在容器中必须是唯一的
//只能在bean的唯一值
Student student = iocContext.getBean(Student.class);
2)、或者可以使用另一个重载的方法,同时指定bean的id值和类型
//指定bean的id值和类型
Student student = iocContext.getBean("student", Student.class);
2.3 给bean的属性赋值
2.3.1 依赖注入的方式
1)、通过bean的setXxx()方法赋值,在POJO中写set属性
2)、通过bean的构造器赋值
Spring自动匹配合适的构造器
<!--会根据顺序自动匹配适合的构造器-->
<bean class="pojo.Student" id="student">
<constructor-arg value="1001"/>
<constructor-arg value="邓大佬"/>
<constructor-arg value="20"/>
</bean>
通过索引值指定参数位置
<bean class="pojo.Student" id="student">
<constructor-arg value="1001" index="0"/>
<constructor-arg value="邓大佬" index="1"/>
<constructor-arg value="20" index="2"/>
</bean>
通过类型区分重载的构造器
<bean class="pojo.Student" id="student">
<constructor-arg value="1001" index="0" type="java.lang.Integer"/>
<constructor-arg value="邓大佬" index="1" type="java.lang.String"/>
<constructor-arg value="20" index="2" type="java.lang.Integer"/>
</bean>
2.3.2 p名称空间
为了简化XML文件的配置,越来越多的XML文件采用属性而非子元素配置信息。Spring从2.5版本开始引入了一个新的p命名空间,可能通过元素属性得到方式配合Bean的属性
使用p命名空间后,基于XML的配置方式将进一步简化
xmlns:p="http://www.springframework.org/schema/p"<!--要引入的约束-->
<bean class="pojo.Student" id="student" p:studentId="1001" p:studentName="邓大佬" p:studentAge="20"/>
2.3.3 可以使用的值
一:字面量
1)、可以使用字符串表示的值,可以通过value属性或value子节点的方式指定
2)、基本数据类型及其封装类、String等类型都可以采取字面值注入的方式
3)、若字面值中包含特殊字符,可以使用<![CDATA]>把字面包裹起来
二:null
<bean class="pojo.Student" id="student">
<property name="studentId" value="1001"/>
<property name="studentName">
<null/>
</property>
<property name="studentAge" value="20"/>
</bean>
三:给bean的级联属性赋值
<bean id="action" class="pojo.Student">
<property name="service" ref="service"/> <!-- 设置级联属性(了解) -->
<property name="service.dao.dataSource" value="DBCP"/>
</bean>
2.4 集合属性
在Spring中可以通过一组内置的XML标签来配置集合属性,例如:,或
2.4.1 数组和List
配置java.util.List类型的属性,需要指定标签,在标签里包含一些元素。这些标签可以通过指定简单的常量值,通过指定对其他Bean的引用。通过指定内置bean定义。通过指定空元素。甚至可以内嵌其他集合
配置java.util.Set需要使用标签,定义的方法与List一样
<bean class="pojo.Student" id="student">
<property name="studentId" value="1001"/>
<property name="studentName" value="邓大佬"/>
<property name="studentAge" value="20"/>
<property name="hobbyList">
<list>
<value>爱好</value>
<value>军事</value>
</list>
</property>
<!--以bean的引用为值的List集合-->
<property name="arrayList">
<list>
<ref bean="student2"/>
</list>
</property>
</bean>
<bean class="pojo.Student" id="student2">
<property name="studentId" value="1002"/>
<property name="studentName" value="吴新"/>
<property name="studentAge" value="12"/>
</bean>
2.4.2 Map
Java.util.Map通过
必须在标签里定义键
因为键和值的类型没有限制,所以可以自由地为它们指定、、或元素
可以将Map的键和值作为的属性定义;简单常量使用key和value来定义;bean引用通过key-ref和value-ref属性定义
<bean class="pojo.Student" id="student">
<property name="mapArr">
<map>
<entry key="1" value="邓老二"/>
<entry key="2" value="吴老大"/>
</map>
</property>
</bean>
2.4.3 集合类型的bean
如果只能将集合对象配置在某个bean内部,则这个集合的配置将不能重用。我们需要将集合bean的配置拿到外面,供其他bean引用
配置集合类型的bean需要引入util名称空间
xmlns:util="http://www.springframework.org/schema/util"<!--要引入的约束-->
<util:list id="list">
<ref bean="student"/>
</util:list>
2.5 FactoryBean
2.5.1 FactoryBean
Spring中有两种类型的bean,一种是普通bean,另一种是工厂bean,即FactoryBean。工厂bean跟普通bean不同,其返回值的对象不是指定类的一个实例,其返回值的是该工厂bean的getObject方法所返回的对象
工厂bean必须实现org.springframework.beans.factory.FactoryBean接口
2.6 bean的高级配置
2.6.1 配置信息的继承
1)、背景
查看下面两个的配置,其中属性是重复的
<bean class="pojo.Student" id="student">
<property name="studentId" value="1001"/>
<property name="studentName" value="邓大佬"/>
<property name="studentAge" value="20"/>
<!--重复的属性值-->
<property name="aClass" ref="aClass"/>
</bean>
2)、配置信息的继承
Spring允许继承bean的配置,被继承的bean称为父bean。继承这个父bean的bean称为子bean
子bean从符bean中继承配置,包括bean的属性配置
子bean也可以覆盖从父bean继承过来的配置
3)、补充说明
父bean可以作为配置模板,也可以作为bean实例。若只想把父bean作为模板,可以设置的abstract属性为true,这样Spring将不会实例化这个bean
如果一个bean的class属性没有指定,则必须是抽象bean
并不是元素里的所有属性都会被继承。比如:autowire,abstract等。也可以忽略父bean的class属性,让子bean指定自己的类,而共享相同属性配置。
此时abstract必须设为true
2.6.2 bean之间的依赖
有的时候创建一个bean的时候需要保证另一个也被创建,这时我们称前面的bean对后面的bean有依赖。例如:要求创建aClasst对象的时候必须创建Student
这里需要注意的是依赖关系不等于引用关系,aClass即使依赖Student也可以不引用它
<!--depends-on取决于student类的创建-->
<bean class="pojo.Class" id="aClass" depends-on="student">
<property name="ID" value="101"/>
<property name="className" value="大数据"/>
<property name="classArea" value="一教"/>
</bean>
2.7 bean的作用域
在Spring中,可以在元素的scope设置bean的作用域,以决定这个bean是单实例的还是多实例
默认情况下,Spring只为每个IOC容器里声明的bean创建唯一一个实例,整个IOC容器范围内都能共享该实例;所有后续的getBean()调用和bean引用都返回这个唯一的bean实例。该作用域被称为singleton,它是所有bean的默认作用域
singleton:在SpringIOC容器中仅存在一个Bean实例,Bean以单实例的方式存在
prototype:每次调用getBean()时都会返回一个新的实例
request:每次HTTP请求都会创建一个新的Bean,该作用域仅适用于WebApplicationContext环境
session:同一个HTTP Session共享一个Bean,不同的HTTP Session使用不同的Bean。该作用域仅适用于WebApplicationContext环境
当bean的作用域为单例是,Spring会在IOC容器对象创建时就创建bean的对象实例
2.8 bean的生命周期
1)、Spring IOC 容器可以管理bean的生命周期,Spring允许在bean生命周期内特定的时间点执行指定的任务
2)、Spring IOC容器对bean的生命周期进行管理的过程
通过构造器或者工厂方法创建bean实例
为bean的属性设置值和对其他bean的引用
调用bean的初始化方法
bean可以使用了
当容器关闭时,调用bean的销毁
3)、在配置bean时,通过init-method和destroy-method属性为bean指定初始化方法和销毁方法
4)、bean的后置处理器
bean后置处理器允许在调用初始化方法前后的bean进行额外的处理
bean后置处理器对IOC容器里的所以bean实例逐一处理,而非单一实例。其典型应用是:检查bean属性的正确性或根据特定的标准更改bean属性
bean后置处理器时需要实现接口:
org.springframework.bean.factory.BeanPostProcessor。在初始化方法被调用前后,spring将把每个bean实例分别传递给上述接口的以下两个方法:
·postProcessBeforInitialization(Object, String)
·postProcessAfterInitialization(Object, String)
5)、添加bean后置处理器后bean的声明周期
通过构造器或者工厂创建bean实例
为bean的属性设置值和对其他bean的引用
将bean实例传递给bean后置处理器的postProcessBeforInitialization()方法
调用bean的初始化方法
将bean实例传递给bean后置处理器的postProcessAfterInitialization
bean可以使用了
当容器关闭时调用bean的销毁方法
2.9引用外部属性文件
当bean的配置信息逐渐增多时,查找和修改一些bean的配置信息就变得愈加困难。这时可以将一部分信息提取到bean配置文件的外部,以properties格式的属性文件保存起来,同时在bean的配置文件中引用properties属性文件中的内容,从而实现一部分属性值在发生变化时仅修改properties属性文件即可。这种技术多用于连接数据库的基本信息的配置
可以直接配置
也可以引入外部文件,要有context名称空间的依赖
<!--引入外部文件,location表示加载路径为classpathon类路径文件下-->
<context:property-placeholder location="classpath:jdbcConfig.properties"/>
<bean class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="driverClassName" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</bean>
2.10 自动装配
2.10.1 自动装配的概念
1)、手动装配:以value或ref的方式明确指定属性值都是手动装配
2)、自动装配:根据指定的装配规则,不需要明确指定,Spring自动将匹配的属性值注入bean中
2.10.2 装配模式
1)、根据类型自动装配,将类型匹配的bean作为属性注入到另一个bean中,若IOC容器中有多个与目标bean类型一致的bean,Spring将无法判定那个bean最适合该属性,所以不能执行自动装配
2)、根据名称自动装配:必须将目标bean的名称和属性名设置的完全相同
3)、通过构造器自动装配:当bean中存在多个构造器时,此种自动装配方式将会很复杂。不推荐使用
2.10.3 选用建议
相对于使用注解的方式实现的自动装配,在XML文档中进行的自动装配略显笨拙,在项目中更多的使用注解的方式实现
2.11 SpEL
2.11.1 简介
Spring Expression Language,Spring表达式语言,简称SpEL。支持运行时查询并可以操作对象图
和JSP页面上的EL表达式、Struts2中用到的OGNL表达式一样,SpEL根据JavaBean风格的getXxx()、setXxx()方式定义的属性访问对象图,完全符合我们熟悉的操作习惯
2.11.2 基本语法
SpEL使用#{…}作为定界符,所有的大框号中的字符都将被认为是SpEL表达式
2.11.3 使用字面量
整数、小数、科学计数、Boolean和使用Sting类型的字面量可以使用单引号或者双引号字面量
2.11.4 引用
引用其他bean或者引用其他bean的属性值作为自己的某个属性的值
<bean class="pojo.Student" id="student">
<property name="studentId" value="1001"/>
<property name="studentName" value="邓大佬"/>
<property name="studentAge" value="20"/>
<!--具体制定到aClassID上-->
<property name="ID" value="#{aClass.ID}"/>
<!--引用bean-->
<property name="aClass" value="#{aClass}"/>
</bean>
2.11.5 调用方法
可以调用静态方法和非静态方法
2.11.6 运算符
1)、算术运算符:+、-、*、/、%、^
2)、字符串连接:+
3)、比较运算符:<、>、==、<=、It、gt、eq、le、ge
4)、逻辑运算符:and、or、not、|
5)、三目运算符:判断条件?判断结果为true时取值:判断结果为false时取值
6)、正则表达式:matches
2.12 通过注解配置bean
2.12.1 概述
相对于XML方法而言,通过注解的方式配置bean更加简洁和优雅,而且和 MVC组件化开发的理念是否契合,是开发中常用的使用方式
2.12.2 使用注解标识组件
1)、普通组件:@Component
标识一个受Spring IOC容器管理的组件
2)、持久化层组价:@Repository
标识一个受Spring IOC容器管理的持久层组件
3)、业务逻辑层组件:@Service
标识一个受Spring IOC容器管理的业务逻辑层组件
4)、表述层控制器组件:@Controller
标识一个受Spring IOC容器管理的表述层控制组件
5)、组件命名规则
默认情况下:使用组件的简单类名首字母小写后得到的字符串作为bean的id
使用组件注解的value属性指定bean的id
注意:事实上Spring并没有能力识别一个组件到底是不是它所标记的类 型,即使将@Respository注解用在一个表述层控制器组件上面也不会产生任何 错误,所以@Responsitory、@Service、@Controller这几个注解仅仅是为了 让开发人员自己明确当前的组件扮演的角色
2.12.3 扫描组件
组件被上述注解标识后还需要通过Spring进行扫描才能够侦测到
1)、指定被扫描的package
<context:component-scan base-package="controller"/>
2)、详细说明
base-package属性指定一个需要扫描的基类包,Spring容器将会扫描这个基类包及其子包中的所有类
当需要扫描多个包时可以使用逗号分隔开
如果仅需要扫描特定的类而非基包下的所有类,可使用resource-pattern属性过滤特定的类,实例:
<context:component-scan base-package="controller" resource-pattern="autowire/*.class"/>
包含与排除
context:include-fliter子节点表示包含的目标类
注意:通常需要户use-default-filter属性配合使用
禁止默认过滤器,然后扫描的就只是include-filter中的规则 指定的组件了
context:exlude-filter子节点表示要排除在外的目标类
component-scan下可以拥有若干个include-filter和exclude-filter子节 点
2.12.4 组件装配
1)、需求
Controller组价中往往需要用到Service组件的实例,Service组价中往往需 要用到Repository组件的实例。Spring可以通过注解的方式帮我们实现属性的 装配
2)、实现依据
在指定要扫描的包时,context:component-sacn元素会自动注册一个bean 的后置处理器:AutowiredAnnotationBeanPostProcessor的实例。该后置处理 器可以自动装配标记了@Autowired、@Resource或@inject注解的属性
3)、@Autowired注解
根据类型实现自动装配
构造器、普通字段(即使是非public)、一切具有参数的方法都可以应用 @Autowired注解
默认情况下。所有使用@Autowired注解的属性都需要被设置。当Spring 找不到匹配的bean装配属性时,会抛出异常
若某一个属性允许不被设置,可以设置@Autowired注解的required属性 为false
默认情况下,当 IOC 容器里存在多个类型兼容的 bean 时,Spring 会尝试 匹配 bean 的 id 值是否与变量名相同,如果相同则进行装配。如果 bean 的 id 值不相同,通过类 型的自动装配将无法工作。此时可以在@Qualifier 注解里 提供bean 的名称。Spring 甚至允许在方法的形参上标注@Qualifiter 注解以指 定注入 bean 的名称
@Autowired 注解也可以应用在数组类型的属性上,此时 Spring 将会把所 有匹配的bean 进行自动装配
@Autowired 注解也可以应用在集合属性上,此时 Spring 读取该集合的类 型信息,然后自动装配所有与之兼容的 bean
@Autowired 注解用在 java.util.Map 上时,若该 Map 的键值为 String,那么 Spring 将自动装配与值类型兼容的 bean 作为值,并以 bean 的 id 值作为键
4)、@Resource
@Resource注解要求提供一个bean名称的属性,若该属性为空,则自动采用标注的变量或方法名作为bean的名称
5)、@inject
@inject和@Autowired注解一样也是按类型注入匹配的bean,但没有 required属性
第三章 AOP前奏
3.1 动态代理
3.1.1 情景:数学计算
1)、要求
执行加减乘除运算
日志:在程序执行期间追踪正在发生的活动
验证:希望计算器只能处理正数的运算
2)、动态代理的原理
代理设计模式的原理:使用一个代理将对象包装起来,然后用该代理对 象取代原始对象的调用都是通过代理。代理对象决定是否以及何时将方法调 用转到原始对象上
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mVaW1ZuY-1617112808709)(D:\笔记整合\img\image-20201006163230779.png)]
3)、动态代理的方式
基于接口实现动态代理:jdk动态代理
基于继承实现动态代理:Cglib、javassist动态代理
3.2 数学计算机的具体实现
1)、日志处理器
/**
* 计算器记录处理程序
* @author shkstart
* @create 2020-10-06-12:21
*/
public class CalculatorLoggingHandler implements InvocationHandler {
private Log log = LogFactory.getLog(this.getClass());
private Object target;
/**
* 在invoke方法编码指定返回的代理对象干的工作
* @param proxy 把代理对象自己传递进来
* @param method 把代理对象当前调用的方法传递进来
* @param args 把方法参数传递进来
* @return
* @throws Throwable
*/
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//method.getName()就可以知道调用哪个方法,Arrays.toString将参数放在数组中
log.info("The method " + method.getName() + "() begins with " + Arrays.toString(args));
Object result = method.invoke(target, args);
log.info("The method " + method.getName() + "() end with " + result);
return result;
}
public CalculatorLoggingHandler(Object target) {
this.target = target;
}
public static Object createProxy(Object target){
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new CalculatorLoggingHandler(target));
}
}
2)、验证处理器
/**
* 计算器验证处理程序
* 实现InvocationHandler接口,实现了invoke方法
* @author shkstart
* @create 2020-10-06-12:39
*/
public class CalculatorValidationHandler implements InvocationHandler {
private Object target;
public CalculatorValidationHandler(Object target) {
this.target = target;
}
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//验证是否为正数
for(Object arg : args){
validate((Integer) arg);
}
//当调用代理对象的四个方法的其中一个时候,但实际上执行的都是invoke方法里面的代码
Object result = method.invoke(target, args);
return result;
}
private void validate(int arg) throws IllegalAccessException {
if(0 > arg){
throw new IllegalAccessException("只能是整数不能是负数");
}
}
public static Object createProxy(Object target){
/**
* 使用Proxy.newProxyInstance(ClassLoader loader, Class<?>[] interfaces, InvocationHandler h)返回某个对象的代理对象
* ClassLoader loader:Java类加载器;可以通过这个类型的加载器,在程序运行时,将生成的代理类加载到JVM即java虚拟机中,以便运行时需要
* Class<?>[] interfaces:被代理类的所有接口信息;便于生成的代理类可以具有代理类接口中的所有方法
* InvocationHandler h:调用处理器,调用实现了InvocationHandel类的一个回调方法
*/
return Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(),new CalculatorValidationHandler(target));
}
}
3)、测试代码
ArithmeticCalculatorImpl calculator = new ArithmeticCalculatorImpl();
ArithmeticCalculator arithmeticCalculator = (ArithmeticCalculator) CalculatorValidationHandler.createProxy(CalculatorLoggingHandler.createProxy(calculator));
System.out.println(arithmeticCalculator.add(1, 2));
System.out.println(arithmeticCalculator.div(0, 2));
System.out.println(arithmeticCalculator.sub(-1, 10));
System.out.println(arithmeticCalculator.mul(0, 1));
第四章 AOP概述
4.1 AOP概述
1)、AOP(Aspect-Oriented Programming,面向切面编程);是一种新的方法论,是对传统OOP(Object-Oriented Programming,面向对象编程)的补充
2)、AOP编程操作的主要对象是切面(aspect),而切面模板化横切关注点
3)、在应用AOP编程时,仍然需要定义公共功能,但可以明确的定义这个功能应用在那里,以什么方式应用,并且不必修改影响的类。这样一来横切关注点就模块化到特殊的类里–这样的类我们通常称之为“切面”
4)、AOP的好处:
每个事物逻辑位于一个位置,代码不分散,便于维护和升级
业务模块更简洁,只包含核心业务代码
AOP图解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-MebK8m85-1617112808714)(D:\笔记整合\img\image-20201006180811156.png)]
4.2 AOP术语
1)、横切关注点
从每个方法中抽取出来的同一类非核心业务
2)、切面(Aspect)
封装横切关注点信息的类,每个关注点体现为一个通知方法
3)、通知(Advice)
切面必须要完成的各个具体工作
4)、目标(Target)
被通知的对象
5)、代理Proxy
向目标对象应用通知之后创建的代理对象
6)、连接点(Joinpoint)
横切关注点在程序代码中的具体体现,对应程序执行的某个特定位置。例 如:类某个方法调用前、调用后、方法捕获到异常后等
在应用程序中可以使用横纵两个坐标来定位一个具体的连接点
7)、切入点(pointcut)
定位连接点的方式。每个类的方法中都包含多个连接点。所以连接点是类中客观存在的事物。如果把连接点看作数据库中的记录,那么切入点就是查询条件–AOP可以通过切入点定位到特定的连接点。切点通过org.springframework.aop.Pointcut接口进行描述,它使用类和方法作为连接点的查询条件
8)、图解
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-mXfFX2lz-1617112808731)(D:\笔记整合\img\image-20201007103241555.png)]
4.3 AspectJ
简介
AspectJ:Java社区里最完善最流行的AOP框架
在Spring2.0以上版本中,可以使用哪个基于AspectJ注解或基于XML配置的 AOP
1)、导入相关的JAR包
<dependency>
<groupId>net.sourceforge.cglib</groupId>
<artifactId>com.springsource.net.sf.cglib</artifactId>
<version>2.2.0</version>
</dependency>
<dependency>
<groupId>org.aopalliance</groupId>
<artifactId>com.springsource.org.aopalliance</artifactId>
<version>1.0.0</version>
</dependency>
<dependency>
<groupId>org.apache.maven.release</groupId>
<artifactId>maven-release-manager</artifactId>
<version>2.5.3</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>4.3.7.RELEASE</version>
</dependency>
2)、引入命名空间
3)、用AspectJ注解声明切面
要在Spring中声明AspectJ切面,只需要在IOC容器中将切面声明为bean实例
当在Sprig IOC容器中初始化AspectJ切面之后,Spring IOC容器就会为那些 与AspectJ切面相匹配的bean创建代理
在AspectJ注解中,切面只是一个带有@Aspect注解的Java类,往往要包含很 多的通知
通知是标注有某种注解的简答的Java方法
AspectJ支持五种类型的通知注解:
@Before:前置通知,在方法执行之前执行
@After:后置通知,在方法之后执行
@AfterRunning:返回通知,在方法返回结果之后执行
@AfterThrowing:异常通知,在方法抛出异常之后执行
@Around:环绕通知,围绕着方法执行
第五章 AOP细节
5.1切入点表达式
作用
通过表达式的方式定位一个或多个具体的连接点
语法细节
切入点表达式的语法格式
e
x
e
c
u
t
i
o
n
(
[
权
限
修
饰
符
]
[
返
回
符
类
型
]
[
简
单
类
名
/
全
类
目
]
[
方
法
名
]
(
[
参
数
列
表
]
)
)
execution([权限修饰符][返回符类型][简单类名/全类目][方法名]([参数列表]))
execution([权限修饰符][返回符类型][简单类名/全类目][方法名]([参数列表]))
举例说明
表达式
e
x
e
c
u
t
i
o
n
(
∗
s
e
r
v
i
c
e
.
A
r
i
t
h
m
e
t
i
c
C
a
l
c
u
l
a
t
o
r
.
∗
(
.
.
)
)
execution(*service.ArithmeticCalculator.*(..))
execution(∗service.ArithmeticCalculator.∗(..))
含义
ArithmeticCalculation接口中声明的所有方法
第一个“*”代表任何修饰符及任意返回值
第二个“*”代表任何方法
“…”匹配任意数量、任意类型的参数
若目标类、接口与该切面类在同一个包中可以省略包名
表达式
e
x
e
c
u
t
i
o
n
(
p
u
b
l
i
c
∗
A
r
i
t
h
m
e
t
i
c
C
a
l
c
u
l
a
t
o
r
.
∗
(
.
.
)
)
execution(public * ArithmeticCalculator.*(..))
execution(public∗ArithmeticCalculator.∗(..))
含义
ArithmeticCalculation接口的所有公有方法
表达式
e
x
e
c
u
t
i
o
n
(
p
u
b
l
i
c
i
n
t
A
r
i
t
h
m
e
t
i
c
C
a
l
c
u
l
a
t
o
r
.
∗
(
.
.
)
)
execution(public int ArithmeticCalculator.*(..))
execution(publicintArithmeticCalculator.∗(..))
含义
ArithmeticCalculationCalculation接口中返回int类型数据的方法
表达式
e
x
e
c
u
t
i
o
n
(
p
u
b
l
i
c
i
n
t
A
r
i
t
h
m
e
t
i
c
C
a
l
c
u
l
a
t
i
o
n
.
∗
(
i
n
t
,
.
.
)
)
execution(public int ArithmeticCalculation.*(int,..))
execution(publicintArithmeticCalculation.∗(int,..))
含义
第一个参数为int类型的方法
“…”匹配任意数量、任意类型的参数
表达式
e
x
e
c
u
t
i
o
n
(
p
u
b
l
i
c
i
n
t
A
r
i
t
h
m
e
t
i
c
C
a
l
c
u
l
a
t
o
r
)
.
∗
(
i
n
t
,
i
n
t
)
execution(public int ArithmeticCalculator).*(int,int)
execution(publicintArithmeticCalculator).∗(int,int)
在AspectJ中,切入点表达式可以通过“&&”、“||”、“!”等操作符结合起来
表达式
e
x
e
c
u
t
i
o
n
(
∗
∗
.
a
d
d
(
i
n
t
,
.
.
)
)
∣
∣
e
x
e
c
u
t
i
o
n
(
∗
∗
.
s
u
b
(
i
n
t
,
.
.
)
)
execution(**.add(int,..)) || execution(**.sub(int,..))
execution(∗∗.add(int,..))∣∣execution(∗∗.sub(int,..))
含义
任意类中第一个参数为int类的add方法或sub
表达式
!
e
x
e
c
u
t
i
o
n
(
∗
∗
.
a
d
d
(
i
n
t
,
.
.
)
)
!execution(* *.add(int,..))
!execution(∗∗.add(int,..))
含义
匹配不是任意类中第一个参数为int类型的add方法
切入点表达式应用到实际的切面类中
/**
* 计算器记录方面
* @author shkstart
* @create 2020-10-07-13:49J
*/
//表示当前类是一个切面类
@Aspect
public class CalculatorLoggingAspect {
private Log log = LogFactory.getLog(this.getClass());
//切入点表达式应用前置通知
@Before("execution(* *.*(..))")
public void logBefor(JoinPoint joinPoint){
log.info("The method" + joinPoint.getSignature().getName() + "() begins with" + Arrays.toString(joinPoint.getArgs()));
}
}
5.2 概述
切入点表达式通常都会从宏观上定位一组方法,和具体某个通知的注解结合起来就能够确定对应的连接点。那么就一个具体的连接点而言,我们可能会关心这个连接点的一些具体信息,例如:当前连接点所在方法的方法名、当前传入的参数值等等。这些信息都封装在JoinPoint接口的实例对象中
5.3 通知
概述
1)、在具体的连接点上要执行的操作
2)、一个切面可以包含一个或者多个通知
3)、通知所使用的注解的值往往是切入点表达式
前置通知
1)、前置通知:在方法执行之前执行的通知
2)、使用@Before注解
//切入点表达式应用前置通知
@Before("pt()")
public void logBefor(JoinPoint joinPoint) {
log.info("The method" + joinPoint.getSignature().getName() + "() begins with " + Arrays.toString(joinPoint.getArgs()));
}
后置通知
1)、后置通知:后置通知是在连接点完成之后执行的,即链接点返回结果 或者抛出异常的时候
2)、使用@After注解
//最终通知,发生异常也会打印logging
@After(value = "pt()")
public void logAfter(JoinPoint joinPoint) {
log.info("The method " + joinPoint.getSignature().getName());
}
返回通知
1)、返回通知:无论连接点是正常返回还是抛出异常,后置通知都会执 行。如果只想在连接点返回的时候记录日志,应使用返回通知替代后置通知
2)、使用@AfterReturning注解,在返回通知中访问连接点的返回值
在返回通知中,只要将returning属性添加到@AfterReturning
注解中,就可以访问连接点的返回值。该属性的值即为用来传入返回值的参数 名称
必须在通知方法的签名中添加一个同名参数。在运行时 Spring AOP 会通 过这个参数传递返回值
原始的切点表达式需要出现在 pointcut 属性中
//后置通知,就是在输出结果通过returning属性来获取最终结果的值
@AfterReturning(pointcut = "pt()", returning = "result")
public void logAfterReturing(JoinPoint joinPoint, Object result){
log.info("The method " + joinPoint.getSignature().getName() + "() ends with " + result);
}
异常通知
异常通知:只在连接点抛出异常时才执行异常通知
将throwing属性添加到@AfterThrowing注解中,也可以访问连接点抛出 的异常。Throwable是所有错误和异常的顶级父类,所以在异常通知方法可以 捕获到任何错误和异常
如果只对某种特殊的异常类感兴趣,可以将参数声明为其他异常的参数类 型。然后通知就只在抛出这个类型及其子类的异常时才被执行
//异常通知,如果出现异常就会报错,可以看到出现异常的方法
@AfterThrowing(pointcut = "pt()")
public void logAfterThrowinig(JoinPoint joinpoint){
log.info("Throwing:" + joinpoint.getSignature().getName() + " 出现了运行时异常");
}
5.4 环绕通知
1)、环绕通知是所有通知类型中功能最为强大的,能够全面地控制连接点,甚至可以控制是否执行连接点
2)、对于环绕通知来说,连接点的参数必须是ProceedingJoinPoint。它是JoinPoint的子接口,允许控制何时执行,是否执行连接点
3)、在环绕通知中需要明确调用ProceedingJoinPoint的proceed()方法来执行被代理的方法。如果忘记这样做就会导致通知被执行了,但目标方法没有被执行
4)、注意:环绕通知的方法需要返回目标方法执行之后的结果,即调用joinPoint.proceed();的返回值,否则会出现空指针异常
@Around(value = "pt()")
public Object logAround(ProceedingJoinPoint joinPoint){
log.info("The method " + joinPoint.getSignature().getName() + "() begins with " + Arrays.toString(joinPoint.getArgs()));
Object result = null;
try {
result = joinPoint.proceed();
log.info("The method " + joinPoint.getSignature().getName() + "() ends " + result);
return result;
}catch (Throwable throwable) {
log.error("An exception" + throwable + " has been throwing in " + joinPoint.getSignature().getName() + "()");
}finally {
log.info("最终通知 " + joinPoint.getSignature().getName());
}
return result;
}
5.5 重用切入点定义
1)、在编写AspectJ切面时,可以直接在通知注解中书写切入点表达式。但是同一个切点表达式可能在多个通知中重复出现
2)、在编写AspectJ切面时,可以通过@Pointcut注解将一个切入点声明成简单的方法。切入点的方法体通常是空的,因为将切入点定义与应用程序逻辑混在一起是不合理的
3)、切入点方法的访问控制符同时也控制着这个切入点的可见性。如果切入点要在多个切面中共用,最好将它们集中在一个公共的类中。在这种情况下,它们必须被声明为public。在引入这个切入点时,必须将类名也包括在内。如果类没有与这个切面放在同一个包中,还必须包含包名
4)、其他通知可以通过方法名称引入该切入点
@Pointcut("execution(* com.service.impl.*.*(..))")
public void pt() {
}
5.6 指定切面的优先级
1)、在同一个连接点上应用不止一个切面时,除非明确指定,否则它们的优先级是不确定的
2)、切面的优先级可以通过实现Ordered接口或利用@Order注解指定
3)、实现Ordered接口,getOrder()方法的返回值越小,优先级越高
4)、若使用@Order注解,序号出现在注解中
第六章 以XML方式配置切面
6.1 概述
除了使用AspectJ注解声明切面,Spring也支持在bean配置文件中声明切面。这种声明是通过aop名称空间中的XML元素完成的
6.2 配置细节
在bean配置文件中,所有的Spring AOP配置都必须定义在aop:config元素内部。对于每个切面而言,都要创建一个aop:aspect元素为具体切面实现引用后端bean实例。
切面bean必须有一个标识符,供aop:aspect元素引用
切入点使用aop:pointcut元素声明
切入点必须定义在aop:aspect元素下,或者直接定义在aop:config元素下。
定义在aop:aspect元素下:只对当前切面有效
定义在aop:config元素下:对所以切面都有效
基于XML的AOP配置不允许在切入点表达式中用名称引用其他切入点
声明通知
1)、在aop名称空间中,每种通知类型都对应一个特定的XML 元素
2)、通知元素需要使用来引用切入点,或用直接嵌入切入点表达式
3)、method属性指定切面类中通知方法的名称
<aop:config>
<aop:pointcut id="pt" expression="execution(* com.service.impl.*.*(..))"/>
<!--指定loggingAspect为切面类-->
<aop:aspect ref="loggingAspect">
<!--前置通知-->
<aop:before method="logAfter" pointcut-ref="pt"/>
<!--后置通知 returning返回的是result属性就是最终的值或者是对象-->
<aop:after-returning method="logAfterReturing" pointcut-ref="pt" returning="result"/>
<!--异常通知-->
<aop:after-throwing method="logAfterThrowinig" pointcut-ref="pt"/>
<!--最终通知-->
<aop:after method="logAfter" pointcut-ref="pt"/>
</aop:aspect>
</aop:config>
第七章 声明式事务
8.1 事务概述
1)、在javaEE企业级开发的应用程序领域,为了保证数据的完整性和一致性,也必须引入数据库事务的概念,所以事务管理是企业级应用程序开发必不可少的技术
2)、事务就是一组由于逻辑上紧密关联而合并成一个整体(工作单元)的多个数据库操作,这些操作要么都执行,要么都不执行
3)、事务的四个关键属性(ACID)
原子性(atomicity):“原子”的本意是“不可再分”,事务的原子 性表现为一个事务中涉及到多个操作在逻辑上缺一不可。事务的原 子性要求中的所有操作要么都执行,要么都不执行
一致性(consistency):“一致”指的是数据的一致,具体是指: 所有数据都处于满足业务规则的一致性状态。一致性原则要求:一 个事务中不管涉及到多少个操作,都必须保证事务执行之前数据是 正确的。如果一个事务在执行的过程中,其中某一个或某几个操作 失败了,则必须将其他所有操作撤销,将数据恢复到事务执行之前 的状态,这就是回滚
隔离性(isolation):在应用程序实际运行过程中,事务往往是并 发执行的,所以很有可能有许多事务同时处理相同的数据,因此每 个事务都应该与其他事务隔离开来,防止数据损坏隔离性原则要求 多个事务在并发执行过程中不会相互干扰
持久性(durability):持久性原则要求事务执行完成后,对数据 的修改永久的保存下来,不会因各种系统错误或其他意外情况而受 到影响。通常情况下,事务对数据的修改应该被写入到持久化存储 器中
8.2 Spring事务管理
8.2.1 编程式事务管理
1)、使用原生的JDBC API进行事务管理
获取数据库连接Connection对象
取消事务的自动提交
执行操作
正常完成操作时手动提交事务
执行失败时回滚事务
关闭相关资源
2)、对于原生的JDBC API实现事务管理是所有事务管理方式的基石,同时也是最典型的编程式事务管理。编程式事务管理需要将事务管理代码嵌入到业务方法中来控制事务的提交和回滚。在使用编程的方式管理事务时,必须在每个事务操作中包含额外的事务管理代码。相对于核心业务而言,事务管理的代码显然属于非核心业务,如果多个模块都使用同样模式的代码进行事务管理,显然会造成较大程度的代码冗余
8.2.2 声明式事务管理
大多数情况下声明事务比编程式事务管理更好;它将事务管理代码从业务方法中分离出来,以声明的方式来实现事务管理
事务管理代码的固定模式作为一种横切关注点,可以通过AOP方法模块化,进而借助Spring AOP框架实现声明式事务管理
Spring在不同的事务管理API之上定义了一个抽象层,通过配置的方法使其生效,从而让应用程序开发人员不必了解事务管理API的底层实现细节,就可以使用Spring的事务管理机制
Spring即支持编程式事务管理,也支持声明式的事务管理
8.2.3 Spring提供的事务管理器
Spring从不同的事务管理API中抽象出了一整套事务管理机制,让事务管理代码从待定的事务技术中独立出来。开发人员通过配置的方法进行事务管理,而不必来了解其底层是如何实现的。
Spring的核心事务管理抽象是PlatformTransactionManage。它为事务管理封装了一组独立于技术的方法。无论使用Spring的哪种事务管理策略(编程式或声明式),事务管理器都是必须的
事务管理器课以普通的bean的形式声明在Spring IOC容器中
8.2.4 事务管理器的主要实现
1)、DataSourceTransactionManage:在应用程序中只需要处理一个数据源,而且通过JDBC存取
2)、JtaTransactionManage:在javaEE应用服务器上用JTA(Java Transaction API)进行事务管理
3)、TransactionManage:用于mybatis框架存取数据库类型
8.3 事务的传播行为
8.3.1 简介
当事务方法被另一个事务放调用时,必须指定事务应该如何传播。例如:方法可能继承在现有事务中运行,也可能开启一个新事务,并在自己的事务中运行。事务的传播行为由传播属性指定。Spring定义了七种类传播行为。
REQUIRED(必须的):
如果有事务在运行,当前的方法就在这个事务内运行,否则, 就启动一个新的事务,并在自己的事务内运行
REQUIRED_NEW(要更新):
当前的方法必须启动新事务,并在它自己的事务内运行,如果 有事务正在运行,应该将它挂起
SUPPORTS(技术支持):
如果有事务在运行,当前的方法就在这个事务内运行,否则它 可以不运行在事务中
NOT_SUPPORTED(不支持):
当前的方法不应该运行在事务中,如果有运行的事务,将它挂 起
MANDATORY(强制的):
当前的方法必须运行在事务内部,如果没有正在运行的事务, 就抛出异常
NEVER(从不):
当前的方法不应该运行在事务中,如果有运行的事务,就抛出 异常
NESTED(嵌套):
如果有事务在运行,当前的方法就应该在这个事务的嵌套事务内运行,否则,就启动一个新的事务,并在它自己的事务内运行
事务传播属性可以在@Transactional注解的propagation属性中定义
8.4 事务的隔离级别
8.4.1 数据库事务并发问题
假设现在有两个事务:Transaction01和Transaction02并发执行
1)、脏读
Transaction01将某条记录的AGE值从20修改到30
Transaction02读取了Transaction01更新后的值:30
Transaction01回滚,AGE值恢复到了20
Transaction02读取到30就是一个无效的值
2)、不可重复读
Transaction01读取了AGE值为20
Transaction02将AGE值修改为30
Transaction01再次读取AGE值为30,和第一次读取不一致
3)、幻读
Transaction01读取了表中的一部分数据
Transaction02向表中插入了新的行
Transaction01读取了表时,多出了一些行
8.4.1 隔离级别
数据库系统必须具有隔离并发运行各个事务的能力,使它们不会互相影响,避免各种并发问题。一个事务与其他事务隔离的程度称为隔离级别。SQL标准规定了多种事务隔离级别,不同隔离级别对应不同的干扰程度,隔离级别越高,数据一致性就越好,但并发性越弱
1)、读未提交:READ UNCOMMITTED
允许Transaction01读取Transaction02未提交的修改
2)、读已提交:READ COMMITTED
要求Transaction01只能读取Transaction02已提交的修改
3)、可重复读:REPEATABLE READ
确保Transaction01可以多次从一个表中读取到相同的行,在 Transaction01执行期间禁止其他事务对这个字段进行更新
4)、串行化:SERIALIZABLE
确保Transaction01可以多次从一个表中读取到相同的行,在Transaction01执行期间,禁止其他事务对这个表进行添加、更新、删除操作。可以避免任何并发问题,但性能十分低下
5)、各个隔离级别解决并发问题的能力见如下表
脏读 | 不可重复读 | 幻读 | |
---|---|---|---|
READ UNCOMMITTED | 有 | 有 | 有 |
READ COMMITTED | 无 | 有 | 有 |
REPEATABLE READ | 无 | 无 | 有 |
SERIALIZABLE | 无 | 无 | 无 |
6)、各种数据库产品对事务隔离级别的支持程度
Oracle | MySQL | |
---|---|---|
READ UNCOMMITTED | x | √ |
READ COMMITTED | √ | √ |
REPEATABLE READ | x | √ |
SERIALZABLE | √ | √ |
8.4.2 在Spring中指定事务隔离级别
1)、注解
用@Transactional注解声明式地管理事务时可以在@Transactional的isolation属性中设置隔离级别
2)、XML
在Spring2.x事务通知中,可以在tx:method元素中指定隔离级 别
8.4.3 触发事务回滚的异常
捕获到RuntimeException或Error时回滚,而捕获到编译时异常不回滚
8.4.4 设置途径
1)、注解@Transaction注解
rollbackFor属性:指定遇到时必须进行回滚的异常类型,可以为多个
noRollbackFor属性:指定遇到时不回滚的异常类型,可以为多个
2)、XML
在Spring2.x事务通知中,可以在tx:method元素中指定回滚规则。如果有不止一种异常则用逗号分隔
8.5 事务的超时和只读属性
由于事务可以在行和表上获得锁,因此长事务会占用资源,并对整体性能产生影响。
如果一个事务只读取数据但不做修改,数据库引擎可以对这个事务进行优化
超时事务属性;事务在强制回滚之前可以保持多久。这样可以防止长期运行的事务占用资源
只读事务属性:表示这个事务只读取数据但不更新数据,这样可以帮助数据库引擎优化事务
第八章 整合mybatis
1)、配置maven环境
<!--所需要的jar包-->
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>RELEASE</version>
<scope>compile</scope>
</dependency>
<!--IOC容器所需要的JAR包-->
<dependency>
<groupId>commons-logging</groupId>
<artifactId>commons-logging</artifactId>
<version>1.2</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-expression</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.5</version>
</dependency>
<!--mybatis框架-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.5</version>
</dependency>
<!--jdbcTemplate所需要的JAR包-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-orm</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
<!--数据库驱动和数据源-->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.23</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.20</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.9.RELEASE</version>
</dependency>
2)、mysql中创建表格为tb_books
CREATE TABLE `tb_books` (
`book_id` int NOT NULL AUTO_INCREMENT,
`book_name` varchar(100) NOT NULL,
`book_counts` int NOT NULL,
`book_detail` varchar(100) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
KEY `book_id` (`book_id`)
) ENGINE=InnoDB AUTO_INCREMENT=32 DEFAULT CHARSET=utf8
3)、创建dao持久层和service接口
public interface BookDao {
//查询所有信息
List<Book> findAll();
//使用map键值对关系更新里面的信息
void update(Map map);
//插入新信息
void insert(Book book);
//根据id删除指定的信息
void delete(int id);
}
public interface BookService {
List<Book> findAll();
void update(Map map);
void insert(Book book);
void delete(int id);
//将插入和更新放在同一个事务中
void insert_update(Map map, Book book);
}
4)、创建Service层,实现BookService接口
public class BookServiceImpl implements BookService {
@Autowired
BookDao bookDao;
//只执行一个事务的时候使用SUPPORTS
@Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public List<Book> findAll() {
return bookDao.findAll();
}
@Transactional(propagation = Propagation.REQUIRED)
public void update(Map map) {
this.bookDao.update(map);
}
@Transactional(propagation = Propagation.REQUIRED)
public void insert(Book book) {
this.bookDao.insert(book);
}
public void delete(int id) {
this.bookDao.delete(id);
}
//有两个事务要同时完成才能修改数据,isolation默认为READ_COMMITTED,超时为30s
@Transactional(propagation = Propagation.REQUIRED,isolation = Isolation.READ_COMMITTED, timeout = 30)
public void insert_update(Map map, Book book) {
this.bookDao.update(map);
this.bookDao.insert(book);
}
5)、创建mapperDao.xml文件
<?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.dao.BookDao">
<resultMap id="book" type="com.pojo.Book">
<id property="id" column="book_id"/>
<result property="name" column="book_name"/>
<result property="count" column="book_counts"/>
<result property="detail" column="book_detail"/>
</resultMap>
<select id="findAll" resultType="com.pojo.Book" resultMap="book">
select * from tb_books
</select>
<insert id="insert" parameterType="com.pojo.Book">
insert into tb_books(book_name,book_counts,book_detail) values(#{name},#{count},#{detail})
</insert>
<delete id="delete">
delete from tb_books
where book_id = #{id}
</delete>
<update id="update" parameterType="java.util.Map">
update tb_books
set book_name = #{name},
book_counts = #{count},
book_detail = #{detail}
where
book_id = #{id}
</update>
</mapper>
6)、Test
@Test
public void update_insert() {
@SuppressWarnings("success")
ClassPathXmlApplicationContext context = new ClassPathXmlApplicationContext("spring/Bean.xml");
BookService bookService = (BookService) context.getBean("bookService");
Book book = new Book();
book.setName("西游记");
book.setCount(11);
book.setDetail("西游");
//根据id修改表中的信息
Map map = new HashMap();
map.put("id","3");
map.put("name·","与正义");
map.put("count","33");
map.put("detail","正义降临");
try {
System.out.println(bookService.findAll());
bookService.insert_update(map,book);
}catch (Exception e){
e.printStackTrace();
}
}
声明式事务都配置到XML中
<!--jdbc的事务管理将数据源注入-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--transaction-manager指明事务管理名称(id)-->
<tx:advice transaction-manager="transactionManager" id="transactionInterceptor">
<tx:attributes>
<!--属性名以find开头的方法名-->
<tx:method name="find*" propagation="SUPPORTS" read-only="true" timeout="30"
rollback-for="java.lang.Exception" no-rollback-for="java.lang.RuntimeException"/>
<tx:method name="update*" propagation="REQUIRED" rollback-for="java.sql.SQLException"/>
<tx:method name="insert*" propagation="REQUIRED" rollback-for="java.sql.SQLException"/>
</tx:attributes>
</tx:advice>
<!--事务控制在service层-->
<aop:config>
<aop:pointcut id="pc" expression="execution(* com.service.impl.*.*(..))"/>
<aop:advisor pointcut-ref="pc" advice-ref="transactionInterceptor"/>
</aop:config>
ew ClassPathXmlApplicationContext(“spring/Bean.xml”);
BookService bookService = (BookService) context.getBean(“bookService”);
Book book = new Book();
book.setName(“西游记”);
book.setCount(11);
book.setDetail(“西游”);
//根据id修改表中的信息
Map map = new HashMap();
map.put("id","3");
map.put("name·","与正义");
map.put("count","33");
map.put("detail","正义降临");
try {
System.out.println(bookService.findAll());
bookService.insert_update(map,book);
}catch (Exception e){
e.printStackTrace();
}
}
声明式事务都配置到XML中
```xml
<!--jdbc的事务管理将数据源注入-->
<bean class="org.springframework.jdbc.datasource.DataSourceTransactionManager" id="transactionManager">
<property name="dataSource" ref="dataSource"/>
</bean>
<!--transaction-manager指明事务管理名称(id)-->
<tx:advice transaction-manager="transactionManager" id="transactionInterceptor">
<tx:attributes>
<!--属性名以find开头的方法名-->
<tx:method name="find*" propagation="SUPPORTS" read-only="true" timeout="30"
rollback-for="java.lang.Exception" no-rollback-for="java.lang.RuntimeException"/>
<tx:method name="update*" propagation="REQUIRED" rollback-for="java.sql.SQLException"/>
<tx:method name="insert*" propagation="REQUIRED" rollback-for="java.sql.SQLException"/>
</tx:attributes>
</tx:advice>
<!--事务控制在service层-->
<aop:config>
<aop:pointcut id="pc" expression="execution(* com.service.impl.*.*(..))"/>
<aop:advisor pointcut-ref="pc" advice-ref="transactionInterceptor"/>
</aop:config>