SSM
SSM所需的前置知识为Javaweb,同时也是SpringBoot所需前置知识
初识Spring
学习重点:IoC/DI、AOP、事务处理(基于AOP产生)、MyBatis
1. Spring核心容器
-
IOC(Inversion of Control)控制反转
- 使用对象时,由主动new对象转换为由外部提供对象,此过程中对象创建控制权由程序转移到外部,此思想称为控制反转;
- Spring技术对IoC思想进行了实现,提供了一个IoC容器,用来充当IoC思想中的“外部”;
- Ioc容器负责对象的创建、初始化等一系列工作,被创建或管理的对象在IoC容器中被统称为bean。
-
DI(Dependency Injection)依赖注入
- 在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入
1.1 入门案例
1.1.1 IoC入门案例
-
管理什么?
Service与Dao
-
如何将被管理的对象告知IoC容器?
配置(将bean注册到application.xml里面,通过xml管理IoC容器)
-
被管理的对象交给IoC容器,如何获取到IoC容器?
一般通过ApplicationContext接口中的实现类ClassPathXmlApplicationContext()
-
IoC容器得到后,如何从容器中获取bean?
通过ApplicationContext接口中的getBean方法
-
使用Spring需要在哪导入哪些坐标?
在pom.xml的depencies标签中导入坐标
pom.xml
<!--步骤1:导入Spring坐标spring-context-->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
// 步骤2:定义Spring管理的类(接口)
public interface BookDao{
public void save();
}
public interface BookService{
public void save();
}
// 实现接口
public class BookDaoImpl implements BookDao{
public void save(){
System.out.println("book dao save...");
}
}
public class BookServiceImpl implements BookService{
private BookDao bookDao = new BookDaoImpl();
public void save(){
System.out.println("book service save...");
bookDao.save();
}
}
applicationContext.xml
<!--步骤3:创建Spring配置文件,配置Spring管理的bean
bean定义时id属性在同一个上下文中不能重复
-->
<?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="bookDao" class="com.wang.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.wang.service.impl.BookServiceImpl"/>
</beans >
Applicaion.java
public class Application{
public static void main(String[] args){
// 步骤4:初始化IoC容器(Spring核心容器/Spring容器),并通过容器获取bean
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao)ctx.getBean("bookDao");
bookDao.save();
BookService bookService = (BookService)ctx.getBean("bookService");
bookService.save();
}
}
1.1.2 DI入门案例
-
基于IoC管理bean
-
Service中使用new形式创建的Dao对象是否保留?
不保留,只保留对对象变量的声明
-
Service中需要的Dao对象如何进入到Service中?
使用依赖注入,同时bookService需要提供setBookDao()方法
-
Service与Dao间的关系如何描述?
配置关系,一个业务层可以控制很多数据层
//步骤1:将业务层中的new的DAO对象删除,使用set方法实现
public class BookServiceImpl implements BookService{
private BookDao;
public void save(){
bookDao.save();
}
//提供对应的set方法
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
System.out.print("提供了对应的set方法");
}
}
applicationContext.xml
<!--步骤2:配置bean进行依赖注入,使用property标签注册一个bean属性-->
<bean id="bookDao" class="com.wang.dao.impl.BookDaoImpl"/>
<bean id="bookService" class="com.wang.service.impl.BookServiceImpl">
<!--配置dao与service的关系
property标签标识配置当前bean的属性
name属性表示配置类中哪一个具体的属性
ref属性表示参照哪一个bean,既可以引用id也可以引用name-->
<property name="bookDao" ref="bookDao"/>
</bean>
1.2 bean的配置
1.2.1 bean基础配置
类别 | 描述 |
---|---|
名称 | bean |
类型 | 标签 |
所属 | beans标签 |
功能 | 定义Spring核心容器管理的对象 |
格式 | <bean/> <bean></bean> |
属性列表 | id:bean的id,使用容器可以通过id值获取对应的bean,在一个容器中id值唯一 |
范例 | <bean id=“bookDao” class=“com.wang.dao.impl.bookDaoImpl”/> <bean id=“bookService” class=“com.wang.dao.impl.bookServiceImpl”></bean> |
1.2.2 bean别名配置
类型 | 描述 |
---|---|
名称 | name |
类型 | 属性 |
所属 | bean标签 |
功能 | 定义bean的别名,可定义多个,使用逗号( , )、分号( ; )、空格( )分隔 |
范例 | <bean id=“bookDao” name=“dao dao1” class=“com.wang.dao.impl.bookDaoImpl”/> <bean id=“bookService” name=“service service1” class=“com.wang.dao.impl.bookServiceImpl”></bean> |
<bean id="bookDao" name="dao dao1" class="com.wang.dao.impl.BookDaoImpl"/>
<bean id="bookService" name="service service1" class="com.wang.service.impl.BookServiceImpl">
<!--配置dao与service的关系-->
<property name="bookDao" ref="dao"/>
</bean>
application.java
public static void main(String[] args) {
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookService bookService = (BookService) context.getBean("service");
// 把getBean()中名称换为bean的别名也是可以的
bookService.save();
}
注意:无论是通过id还是name获取获取bean,无法获取时将抛出NoSuchBeanDefinitionException
1.2.3 bean作用范围
类型 | 描述 |
---|---|
名称 | scope |
类型 | 属性 |
所属 | bean标签 |
功能 | 定义bean的作用范围,可选值:singleton(单例,默认);prototype(非单例) |
范例 | <bean id=“bookDao” class=“com.wang.dao.impl.bookDaoImpl” scope=“prototype” /> |
<bean id="bookDao" class="com.wang.dao.impl.BookDaoImpl" scope="prototype"/>
- bean默认单例的优势:便于管理复用对象,提高效率
- 不适合交给IoC容器进行管理的bean:封装实体的域对象(有状态的)
1.3 bean的实例化方法
- Bean本质上就是对象,创建bean使用构造方法完成
方式一:构造方法实现bean
// 首先在application中注册bean
// 其次在实现类中提供构造方法用来实例化,若无参构造方法不存在则抛出异常BeanCreationException
public class BookDaoImpl implements BookDao{
private BookDaoImpl(){
System.out.println("Book constructor is running ...");
}
public void save(){
Syustem.out.println("book dao save...");
}
}
// 无论是使用private还是public都能访问到构造方法,原理是利用了反射
applicationContext.xml
<bean id="bookDao" class="com.wang.dao.impl.BookDaoImpl"/>
方法二:静态工厂实现bean
//新建一个静态工厂
public class OrderDaoFactory{
public static OrderDao getOrderDao(){
return new OrderDaoImpl();
}
}
// 实例化对象时getBean()方法调用静态工厂实例化对象
applicationContext.xml
<bean id="orderDao" class="com.wang.factory.OrderDaoFactory" factory-method="getOrderDao"/>
方法三:实例工厂实现bean
// 新建一个实例工厂
public class UserDaoFactory{
public UserDao getUserDao(){
retrun new UserDaoImpl();
}
}
applicationContext.xml
<bean id="userFactory" class="com.wang.factory.UserDaoFactory"/>
<bean id="userDao" factory-bean="userFactory" factory-method="getUserDao" />
方法四:FactoryBean实例化bean
public class UserDaoFactoryBean implements FactoryBean<UserDao>{
// 代替原始实例工厂中创建对象的方法
public UserDao getObject() throws Expection{
return new UserDaoImpl();
}
public Class<?> getObjectType(){
return UserDao.class;
}
}
applicationContext.xml
<bean id="userDao" class="com.wang.factory.UserDaoFactoryBean"/>
1.4 bean的生命周期
- 生命周期:从创建到消亡的完整过程
- bean生命周期:bean从创建到销毁的整体过程
- bean生命周期控制:在bean创建后到销毁前做一些事情
控制bean生命周期的两种方法
方法 | 参数 |
---|---|
配置方法 | init-method destroy-method |
接口方法 | InitializingBean DisposableBean |
1.4.1 配置控制
public class BookDaoImpl implements BookDao{
public void save(){
System.out.print("book dao save...");
}
public void init(){
System.out.print("Bean初始化操作");
}
public void destroy(){
System.out.print("Bean销毁操作");
}
}
<bean id="bookDao" class="com.wang.impl.BookDaoImpl" init-method="init" destroy-method="destroy"/>
执行后发现未调用销毁方式,原因为JVM直接退出,未执行关闭容器ClassPathXmlApplicationContext对象
bean销毁时机:
- 容器关闭前触发bean的销毁
- 关闭容器方式
- 手工关闭容器
ConfigurableApplicationContext接口的close()操作 - 注册关闭钩子,在虚拟机退出前先关闭容器再退出虚拟机
ConfigurableApplicationContext接口registerShutdownHook()操作
- 手工关闭容器
public class Application{
public static void main(String[] args){
/* 不仅可以用ApplicationContext接口下ClassPathXmlApplicationContext实现类型下的close()方法,
还可以用ApplicationContext下的实现类AnnotationConfigApplicationContext()实现下的close()方法*/
ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
BookDao bookDao = (BookDao) context.getBean("bookDao");
// 暴力方法
ctx.close();
// 灵巧方法,设置IoC容器关闭钩子,告诉JVM关闭前注销bean并关闭容器
ctx.registerShutdownHook();
}
}
1.4.2 接口控制
// bean生命周期,标准实现方式,实现InitializingBean, DisposableBean两个接口中的方法
public class BookServiceImpl implements BookService, InitializingBean, DisposableBean {
private BookDao bookDao;
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
// 提供对应的set方法
public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
System.out.println("提供了对应的set方法");
}
@Override
public void destroy() throws Exception {
System.out.println("service destroy");
}
@Override
public void afterPropertiesSet() throws Exception {
System.out.println("service init");
}
}
bean生命周期总结
阶段 | 操作 |
---|---|
初始化容器 | 1. 创建对象(内存分配) 2.执行构造方法 3.执行属性注入(set操作) 4.执行bean初始化方法 |
使用bean | 执行业务操作 |
关闭/销毁容器 | 执行bean销毁方法 |
1.5 依赖注入
- 向一个类中传递数据的方式有两种:普通方法(set方法)、构造方法
- 容器中依赖注入的对象类型有两种:引用类型、简单类型(基本数据类型与String)
因此依赖注入方式分以下四种:
- setter注入
- 简单类型
- 引用类型
- 构造器注入
- 简单类型
- 引用类型
1.5.1 setter注入——引用类型
- 在bean中定义引用类型属性并提供可访问的set方法
public class BookServiceImpl implements BookService(
private BookDao bookDao;
public void setBookDao(BookDao bookDao){
this.bookDao = bookDao;
}
)
- 配置中使用property标签ref属性注入引用类型对象
<bean id="bookService" class="com.wang.service.impl.BookServiceImpl">
<property name="bookDao" ref="bookDao"/>
</bean>
<bean id="bookDao" class="com.wang.dao.impl.BookDaoImpl"/>
1.5.2 Setter注入——简单类型
- 在bean中定义简单类型属性并提供可访问的set方法
public class BookDaoImpl implements BookDao(
private int connectionNum;
private String databaseName;
public void setConnectionNum(int connectionNum){
this.connectionNum = connectionNum;
}
public void setDatabaseName(String databaseName){
this.databaseName = datanaseName;
}
)
- 配置中使用property标签value属性注入基本类型对象
<bean id="bookDao" class="com.wang.dao.impl.BookDaoImpl">
<property name="databaseName" value="MySql"/>
<property name="connectionNum" value="100"/>
</bean>
1.5.3 构造器注入——引用类型
- 在bean中定义引用类型属性并提供可访问的构造方法
public class BookServiceImpl implements BookService(
private BookDao bookDao;
public BookServiceImpl(BookDao bookDao){
this.bookDao = bookDao;
}
)
- 配置中使用constructor-arg标签ref属性注入引用类型对象
<bean id="bookService" class="com.wang.service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
</bean>
<bean id="bookDao" class="com.wang.dao.impl.BookDaoImpl"/>
1.5.4 构造器注入——简单类型
- 在bean中定义简单类型属性并提供可访问的构造方法
public class BookDaoImpl implements BookDao{
private int connectionNumber;
public BookDaoImpl(int connectionNumber){
this.connectionNumber = connectionNumber;
}
}
- 在配置中使用constructor-arg标签value属性注入简单数据类型
<bean id="bookDao" class="com.wang.dao.impl.BookDaoImpl">
<constructor-arg name="connectionNumber" value="7"/>
</bean>
1.5.5 构造器注入——参数适配
- 配置中使用constructor-arg标签type属性设置按形参类型注入
<bean id="bookDao" class="com.wang.dao.impl.BookDaoImpl">
<constructor-arg type="int" value="7"/>
<constructor-arg type="java.lang.String" value="mysql"/>
</bean>
- 配置中使用constructor-arg标签index属性设置按形参位置注入
<bean id="bookDao" class="com.wang.dao.impl.BookDaoImpl">
<constructor-arg index="0" value="10"/>
<constructor-arg index="1" value="MYSQL"/>
</bean>
1.5.6 依赖注入方式选择
- 强制依赖使用构造器进行,使用setter注入有概率未注入导致null对象出现;
- 可选依赖使用setter注入进行,灵活性强;
- Spring框架倡导使用构造器注入,第三方框架内大多数采用构造器注入的形式进行数据初始化,相对严谨;
- 如果有必要可以两者同时使用,使用构造器注入完成强制依赖的注入,使用setter注入完成可选项依赖的注入;
- 实际开发过程中还要根据实际情况分析,如果受控对象没有提供setter方法就必须使用构造器注入;
- 自己开发的模块推荐使用setter注入
1.6 依赖自动装配
- IoC容器根据bean所依赖的资源在容器中自动查找并注入到bean中的过程称之为自动装配;
- 自动装配方式:
- 按类型(常用)
- 按名称
- 按构造方法
- 不使用自动装配
- 配置中使用bean标签autowire属性设置自动装配的类型;
<bean id="bookDao" class="com.wang.dao.impl.BookDaoImpl"/> <bean id="bookService" class="com.wang.service.impl.BookServiceImpl" autowire="byType"/>
- 自动装配用于引用类型依赖注入,不能对简单类型进行操作;
- 使用按类型装配时(byType)必须保障容器中相同类型的bean唯一,推荐使用;
- 使用按名称装配时(byName)必须保障容器中具有指定名称的bean,因变量名和配置耦合,不推荐使用;
- 自动装配优先级低于setter注入和构造器注入,同时出现时自动装配失效
1.7 集合注入
-
数组
<bean id="bookDao" class="com.wang.dao.impl.BookDaoImpl"> <property name="array"> <array> <value>100</value> <value>200</value> <value>300</value> </array> </property> </bean>
-
List
<bean id="bookDao" class="com.wang.dao.impl.BookDaoImpl"> <property name="list"> <list> <value>每天</value> <value>加油</value> <value>学习</value> </list> </property> </bean>
-
Set
<bean id="bookDao" class="com.wang.dao.impl.BookDaoImpl"> <property name="set"> <set> <value>好好</value> <value>工作</value> <value>跑路</value> <value>跑路</value> </set> </property> </bean>
-
Map
<bean id="bookDao" class="com.wang.dao.impl.BookDaoImpl"> <property name="map"> <map> <entry key="country" value="china"></entry> <entry key="province" value="广东"></entry> <entry key="city" value="深圳"></entry> </map> </property> </bean>
-
Properties
<bean id="bookDao" class="com.wang.dao.impl.BookDaoImpl"> <property name="properties"> <props> <prop key="country">中国</prop> <prop key="province">河南</prop> <prop key="city">郑州</prop> </props> </property> </bean>
案例:数据源对象管理
- 导入druid坐标
<dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.10</version> </dependency>
- 配置数据源对象作为spring管理的bean
<!--管理DruidDataSource对象--> <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/iot_platform"/> <property name="username" value="root"/> <property name="password" value="admin##2022"/> </bean>
- 导入c3p0坐标和mysql连接坐标
<dependency> <groupId>c3p0</groupId> <artifactId>c3p0</artifactId> <version>0.9.1.2</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>5.1.6</version> </dependency>
- 配置数据源对象作为spring管理的bean
<!--管理c3p0对象--> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="com.mysql.jdbc.Driver"/> <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/iot_platform"/> <property name="user" value="root"/> <property name="password" value="admin##2022"/> </bean>
1.8 通过properties文件管理数据源对象
- 开启context命名空间
<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 "> </beans>
- 使用context空间加载properties文件
<context:property-placeholder location="jdbc.properties"/>
- 使用属性占位符 ${} 读取properties文件中的属性值
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> <property name="driverClassName" value="${jdbc.Driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </bean>
补充知识点:
- 不加载系统属性
<context:property-placeholder location="jdbc.properties" system-properties-mode="NEVER"/>
- 加载多个properties文件
<context:property-placeholder location="jdbc.properties, jdbc2.properties"/>
- 加载所有properties文件
<context:property-placeholder location="*.properties"/>
- 加载properties文件标准格式
<context:property-placeholder location="classpath:*.properties"/>
- 从类路径或jar包中搜索并加载properties文件
<context:property-placeholder location="classpath*:*.properties"/>
1.9 容器
容器创建(顶级父类BeanFactory接口)
- 方式一:类路径加载配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
- 方式二:文件路径加载配置文件
ApplicationContext ctx = new FileSystemXmlApplicationContext("D:\\applicationContext.xml");
- 方式三:读取web下的资源文件
XmlWebApplicationContext ctx = new XmlWebApplicationContext();
- 加载多个配置文件
ApplicationContext ctx = new ClassPathXmlApplicationContext("bean1.xml", "bean2.xml");
获取bean
- 方式一:使用bean名称获取
BookDao bookDao = (BookDao) ctx.getBean("bookDao");
- 方式二:使用bean名称获取并指定类型
BookDao bookDao = ctx.getBean("bookDao", BookDao.class);
- 方式三:使用bean类型获取
BookDao bookDao = ctx.getBean(BookDao.class);
容器层次结构
总览BeanFactory
体系,按照接口的抽象层次,大体可以分层四层:
- 第一层:
BeanFactory
;(顶层接口) - 第二层:
HierarchicalBeanFactory
,ListableBeanFactory
,AutowireCapableBeanFactory
; - 第三层:
ConfigurableBeanFactory
,ApplicationContext
(常用接口); - 第四层:
ConfigurableApplicationContext
(提供关闭容器功能) - 中间存在若干实现类…
- 最底层:
ClassPathXmlApplicationContext
(常用实现类);
BeanFactory初始化
- 类路径加载配置文件
Resource resources = new ClassPathResource("applicationContext.xml"); BeanFactory bf = new XmlBeanFactory(resources); BookDao bookDao = bf.getBean("bookDao", BookDao.class); bookDao.save();
BeanFactory创建完毕后,所有的bean均为延迟加载。
延时加载好处:当为了追求传输效率需要什么就创建什么时有一个缓冲时间。
1.10 核心容器总结
容器相关
BeanFactory
是IoC容器的顶层接口,初始化BeanFactory对象时,加载的bean延时加载ApplicationContext
接口是Spring容器的核心接口,初始化时bean立即加载ApplicationContext
接口提供基础的bean操作相关方法,通过其他接口扩展其功能ApplicationContext
接口常用初始化类ClassPathXmlApplicationContext
FileSystemXmlApplicationContext
bean相关
<bean
id="bookDao" bean的Id
name="dao bookDaoImpl daoImpl" bean的别名
class="com.itheima.dao.impl.BookDaoImpl" bean的类型,静态工厂,FactoryBean类
scope="singleton" 控制bean的实例数量
init-method="init" 生命周期初始化方法
destroy-method="destroy" 生命周期销毁方法
autowire="byType" 自动装配类型
factory-method="getInstance" bean工厂方法,应用于静态工厂或实例工厂
factory-bean="com.itheima.factory.BookDaoFactory" 实例工厂bean
lazy-init="true" 控制bean延时加载
/>
依赖注入
<bean id="bookSerbice" class="com.hcx.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/> 构造器注入引用类型
<constructor-arg name="userDao" ref="userDao"/>
<constructor-arg name="msg" value="WARN"/> 构造器注入简单类型
<constructor-arg type="java.lang.String" index="3" value="WARN"/> 类型匹配与索引匹配
<property name="bookDao" ref="bookDao"/> setter注入引用类型
<property name="userDao" ref="userDao"/>
<property name="msg" value="WARN"/> setter注入简单类型
<property name="names"> list集合
<list>
<value>itcast</value> 集合注入简单类型
<ref bean="dataSource"/> 集合注入引用类型
</list>
</property>
</bean>
2. 注解开发
2.1 注解开发定义bean
- 使用@Component定义bean
@Component("bookDao")
public class BookDaoImpl implements BookDao{
}
@Component
public class BookServiceImpl implements BookService{
}
- 核心配置文件中通过组件扫描加载bean
<context:component-scan base-package="com.wang"/>
- Spring提供
@Component
注解的三个衍生注解@Repository
:用于数据层bean定义@Service
:用于业务层bean定义@Controller
:用于表现层bean定义
@Repository("bookDao")
public class BookDaoImpl implements BookDao{
}
@Service
public class BookServiceImpl implements BookService{
}
2.2 纯注解开发
- Spring 3.0 升级了纯注解开发模式,使用Java类代替配置文件,开启了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">
<context:component-scan base-package="com.wang"/>
</beans>
被替换为
@Configuration
@ComponentScan("com.wang")
public class SpringConfig{
}
@Configuration
注解用于设定当前类为配置类@ComponentScan
注解用于设定扫描路径,此注解只能添加一次,多个数据用数组格式(代码如下)
@Configuration
@ComponentScan({"com.wang.dao", "com.wang.service"})
public class SpringConfig{
}
- 读取Spring核心配置文件初始化容器对象切换为读取Java配置类初始化容器对象
//加载配置文件初始化容器(xml版)
ApplicationContext ctx = new ClassPathXmlApplicationContext("applicationContext.xml");
//加载配置类初始化容器
ApplicationContext ctx = new AnnotationConfigApplicationContext(SpringConfig.class);
2.3 bean管理
2.3.1 bean作用范围
- 使用
@Scope
定义bean作用范围
@Repository
@Scope("singleton")
public class BookDaoImpl implements BookDao {
}
2.3.2 bean生命周期
- 使用
@PostConstruct
、@PreDestroy
定义bean生命周期
@Repository
@Scope("singleton")
public class BookDaoImpl implements BookDao {
public BookDaoImpl(){
System.out.println("book dao constructor ...");
}
@PostConstruct
public void init(){
System.out.println("book init ...");
}
@PreDestroy
public void destroy(){
System.out.println("book destroy ...");
}
}
2.4 注解开发依赖注入
2.4.1 自动装配
引用类型注入
- 使用
@Autowired
注解开启自动装配模式(按类型)
@Servcie
public class BookServiceImpl implements BookService{
@Autowired
private BookDao bookDao;
public void save(){
System.out.print("book service save ...");
bookDao.save();
}
}
- 注意:自动装配基于反射设计创建对象并暴力反射对应属性为私有属性初始化数据,因此无需提供setter()方法;
- 注意:自动装配建议使用无参构造方法创建对象(默认),如果不提供对应构造方法,请提供唯一的构造方法;
- 使用
@Qualifier
注解开启指定名称装配bean;注意:@Qualifier
注解无法单独使用,必须配合@Autowired
注解一起使用
@Service
public class BookServiceImpl implements BookService{
@Autowired
@Qualifier("bookDao")
private BookDao bookDAO;
......
}
简单数据类型注入
- 使用
@Value
实现简单类型注入
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
@Value("7")
private String connectionNum;
}
2.4.2 加载properties文件
- 在SpringConfig类中使用
@PropertySource
注解加载外部properties文件
@Configuration
@ComponentScan("com.wang")
@PropertySource("classpath:jdbc.properties")
public class SpringConfig{
}
- 在给属性注入简单类型的值时
@Repository("bookDao")
public class BookDaoImpl implements BookDao {
@Value("${properties文件中定义的属性名}")
private String connectionNum;
}
- 注意:路径仅支持单一文件配置,多文件请使用数组格式配置,不允许使用通配符*
2.5 第三方bean管理
2.5.1 第三方bean配置
- 使用
@Bean
注解配置第三方bean,表示返回值是一个bean
@Configuration
public class SpringConfig{
// 1. 定义一个方法获得要管理的bean对象
// 2. 添加@Bean表示当前方法的返回值是一个bean
@Bean
public DataSource dataSource(){
DruidDataSource ds = new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/iot_platform");
ds.setUsername("root");
ds.setPassword("admin##2022");
return ds;
}
}
- 规范后,使用独立的配置类管理第三方bean并将其加入核心配置,加入方法有导入式和扫描式两种
public class JdbcConfig{ @Bean public DataSource dataSource(){ DruidDataSource ds = new DruidDataSource(); // 相关配置 return ds; } }
- 方式一:导入式,使用
@Import
注解手动加入配置类到核心配置,此注解只能添加一次,多个数据时用数组格式添加(推荐使用)
public class JdbcConfig{ @Bean public DataSource dataSource(){ DruidDataSource ds = new DruidDataSource(); // 相关配置 return ds; } }
@Configuration @Import(JdbcConfig.class) public class SpringConfig{ }
- 方式二:扫描式,使用
@ComponentScan
注解扫描配置类所在的包,加载对应的配置类信息(不便于看出加载的配置信息,故不推荐使用)
@Configuration public class JdbcConfig{ @Bean public DataSource dataSource(){ DruidDataSource ds = new DruidDataSource(); // 相关配置 return ds; } }
@Configuration @ComponentScan({"com.wang.config", "com.wang.dao", "com.wang.service"}) public class SpringConfig{ }
- 方式一:导入式,使用
2.5.2 第三方bean依赖注入
- 简单类型依赖注入
public class JdbcConfig{ @Value("com.mysql.jdbc.Driver") private String dirver; @Value("jdbc:mysql://localhost:3306/iot_platform") private String url; @Value("root") private String userName; @Value("admin##2022") private String password; @Bean public DataSource dataSource(){ DruidDataSource ds = new DruidDatSource(); ds.setDriverClassName(driver); ds.setUrl(url); ds.setUserName(userName); ds.setPassword(password); return ds; } }
- 引用类型依赖注入
@Bean public DataSource dataSource(BookService bookService){ Sytstem.out.print(bookService); DruidDataSource ds = new DruidDataSource(); // 相关配置 return ds; }
注意:引用类型依赖注入只需要为bean定义方法设置形参即可,容器会根据类型自动装配对象
2.6 XML配置对比注解配置
功能 | XML配置 | 注解 |
---|---|---|
定义bean | bean标签: id属性 class属性 | @ComponentScan @Component :@Repository 、@Service 、@Controller |
设置依赖注入 | setter注入(set方法):引用/简单 构造器注入(构造方法):引用/简单 自动装配 | @Autowired :@Qualifier @Value |
配置第三方bean | bean标签 静态工厂、实例工厂、FactoryBean | @Bean |
作用范围 | scope属性 | @Scope |
生命周期 | 标准接口: init-method destory-method | @PostConstryctor @ProDestory |
3. Spring整合
3.1 整合MyBatis思路分析
- MyBatis独立开发程序核心对象分析
/*=====初始化SqlSessionFactroy=====*/
//1.创建SqlSessionFactoryBuilder对象
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
//2.加载SqlMapConfig.xml配置文件
InpiyStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
//3.创建SqlSessionFactory对象
SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(inputStream);
/*=====获取连接,获取实现=====*/
//4.获取SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
//5.执行SqlSession对象执行查询,获取结果User
AccountDao accountDao = sqlSession.getMapper(AccountDao.class);
/*=====获取数据层接口======*/
Account ac = accountDao.findById(2);
System.out.printIn(ac);
/*=====关闭连接=====*/
//6.释放资源
sqlSession.close();
SqlMapConfig.xml
<configuration>
<!--初始化属性数据-->
<properties resource="jdbc.properties"></properties>
<!--初始化类型别名-->
<typeAliases>
<package name="com.wang.domain"/>
</typeAliases>
<!--初始化dataSource-->
<environments default="mysql">
<environment id="mysql">
<transactionManager type="JDBC">
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}"></property>
<property name="url" value="${jdbc.url}"></property>
<property name="username" value="${jdbc.username}"></property>
<property name="password" value="${jdbc.password}"></property>
</dataSource>
</transactionManager>
</environment>
</environments>
<!--初始化映射配置-->
<mappers>
<package name="com.wang.dao"></package>
</mappers>
</configuration>
3.2 注解方式实现MyBatis整合
- 添加两个整合所需的依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis-spring</artifactId>
<version>1.3.0</version>
</dependency>
- 注册SqlSessionFactoryBean类,并配置数据库
@Bean
public SqlSessionFactoryBean sqlSessionFactory(DataSource dataSource){
SqlSessionFactryBean ssfb = new SqlSessionFactoryBean();
ssfb.setTypeAliasePackage("com.wang.domain");
ssfb.setDataSource(dataSource);
return ssfb;
}
- 配置映射包
@Bean
public MapperScannerConfigurer mapperScannerConfigurer(){
MapperScannerConfigurer msc = new MapperScannerConfigurer();
msc.setBasePackage("com.wang.dao");
return msc;
}
3.3 整合JUnit
- 导入相关依赖
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.10.RELEASE</version>
</dependency>
- 使用Spring整合Junit专业类加载器
@RunWith(SpringJUnit4ClassRunner.class) // Spring整合JUnit的专用类运行器
@ConetextConfiguration(classes = SpringConfig.class) // 指明Spring所用配置类
public Class BookServiceTest{
@Autowired
private BookService bookService;
@Test
public void testSave(){
bookService.save();
}
}
4. AOP
4.1 AOP简介
- AOP(Aspect Oriented Programming)面向切面编程,是一种编程范式,指导开发者如何组织程序结构
- OOP(Object Oriented Programming)面向对象编程
- 作用:在不惊动原始设计的基础上进行功能增强,追加功能
- Spring理念:无侵入式/无入侵式
AOP核心概念
- 连接点(JoinPoint):程序执行过程中的任意位置,粒度为执行方法,抛出异常,设置变量等
- 在SpringAOP中,理解为方法的执行
- 切入点(Pointcut):匹配连接点的式子
- 在SpringAop中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
- 一个具体方法:com.wang.dao包下的BookDao接口中的无形参无返回值的save方法
- 匹配多个方法,所有的save方法,所有的get开头的方法,所有以Dao结尾的接口中的任意方法,所有带有一个参数的方法
- 在SpringAop中,一个切入点可以只描述一个具体方法,也可以匹配多个方法
- 通知(Advice):在切入点处执行的操作,也就是共性功能
- 在Spring中,功能最终以方法的形式呈现
- 通知类:定义通知的类
- 切面(Aspect):描述通知与切入点的对应关系
4.2 AOP入门案例(注解版)
- 导入AOP相关依赖
<!--AOP的依赖在spring-context中已经包含了,因此只需导入aspectjweaver即可--> <dependency> <groupId>org.aspectj</groupId> <artifactId>aspectjweaver</artifactId> <version>1.9.4</version> </dependency>
- 定义dao接口和实现类
public interface BookDao{ public void save(); public void update(); }
@Repository public class BookDaoImpl implements BookDao{ public void save(){ System.out.println(System.currentTimeMills()); System.out.println("book dao save..."); } public void update(){ System.out.println("book dao update..."); } }
- 定义通知类,完成切入点和通知的对应方法并将其绑定
@Component // 定义通知类受Spring容器管理 @Aspect // 定义当前类为切面类 public class MyAdvice{ /*切入点定义依托一个不具有实际意义的方法进行,即无参数,无返回值,方法体无实际逻辑*/ @Pointcut("execution(void com.wang.dao.BookDao.update())") private void pt(){ // 该方法为切入点 } @Before("pt()") // 绑定切入点与通知关系,并指定通知添加到原始连接点的具体执行位置 public void before(){ // 该方法为通知 System.out.println(System.currentTimeMillis()); } }
- 开启Spring对AOP注解驱动支持
@Configuration @ComponetScan("com.wang") @EnableAspectAutoProxy // 告诉Spring程序中含有注解开发的AOP public class SpringConfig{ }
4.3 AOP工作流程与核心概念
4.3.1 AOP工作流程
- Spring容器启动
- 读取所有切面配置中的切入点
@Component @Aspect public class MyAdvice{ @Pointcut("excution(void com.wang.dao.BookDao.save())") private void ptx(){} // 未配置,不读取 @Pointcut("excution(void com.wang.dao.BookDao.update())") private void pt(){} // 已配置,读取 @Before("pt()") public void method(){ System.out.println(System.currentTimeMillis()); } }
- 初始化bean,判定bean对应类中的方法是否匹配到任意切入点
- 匹配失败,创建对象
- 匹配成功,创建原始对象(目标对象)的代理对象
- 获取bean执行方法
- 获取bean,调用方法并执行,完成
- 获取的bean是代理对象时,根据代理对象的运行模式运行原始方法与增强的内容,完成操作
4.3.2 AOP核心概念
- 目标对象(Target):原始功能去掉共性功能对应的类产生的对象,这种对象是无法直接完成最终工作的
- 代理(Proxy):目标对象无法直接工作,需要对其进行功能回填,通过原始对象的代理对象实现
- SpringAOP本质:代理模式
4.4 AOP切入点表达式
- 切入点:要进行增强的方法
- 切入点表达式:要进行增强方法的描述方式
4.4.1 语法格式
public Interface BookDao{
public void update();
}
public class BookDaoImpl implements BookDao{
public void update(){
System.out.println("book dao update ...");
}
}
描述方式一:执行com.wang.dao包下的BookDao接口中无参数无返回值的update方法
execution(void com.wang.dao.BookDao.update())
描述方法二:执行com.wang.dao.impl包下的BookDaoImpl类中无参数无返回值的update方法
execution(void com.wang.iml.BookDaoImpl.update())
- 切入点表达式标准格式:动作关键字(访问修饰符,返回值,包名,类/接口.方法名(参数)异常名)
execution(public User com.wang.service.UserService.findById(int))
- 动作关键字:描述切入点的行为动作,例如excution表示执行到指定切入点
- 访问修饰符:public,private 等,可以省略
- 返回值
- 包名
- 类/接口名
- 方法名
- 参数
- 异常名:方法定义中抛出异常,可以省略
4.4.2 通配符
- *:单个独立的任意符号,可以独立出现,也可以作为前缀或后缀的匹配符出现;
execution(public * com.wang.*.UserService.find*(*))
- …:多个连续的任意符号,可以独立出现,常用于简化包名与参数的书写;
execution(public User com..UserService.findById(..))
- +:专用于匹配子类类型
execution(* *..*Service+.*(..))
4.4.3 书写技巧
- 所有代码按照标准规范开发,否则以下技巧全部失效;
- 描述切入点通常描述接口,而不描述实现类;
- 访问控制修饰符针对接口开发均采用public描述(可省略访问控制修饰符描述)
- 返回值类型对于增删改类使用精准类型加速匹配,对于查询类使用*通配符快速描述;
- 包名书写尽量不使用…匹配,效率过低,常用*做单个包描述匹配或精准匹配;
- 接口名/类名书写名称与模块相关的采用*匹配,例如UserService书写成*Service,绑定业务层接口名;
- 方法名书写以动词进行精准匹配,名词采用*匹配,例如getById书写成getBy*,selectAll书写成selectAll;
- 参数规则较为复杂,根据业务方法灵活调整;
- 通常不使用异常作为匹配规则
4.5 AOP通知类型
- AOP通知描述了抽取的共性功能,根据共性功能抽取位置不同,最终运行代码时要将其加入到合理的位置
- AOP通知分为5种类型
- 前置通知
- 后置通知
- 环绕通知(重点)
- 返回通知(了解)
- 抛出执行异常后通知(了解)
AOP通知类型 | 前置通知@Before |
---|---|
名称 | @Before |
类型 | 方法注解 |
位置 | 通知方法定义上方 |
作用 | 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前运行 |
范例 | @Before(“pt()”) public void before(){ System.out.println(“before advice …”); } |
相关属性 | value(默认):切入点方法名,格式为类名.方法名 |
AOP通知类型 | 后置通知@After |
---|---|
名称 | @After |
类型 | 方法注解 |
位置 | 通知方法定义上方 |
作用 | 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法后运行 |
范例 | @After(“pt()”) public void after(){ System.out.println(“after advice …”); } |
相关属性 | value(默认):切入点方法名,格式为类名.方法名 |
AOP通知类型 | 环绕通知@Around(重点) |
---|---|
名称 | @Around |
类型 | 方法注解 |
位置 | 通知方法定义上方 |
作用 | 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法前后运行 |
范例 | @Around(“pt()”) public Object around(ProceedingJoinPoint pjp) throws Throwable { System.out.println(“around before advice …”); Object ret = pjp.proceed(); System.out.println(“around after advice …”); return ret; } |
注意事项 | 1. 环绕通知必须依赖形参ProceedingJoinPoint才能实现对原始方法的调用,进而实现原始方法调用前后同时添加通知 2. 通知中如果未使用ProceedIngJoinPoint对原始方法进行调用将跳过原始方法的执行 3. 对原始方法的调用可以不接收返回值,通知方法设置成void即可,如果接收返回值,必须设定为Object类型 4. 原始方法的返回值如果是void类型,通知方法的返回值类型可以设置成void,也可以设置成Object 5. 由于无法预知原始方法运行后是否会抛出异常,因此环绕通知方法必须抛出Throwable对象 |
AOP通知类型 | 返回通知@AfterReturning(了解) |
---|---|
名称 | @AfterReturning |
类型 | 方法注解 |
位置 | 通知方法定义上方 |
作用 | 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法正常执行完毕后运行 |
范例 | @AfterReturning(“pt()”) public void afterReturning(){ System.out.println(“afterReturning advice …”); } |
相关属性 | value(默认):切入点方法名,格式为类名.方法名 |
AOP通知类型 | 抛出执行异常后通知@AfterThrowing(了解) |
---|---|
名称 | @Before |
类型 | 方法注解 |
位置 | 通知方法定义上方 |
作用 | 设置当前通知方法与切入点之间的绑定关系,当前通知方法在原始切入点方法运行抛出异常后执行 |
范例 | @AfterThrowing(“pt()”) public void afterThrowing() { System.out.println(“afterThrowing advice …”); } |
相关属性 | value(默认):切入点方法名,格式为类名.方法名 |
案例:测量业务层接口万次执行效率
@Component
@Aspect
public class ProjectAdvice {
// 匹配业务层的所有方法
@Pointcut("execution(* com.wang.service.*Service.*(..))")
private void servicePt(){}
@Around("ProjectAdvice.servicePt()")
public void runSpeed(ProceedingJoinPoint pjp) throws Throwable {
// 获取执行签名信息
Signature signature = pjp.getSignature();
// 通过签名获取执行类型(接口名)
String className = signature.getDeclaringTypeName();
// 通过签名获取执行操作名称(方法名)
String methodName = signature.getName();
long start = System.currentTimeMillis();
for(int i = 0; i < 10000; i++){
pjp.proceed();
}
long end = System.currentTimeMillis();
System.out.println("万次执行" + className + "." + methodnName + "时间:" + (end-start) + "ms");
}
}
4.6 AOP通知获取数据
- 获取切入点方法的参数
- JoinPoint:适用于前置,后置,返回后,抛出异常通知
- ProceedJointPoint:适用于环绕通知
- 获取切入点方法返回值
- 返回后通知
- 环绕通知
- 获取切入点方法运行异常信息
- 抛出异常后通知
- 环绕通知
4.6.1 AOP通知获取参数数据
- JointPoint对象描述了连接点方法的运行状态,可以获取到原始方法的调用参数
@Before("pt()") public void before(JointPoint jp){ Object[] args = jp.getArgs(); System.out.println(Arrays.toString(args)); }
- JointPoint的子类ProceedingJointPoint
@Around("pt()") public Object around(ProceedingJointPoint pjp) throws Throwable{ Object[] args = pjp.getArgs(); System.out.println(Arrays.toString(args)); Object ret = pjp.proceed(); return ret; }
4.6.2 AOP通知获取返回值数据
- 切入点方法执行完后通知可以获取其返回值,使用形参可以接收对应的返回值数据
@AfterReturning(Value = "pt()", returning = "ret") public void afterReturning(String ret) { System.out.println("afterRetruning advice ..."+ ret); }
- 环绕通知中可以手工书写对原始方法的调用,得到的结果即为原始方法的返回值
@Around("pt()") public Object around(ProceedingJoinPoint pjp) throws Throwable{ Object ret = pjp.proceed(); return ret; }
4.6.3 AOP通知获取异常数据(了解)
- 抛出异常后通知可以获取切入点方法中出现的异常信息,使用形参可以接收对应的异常对象
@AfterThrowing(value = "pt()", throwing = "t") public void afterThrowing(Throwable t){ System.out.println("afterThrowing adivce ..."+ t); }
- 抛出异常后通知可以获取切入点方法运行的异常信息,使用形参可以接收运行时抛出的异常对象
@Around("pt()") public Object around(ProceedingJoinPoint pjp){ Object ret = null; try{ ret= pjp.proceed(); }catch(Throwable t){ t.printStackTrace(); } return ret; }
4.6.4 案例:百度网盘密码数据兼容处理
百度网盘分析链接输入密码数据,对于误输入空格的
@Component
@Aspect
public void DataAdvice{
@Pointcut("execution(boolean com.wang.service.*Service.*(*, *))")
private void servicePt(){}
@Around("DataAdvice.servicePt()")
public Object trimString(ProceedingJoinPoint pjp) throws Throwable {
Object[] args = pjp.getArgs();
// 对原始参数的每一个参数分别进行操作
for(int i = 0; i < args.length; i++){
// 判断参数是不是字符串
if(args[i].getClass().equals(String.class)){
// 取出数据进行trim()操作删除前后空格后更新数据
args[i] = args[i].toString().trim();
}
}
return pjp.proceed(args);
}
}
5. Spring事务
5.1 Spring事务简介
- 事务作用:在数据层保障一系列的数据库操作同时成功同时失败
- Spring事务作用:在数据层或业务层保障一系列的数据操作同时成功同时失败
public interface PlatformTransactionManager {
void commit(TransactionStatus status) throws TransactionException;
void rollback(TransactionSattus status) throws TransactionException;
}
public class DataSourceTransactionManager {
...
}
案例:银行账户转账
需求:实现任意两个账户间转账操作
需求微缩:A账户减钱,B账户加钱
分析:
- 数据层提供基础操作,指定账户减钱(outMoney),指定账户加钱(inMoney)
- 业务层提供转账操作(transfer),调用减钱和加钱的操作
- 提供2个账号和操作金额执行转账操作
- 基于Spring整合MyBatis环境搭建上述操作
结果分析:
- 程序正常执行时,账号金额A减B加,没有问题
- 程序出现异常,转账失败,但是异常之前操作成功,异常之后操作失败,整体业务失败
添加事务管理操作流程
-
在业务层接口上添加Spring事务管理
public interface AccountService{ @Transactional public void transfer(String out, String in ,Double money); }
注意:Spring事务注解通常添加在业务层接口中而不会添加到业务层实现类中,降低耦合度;业务注解可以添加到业务方法上表示开启方法事务,也可以添加到接口上表示当前接口所有方法开启事务。
-
设置事务管理
/*在config包下的JdbcConfig.java中添加事务对应的bean*/ @Bean public PlatformTransactionManager transactionManager(DataSource dataSoure){ DataSourceTransactionManager transactionManager = new DataSourceTransactionManager(); transactionManager.setDataSource(dataSource); return transactionManager; }
注意:事务管理要根据实现技术进行选择;MyBatis框架使用JDBC事务
-
开启注解事务驱动
@Configuration @ComponentScan("com.wang") @PropertySource("classpath:jdbc.properties") @Import({JdbcConfig.class, MybatisConfig.class}) @EnableTransactionManagement public class SpringConfig{ }
5.2 Spring事务角色(通过相同数据源来管理)
- 事务角色
- 事务管理员:发起事务方,在Spring中通常指代业务层开启事务的方法
- 事务协调员:加入事务方,在Spring中通常带着数据层方法,也可以是业务层方法
5.3 Spring事务属性
5.3.1 事务配置
属性 | 作用 | 示例 |
---|---|---|
readOnly | 设置是否为只读事务 | readOnly = true 只读事务 |
timeout | 设置事务超时时间 | timeout = -1 永不超时 |
rollbackFor | 设置事务回滚异常(class) | rollbackFor = {NullPointException.class} |
rollbackForClassName | 设置事务回滚异常(String) | 同上格式为字符串 |
noRollbackFor | 设置事务不回滚异常(class) | noRollbackFor = {NullPointException.class} |
noRollbackForClassName | 设置事务不回滚异常(String) | 同上格式为字符串 |
propagation | 设置事务传播行为 | … |
注意:IOException不属于运行时异常,事务回滚不管理
5.3.2 事务传播行为
- 事务传播行为:事务协调员对事务管理员所携带事务的处理态度
Spring中七种事务传播行为
事务传播行为类型 | 事务管理员 | 事务协调员 | 说明 |
---|---|---|---|
REQUIRED(默认) | 开启T
无 | 加入T
新建T2 | 如果当前没有事务,就新建一个事务,如果已经存在一个事务,就加入到这个事务中 |
REQUIRES_NEW | 开启T
无 | 新建T2
新建T2 | 新建事务,如果当前存在事务,把当前事务挂起。 |
SUPPORTS | 开启T
无 | 加入T
无 | 支持当前事务,如果当前没有事务,就以非事务方式执行。 |
NOT_SUPPORTED | 开启T
无 | 无
无 | 以非事务方式执行操作,如果当前存在事务,就把当前事务挂起。 |
MANDATORY | 开启T
无 | 加入T
ERROR | 使用当前的事务,如果当前没有事务,就抛出异常。 |
NEVER | 开启T
无 | ERROR
无 | 以非事务方式执行,如果当前存在事务,则抛出异常。 |
NESTED | / | / | 设置savePoint,一旦事务回滚,将回滚到savePoint处,交由客户响应提交/回滚 |
5.3.3 案例:转账业务追加日志
-
需求:实现任意两个账户间转账操作,并对每次转账操作在数据库进行留痕
-
需求微缩:A账户减钱,B账户加钱,数据库记录日志
-
分析:
- 基于转账操作案例添加日志模块,实现数据库中记录日志
- 业务层转账操作(transfer),调用减钱、加钱与记录日志功能
-
实现效果预期:
- 无论转账是否成功,均进行转账操作的日志留痕
-
存在问题:
- 日志的记录与转账操作隶属于同一事务,同成功同失败,日志记录不完全
-
失效效果预取改进:
- 无论转账操作是否成功,日志必须保留
改进内容:
- 在业务层接口上添加Spring事务,设置事务传播行为REQUIRES_NEW(需要新事务)
public interface LogService { @Transactional(propagation = Propagation.REQUIRES_NEW) void log(String out, String in, Double money); }
@Service public class LogServiceImpl implements LogService{ @Autowired private LogDao logDao; public void log(String out, String in ,Double money){ logDao.log("转账操作由" + out + "到" + in + ",转账金额为:" + money + "RMB"); } }