Spring Framework
1. 介绍
1.1 官方网站
spring.io
1.2 使用版本 5.3.22
推荐使用5.3.x,与最新版差异不大,但比较稳定,使用较多
1.3 概述
Spring
是一个轻量级开源框架
轻量级:非侵入式,基于Spring框架开发,可不依赖Spring的API
非侵入式:不依赖某个接口才能实现功能,如要实现过滤器侵入式就必须实现FIlter接口
作用:管理java对象
MVC
持久层:jdbc->mybatis
控制层:servlet->SpringMVC->SpringBoot
业务层:业务处理,事务
2. 基础使用
-
创建空工程 spring-study
-
作为父工程,将其变为管理项目
- pom添加配置
<packaging>pom</packaging>
- pom添加配置
-
删除src目录
-
引入依赖
<packaging>pom</packaging> <dependencies> <dependency> <groupId>org.springframework</groupId> <artifactId>spring-context</artifactId> <version>5.3.22</version> </dependency> <dependency> <groupId>junit</groupId> <artifactId>junit</artifactId> <version>4.13.2</version> <scope>test</scope> </dependency> </dependencies>
-
-
创建子模块Moudle, Maven项目spring01-ioc,会自动继承父项目spring-study,自动继承父项目的依赖
-
创建com.example.bean.HelloWorld
-
public class HelloWorld{ private String hello(){ System.out.println("Hello World !"); return "hello world !!!" } }
-
-
创建applicationContext.xml (resources目录下)
-
<?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"> <bean id="helloworld" class="com.example.bean.HelloWorld"/> </beans>
-
-
测试类
-
public class IOTests{ @Test public void test(){ //1.初始化IOC容器 //ClassPathXmlApplicationContext从类路径下读取配置文件 ApplicationContext ioc = new ClassPathXmlApplicationContext("applicationContext.xml"); //2.根据对象名从IOC容器获取对象 HelloWorld helloword = (HelloWorld) ioc.getBean("helloworld"); System.out.println(helloworld.show()); } }
-
3 IOC容器
3.1 控制反转
**控制反转:**将开发者对java对象的控制权交给Spring管理的过程,配置Bean
IOC容器:存放java对象的容器
3.2 依赖注入DI
**类级别依赖:**如果一个类中定义了另一个类的属性,则当前类对其属性类产生了依赖关系
**依赖注入:**为依赖类型的变量赋值,前提是控制反转。只有将对象交给Spring,才可以让其帮助创建对象 (控制反转) 为对象属性赋值 (依赖注入)
3.3 Bean
3.3.1 获取Bean
Object getBean(String beanName);
T getBean(String beanName, Class<T> beanClass);
要求容器中只存在一个beanClass类型的bean(没有主bean时)T getBean(Class<T> beanClass);
3.3.2 Bean标签
- id
- String类型
- 唯一标识,没有配置name属性时,name默认赋值和id相同
- name
- String类型
- 一个bean可以有多个名字
- primary
- boolean类型
- 标识当前bean为主bean,存在主bean时,使用getBean(Class class), 允许存在多个同类型bean,但只会获得主bean
- scope
- 设置为singleton时,则此bean为单例的,ioc容器只存在一个对象,ioc初始化时创建单例bean
- 设置为prototype时,原型bean,每次获取时才创建,且每次创建的是新对象
- 默认为singleton
- init-method
- 值为在类中自定义的初始化方法名
- 对象初始化时调用
- destroy-method
- 值为在类中自定义的方法名
- 对象销毁之前会执行自定义的方法
- depends-on
- 值为其他bean
- 创建当前bean时会优先创建其依赖的bean再回头创建此bean
3.3.3 Bean的作用域
Bean的使用范围
- singleton
- 单例,多次获取此bean,返回同一个对象。
- ioc容器初始化时创建一个此类型对象
- prototype
- 原型bean,每次获取时才创建,且每次创建的是新对象
- ioc初始化时不会创建此类型对象
- request
- 一次请求,基于web项目的bean的作用
- session
- 一次会话,基于web项目的bean的作用
3.3.4 Bean的生命周期
- 通过构造方法或工厂方法创建Bean对象
- 属性赋值
- 调用Bean对象中提供的自定义的初始化方法(init-method)
- Bean对象使用
- 当容器关闭之前,会消耗容器中所有Bean对象,销毁之前先调用对象的destroy-method方法
3.3.5 Bean的创建方式
-
静态工厂配置Bean
-
<Bean id="cat" class="com.example.factory.CatFactory" factory-method="getCatInstance()"/>
-
public CatFactory{ public static Cat getCatInstance(){ return new Cat(); } }
-
Cat cat = ioc.getBean("cat", Cat.class);
-
可将Cat对象直接交给容器管理
-
-
实例工厂配置Bean
-
<bean id="dogFactory" class"com.example.factory.DogFactory"/> <bean id="dog" factory-bean="dogFactory" factory-method="getDogInstance"/>
-
public DogFactory{ public Dog getDogInstance(){ return new Dog(); } }
-
Dog dog = ioc.getBean("dog", Dog.class);
-
将对象交给容器管理
ioc中没有静态工厂实例,但是有实例工厂实例
-
-
FactoryBean方式配置
-
<bean id="bird" class="com.example.factory.BirdFactoryBean"/>
-
public class BirdFactoryBean implements FactoryBean<Cat>{ //获取Bean对象,Spring判定bean类型为FactoryBean则会自动调用其getObject(),来返回对象 @Override public Bird getObject() throws Exception{ return new Bird(); } @Override public Class<?> getObjectType(){ return Bird.class; } @Override public boolean isSingleton(){ return true; } }
-
Object obj = ioc.getBean("bird");
-
3.3.6 Bean属性配置
又叫依赖注入
构造方法注入
public class Car{
private String name;
private String type;
private Double price;
private Integer doors;
public Car(String name, String type, Double price, Integer doors){
}
}
<!--可用index=“0”或name="price"或type="Integer"等来指定赋值,否则必须按顺序填属性-->
<bean id="car" class="com.example.bean.Car">
<constructor-arg value="大众"/>
<constructor-arg value="轿车"/>
<constructor-arg value="9000.0"/>
<constructor-arg value="4"/>
<!--<constructor-arg value="大众" index="0"/>
<constructor-arg value="大众" name="name"-->
</bean>
Car car = ioc.getBean("car", Car.class);
set注入
-
普通类型
-
<bean id="student" class="com.example.bean.Student"> <!--name对应的是setName()--> <property name="name" value="zhangsan"/> <property name="birthday" value="yyyy/MM/dd"/> <!--引用其他bean--> <property name="dog" ref="xiugou"/> </bean> <bean id="xiugou" class="com.example.bean.Doa"/>
-
-
数组
-
<property name="course"> <array> <!-- 简单数据类型 <value>值1</value> <value>值2</value> --> <!--引用自定义数据类型--> <ref>dog</ref> </array> </property>
-
-
list集合
-
<property name="list"> <list> <value>aaa</value> </list> </property>
-
-
set集合
-
<property name="set"> <set> <value>12</value> </set> </property>
-
-
map集合
-
<property name="map"> <map> <!--简单类型--> <entry key="" value=""/> <!--自定义类型--> <entry key-ref="" value-ref=""/> </map> </property>
-
-
properties类型
-
<property name="props"> <props> <prop key="jdbc.driver">com.mysql.jdbc.Driver</prop> </props> </property>
-
-
内部bean
-
<property name="dog"> <bean class="com.example.bean.Dog"/> </property>
-
Autowired自动装配
autowire
- no:不自动装配,不注入
- byName:通过属性名称在ioc容器中找到同名bean进行装配
- byType:通过属性类型在ioc容器中找到同类型bean进行装配,没有则不注入,发现多个此类型bean则报错,除非有主bean
- default:默认值,参照beans根标签中的default-autowire
<!--会把cat属性注入,但找不到name为dog的bean则dog不注入-->
<bean id="student" class="com.example.bean.Student" autowire="byName"/>
<bean id="xiugou" class="com.example.bean.Dog"/>
<bean id="cat" class="com.example.bean.Cat"/>
public class Student{
private Dog dog;
private Cat cat;
public void setDog(Dog dog){
this.dog = dog;
}
public void setCat(Cat cat){
this.cat = cat;
}
}
不能既根据名称又根据类型匹配,所以灵活性较差
3.3.7 注解配置Bean
扫描组件(配置spring.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: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
http://www.springframework.org/schema/context/spring-context.xsd">
<!-- 扫描注解,扫描此包下所有类及子包下的类的注解 -->
<context:component-scan base-package="com.example.service" />
<!--BookServiceImpl注入到IOC容器中-->
<!-- 配置事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 注入数据库连接池 -->
<property name="dataSource" ref="dataSource" />
</bean>
</beans>
@Component
@Component注解只能作用在类上,将当前类添加到ioc容器
配置项vlaue,创建的对象的名称,默认为类名首字母小写
@Component
public class Person{}
Bean对象注解
@Component
@Controller 控制层
@Service 业务层
@Repository 持久层
上述注解功能相同,都是将当前类(不能是接口)交给ioc容器管理,只是用处不同,配置注解则无需在xml中配置Bean
@Value(value=“”)
依赖注入的注解方式,配置在属性上,无需setter即可在初始化时为属性赋值,当()中只有value="值"时,“value="可省略,为@Value(“值”)
@Component
public class Person{
@Value("zhangsan")
private String name;
}
@Autowired
实现自动装配,到ioc中查找对象给当前属性赋值,没找到则为null
装配过程:先根据类型匹配,匹配失败(同类型有很多),再根据name匹配,name匹配失败则抛异常
配置项: required,ioc中必须有此类型对象,否则报错
设置required=false,表示ioc中有则装配,没有为null
@Component
public class Person{
@Autowired
//自动装配时,类型匹配失败,dog名称匹配失败,则根据dog1名称匹配
@Qualifier("dog1")
private Dog dog;
@Autowired(required)
private Cat cat;
}
@Primary
标明当前Bean为主Bean
@Cmoponent("dog1")
@Primary //dog1为主bean,自动装配根据类型匹配同类有多个bean时,则匹配主bean
public class Dog{}
<bean id="dog2" class="com.example.bean.Dog"/>
@Resource 与 @Autowired
@Resource,java中自定的注解,spring扩展了此注解的解析,实现了自动装配,先根据名称匹配,再根据类型匹配
@Autowired,spring注解,现根据类型匹配,再根据名称匹配
4 AOP
- 需求:记录数据变更操作日志
- 直接解决:在业务处理前后加入日志操作
- 问题:
- 与业务无关的日志操作与业务逻辑混杂在一起,造成代码臃肿膨胀
- 代码散乱:次要业务逻辑代码附属在各个主业务逻辑代码中,并不是在一起的
代理模式
4.1 静态代理
- 静态代理类和目标类必须同时实现同一个一个父接口
- 编写静态代理功能
- 调用目标类
//父接口
public interface StudentDao{
int insert(String name);
}
//实现类
public class StudentDaoImpl implements StudentDao{
@Override
public int insert(String name){
System.out.println();
}
}
//静态代理类
public class StudentDaoProxy implements StudentDao{
private StudentDao studentDao;
public StudentDaoProxy(StudentDao studentDao){
this.studentDao = studentDao;
}
@Override
public int insert(String name){
System.out.println("插入之前");
int count = studentDao.insert(name);
System.out.println("插入完成");
return count;
}
}
//测试类
public class Main{
public static void main(String[] args){
StudentDao studentDao = new StudentDaoImpl();
StudentDao proxy = new StudentDaoProxy(studentDao);
int count = proxy.insert("zhangsan");
}
}
缺点:静态代理仅能服务一个目标,业务类过多时,代理类也会过多,造成代理类膨胀
4.2 动态代理
JDK动态代理
JDK 动态代理是基于拦截器和反射实现的,JDK集成,无需依赖jar包
目标类必须有父接口
//父接口
public interface StudentDao{
int insert(String name);
}
//实现类
public class StudentDaoImpl implements StudentDao{
@Override
public int insert(String name){
System.out.println();
}
}
//动态代理类
public class StudentDaoProxy implements InvocationHandler {
private Object target;
public StudentDaoProxy(Object target){
this.target = target;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("代理类正在执行");
System.out.println("前置通知");
int count = (int) method.invoke(target, args);
System.out.println("后置通知");
return count;
}
}
//测试类
public class Main{
public static void main(String[] args){
StudentDao studentDao = new StudentDaoImpl();
//创建代理类对象
StudentDaoProxy handler = new StudentDaoProxy(studentDao);
//创建代理对象,StudentDao类型
StudentDao proxy = Proxy.newProxyInstance(StudentDao.class.getClassLoader(), StudentDao.class.getInterface(), handler);
int count = proxy.insert("zhangsan");
}
}
cglib动态代理
cglib动态代理是动态创建一个目标类的子类,由子类实现所有目标类中非final类,执行中在子类中拦截所有父类方法的调用,顺势织入前后操作;只需要目标类,不需要目标类实现接口;Spring中已经实现了cglib,不导入依赖jar包也可使用
//目标类
public class UserDao {
public String getName(String name) {
System.out.println("name是"+name);
return name;
}
public int getPrice(int account) {
System.out.println("account是"+account);
return account*10;
}
}
public class UserProxy implements MethodInterceptor {
@Override
public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable {
System.out.println("cglib动态代理类执行中");
System.out.println("前置通知");
System.out.println(method.getName());
Object object = methodProxy.invokeSuper(o, objects);
System.out.println("后置处理");
return object;
}
public Object getProxy(Class targetClass){
Enhancer enhancer = new Enhancer();
enhancer.setSuperclass(targetClass);
enhancer.setCallback(new UserProxy());
return enhancer.create();
}
}
//测试
@Test
public void test03(){
UserDao userDao = (UserDao) new UserProxy().getProxy(UserDao.class);
System.out.println(userDao.getName("zhangsan"));
System.out.println(userDao.getPrice(32));
}
相同点:都是利用反射实现
不同:
jdk:创建的代理对象与目标对象是兄弟关系,因为他们实现同一个父接口,要求:service必须为 接口+实现类形式
cglib:创建的代理对象是父子关系,因为创建代理对象时需要设置父类型,而父类型就是目标对象的类型
spring中实现了两种动态代理方式
4.3 切面编程
切面:将各个模块中相同的与业务无关的部分提取出来形成一个单独的模块,当需要此功能时,将其切入模块实现此功能
通知/增强:切面的功能,前后通知等
连接点:被拦截的点,spring中指的是方法,因为spring只支持方法类型的连接点,某类中某方法中的某位置
切点/切入点:某一批连接点的统称
织入:把Advice应用到目标类的过程
通知类型:
- 前置通知:方法执行前
- 后置通知:方法执行后
- 返回通知:方法返回数据之后,方法正常结束
- 异常通知:方法执行抛出异常后,方法异常结束
- 环绕通知:综合通知,方法何处都能用
注意:返回和异常通知不会同时执行,但后置通知无论方法是否成功结束都会执行
4.3.1 引入依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.22</version>
</dependency>
4.3.2 切面类
//无需实现任何接口
//无需使用反射机制
public class LogAspect{
//方法名是任意的,配置时指定方法名即可
//普通通知方法可设置形参JoinPoint joinPoint来获取执行方法参数
//前置通知
public void beforeMethod(){
System.our.println("前");
}
//后置通知
public void afterMethod(){
System.our.println("后");
}
//返回通知,可以设置形参获取目标方法的返回值,不设置形参就是不获取返回值
public void returnMethod(Object result){
System.our.println("返回通知..."+result);
}
//异常通知,发生异常时会执行此通知
public void throwMethod(Exception e){
System.our.println("异常通知..."+e);
}
//环绕通知,通过拦截目标方法,手动调用来实现在执行在各个位置的方法
public void aroundMethod(ProceedingJoinPoint pjp){
System.our.println("环绕通知,前...");
try{
Object obj = pjp.proceed(); //执行目标方法
System.our.println("环绕通知,返回..."+obj);
} catch(Throwable e){
System.our.println("环绕通知,异常..."+e);
}
System.our.println("环绕通知,后...");
}
}
4.3.3 配置AOP
<?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"
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/context
https://www.springframework.org/schema/context/spring-context.xsd
http://www.springframework.org/schema/aop
https://www.springframework.org/schema/aop/spring-aop.xsd">
<!--配置Bean-->
<beans>
<bean id="userDao" class=""/>
<bean id="studentDao" class=""/>
<bean id="log" class=""/>
</beans>
<!--配置AOP-->
<aop:config>
<!--公共切面表达式-->
<aop:pointcut id="pointAll" expression="execution(* com.example.dao.impl.*Impl.*(..))"/>
<!--配置切面,ref表示关联的切面类,order表示配置多个切面时的执行顺序优先级,小的优先级高,不配置优先级则按切面的配置顺序决定执行顺序,后置通知执行顺序与此相反-->
<aop:aspect ref="log" order="1">
<!--配置通知-->
<!--aop:before前置通知
method 切面中的方法
pointcut 切入点,切点表达式
execution():
切点表达式:通过表达式匹配应用到那些方法上
1.单个方法:execution(方法访问修饰符 方法返回值类型 方法所在包名.类名.方法名(参数类型,)
2.多个方法:execution(* *.*Impl.*(..)
*匹配任意修饰符,返回值,包,任意Impl结尾的类,方法,..匹配所有参数类型,数量0个或多个)-->
<aop:before method="beforeMethod" pointcut="execution()"/>
<aop:after method="beforeMethod" pointcut="execution()"/>
<!--返回通知,如果通知方法设置接收返回值,则需设置returning属性绑定形参,若实际方法并没有返回值,则result接收为null-->
<aop:after-returning method="beforeMethod" pointcut="execution()" returning="result"/>
<aop:throwing method="throwMethod" pointcut="execution()" throwing="e"/>
</aop:aspect>
<aop:around method="aroundMethod" point-ref="pointAll"/>
</aop:aspect>
<!--切面2-->
<aop:aspect ref="" order="2"></aop:aspect>
</aop:config>
4.3.4 注解配置切面
<!--组件扫描-->
<context:component-scan base-package="com.example"/>
<!--配置开启AOP注解-->
<aop:aspectj-autoproxy/>
@Component
@Aspect
@Order(1)
public class LogAspect{
//前置通知
@Before("execution()")
public void beforeMethod(){}
@After("execution()")
public void afterMethod(){}
//返回通知,可以设置形参获取目标方法的返回值,不设置形参就是不获取返回值
@AfterReturning(value="execution()", returning="result")
public void returnMethod(Object result){}
//异常通知,发生异常时会执行此通知
@AfterThrowing(value="execution()", throwing="e")
public void throwMethod(Exception e){}
//环绕通知,通过拦截目标方法,手动调用来实现在执行在各个位置的方法
@Around
public void aroundMethod(ProceedingJoinPoint pjp){}
}
5 Spring整合Mybatis
5.1 pom.xml
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.3.22</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.29</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.10</version>
</dependency>
<!--mybatis与spring整合的依赖包-->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>2.0.7</version>
</dependency>
<!--分页-->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.3.0</version>
</dependency>
<!--spring相关依赖-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.3.4</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aspects</artifactId>
<version>5.3.4</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
5.2 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:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans
http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<!--配置组件扫描-->
<context:component-scan base-package="com.example"/>
<!-- 配置整合mybatis -->
<!-- 1.关联数据库文件 -->
<context:property-placeholder location="classpath:database.properties"/>
<!-- 2.数据库连接池 -->
<!--数据库连接池
dbcp 半自动化操作 不能自动连接
c3p0 自动化操作(自动的加载配置文件 并且设置到对象里面)
-->
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 配置连接池属性 -->
<property name="driverClass" value="${jdbc.mysql.driver}"/>
<property name="jdbcUrl" value="${jdbc.mysql.url}"/>
<property name="user" value="${jdbc.mysql.username}"/>
<property name="password" value="${jdbc.mysql.password}"/>
<!-- c3p0连接池的私有属性 -->
<property name="maxPoolSize" value="30"/>
<property name="minPoolSize" value="10"/>
<!-- 关闭连接后不自动commit -->
<property name="autoCommitOnClose" value="false"/>
<!-- 获取连接超时时间 -->
<property name="checkoutTimeout" value="10000"/>
<!-- 当获取连接失败重试次数 -->
<property name="acquireRetryAttempts" value="2"/>
</bean>
<!-- 3.配置SqlSessionFactory对象 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 注入数据库连接池 -->
<property name="dataSource" ref="dataSource"/>
<!-- 配置MyBaties全局配置文件:mybatis-config.xml -->
<property name="configLocation" value="classpath:mybatis-config.xml"/>
<!--加载mapper映射文件,通过通配符实现批量加载-->
<property name="mapperLocations" value="classpath:mapper/*.xml"/>
</bean>
<!-- 4.配置扫描Dao接口包,动态实现Dao接口注入到spring容器中 -->
<!--解释 :https://www.cnblogs.com/jpfss/p/7799806.html-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<!-- 注入sqlSessionFactory -->
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
<!-- 给出需要扫描Dao接口包 -->
<property name="basePackage" value="com.dorm.dao"/>
</bean>
</beans>
5.3 mybatis-config.xml
<?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>
<!--打印sql语句-->
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<!--entities实体类配置别名-->
<typeAliases>
<package name="com.example.entities"/>
</typeAliases>
<!--分页插件-->
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor" />
</plugins>
</configuration>
5.4 database.properties
jdbc.mysql.driver=com.mysql.cj.jdbc.Driver
jdbc.mysql.url=jdbc:mysql://localhost:3306/dormitory?useUnicode=true&characterEncoding=utf8serverTimezone=UTC&useSSL=false&useUnicode=true&characterEncoding=utf8
jdbc.mysql.username=root
jdbc.mysql.password=root
5.5 mapper.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.example.dao.Dao">
<select id="" parameterType="" resultType=""></select>
</mapper
6 Spring中的事务
6.1 概述
事务是一系列动作(执行SQL),一个事务是一个独立不可拆分的工作单元,事务执行的所有SQL要么同时成功要么同时失败
事务特性
- 原子性A
- 一致性C
- 隔离性I
- 持久性D
编程式事务
connection.setAutocommint(false);
try{
//执行多个sql
//手动提交
connection.commit();
}catch (Exception e){
connection.rollback();
}
}
声明式事务
利用AOP实现
JDBC、Mybatis事务管理器
6.2 Xml配置文件配置事务
1. 命名空间
<?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"
xmlns:aop="http://www.springframework.org/schema/aop"
xmlns:tx="http://www.springframework.org/schema/tx"
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
http://www.springframework.org/schema/aop https://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/tx https://www.springframework.org/schema/tx/spring-tx.xsd">
2. 事务管理器
<!--分三步,配置事务管理器,配置事务属性增强,配置事务切面-->
<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 配置数据源 指定要管理那个数据源的事务 -->
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 配置事务增强 -->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!-- 配置增强规则 -->
<tx:attributes>
<!--
name : 进行数据库操作方法的名称 add* 表示 add开头的方法 * 指代任意字符
propagation : 事务传播性 面试重点
read-only : 只读事务 默认 false
rollback-for : 指定回滚的异常 默认是 RunTimeException 下的异常会自动回滚
no-rollback-for : 不回滚的异常
timeout : 事务的超时时间
isolation : 事务隔离级别 面试重点
1. 读未提交
2. 读已提交
3. 可重复读
4. 串行化
-->
<tx:method name="add*" rollback-for="Exception"/>
<tx:method name="insert*" rollback-for="Exception" />
<tx:method name="update*" rollback-for="Exception" />
<tx:method name="delete*" rollback-for="Exception" />
<tx:method name="*" read-only="true" />
</tx:attributes>
</tx:advice>
<!-- 配置切面 -->
<aop:config>
<!-- 注意一般切点在 service -->
<aop:pointcut id="mypoint" expression="execution(* com.exam.service.impl.*.*(..))"/>
<!-- 织入增强 -->
<aop:advisor advice-ref="txAdvice" pointcut-ref="mypoint" />
</aop:config>
6.3 注解配置事务
三步:
- 配置事务处理器
- 开启注解事务
- 给指定方法或类配置事务注解@Transactional
<!-- 事务管理器 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<!-- 配置数据源 指定要管理那个数据源的事务 -->
<property name="dataSource" ref="dataSource" />
</bean>
<!--开启事务注解-->
<tx:annotation-driven transaction-manager="transactionManager" />
//在类上配置,则类中所有方法都被配置此事务类型
@Service
//@Transactional(rollbackFor = Exception.class)
public class StudentServiceImpl{
public List<Student> getAll(){}
//方法上配置的事务只在此方法上生效
@Transactional(rollbackFor = Exception.class, propagation = Propagation.NESTED)
public void add(){}
}
6.4 事务的传播性
Propagation
- PROPAGATION_REQUIRED–支持当前事务,如果当前没有事务,就新建一个事务。这是最常见的选择。
- PROPAGATION_SUPPORTS–支持当前事务,如果当前没有事务,就以非事务方式执行。
- PROPAGATION_MANDATORY–支持当前事务,如果当前没有事务,就抛出异常。
- PROPAGATION_REQUIRES_NEW–新建事务,如果当前存在事务,把当前事务挂起。
- PROPAGATION_NOT_SUPPORTED–以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。
- PROPAGATION_NEVER–以非事务方式执行,如果当前存在事务,则抛出异常。
6.5 事务的实现原理
spring对于事务的实现:
对于某个内部方法配置事务的Bean,spring会实现生成一个其子类代理类,将代理类置于事务中,当执行到配置事务的方法时,则调用代理类的方法来实现事务,而普通方法则调用Bean中的方法,则没有事务。可见一个类只对应了一个事务。
出现问题
- 如果调用了Bean中的普通方法,而此方法中调用了此类中 的其他配置事务的方法,则由于是通过Bean来间接调用的事务方法,则此方法无法与代理类产生联系,则无法启动事务;
- 如果执行代理子类中的事务方法时,此方法调用了本类中的其他配置Propagation属性为REQUIRES_NEW的事务方法,由于处于同一个类中,只存在一个事务,则无法创建新事务,即REQUIRES_NEW失效
解决方法
- 用@Autowired 注入自己 ,然后在用注入的bean,调用事务相关方法
- 获取代理类AopContext.currentProxy(),通过代理类调用事务相关方法
- 将事务方法放到另一个类中