本文素材来自动力结点王妈妈的课程
什么是Spring
Spring 是于 2003 年兴起的一个轻量级的 Java 开发框架,它是为了解 决企业应用开发的复杂性而创建的。Spring的核心是控制反转(IoC)和面向切面编程(AOP)。Spring 根据代码的功能特点,使用 Ioc 降低业务对象之间耦合度。通过 Spring 提供的 AOP 功能,方便进行面向切面的编程。
IoC控制反转
控制反转(IoC,Inversion of Control),是一种思想。指将传统上由程序代码直接操控的对象调用权交给容器,通过容器来实现对象的装配和管理。Spring 框架使用依赖注入(DI)实现 IoC。
初识Spring
1、创建maven项目
2、引入 maven 依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
3、定义接口与实体类
public interface SomeService {
void doSome();
}
public class SomeServiceImpl implements SomeService {
public SomeServiceImpl() {
super();
System.out.println("SomeServiceImpl无参数构造方法");
}
@Override
public void doSome() {
System.out.println("doSome业务方法");
}
}
4、创建 Spring 配置文件
在 src/main/resources/目录现创建一个 xml 文件,文件名可以随意,但Spring 建议的名称为applicationContext.xml。注意要将resources文件将指定为资源文件夹(这个文件夹中的文件编译之后都会放在类路径下),如何指定?
右键文件夹 → Mark Directory as → Resources Root
<?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 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--
注册bean对象
id:自定义对象的名称
class:类的全限定名称,不能是接口
-->
<bean id="someService" class="com.why.SomeServiceImpl"></bean>
</beans>
5、定义测试类
public class MyTest {
@Test
public void test01(){
//指定spring配置文件的位置和名称
String resource = "applicationContext.xml";
//创建spring容器对象
ApplicationContext ac = new ClassPathXmlApplicationContext(resource);
//使用id从spring容器中获取对象
SomeService someService = (SomeService) ac.getBean("someService");
//执行对象的方法
someService.doSome();
}
}
基于XML依赖注入(DI)
set注入
public class School {
private String schoolName;
private String addr;
/*setter*/
/*toString*/
}
public class Student {
private String name;
private int age;
private School school;
/*setter*/
/*toString*/
}
<!--使用set方法注入参数-->
<bean id="mySchool" class="com.why.School">
<!--简单类型-->
<property name="schoolName" value="浙江中医药大学"></property>
<property name="addr" value="浙江杭州"></property>
</bean>
<bean id="zs" class="com.why.Student">
<property name="name" value="张三"></property>
<property name="age" value="20"></property>
<!--引用类型-->
<property name="school" ref="mySchool"></property>
</bean>
构造注入
//Student构造函数
public Student(String stuName, int stuAge, School stuSchool) {
this.name = stuName;
this.age = stuAge;
this.school = stuSchool;
}
<!--构造注入-->
<bean id="ls" class="com.why.Student">
<constructor-arg name="stuName" value="李四"></constructor-arg>
<constructor-arg name="stuAge" value="20"></constructor-arg>
<constructor-arg name="stuSchool" ref="mySchool"></constructor-arg>
</bean>
<!--index:指明该参数对应着构造器的第几个参数,从0开始(少用)-->
<bean id="ww" class="com.why.Student">
<constructor-arg index="0" value="王五"></constructor-arg>
<constructor-arg index="1" value="20"></constructor-arg>
<constructor-arg index="2" ref="mySchool"></constructor-arg>
</bean>
<!--index属性不要也行,但要注意:若参数类型相同或之间有包含关系,则需要保证赋值顺序要与构造器中的参数顺序一致-->
<bean id="zl" class="com.why.Student">
<constructor-arg value="赵六"></constructor-arg>
<constructor-arg value="20"></constructor-arg>
<constructor-arg ref="mySchool"></constructor-arg>
</bean>
引用类型属性自动注入
byName方式自动注入
容器是通过调用者的 bean 类的属性名与配置文件的被调用者 bean 的id 进行比较而实现自动注入的
public class Student {
private String name;
private int age;
private School schoolXXX;
/*setter*/
/*toString*/
}
byName方式自动注入时Spring会根据用于类型的变量名去调用相应的setter方法
(比如这里我们要注入zy对象 Spring根据autowire的属性发现是根据byName方式自动注入。Spring会找到Student中的Schoo引用类型,根据schoolXXX属性名在容器中找到名为schoolXXX的对象(如果在找不到schoolXXX对象Spring就会去找引用类型名首字母小写的对象本例中为school 如果两个都找不到就会报错)然后去调用setSchoolXXX方法将引用类型注入)
<bean id="schoolXXX" class="com.why.School">
<property name="schoolName" value="温州大学"></property>
<property name="addr" value="浙江温州"></property>
</bean>
<bean id="zy" class="com.why.Student" autowire="byName">
<property name="name" value="张扬"></property>
<property name="age" value="20"></property>
</bean>
byType方式自动注入
使用 byType 方式自动注入,要求:配置文件中被调用者 bean 的 class 属性指定的类,要与代码中调用者 bean 类的某引用类型属性类型同源。即要么相同,要么有 is-a 关系(子类或是实现类)。
<bean id="cb" class="com.why.Student" autowire="byType">
<property name="name" value="陈八"></property>
<property name="age" value="20"></property>
</bean>
如果使用byType方式自动注入要求同源的被调用 bean只能有一个否则会报以下错误
Caused by: org.springframework.beans.factory.NoUniqueBeanDefinitionException: No qualifying bean of type ‘com.why.School’ available: expected single matching bean but found 2: mySchool,school
为应用指定多个 Spring 配置文
在实际应用里,随着应用规模的增加,系统中 Bean 数量也大量增加,导致配置文件变得非常庞大、臃肿。为了避免这种情况的产生,提高配置文件的可读性与可维护性,可以将 Spring 配置文件分解成多个配置文件。
spring-school.xml
<bean id="middleSchool" class="com.why.School">
<property name="schoolName" value="温州中学"></property>
<property name="addr" value="浙江温州"></property>
</bean>
spring-student.xml
<bean id="lh" class="com.why.Student">
<property name="name" value="林浩"></property>
<property name="age" value="20"></property>
<!--引用类型-->
<property name="school" ref="middleSchool"></property>
</bean>
<!--引入spring-school.xml-->
<import resource="classpath:spring-school.xml"/>
applicationContext.xml(主配置文件)
<import resource="classpath:spring-school.xml"/>
<import resource="classpath:spring-student.xml"/>
<!--也可使用通配符*-->
<!-- <import resource="classpath*:spring-*.xml"/> -->
基于注解的 DI
对于 DI 使用注解,将不再需要在 Spring 配置文件中声明 bean 实例。需要在 Spring 配置文件中配置组件扫描器,用于在指定的基本包中扫描注解。
<!--声明组件扫描器:指定注解所在的包-->
<context:component-scan base-package="com.why"/>
<!-- 指定多个包
<context:component-scan base-package="com.why,com.ehy"/> 第1种方式:逗号分隔
<context:component-scan base-package="com.why;com.ehy"/> 第2种方式:分号分隔
<context:component-scan base-package="com.why com.ehy"/> 第3种方式:空格分隔
<context:component-scan base-package="com"/> 第4种方式:指定到父包,也会扫描到子包下级的子包-->
定义bean的注解@Component
需要在类上使用注解@Component,该注解的 value 属性用于指定该bean 的 id 值
//@Component 不指定value属性,bean的id是类名的首字母小写school
@Component("collage")//类似xml中的<bean id="collage" class="com.why.School"></bean>
public class School {
private String schoolName;
private String addr;
}
- @Repository 用于对 DAO 实现类进行注解
- @Service 用于对 Service 实现类进行注解
- @Controller 用于对 Controller 实现类进行注解
这三个注解与@Component 都可以创建对象,但这三个注解还有其他的含义,@Service 创建业务层对象,业务层对象可以加入事务功能,@Controller 注解创建的对象可以作为处理器接收用户的请求。
简单类型属性注入@Value
使用该注解完成属性注入时,类中无需 setter。当然,若属性有 setter,则也可将其加到 setter 上。
@Component("collage")
public class School {
@Value("浙江大学")
private String schoolName;
@Value("浙江杭州")
private String addr;
/*toString()*/
}
byType 自动注入@Autowired
@Component("tq")
public class Student {
@Value("田七")
private String name;
@Value("20")
private int age;
@Autowired
private School school;
/*相当于
<bean id="tq" class="com.why.Student" autowire="byType">
<property name="name" value="田七"></property>
<property name="age" value="20"></property>
</bean>
*/
}
byName 自动注入@Autowired 与@Qualifier
需要在引用属性上联合使用注解@Autowired 与@Qualifier。@Qualifier的 value 属性用于指定要匹配的 Bean 的 id 值。类中无需 set 方法,也可加到set 方法上。
@Component("tq")
public class Student {
@Value("田七")
private String name;
@Value("20")
private int age;
@Autowired
@Qualifier("middleSchool")
private School school;
@Autowired 还有一个属性 required,默认值为 true,表示当匹配失败后,会终止程序运行。若将其值设置为 false,则匹配失败,将被忽略,未匹配的属性值为 null。
JDK 注解@Resource 自动注入
Spring 提供了对 jdk 中@Resource 注解的支持。@Resource 注解既可以按名称匹配 Bean,也可以按类型匹配 Bean。默认是按名称注入,采用默认按名称的方式注入按名称不能注入 bean时,则会按照类型进行 Bean 的匹配注入。
@Component("tq")
public class Student {
@Value("田七")
private String name;
@Value("20")
private int age;
@Resource //byType
//@Resouces(name="middleSchool") byName
private School school;
}
AOP 面向切面编程
AOP(Aspect Orient Programming),面向切面编程。面向切面编程是从动态角度考虑程序运行过程。利用 AOP 可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度降低,提高程序的可重用性,同时提高了开发的效率。
AOP 编程术语
- 切面(Aspect)
切面泛指交叉业务逻辑。如事务处理、日志处理就可以理解为切面 - 连接点(JoinPoint)
连接点指可以被切面织入的具体方法。通常业务接口中的方法均为连接点。 - 切入点(Pointcut)
切入点指声明的一个或多个连接点的集合。通过切入点指定一组方法。被标记为 final 的方法是不能作为连接点与切入点的。因为最终的是不能被修改的,不能被增强的。 - 目标对象(Target)
目标对象指将要被增强的对象。即包含主业务逻辑的类的对象。 - 通知(Advice)
通知表示切面的执行时间,Advice 也叫增强。通知定义了增强代码切入到目标代码的时间点,是目标方法执行之前执行,还是之后执行等。通知类型不同,切入时间不同。切入点定义切入的位置,通知定义切入的时间。
AspectJ
对于 AOP 这种编程思想,很多框架都进行了实现。Spring 就是其中之一,可以完成面向切面编程。然而,AspectJ 也实现了 AOP 的功能,且其实现方式更为简捷,使用更为方便,而且还支持注解式开发。所以,Spring 又将AspectJ 的对于 AOP 的实现也引入到了自己的框架中。
AspectJ 的通知类型
AspectJ 中常用的通知有五种类型:
(1)前置通知 @Before
(2)后置通知 @AfterReturning
(3)环绕通知 @Around
(4)异常通知 @AfterThrowing
(5)最终通知 @After
AspectJ 的切入点表达式
execution(访问权限类型? 返回值类型 包名类名? 方法名(参数类型和参数个数) 抛出异常类型?)
?表示可选部分
返回值类型和方法名(参数类型和参数数量)是必须要有的
符号 | 意义 |
---|---|
* | 0至多个任意字符 |
… | 用在方法参数中表示任意多个参数 用在包名后面表示当前包以及其子包路径 |
+ | 用在类名后面表示当前类及子类 用在接口后表示当前接口及其实现类 |
例:
- execution(public * *(…))
指定切入点为:任意公共方法。 - execution(* set*(…))
指定切入点为:任何一个以“set”开始的方法。 - execution(* *…service.*.*(…))
指定所有包下的 serivce 子包下所有类(接口)中所有方法为切入点
AspectJ 基于注解的 AOP 实现
1、创建maven工程
2、引入相关依赖
完整依赖
<?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.why</groupId>
<artifactId>ch08-Spring-aop</artifactId>
<version>1.0-SNAPSHOT</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>
<!--AspectJ依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
</project>
3、定义业务接口和实现类
public interface SomeService {
void doSome();
int doOther(int x,int y);
}
public class SomeServiceImpl implements SomeService {
@Override
public void doSome() {
System.out.println("执行了doSome业务方法");
}
@Override
public int doOther(int x, int y) {
return x>y ? x : y;
}
}
4、定义切面类
/**
*@Aspect:是AspectJ框架的注解表示当前类是切面类
*/
@Aspect
public class MyAspect {
@Before(value = "execution(* com.why.SomeServiceImpl.doSome(..))")
public void before(){
System.out.println("前置通知:在目标方法之前执行,例如输出日志");
}
}
5、声明目标对象切面类对象
<!--声明目标类对象-->
<bean id="target" class="com.why.SomeServiceImpl"></bean>
<!--声明切面类对象-->
<bean id="myAspect" class="com.why.MyAspect"></bean>
6、注册 AspectJ 的自动代理
<!--声明自动代理生成器,创建代理-->
<aop:aspectj-autoproxy/>
7、测试
@Test
public void test01(){
String resource = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(resource);
SomeService target = (SomeService) ac.getBean("target");
target.doSome();
}
结果
前置通知:在目标方法之前执行,例如输出日志
执行了doSome业务方法
Process finished with exit code 0
JoinPoint 参数
@Before(value = "execution(* com.why.SomeServiceImpl.doOther(..))")
public void beforeOther(JoinPoint joinPoint){
/*不光前置通知的方法,可以包含一个 JoinPoint 类型参数,所有的通知方法均可包含该参数。*/
System.out.println("连接点的方法定义:"+joinPoint.getSignature());
System.out.println("连接点方法的参数个数:"+joinPoint.getArgs().length);
/*
方法参数信息
Object[] args = joinPoint.getArgs();
*/
}
调用doOther方法的执行结果
连接点的方法定义:int com.why.SomeService.doOther(int,int)
连接点方法的参数个数:2
执行了other业务方法
后置通知@AfterReturning注解的 returning 属性
在目标方法执行之后执行。由于是目标方法之后执行,所以可以获取到目
标方法的返回值。该注解的 returning 属性就是用于指定接收方法返回值的变
量名的。
@AfterReturning(value = "execution(* com.why.SomeServiceImpl.doOther(..))",returning = "result")
public void afterReturning(Object result){
if (result != null) {
Integer i = (Integer) result;
result = i * 100;
}
System.out.println("后置通知:在目标方法执行后的功能增强,如事务的处理");
System.out.println("较大数的100倍:"+result);
}
调用doOther方法后的执行结果
执行了other业务方法
后置通知:在目标方法执行后的功能增强,如事务的处理
较大数的100倍:200
Process finished with exit code 0
环绕通知@Around 方法有ProceedingJoinPoint 参数
在目标方法执行之前之后执行。被注解为环绕增强的方法要有返回值Object 类型。并且方法可以包含一个 ProceedingJoinPoint 类型的参数。接口 ProceedingJoinPoint 其有一个 proceed()方法,用于执行目标方法。若目标方法有返回值,则该方法的返回值就是目标方法的返回值。最后,环绕增强
方法将其返回值返回。该增强方法实际是拦截了目标方法的执行。
//接口方法
String doFirst(String name,int age);
//接口方法实现类
@Override
public String doFirst(String name,int age) {
System.out.println("执行了doFirst业务方法");
return "doFirst";
}
定义切面
@Around(value = "execution(* com.why.SomeServiceImpl.doFirst(..))")
public Object around(ProceedingJoinPoint pjp) throws Throwable {
Object object;
System.out.println("环绕通知:在目标方法之前执行,如输出日志");
//执行目标方法
object = pjp.proceed();
System.out.println("环绕通知:在目标方法之后执行,如事务处理");
System.out.println(object.toString());
return object;
}
异常通知@AfterThrowing注解的 throwing 属性
在目标方法抛出异常后执行。该注解的 throwing 属性用于指定所发生的异常类对象。当然,被注解为异常通知的方法可以包含一个参数 Throwable,参数名称为 throwing 指定的名称,表示发生的异常对象。
@AfterThrowing(value = "execution(public * com.why.SomeServiceImpl.doSecond(..))",throwing = "ex")
public void afterThrowing(Throwable ex){
/*
异常通知可以做什么?
把异常发生的时间、地点、原因记录到数据库,日志文件等等
可以在异常发生时,把异常信息通过邮件、短信发送给开发人员
*/
System.out.println("异常通知:在目标方法抛出异常执行,异常原因:"+ex.getMessage());
}
异常通知:在目标方法抛出异常执行,异常原因:/ by zero
java.lang.ArithmeticException: / by zero
Process finished with exit code -1
最终通知@After
无论目标方法是否抛出异常,该增强均会被执行。
@After("execution(public * com.why.SomeServiceImpl.doSecond())")
public void after(){
System.out.println("最终通知,总是会执行");
}
@Pointcut 定义切入点
当较多的通知增强方法使用相同的 execution 切入点表达式时,编写、维护均较为麻烦。AspectJ 提供了@Pointcut 注解,用于定义 execution 切入点表达式。其用法是,将@Pointcut 注解在一个方法之上,以后所有的 execution 的value 属性值均可使用该方法名作为切入点。代表的就是@Pointcut 定义的切入点。这个使用@Pointcut 注解的方法一般使用 private 的标识方法,即没有实际作用的方法。
@After("myPoint()")
public void after(){
System.out.println("最终通知,总是会执行");
}
/**
* @Pointcut:用来定义和管理切面点,简化切入点的定义
*/
@Pointcut(value = "execution(public * com.why.SomeServiceImpl.doSecond())")
public void myPoint(){
//无需写代码
}
Spring集成Mybatis
将 MyBatis 与 Spring 进行整合,主要解决的问题就是将SqlSessionFactory 对象交由 Spring 来管理。只需要将SqlSessionFactory 的对象生成器 SqlSessionFactoryBean 注册在 Spring 容器中,再将其注入给 Dao 的实现类即可完成整合。
1、准备数据库表
create table student
(
id int not null,
name varchar(10) null,
sex varchar(2),
age int null,
constraint student_pk primary key (id)
);
2、创建maven工程
3、导入依赖
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
<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>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.1</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.1</version>
</dependency>
<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>
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.properties</include>
<include>**/*.xml</include>
</includes>
<filtering>false</filtering>
</resource>
</resources>
<plugins>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.1</version>
<configuration>
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
</plugins>
</build>
4、定义实体类、dao接口
//实体类
public class Student {
private int id;
private String name;
private char sex;
private int age;
/*getter and setter*/
/*toString()*/
}
//StudentDao接口
public interface StudentDao {
int insertStudent(Student student);
List<Student> selectStudent();
}
5、定义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.why.dao.StudentDao">
<insert id="insertStudent" parameterType="com.why.Student">
insert into t_student(name,sex,age) value (#{name},#{sex},#{age})
</insert>
<select id="selectStudent" resultType="com.why.Student">
select name,sex,age from t_student
</select>
</mapper>
6、定义 Service 接口和实现类
public interface StudentService {
int addStudent(Student student);
List<Student> queryStudent();
}
public class StudentServiceImpl implements StudentService {
private StudentDao studentDao;
public void setStudentDao(StudentDao studentDao) {
this.studentDao = studentDao;
}
@Override
public int addStudent(Student student) {
int rows = studentDao.insertStudent(student);
return rows;
}
@Override
public List<Student> queryStudent() {
List<Student> students = studentDao.selectStudent();
return students;
}
}
7、定义 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>
<!--日志:将sql语句打印到控制台-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<mappers>
<!--引入mapper的第一种方式-->
<!--<mapper resource="com/why/dao/StudentDao.xml"/>-->
<!--引入mapper的第二种方式-->
<package name="com.why.dao"/>
</mappers>
</configuration>
8、修改 Spring 配置文件
8.1、数据源的配置
Druid 是阿里的开源数据库连接池。是 Java 语言中最好的数据库连接池。Druid 能够提供强大的监控和扩展功能。
Druid官方网址
<!--声明数据源DataSource:阿里druid连接池-->
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/whynode"/>
<property name="username" value="root"/>
<property name="password" value="xxxxx"/>
</bean>
8.2、注册 SqlSessionFactoryBean
<!--声明sqlSessionFactoryBean-->
<bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="myDataSource"/>
<property name="configLocation" value="classpath:mybatis.xml"/>
</bean>
8.3、定义 Mapper 扫描配置器 MapperScannerConfigurer
<!--声明MapperScannerConfigurer,创建Dao代理对象,循环basePackage包中所有的接口
调用sqlSession的getMapper方法创建Dao代理对象-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="factory"/>
<property name="basePackage" value="com.why.dao"/>
</bean>
向 Service 注入接口名
<bean id="studentService" class="com.why.Service.StudentServiceImpl">
<property name="studentDao" ref="studentDao"/>
</bean>
完整的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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<!--声明数据源DataSource:阿里druid连接池-->
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="jdbc:mysql://localhost:3306/whynode"/>
<property name="username" value="root"/>
<property name="password" value="xxxx"/>
</bean>
<!--声明sqlSessionFactoryBean-->
<bean id="factory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="myDataSource"/>
<property name="configLocation" value="classpath:mybatis.xml"/>
</bean>
<!--声明MapperScannerConfigurer,创建Dao代理对象,循环basePackage包中所有的接口
调用sqlSession的getMapper方法创建Dao代理对象-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="sqlSessionFactoryBeanName" value="factory"/>
<property name="basePackage" value="com.why.dao"/>
</bean>
<bean id="studentService" class="com.why.Service.StudentServiceImpl">
<property name="studentDao" ref="studentDao"/>
</bean>
</beans>
从属性文件读取数据库连接信息
在resources目录下创建jdbc.properties文件
jdbc.driver = com.mysql.jdbc.Driver
jdbc.url = jdbc:mysql://localhost:3306/whynode
jdbc.user = root
jdbc.password = why0417
Spring 配置文件从属性文件中读取数据时,需要在的 value属性中使用${ },将在属性文件中定义的 key 括起来,以引用指定属性的值。
<!--
引入属性配置文件 spring-context.xsd
location:指定属性配置问价位置,使用classpath表示类路径
-->
<context:property-placeholder location="classpath:jdbc.properties"/>
<!--声明数据源DataSource:阿里druid连接池-->
<bean id="myDataSource" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.user}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
测试
@Test
public void Test01(){
String config = "applicationContext.xml";
ApplicationContext act = new ClassPathXmlApplicationContext(config);
StudentDao studentDao = (StudentDao) act.getBean("studentDao");
Student student = new Student();
student.setName("张三");
student.setSex('男');
student.setAge(20);
studentDao.insertStudent(student);
}
Spring事务
使用spring的事务管理器,管理不同数据库访问技术的事务处理。 开发人员只需要掌握spring的事务处理一个方案, 就可以实现使用不同数据库访问技术的事务管理。
事务管理器接口
事务管理器是 PlatformTransactionManager 接口对象。其主要用于完成事务的提交、回滚,及获取事务的状态信息。PlatformTransactionManager 接口有两个常用的实现类:
- DataSourceTransactionManager:使用 JDBC 或 MyBatis 进行数据库操作时使用。
- HibernateTransactionManager:使用 Hibernate 进行持久化数据时使用。
事务定义接口
事务定义接口 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
指定的方法必须在事务内执行。若当前存在事务,就加入到当前事务中;若当前没有事务,则创建一个新事务。这种传播行为是最常见的选择,也是Spring 默认的事务传播行为 - PROPAGATION_REQUIRES_NEW
总是新建一个事务,若当前存在事务,就将当前事务挂起,直到新事务执行完毕 - PROPAGATION_SUPPORTS
指定的方法支持当前事务,但若当前没有事务,也可以以非事务方式执行 - PROPAGATION_MANDATORY
- PROPAGATION_NESTED
- PROPAGATION_NEVER
- PROPAGATION_NOT_SUPPORTED
定义了默认事务超时时限
常量 TIMEOUT_DEFAULT 定义了事务底层默认的超时时限,sql 语句的执行时长。事务的超时时限起作用的条件比较多,且超时的时间计算点较复杂。该值一般就使用默认值即可。
使用 Spring 的事务注解管理事务
@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 方法上
实现注解的事务步骤
1、声明事务管理器
<!--声明事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="myDataSource"/>
</bean>
2、开启注解驱动
<!--选http://www.springframework.org/schema/tx-->
<tx:annotation-driven transaction-manager="transactionManager"/>
3、@Transactional注解
@Transactional(
propagation = Propagation.REQUIRED,
isolation = Isolation.DEFAULT,
readOnly = false, timeout = 20,
rollbackFor =
{NullPointerException.class,NotEnougthException.class})
public void buy(Integer goodsId, Integer num) { }
使用 AspectJ 的 AOP 配置管理事务
1、加入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
2、在容器中添加事务管理器
<!--声明事务管理器-->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="myDataSource"/>
</bean>
3、配置事务通知
<tx:advice id="buyAdvice" transaction-manager="transactionManager">
<tx:attributes>
<tx:method name="buy" propagation="REQUIRED" isolation="DEFAULT" rollback-for="java.lang.NullPointerException"/>
<tx:method name="*" propagation="SUPPORTS"/>
<!--...-->
</tx:attributes>
</tx:advice>
4、配置增强器
<!--声明切入点表达式: 表示那些包中的类,类中的方法参与事务-->
<aop:config>
<!--声明切入点表达式-->
<aop:pointcut id="servicePointcut" expression="execution(* com.why.*(..))" />
<!--关联切入点表达式和事务通知-->
<aop:advisor advice-ref="buyAdvice" pointcut-ref="servicePointcut" />
</aop:config>
Spring-Web项目
在 Web 项目中使用 Spring 框架,首先要解决在 web 层(这里指Servlet)中获取到 Spring 容器的问题。只要在 web 层获取到了 Spring 容器,便可从容器中获取到 Service 对象。
1、创建web项目
2、将整合mybatis项目中以下内容复制到当前项目中:
(1) Service 层、Dao 层全部代码
(2) 配置文件 applicationContext.xml 及 jdbc.properties,mybatis.xml
(3) pom.xml
3、在pom.xml文件中添加下面依赖
<!-- servlet依赖 -->
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>javax.servlet-api</artifactId>
<version>3.1.0</version>
<scope>provided</scope>
</dependency>
<!-- jsp依赖 -->
<dependency>
<groupId>javax.servlet.jsp</groupId>
<artifactId>jsp-api</artifactId>
<version>2.2.1-b03</version>
<scope>provided</scope>
</dependency>
4、web.xml 注册 Servlet
<servlet>
<servlet-name>myServlet</servlet-name>
<servlet-class>com.why.controller.MyServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>myServlet</servlet-name>
<url-pattern>/test</url-pattern>
</servlet-mapping>
5、定义Servlet
public class MyServlet extends HttpServlet {
@Override
protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
//接收请求参数
String stuName = request.getParameter("name");
String stuAge = request.getParameter("age");
//创建spring容器,获取Service对象
String configLocation = "applicationContext.xml";
ApplicationContext ac = new ClassPathXmlApplicationContext(configLocation);
System.out.println(ac);
StudentService studentService = (StudentService)ac.getBean("studentService");
//添加对象
Student student = new Student();
student.setName(stuName);
student.setAge(Integer.parseInt(stuAge));
studentService.addStudent(student);
//显示处理结果的jsp
request.getRequestDispatcher("/result.jsp").forward(request,response);
}
}
6、定义index和result页面
<!--index页面-->
<body>
<form action="/test" method="post">
姓名:<input type="text" name="name"><br>
年龄:<input type="text" name="age"><br>
<input type="submit" value="注册">
</form>
</body>
<!--result页面-->
<body>
注册成功
</body>
7、配置tomcat并且启动
以上程序存在的问题:表单提交三次 我们发现就new了3个srping容器。但是对于一个应用来说,只需要一个 Spring 容器即可。所以,将 Spring 容器的创建语句放在 Servlet 的 doGet()或 doPost()方法中是有问题的。
10-Mar-2022 20:35:56.445 淇℃伅 [http-nio-8080-exec-7] com.alibaba.druid.support.logging.JakartaCommonsLoggingImpl.info {dataSource-4} inited
org.springframework.context.support.ClassPathXmlApplicationContext@187ec5a8, started on Thu Mar 10 20:35:56 CST 2022
10-Mar-2022 20:35:58.397 淇℃伅 [http-nio-8080-exec-8] com.alibaba.druid.support.logging.JakartaCommonsLoggingImpl.info {dataSource-5} inited
org.springframework.context.support.ClassPathXmlApplicationContext@da69797, started on Thu Mar 10 20:35:58 CST 2022
10-Mar-2022 20:36:00.513 淇℃伅 [http-nio-8080-exec-9] com.alibaba.druid.support.logging.JakartaCommonsLoggingImpl.info {dataSource-6} inited
org.springframework.context.support.ClassPathXmlApplicationContext@34a5a436, started on Thu Mar 10 20:36:00 CST 2022
那么如何保证创建容器时唯一的呢?
使用Spring 的监听器 ContextLoaderListener
对于 Web 应用来说,ServletContext 对象是唯一的,一个 Web 应用,只有一个 ServletContext 对象,该对象是在 Web 应用装载时初始化的。若将Spring 容器的创建时机,放在 ServletContext 初始化时,就可以保证 Spring容器的创建只会执行一次,也就保证了 Spring 容器在整个应用中的唯一性。
1、引入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
<version>5.2.5.RELEASE</version>
</dependency>
2、注册监听器 ContextLoaderListener
若要在 ServletContext 初始化时创建 Spring 容器,就需要使用监听器接口 ServletContextListener 对 ServletContext 进行监听。在 web.xml 中注册该监听器。
<!--自定义容器使用的配置文件路径、
context-param:叫做上下文参数,给监听器提供参数的
如果不想把applicationContext.xml放在WEB-INF下就必须配置这个-->
<context-param>
<!--固定写法-->
<param-name>contextConfigLocation</param-name>
<!--配置文件路径-->
<param-value>classpath:applicationContext.xml</param-value>
</context-param>
<!--默认监听器:创建容器对象时,读取配置文件: [/WEB-INF/applicationContext.xml]-->
<listener>
<!-- 创建容器对象,并将容器对象放入到了 ServletContext的空间中。 -->
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
3、获取 Spring 容器对象
在 Servlet 中获取容器对象的常用方式有两种:
直接从 ServletContext 中获取
String attr = WebApplicationContext.ROOT_WEB_APPLICATION_CONTEXT_ATTRIBUTE;
WebApplicationContext ac = (WebApplicationContext)this.getServletContext().getAttribute(attr);
//或者这种
//WebApplicationContext ac = (WebApplicationContext)request.getServletContext().getAttribute(attr);
通过 WebApplicationContextUtils 获取
WebApplicationContext ac = WebApplicationContextUtils.getRequiredWebApplicationContext(getServletContext());
再提交三次表单
Root WebApplicationContext, started on Thu Mar 10 21:03:33 CST 2022
Root WebApplicationContext, started on Thu Mar 10 21:03:33 CST 2022
Root WebApplicationContext, started on Thu Mar 10 21:03:33 CST 2022