文章由B站动力站点相关课程视频整理而成,此博客为学习备忘笔记。
文章目录
第一章 Spring概述
1.1 概述
Spring:出现在2002年左右,降低企业级开发难度。帮助进行模块之间、类与类之间的管理,帮助开发人员创建对象,管理对象之间的关系。
2003年传入国内,被大量使用。
2017年出新的流行框架SpringBoot,核心思想与Spring相同。
核心技术:IoC、AOP,能使模块之间、类之间耦合。
依赖:class A使用class B的属性或方法,称之为classA 依赖class B。
官网:spring.io
1.2 优点
(1)轻量:Spring所需要的jar包都非常小,一般1M以下,几百KB。核心功能所需要的jar包总共3M左右。
Spring框架运行占有资源少,运行效率搞,不依赖其他jar。
(2)针对接口编程,解耦合
(3)AOP编程的支持
(4)方便集成各种优秀框架
1.3 Spring体系结构
Spring由20多个模块组成,它们可以分为数据访问/集成(Data Access/Integration)、Web、面向切面编程(AOP,Aspects)、提供JVM的代理(Instrumentation)、消息发送(Messaging)、核型容器(Core Container)和测试(Test)。
第二章 IoC控制反转
控制反转(IoC,Inversion of Control),是一个概念,是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器,通过容器类实现对象的装配和管理。控制反转就是对对象控制权的转移,从程序代码本身反转到了外部容器。通过容器实现兑现的创建,属性赋值,依赖的管理。
IoC的实现:
1.依赖查找:DL(Dependency Lookup),容器提供回调接口和上下文环境给组件
2.依赖注入:DI(Dependency Injection),程序代码不做定位查询,这些工作由容器自行完成。
Spring 框架使用依赖注入 (DI) 实现IoC。
2.1 Spring的第一个程序
2.1.1 创建Maven项目,导入Spring依赖
(Maven项目采用quickstart模版,建立好resources目录)
pom.xml配置文件:
<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.8.RELEASE</version>
</dependency>
</dependencies>
2.1.2 创建业务接口和实现类
接口:
package Arbin.service;
public interface SomeService {
void doSome();
}
实现类:
package Arbin.service.impl;
import Arbin.service.SomeService;
public class SomeServiceImpl implements SomeService {
public SomeServiceImpl() {
System.out.println("SomeServiceImpl的无参构造方法");
}
@Override
public void doSome(){
System.out.println("执行了SomeServiceImpl的doSome()方法");
}
}
2.1.3 创建Spring配置文件
如同在Servlet中我们需要在web.xml中注册我们希望服务器自动创建管理的sewrvlet对象一样,在Spring中也需要蕾丝的配置,来自动创建刚才的SomeServiceImpl对象。
在src/main/resources目录下创建一个名为“applicationContext"的Spring Config的xml文件。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd">
</beans>
在Spring配置文件中:
<beans>
是根标签,Spring把java对象称为bean。用bean标签声明对象:
<bean id="someService" class="Arbin.service.impl.SomeServiceImpl"/>
1.声明java对象交给Spring创建和管理,这个步骤称之为声明bean。
<bean>
相当于:SomeService someService = new SomeServiceImp();
。Spring把创建好的对象放入map中:springMap.put(id的值,对象)。例如:
springMap.put("someService" , new SomeServiceImpl());
2.<bean>
标签的属性:
id
:对象的自定义名称,是唯一值。Spring通过这个名称找到对象。
class
:类的全限定名称(不能是接口,因为spring是反射机制创建对象,必须使用类)。
scope
:指定bean对象的作用域,(对象的存在范围和可见性。)可取值:
- 单例:singleton,默认值,表示叫这个名称的对象在spring容器中只有一个。
- 原型:prototype,表示每次使用getBean()都创建一个新的对象。
2.1.4 创建单元测试方法
pom.xml加入依赖:
<!--单元测试-->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
测试方法:
@Test
public void test02(){
//使用spring容器创建的对象
//1.指定spring配置文件的名称
String config = "beans.xml";
//2.创建表示spring容器的对象 , ApplicationContext
//ApplicationContext表示Spring容器,通过容器对象获取对象了
//ClassPathXmlApplicationContext:表示从类路径中加载spring的配置文件
//FileSystemXmlApplicationContext:表示从项目的根之下(src、target同级目录)加载spring的配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext(config);
//从容器中获取某个对象,调用对象的方法
//getBean("<bean>的id")
SomeService someService = (SomeService) ctx.getBean("someService");
//调用业务方法
someService.doSome();
}
2.1.5 第二章未更新完整 后续补充完整笔记…
第三章 AOP面向切面编程
3.1 动态代理
动态代理:程序在整个运行过程中根本就不存在目标类和代理类,目标对象的代理对象只是由代理生成工具(不是真实定义的类)在程序运行时由JVM根据反射等机制动态生成的。代理对象与目标对象的代理关系在程序运行时才确立。
1.实现方式:
- jdk动态代理:使用jdk中的Proxy,Method,InvocaitonHanderl创建代理对象。jdk动态代理要求目标类必须实现接口。
- cglib动态代理:第三方的工具库,创建代理对象,原理是集成。通过集成目标类,创建子类。子类就是代理对象。要求目标类不能是final的,方法也不能说final的。
2.动态代理的作用:
- 在目标类源代码不改变的情况下,增加功能。
- 减少代码的重复
- 专注业务逻辑代码
- 解耦合,让业务功能和日志,事务非业务功能分离。
3.AOP:面向切面编程,基于动态代理的,可以使用jdk,cglib两种代理方式。Aop就是动态代理的规范化,把动态代理的实现步骤,方式都定义好,让开发人员使用统一的方式进行动态代理。
3.2 AOP概述
面向切面编程,是面向对象编程 OOP 的一种补充。面向对象编程是从静态角度考虑程序的结构,而面向切面编程是从动态角度考虑程序运行过程。
AOP 底层,就是采用动态代理模式实现的。采用了两种代理:JDK 的动态代理,与 CGLIB 的动态代理。
面向切面编程,就是将交叉业务逻辑封装成切面,利用 AOP 容器的功能将切面织入到 主业务逻辑中。所谓交叉业务逻辑是指,通用的、与主业务逻辑无关的代码,如安全检查、 事务、日志等。若不使用 AOP,则会出现代码纠缠,即交叉业务逻辑与主业务逻辑混合在一起。这样, 会使主业务逻辑变的混杂不清。
例如,转账,在真正转账业务逻辑前后,需要权限控制、日志记录、加载事务、结束事 务等交叉业务逻辑,而这些业务逻辑与主业务逻辑间并无直接关系。但,它们的代码量所占 比重能达到总代码量的一半甚至还多。它们的存在,不仅产生了大量的“冗余”代码,还大 大干扰了主业务逻辑—转账。
3.3 AOP编程术语
(1)切面(Aspect)
切面泛指交叉业务逻辑。上例中的事务处理、日志处理就可以理解为切面。常用的切面有通知与顾问。实际就是对主业务逻辑的一种增强。
(2)织入(Weaving)
织入是指将切面代码插入到目标对象的过程。上例中 MyInvocationHandler 类中的 invoke()
方法完成的工作,就可以称为织入。
(3)连接点(JoinPoint)
连接点指可以被切面织入的方法。通常业务接口中的方法均为连接点。
(4)切入点(Pointcut)
切入点指切面具体织入的方法。在 StudentServiceImpl 类中,若 doSome()将被增强,而 doOther()不被增强,则 doSome()为切入点,而 doOther()仅为连接点。被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。
(5)目标对象(Target)
目标对象指将要被增强的对象。即包含主业务逻辑的类的对象。上例中的 StudentServiceImpl 的对象若被增强,则该类称为目标类,该类对象称为目标对象。当然,不被增强,也就无所谓目标不目标了。
(6)通知(Advice)
通知是切面的一种实现,可以完成简单织入功能(织入功能就是在这里完成的)。换个角度来说,通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。
切入点定义切入的位置,通知定义切入的时间。
(7)顾问(Advisor)
顾问是切面的另一种实现,能够将通知以更为复杂的方式织入到目标对象中,是将通知 包装为更复杂切面的装配器。
3.4 AOP的实现
AOP的技术实现框架:
- Spring:Spring在内部实现了AOP规范,能做AOP的工作。Spring的AOP实现比较笨重。主要在事务处理时使用AOP。
- aspectJ:一个开源的专门做AOP的框架。Spring框架中集成了aspectJ框架,通过Spring就能使用aspectJ的功能。
aspectJ框架实现AOP的两种方式:
(1)使用xml配置文件,配置全局事务。
(2)使用注解。一般在项目开发中使用这种方式。aspectJ有五种注解。
3.5 AspectJ框架d对AOP的实现
3.5.1 Adivice(通知)类型
Advice(通知、增强):切面的执行时间。在aspectJ框架中使用注解表示:
(1)@Before
前置通知
(2)@AfterReturning
后置通知
(3)@Around
环绕通知
(4)@AfterThrowing
异常通知
(5)@After
最终通知
3.5.2 AspectJ的切入点表达式
AspectJ定义了专门的表达式用于指定切入点。表达式的原型为:
execution ( [modifiers-pattern] 访问权限类型
ret-type-pattern 返回值类型
[declaring-type-pattern] 全限定性类名
name-pattern(param-pattern) 方法名(参数名)
[throws-pattern] 抛出异常类型)
切入点表达式要匹配的对象就是目标方法的方法名。execution 表达式中明显就是方法的签名。注意,表达式中加[ ]的部分表示可省略部分,各部分间用空格分开。
在其中可以使用以下符号:
*
:0至多个任意字符。
..
:用在方法参数中,表示任意多个参数用在包名后,表示当钱包及其子包路径。
+
:用在类名后,表示当前类及其子类;用在接口后,表示当前接口及其实现类。
3.5.3 AspectJ的开发环境
- 创建Maven项目(quickstart骨架),引入依赖:
<!--单元测试依赖-->
<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.8.RELEASE</version>
</dependency>
<!--aspectj依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
</dependencies>
- 引入约束(applicationContext.xml)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
</beans>
在IDEA中开发,添加<aop:aspectj-autoproxy>
标签会自动引入。
3.5.4 AspectJ基于注解的AOP实现
AspectJ提供了以注解方式对于AOP的实现
3.5.4.1 实现步骤
- 定义业务接口与实现类
package com.Arbin.bao01;
public interface SomeService {
void doSome(String name,Integer age);
}
package com.Arbin.bao01;
//目标类
public class SomeServiceImpl implements SomeService {
@Override
public void doSome(String name,Integer age) {
//给doSome方法增加一个功能,在doSome()执行之前,输出方法的执行时间
System.out.println("====目标方法doSome()====");
}
}
- 定义切面POJO类
在定义的POJO类上添加@Aspect
注解,指定当前POJO类为切面。
package com.Arbin.bao01;
import org.aspectj.lang.annotation.Aspect;
import java.util.Date;
/**
* @Aspect:是aspectj框架中的注解。
* 作用:表示当前类是切面类。
* 切面类:是用来给业务方法增加功能的类,在这类中有切面的功能代码
* 位置:在类定义的上面
*/
@Aspect
public class MyAspect {
/**
* 定义实现切面功能的方法。
* 方法的定义要求:
* 1.公共方法 public
* 2.方法没有返回值
* 3.方法名称自定义
* 4.方法可以用参数,也可以没有参数。
* 如果有参数,参数不是自定义的,有几个参数类型可以使用。
*/
/**
* @Before:前置通知注解
* 属性:value,切入点表达式,表示切面的功能执行位置。
* 位置:在方法的上面
* 特点:
* 1.在目标方法之前先执行的。
* 2.不会改变目标方法的执行结果
* 3.不会影响目标方法的执行。
*/
@Before(value = "execution(public void com.Arbin.bao01.SomeServiceImpl.doSome(String,Integer))")
public void myBefore(){
//切面执行的功能代码
System.out.println("切面功能:在目标方法之前输出执行时间:"+ new Date());
}
}
- 注册AspectJ的代理,声明目标对象与切面类对象(applicationContext.xml)
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--把对象交给-spring容器,由spring容器统一创建-,管理对象-->
<!--声明目标对象-->
<bean id="someService" class="com.Arbin.bao01.SomeServiceImpl"/>
<!--声明切面类对象-->
<bean id="myAspoect" class="com.Arbin.bao01.MyAspect"/>
<!--声明自动代理生成器:使用aspectj框架内部功能,创建目标对象的代理对象
创建代理对象是在内存中实现的,修改目标对象和的内存中的结构,创建为代理对象。
所以目标对象就是被修改后的代理对象。
aop:aspectj-autoproxy:会把spring容器中的所以的目标对象,一次性都生成代理对象。
-->
<aop:aspectj-autoproxy/>
</beans>
<aop:aspectj-autoproxy/>
通过扫描找到@Aspect
定义的切面类,再由切面类根据切入点找到目标类的目标方法,再由通知类型找到切入的时间点。
- 测试类中使用目标对象的id.
@Test
public void test01(){
String config = "applicationContext.xml";
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(config);
//从容器中获取目标对象
SomeService proxy = (SomeService) ctx.getBean("someService");
//通过代理对象执行方法,实现目标方法执行时,增强的功能
proxy.doSome("lisi",20);
}
测试结果:
切面功能:在目标方法之前输出执行时间:Mon Aug 24 11:50:47 CST 2020
目标方法doSome()
3.5.4.2 AspectJ 通知注解
3.5.4.2.1 @Before 前置通知-增强方法由JoinPoint参数
在目标方法执行之前执行。被注解为前置通知的方法,可以包含一个JoinPoint类型参数。该类型的对象本身就是切入点表达式。通过参数,可获取切入点表达式、方法签名、目标对象等。
/**
* 指定通知方法中的参数:JoinPoint
* JoinPoint:业务方法,加入切面功能的业务方法
* 作用是:可以在通知方法中获取方法执行时的信息,例如方法名称、方法的实参。
* 如果切面功能中需要用到方法的信息,就加入JoinPoint。
* JoinPoint的参数值是由框架赋予,必须是第一个位置的参数
*/
@Before(value = "execution(* *..SomeServiceImpl.doSome(..))")
public void myBefore(JoinPoint jp){
//获取方法的完整定义
System.out.println("方法的签名(定义)="+jp.getSignature());
System.out.println("方法的名称="+jp.getSignature().getName());
//获取方法的实参
Object[] args = jp.getArgs();
for (Object arg:args){
System.out.println("参数="+arg);
}
}
3.5.4.2.2 @AfterReturning 后置通知-注解由 returning属性
在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目标方法的返回值。该注解的 returning 属性就是用于指定接受方法返回值的变量名。所以,备注结尾后置通知的方法,除了可以包含 JoinPoint 参数外,还可以包含用于接受返回值的变量。该变量最好为 Object 类型,因为目标方法的返回值可能是任何类型。
例:
- 在接口中定义一个有返回值的抽象方法:
String doOther(String name,Integer age);
- 在类中实现:
@Override
public String doOther(String name, Integer age) {
System.out.println("====目标方法doOther()====");
return "abcd";
}
- 在切面类中定义一个切面:
后置通知方法定义格式:
1.public
2.方法没有返回值
3.方法名称自定义
4.4.方法有参数的,推荐是Object,参数名自定义
/**
* @AfterReturning:后置通知
* 属性:1.value 切入点表达式
* 2.returning:自定义的变量,表示目标方法的返回值的。
* 自定义变量名必须和通知方法的形参名一样。
* 位置:在方法定义的上面
* 特点:
* 1.在目标方法之后执行的。
* 2.能够获取到目标方法的返回值,可以根据这个返回值做不同的处理功能
* Object res = doOther();
* 3.可以修改这个返回值
*/
@AfterReturning(value = "execution(* *..SomeServiceImpl.doOther(..))",returning = "res")
public void myAfterReturning(Object res){
// Object res:是目标方法执行后的返回值,根据返回值做切面的功能处理
System.out.println("后置通知:在目标方法之后执行的,获取的返回值是:"+res);
}
3.5.4.2.3 @Around 环绕通知-增强方法由 ProceedingJoinPoint 参数
在目标方法执行之前之后执行。
被注解为环绕增强的方法是要有返回值(Object类型)。并且方法可以包含一个ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 其有一个proceed(),用于执行目标方法。若目标方法由返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。
- 在接口中定义一个有返回值的抽象方法:
String doFirst(String name, Integer age);
- 在目标类中实现该方法:
@Override
public String doFirst(String name, Integer age) {
System.out.println("====业务方法doFirst()====");
return "doFirst";
}
- 增加切面
环绕通知方法的定义格式:
1.public
2.必须有一个返回值,推荐使用Object
3.方法名称自定义
4.方法有固定参数 ProceedingJoinPoint
/**
* @Around:环绕通知
* 属性:value 切入点表达式
* 位置:在方法的定义上面
* 特点:
* 1.它是功能最强的通知
* 2.在目标方法的前和后都能增强功能。
* 3.控制目标方法是否被调用执行
* 4.修改原来的目标方法的执行结果,影响最后的调用结果。
*
* 环绕通知,等同于jdk动态代理的 InvocationHandler 接口
*
* 参数: ProceedingJoinPoint 等同于 Method
* 作用:执行目标方法的
* 返回值:就是目标方法的执行结果,可以被修改。
*
* 环绕通知:经常做事务,在目标方法之前开启事务,执行目标方法,在目标方法之后提交事务
*/
@Around(value = "execution(* *..SomeServiceImpl.doFirst(..))")
public Object myAround(ProceedingJoinPoint pjp) throws Throwable {
//获取第一个参数值
String name = "";
Object[] args = pjp.getArgs();
if(args!=null && args.length>1){
name = (String) args[0];
}
//实现环绕通知
Object result =null;
System.out.println("环绕通知:在目标方法之前,输出时间:"+new Date());
//1.目标方法调用
if("zhangsan".equals(name)){
//符合条件,调用目标方法
result = pjp.proceed(); //method.invoke(); Object result = doFirst();
}
System.out.println("环绕通知:在目标方法之后,提交事务");
//2.在目标方法的前或后加入功能
//修改目标方法的执行结果,影响方法最后的调用结果
if( result !=null ){
result = "Hello AspectJ AOP";
}
//返回目标方法的执行结果
return result;
}
3.5.4.2.4@AfterThrowing 异常通知-注解中有 throwing 属性
在目标方法抛出异常后执行,该注解的throwing 属性用于指定所发生的异常类对象。当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为throwing指定的名称,表示发生的异常对象。
示例:
- 在接口中定义一个方法:
void doSecond();
- 在目标类中实现该方法
@Override
public void doSecond() {
System.out.println("执行业务方法doSecond()"+(1/0));//方法中出现除数为0异常
}
- 增加切面
异常通知方法的定义格式:
1.public
2.没有返回值
3.方法名称自定义
4.方法可以没有参数,如果有是JoinPoint
/**
* @AfterThrowing:异常通知
* 属性:1.value 切入点表达式
* 2.throwing 自定义的变量,表示目标方法抛出的异常对象。
* 变量名必须和方法的参数名一样
* 特点:
* 1.在目标方法抛出异常时执行的
* 2.可以做异常的监控程序,监控目标方法执行时是不是有一场
* 如果有异常,可以发送邮件、短信进行通知
* 执行:
* try{
* SomeServiceImpl.doSecond(..)
* }catch(Exception e){
* myAfterThrowing(e);
* }
*/
@AfterThrowing(value = "execution(* *..SomeServiceImpl.doSecond(..))",throwing = "ex")
public void myAfterThrowing(Exception ex){
System.out.println("异常通知:方法发生异常时,执行:"+ex.getMessage());
//发送邮件、短信通知开发人员
}
3.5.4.2.5 @After 最终通知
无论目标方法是否抛出异常,该增强均会被执行。
特点:无论是否抛出异常,都会在目标方法之后执行。
执行模式:
try{
SomeServiceImpl.doThird(..)
}catch(Exception e){
}finally{
myAfter();
}
3.5.4.2.6 @Pointcut 定义切入点
当较多的通知增强方法使用相同的 execution 切入点表达式后,编写、维护均较为麻烦。
AspectJ提供了 @Poincut 注解,用于定义execution切入点表达式。
用法:将 @Poincut 注解在一个方法之上,以后所有的 execution 的value 属性值均可使用该方法名作为切入点。 代表作就是 @Pointcut 定义的切入点。这个使用 @Pointcut 注解的方法一般使用 private 的标识方法,即没有实际作用的方法。
@Before(value = "mypt()")
public void myBefore(){
System.out.println("前置通知,在目标方法之前执行");
}
@After(value = "mypt()")
public void myAfter(){
System.out.println("执行最终通知,总是会被执行的代码");
}
/**
* @Pointcut:定义和管理切入点,如果项目中有多个切入点表达式是重复的,可以复用的。
* 可以使用 @Pointcut
* 属性:value 切入点表达式
* 位置:自定义方法上面
* 特点:
* 当使用@Pointcut定义在方法的上面,此时该方法的名称就是切入点表达式的别名。
* 其他通知中,value属性就可以使用方法名称,代替切入点表达式
*/
@Pointcut(value = "execution(* *..SomeServiceImpl.doSecond(..))")
public void mypt(){
//无需代码
}
3.5.5 AspectJ基于XML的AOP实现
AspectJ除了提供基于注解的AOP的实现外,还提供了以XML方式的实现。
切面就是一个POJO类,而用于增强的方法就是普通的方法。通过配置文件,将切面中的功能增强织入到了目标类的目标方法中。
后续补充相关知识点
第四章 Spring 集成 MyBatis
4.1 集成概述
将Mybatis框架与Spring集成在一起,像一个框架一样使用。主要原理在于IoC能创建对象,可以将Mybatis狂降中的对象交给Spring统一创建,开发人员从Spring中获取对象。
实现 Spring 与 MyBatis的整合常用方式:扫描的 Mapper 动态代理
Spring创建以下对象:
- 独立的连接池对象,使用阿里的druid连接池
- SqlSessionFactory对象
- 创建出Dao对象
4.2 项目流程
- MySQL 创建数据库spring-mybatis,新建表student
建表:
CREATE TABLE student(
id int(11) NOT NULL AUTO_INCREMENT,
name varchar(80) DEFAULT NULL,
email varchar(100) DEFAULT NULL,
age int(11) DEFAULT NULL,
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
-
加入依赖与插件
1)junit (单元测试)
2)spring-context (spring核心ioc)
3)spring-tx (spring的事务)
4)spring-jdbc (spring访问数据库)
5)mybatis (mybatis依赖)
6)mybatis-spring (mybatis和spring的集成依赖)
7)mysql-connector-java (mysql驱动)
8)druid (国内阿里公司的数据库连接池)<dependencies> <!--单元测试--> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.11</version> <scope>test</scope> </dependency> <!---spring核心ioc--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.2.8.RELEASE</version> </dependency> <!---spring的事务--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-tx</artifactId> <version>5.2.8.RELEASE</version> </dependency> <!--spring访问数据库--> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-jdbc</artifactId> <version>5.2.8.RELEASE</version> </dependency> <!--mybatis依赖--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.5.5</version> </dependency> <!--mybatis和spring集成依赖--> <dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis-spring</artifactId> <version>2.0.5</version> </dependency> <!--mysql驱动--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.21</version> </dependency> <!--阿里公司的数据库连接池--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.23</version> </dependency> </dependencies>
如果我们的mapper.xml文件没有放置到src-main-resources下面,是不会被maven build plugin给默认扫描到的。此时需要修改启动的模块的pom文件,在build标签里面加入:
<build> <resources> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> <include>**/*.properties</include> </includes> <filtering>true</filtering> </resource> <resource> <directory>src/main/resources</directory> <includes> <include>**/*.xml</include> <include>**/*.properties</include> </includes> <filtering>true</filtering> </resource> </resources> </build>
-
新建实体类 Student
public class Student { //为了方便,属性名和列名设置一样。 private Integer id; private String name; private String email; private Integer age; // onstructor / setter / getter
-
新建Dao接口
public interface StudentDao { int insertStudent(Student student); List<Student> selectStudents(); }
-
定义映射文件Mapper
<?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.Arbin.dao.StudentDao"> <insert id="insertStudent"> insert into student values (#{id},#{name},#{email},#{age}) </insert> <select id="selectStudents" resultType="com.Arbin.entity.Student"> select id,name,email,age from student order by id desc </select> </mapper>
-
定义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="cacheEnabled" value="true"/> <setting name="useGeneratedKeys" value="true"/> <setting name="defaultExecutorType" value="REUSE"/> <setting name="logImpl" value="STDOUT_LOGGING"/> </settings> <!--设置别名--> <typeAliases> <!--name:实体类所在的包名--> <package name="com.Arbin.entity"/> </typeAliases> <!--sql mapper(sql映射文件)的位置--> <mappers> <package name="com.Arbin.dao"/> </mappers> </configuration>
-
新建Service接口和实现类
package com.Arbin.service; import com.Arbin.entity.Student; import java.util.List; public interface StudentService { int addStudent(Student student); List<Student> queryStudents(); }
package com.Arbin.service.impl; import com.Arbin.dao.StudentDao; import com.Arbin.entity.Student; import com.Arbin.service.StudentService; import java.util.List; public class StudentServiceImpl implements StudentService { //引用类型 private StudentDao studentDao; //使用set注入,赋值 public void setStudentDao(StudentDao studentDao) { this.studentDao = studentDao; } @Override public int addStudent(Student student) { int nums =studentDao.insertStudent(student); return nums; } @Override public List<Student> queryStudents() { List<Student> students = studentDao.selectStudents(); return students; } }
-
定义Spring配置文件(声明mybatis对象交由Spring创建)
1)声明数据源DataSource对象
2)声明SqlSessionFactoryBean,创建SqlSessionFactory对象
3)声明MapeperScannerConfigurer扫描器,创建Dao对象
4)声明自定义的Service<?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:property-placeholder location="jdbc.properties"/> <!--声明数据源DataSource,作用是连接数据库的--> <bean id="muDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="clone"> <!--set注入给DruidDataSource提供连接数据库信息--> <!--数据库的url--> <property name="url" value="${jdbc.url}" /> <!--setUrl()--> <!--数据库的用户名--> <property name="username" value="${jdbc.username}"/> <!--数据库访问密码--> <property name="password" value="${jdbc.passowrd}"/> <!--数据库最大连接数--> <property name="maxActive" value="${jdbc.max}"/> </bean> <!--声明SqlSessionFactoryBean类,该类内部创建这创建SqlSessionFactory对象--> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <!--set注入,把数据库的连接池赋给dataSource属性--> <property name="dataSource" ref="muDataSource"/> <!--mybatis主配置文件的位置 configLocation属性是Resource类型,读取配置文件 赋值使用value,指令文件的路径,使用classpath:表示文件的位置 --> <property name="configLocation" value="classpath:mybatis.xml"/> </bean> <!--创建dao对象,使用SqlSession的getMapper(StudentDao.class) MapperScannerConfigurer:在内部调用getMapper()生成每个dao接口的代理对象。--> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <!--指定SqlSessionFactory对象的id,可以获取SqlSession--> <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> <!--指定Dao接口所在的包名 MapperScannerConfigurer会扫描这个包中的所有接口, 把每个接口都执行一次getMapper()方法,得到每个接口的dao对象。 创建好的dao对象放入到spring容器中 --> <property name="basePackage" value="com.Arbin.dao"/> </bean> <!--声明service--> <bean id="studentService" class="com.Arbin.service.impl.StudentServiceImpl"> <property name="studentDao" ref="studentDao"/> </bean> </beans>
jdbc.properties:
jdbc.url=jdbc:mysql://localhost:3306/spring-mybatis jdbc.username=root jdbc.passowrd=**** jdbc.max=20
-
测试类
package com.Arbin; import com.Arbin.dao.StudentDao; import com.Arbin.entity.Student; import com.Arbin.service.StudentService; import org.junit.Test; import org.springframework.context.support.ClassPathXmlApplicationContext; import java.util.List; public class MyTest { @Test public void test01(){ String config = "applicationContext.xml"; ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(config); String[] names = ctx.getBeanDefinitionNames(); for (String name:names){ System.out.println("容器中对象名称:"+name+"|"+ctx.getBean(name)); } } @Test public void testDaoInsert(){ String config = "applicationContext.xml"; ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(config); //获取spring容器中的dao对象 StudentDao dao = (StudentDao) ctx.getBean("studentDao"); Student student = new Student(); student.setId(2); student.setName("张三"); student.setEmail("zhangsan@qq.com"); student.setAge(20); int nums = dao.insertStudent(student); System.out.println("nums:"+nums); } @Test public void testServiceInsert(){ String config = "applicationContext.xml"; ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(config); //获取spring容器中的dao对象 StudentService service = (StudentService) ctx.getBean("studentService"); Student student = new Student(); student.setId(3); student.setName("李四"); student.setEmail("lisi@qq.com"); student.setAge(20); int nums = service.addStudent(student); //spring和mybatis整合在一起使用,事务四自动提交的。无需执行SqlSession.commit(); System.out.println("nums:"+nums); } @Test public void testServicesSelect(){ String config = "applicationContext.xml"; ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext(config); //获取spring容器中的dao对象 StudentService service = (StudentService) ctx.getBean("studentService"); List<Student> students = service.queryStudents(); for (Student stu:students) System.out.println(stu); } }
第五章 Spring的事务管理
5.1 章节问题
-
什么是事务?
事务是指多条语句的集合,这些语句的执行是一致的并作为一个整体执行。如:在mysql中的事务是指一组sql语句的集合。
-
在什么时候使用事务?
当操作涉及多个表或多个sql语句。需要保证这些语句结果的一致性使得操作是符合要求的。而在java中事务一般在service类的业务方法上,因为业务方法会调用多个dao方法,执行多个sql语句。
-
使用JDBC访问数据库或、mybatis访问数据库怎么处理事务?
连接数据库、处理业务:
jdbc: Connection conn; conn.commit(); conn.rollback();
mybatis: SqlSession.commit(); SqlSession.rollback();
hibernate:Session.commit(); Session.roolback(); -
问题3中的事务处理方式,有什么不足
1)不同的数据库访问技术,处理事务的对象,方法不同,需要了解不同数据库访问技术使用事务的原理。
2)掌握多种数据库中事务的处理逻辑。如:提交事务、回滚事务的时间。
3)处理事务的多种方法。
多种数据库访问技术具有不同的事务处理机制、对象、方法。 -
怎么解决不足
Spring提供了处理事务的统一模型,能使用统一步骤、方式完成多种不同数据库访问技术的事务处理。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-99efgzZS-1600265386842)(https://s1.ax1x.com/2020/09/01/dvInRH.md.png)] -
处理事务,需要怎么做
Spring处理事务的模型以及步骤都是固定的、只需将事务所使用的信息提供给Spring。下面该章节介绍具体方法与步骤。
5.2 Spring 事务管理API
Sring的事务管理,主要用到两个事务相关的接口。
5.2.1 事务管理接口(重点)
事务管理器是 PlatformTransactionManager 接口对象。其主要用于完成事务的提交、回滚以及事务的状态信息。PlatformTransactionManager 接口常用的两个实现类:
- DataSourceTransactionManager:使用 JDBC 或 MyBatis 进行数据库操作时使。
- HibernateTranscationManager:使用 Hibernate 进行持久化数据时使用。
5.2.2 Spring 的回滚方式
Spring 事务的默认回滚方式是:发生运行时异常和error时异常,发生受查(编译)异常时提交。不过对于受查异常,,程序员也可以手工设置其回滚方式。
运行时异常:是 RuntimeException 类及其子类,即只有在运行时才出现的异常。如:NullPointerException、ArrayInDexOutOfBoundsException、illegalArgumentException 等均属于运行时异常。这些异常由 JVM 抛出,在编译时不要求必须处理(捕获或抛出)。但只要代码编写足够仔细,程序足够健壮,运行时异常是可以异常的。
受查异常:也称为编译时异常,即在代码编写时要求必须捕获或抛出的异常,若不处理,则无法通过编译。如:SQLException、ClassNotFoundException、IOException等均属于受查异常。
5.2.3 事务定义接口
事务定义接口 TransactionDefinition 中定义了事务描述相关的三类常量:事务隔离级别、事务传播行为、事务默认超时时限,及对它们的操作。
- 定义了五个事务隔离级别常量
这些常量均是以 ISOLATION_开头。即形如 ISOLATION_XXX。
- DEFAULT:采用 DB 默认的事务隔离级别。MySql 的默认为 REPEATABLE_READ; Oracle 默认为 READ_COMMITTED。
- READ_UNCOMMITTED:读未提交。未解决任何并发问题。
- READ_COMMITTED:读已提交。解决脏读,存在不可重复读与幻读。
- REPEATABLE_READ:可重复读。解决脏读、不可重复读,存在幻读
- SERIALIZABLE:串行化。不存在并发问题。
- 定义了七个事务传播行为常量
所谓事务传播行为是指,处于不同事务中的方法在相互调用时,执行期间事务的维护情 况。如,A 事务中的方法 doSome()调用 B 事务中的方法 doOther(),在调用执行期间事务的 维护情况,就称为事务传播行为。事务传播行为是加在方法上的。
事务传播行为常量都是以 PROPAGATION_ 开头,形如PROPAGATION_XXX。
PROPAGATION_REQUIRED
PROPAGATION_REQUIRES_NEW
PROPAGATION_SUPPORTS
PROPAGATION_MANDATORY
PROPAGATION_NESTED
PROPAGATION_NEVER
PROPAGATION_NOT_SUPPORTED
1)PROPAGATION_REQUIRED
指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事务,则创建一个新事务。这种传播行为是最常见的选择,也是 Spring 默认的事务传播行为。 如该传播行为加在 doOther()方法上。若 doSome()方法在调用 doOther()方法时就是在事 务内运行的,则 doOther()方法的执行也加入到该事务内执行。若 doSome()方法在调用 doOther()方法时没有在事务内执行,则 doOther()方法会创建一个事务,并在其中执行。
2)PROPAGATION_SUPPORTS
指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行。
3)PROPAGATION_REQUIRES_NEW
总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕。
5.3 程序举例环境搭建
详细例子请查看:https://blog.csdn.net/qq_43543442/article/details/108346228
5.4 使用 Spring 的事务注解管理事务
5.4.1 @Transactional注解方式
通过 @Transactional 注解方式,可将事务织入到相应 public 方法中,实现事务管理。
@Transactional 的所有可选属性如下所示:
- propagation:用于设置事务传播属性。该属性类型为 Propagation 枚举,默认值为Propagation.REQUIRED。
- isolation:用于设置事务的隔离级别。该属性类型为 Isolation 枚举,默认值为Isolation.DEFAULT。
- readOnly:用于设置该方法对数据库的操作是否是只读的。该属性为 boolean,默认值为 false。
- timeout:用于设置本操作与数据库连接的超时时限。单位为秒,类型为int,默认值为-1,即没有时限。
- rollbackFor:指定需要回滚的异常类。类型为 Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
- rollbackForClassName:指定需要回滚的异常类类名。类型为String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
- noRollbackFor:指定不需要回滚的异常类。类型为Class[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
- noRollbackForClassName:指定不需要回滚的异常类类名。类型为 String[],默认值为空数组。当然,若只有一个异常类时,可以不使用数组。
需要注意的是:@Transactional 若用在方法上,只能用于 public 方法上。对于其他非 public 方法,如果加上了注解@Transactional,虽然 Spring 不会报错,但不会将指定事务织入到该 方法中。因为 Spring 会忽略掉所有非 public 方法上的@Transaction 注解。
若@Transaction 注解在类上,则表示该类上所有的方法均将在执行时织入事务。
5.4.2 使用 @Transactional 步骤
1. 声明事务管理器
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--连接的数据库,指定数据源-->
<property name="dataSource" ref="myDataSource"/>
</bean>
2. 开启注解驱动
spring使用aop机制,创建@Transactional所在的类代理对象,给方法加入事务的功能。
spirng给业务方法加入事务:
在你的业务方法执行之前,先开启事务,在业务方法之后提交或回滚事务,使用aop的环绕通知。
<!--2.开启事务注解驱动,告诉 spring使用注解管理事务,创建代理对象
transaction-manage:事务管理器对象的id
-->
<tx:annotation-driven transaction-manager="transactionManager"/>
3. 业务层 public 方法加入事务属性
/*
* rollbackFor:表示发生指定的异常一定回滚
* 处理逻辑:
* 1)spring框架会首先检查方法抛出的异常是不是在rollbackFor的属性值中
* 如果异常在rollbackFor列表中,不管是什么类型的异常,一定回滚。
* 2)如果你抛出的异常不再rollbackFor列表中,spring会判断异常是不是RuntimeException,
* 如果是一定回滚。
*/
@Transactional(
propagation = Propagation.REQUIRED, //传播行为
isolation = Isolation.DEFAULT,
readOnly = false,
rollbackFor = {
NullPointerException.class, NotEnoughException.class
}
)
5.5 使用 AspectJ 的 AOP 配置管理事务
5.5.1 概述
使用 XML 配置事务代理的方式的不足是,每个目标类都需要配置事务代理。当目标类 较多,配置文件会变得非常臃肿。
适用范围:大型项目,大量的类和方法需要大量的配置事务,使用aspectj框架功能,在spring配置文件中声明类,方法需要的事务。这种方式使业务方法和事务配置完全分离。
5.5.2 实现步骤
1. maven 依赖 pom.xml
加入aspectj的依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
2. 声明事务管理器对象
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!--连接的数据库,指定数据源-->
<property name="dataSource" ref="myDataSource"/>
</bean>
3. 声明方法需要的事务类型(配置方法的事务属性【隔离级别,传播行为,超时】)
<!--2.声明业务方法它的业务属性
id:自定义名称,表示<tx:advice> 和 </tx:advice>之间的配置内容
transaction-manager:事务管理器对象的id
-->
<tx:advice id="myAdvice" transaction-manager="transactionManager">
<!--tx:attributes:配置事务属性-->
<tx:attributes>
<!--tx:method:给具体的方法配置事务属性,method可以有多个,分别给不同的方法设置事务属性
name:方法名称,1)完整的方法名称,不带有包和类。
2)方法可以使用通配符,* 表示任意字符
propagation:传播行为,枚举值
isolation:隔离级别
rollback-for:指定的异常类名,全限定类名。发生异常一定回滚。
-->
<tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT"
rollback-for="java.lang.NullPointerException,com.Arbin.excep.NotEnoughException"/>
<!--使用通配符,指定多个方法-->
<tx:method name="add*" propagation="REQUIRES_NEW"/>
<!--指定修改方法-->
<tx:method name="modify*"/>
<!--删除方法-->
<tx:method name="remove*"/>
<!--查询方法,query,search,find-->
<tx:method name="*" propagation="SUPPORTS" read-only="true"/>
</tx:attributes>
</tx:advice>
4. 配置aop:指定所要创建代理的类
<!--配置aop-->
<aop:config>
<!--配置切入点表达式:指定所需使用事务的类
id:切入点表达式的名称,唯一值
expression:切入点表达式,指定哪些类要使用事务,aspectj会创建代理对象
-->
<aop:pointcut id="servicePt" expression="execution(* *..service..*.*(..))"/>
<!--配置增强器:关联advice和pointcut
advice-ref:通知,上述的tx:advice的配置
pointcut-ref:切入点表达式的id
-->
<aop:advisor advice-ref="myAdvice" pointcut-ref="servicePt"/>
</aop:config>
第六章 Spring 与 Web
对于 Web 应用来说,ServletContext 对象是唯一的,一个 Web 应用,只有一个 ServletContext 对象,该对象是在 Web 应用装载时初始化的。若将 Spring 容器的创建时机, 放在 ServletContext 初始化时,就可以保证 Spring 容器的创建只会执行一次,也就保证了 Spring 容器在整个应用中的唯一性。
当 Spring 容器创建好后,在整个应用的生命周期过程中,Spring 容器应该是随时可以被访问的。即,Spring 容器应具有全局性。而放入 ServletContext 对象的属性,就具有应用的 全局性。所以,将创建好的 Spring 容器,以属性的形式放入到 ServletContext 的空间中,就 保证了 Spring 容器的全局性。
上述的这些工作,已经被封装在了如下的 Spring 的 Jar 包的相关 API 中: spring-web-5.2.5.RELEASE
1. 添加Maven依赖
<!--servlet依赖-->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>4.0.1</version>
</dependency>
<!--jsp依赖-->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2.1-b03</version>
</dependency>
<!--监听器依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.8.RELEASE</version>
</dependency>
2. 注册监听器ContextLoader
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
3. 指定Spring配置文件中的位置
<!--注册监听器ContextLoaderListener
监听器被创建对象后,会读取/WEB-INF/spring.xml
读取文件的原因:因为在监听器中要创建ApplicationContext,需要加载配置文件。
会读取/WEB-INF/applicationContext.xml是监听器默认读取的spring配置文件路径
可以修改默认默认的文件位置,使用context-param重新指定文件的位置
-->
<context-param>
<!-- contextConfigLocation:配置文件的路径-->
<param-name>contextConfigLocation</param-name>
<param-value>classpath:spring.xml</param-value>
</context-param>
4. 在Sevlet中获取ApplicationContext对象
//获取ServletContext重的容器对象,创建好的容器对象,拿来就用
String key = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
Object attr = getServletContext().getAttribute(key);
if( attr != null){
ctx = (WebApplicationContext)attr;
}
或使用工具类获取WebApplicationContext:
ServletContext sc = getServletContext();
ctx = WebApplicationContextUtils.getRequiredWebApplicationContext(sc);