1. Spring入门
首先要了解一下什么是spring。通过一个简单的样例来分析入门。
首先的自学必备,官方文档:https://docs.spring.io/spring/docs/5.2.4.RELEASE/spring-framework-reference/
1.1 Spring简介
- spring框架是以interface21框架为基础,重新设计丰富的成果。
- spring就是整合了很多框架的一个工具
- spring是一个免费的开源框架(容器)。
- spring是一个轻量级的,非侵入式的框架。
- spring核心:可控制反转(IOC),面向切面(AOP)。
- 支持事务的处理。
spring这么牛逼,官网也这样介绍的:https://spring.io/why-spring。
反正一起吹spring牛逼就对了。
官方文档的介绍顺序是:
title | content | 解释 |
---|---|---|
Overview | history, design philosophy, feedback, getting started. | 概述:讲一些历史,设计理念什么的 |
Core | IoC Container, Events, Resources, i18n, Validation, Data Binding, Type Conversion, SpEL, AOP. | 核心:讲IOC和AOP,事件和配置 |
Testing | Mock Objects, TestContext Framework, Spring MVC Test, WebTestClient. | 测试:MVC |
Data Access | Transactions, DAO Support, JDBC, O/R Mapping, XML Marshalling. | 数据访问:事务,JDBC,XML |
Web Servlet | Spring MVC, WebSocket, SockJS, STOMP Messaging. | WebServlet:SpringMVC等内容 |
Web Reactive | Spring WebFlux, WebClient, WebSocket. | Web:WenSocket等内容 |
Integration | Remoting, JMS, JCA, JMX, Email, Tasks, Scheduling, Caching. | 集成:邮件,任务调度,缓存 |
Languages | Kotlin, Groovy, Dynamic Languages. | 语言:动态语言什么的 |
概述我们大概掠过。
打开官方文档的Core,一开始就是IOC容器介绍。(由于都是英文,我英语又不好,就翻译成中文看了)下面就不放英文原文了,有兴趣的同学可以去自己看一下。
1.2 环境配置
没什么环境要配的,轻量级的spring只需要导一下maven依赖就好了。
直接导webmvc的,一次到位
<!-- https://mvnrepository.com/artifact/org.springframework/spring-webmvc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-webmvc</artifactId>
<version>5.2.4.RELEASE</version>
</dependency>
和一个整合:
<!-- https://mvnrepository.com/artifact/org.springframework/spring-test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>5.2.4.RELEASE</version>
<scope>test</scope>
</dependency>
1.3 简单入门
我们首先进行一个简单的小样例,然后通过样例来分析spring。
通常情况下我们的三层实现是这样的:
- pojo一个实体类。
- dao实现接口和方法
public class UserDaoImpl implements UserDao {
public void findAll() {
System.out.println("dao实现");
}
}
- service层调用dao层
import com.admin.dao.UserDao;
import com.admin.dao.UserDaoImpl;
public class UserServiceImpl implements UserService {
UserDao userDao = new UserDaoImpl();
public void findAll() {
userDao.findAll();
}
}
- 用户层调用service层:
import com.admin.service.UserService;
import com.admin.service.UserServiceImpl;
public class MyTest {
public static void main(String[] args) {
UserService userService = new UserServiceImpl();
userService.findAll();
}
}
这样的三层架构,使得程序的耦合性非常高。用new 的方法创建对象,需要对象的实现类。修改任何一个实现,我们都需要将所有的代码修改好,不小心还可能出现很多的bug。
为了避免这样的情况,需要换一种思路,这种思路就是IOC思想,利用IOC思想来实现程序,通过set方法来实现修改参数。(IOC思想后面在讨论)
上面的可修改为:
- dao层不变。
- service修改为:
import com.admin.dao.UserDao;
public class UserServiceImpl implements UserService {
UserDao userDao;
public UserDao getUserDao() {
return userDao;
}
public void setUserDao(UserDao userDao) {
this.userDao = userDao;
}
public void findAll() {
userDao.findAll();
}
}
- 在resources中配置ApplicationContext.xml(官方文档1.2.1模板,参考1.2.2配置该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
https://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="UserDaoImpl" class="com.admin.dao.UserDaoImpl"/>
<bean id="UserServiceImpl" class="com.admin.service.UserServiceImpl">
<property name="userDao" ref="UserDaoImpl"/>
</bean>
</beans>
- 用户层:使用我们配置的IOC容器
import com.admin.service.UserService;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("ApplicationContext.xml");
UserService userService =(UserService) context.getBean("UserServiceImpl");
userService.findAll();
}
}
bean的格式:
- id:唯一标识符。
- class:指向目标的全限定类名。
- property:赋值。其中name表示的是赋值对象id,ref表示引用类型,value表示基本数据类型(int,string)。
1.4 IOC初体验
IOC,全称:Inversion of Control,控制反转。
1.4.1 什么是控制反转?
就是说,我们对程序的控制,不再是程序主动控制了,而是人为控制程序的动作。
比如,一开始都是程序需要什么,我们就new一个对象给程序。
现在是,我们给程序了什么,程序用什么。
注意一下我上面那段话的意思。
前者意思是,人需要按照程序来走,就像从A地到B地,我们只先修了一个铁路,就需要一个火车,只有公路,就需要一个汽车。需要什么是写程序时决定的,我们所作的事情就是获得这个需要的对象。
而后者的意思就是,从A地到B地,我们想通过汽车,就可以走公路,我们想坐火车,就走铁路就好了。就是说,路是准备好的,我们只需要自己随意定义工具就好了。
以上是关于控制反转的理解。(个人总结,如有错误欢迎指正)
1.4.2 什么是IOC容器?
官网给了一个图来解释:
大概意思就是pojo和控制元数据都是被Spring容器统一掌握,然后分配使用。
从我们上面的入门样例可以看出来,我们把所有东西都交给了Spring容器来管理。
这个bean就是一个容器,将所有对象交给bean来管理。
需要什么就通过bean来获得。不需要再new东西了。
可以把bean理解成一个map,通过key来获得value。
我们所作的就是修改配置文件就好了。配置文件说白了就是一个记事本,记录着配置,完全可以不用操作源码了。
比如:我们在bean中配置了UserDaoImpl和UserServiceImpl,
那么我们在test中,直接new出来这个bean容器。用到了UserService,直接从bean中传入目标key,然后就获得了UserServiceImpl对象。
同样道理,在UserService中需要用到了UserDao,我们在bean中配置了目标UserDao,那么我们就直接会通过无参构造来获得。
期间我们不用管bean是如何创建对象的,这已经和我们没有关系了,我们只需要修改对应的bean即可。
这就是关于IOC容器的理解。
2.IOC容器
2.1 Bean总览
Spring IOC容器管理一个或多个bean。这些bean是通过我们对bean的配置创建的(<bean/>
)
这些容器中默认包含一下元数据:
- 全限定类名:该bean的实现类
- bean的配置元素,用于声明bean在容器中的行为(作用域,生命周期,等)
- 引用其他bean(依赖)。
- 新建对象中设置其他配置(管理连接池bean的池大小,链接数等限制)。
这个表可以让我们很明确的知道各个属性都干了什么:
Property | 解释 |
---|---|
Class | 全限定类名 |
Name | bean的唯一标识符 |
Scope | 作用域 |
Constructor arguments | 构造函数参数 |
Properties | 属性 |
Autowiring mode | 自动连接模式 |
Lazy initialization mode | 延迟初始化模式 |
Initialization method | 初始化方法(回调) |
Destruction method | 销毁方法(回调) |
2.2 IOC容器创建对象的方式:DI
依赖注入(DI),全称Dependency Injection。它是一个过程,通过该过程,对象只能通过构造函数参数,工厂方法参数,创建对象后的设置等方式来定义其依赖关系。
2.2.1 依赖解析
在IOC容器执行bean依赖解析:
- 使用ApplicationContext.xml来配置和初始化所有bean的元数据(通过xml,注解,Java代码等方式)。
- 对于每个bean,都要在创建bean时将这些依赖项提供给bean
- 每个属性(构造参数)都要设置其值,或是对IOC容器中另一个bean的引用。
创建容器时的活动:
- spring容器会验证每个bean的配置,但不会立刻创建bean。
- 先创建具有单例作用域并设置为预先实例化的bean。
- 创建bean会创建bean的依赖。(循环依赖会报错)。
2.2.1 基于构造函数的依赖注入。
-
无参构造创建对象。上面我们用的就是无参构造创建对象,也是默认的创建方法。
-
有参构造创建对象。
-
通过下标赋值
<bean id="UserServiceImpl" class="com.admin.service.UserServiceImpl"> <constructor-arg index="0" ref="UserDaoImpl"/> </bean>
index表示下标索引,ref表示引用对象。
-
通过类型赋值
<bean id="UserServiceImpl" class="com.admin.service.UserServiceImpl"> <constructor-arg type="com.admin.dao.UserDao" ref="UserDaoImpl"/> </bean>
type:全限定类名,参数类型是什么,就传什么。ref:引用对象
-
通过参数名赋值
<bean id="UserServiceImpl" class="com.admin.service.UserServiceImpl"> <constructor-arg name="userDao" ref="UserDaoImpl"/> </bean>
name:对象名,ref:引用对象。
-
2.2.2 基于set的依赖注入。
set的DI是通过在调用无参(有参)构造bean之后,通过调用bean上的setter方法来实现的。
public class ExampleBean {
private AnotherBean beanOne;
private YetAnotherBean beanTwo;
private int i;
public void setBeanOne(AnotherBean beanOne) {
this.beanOne = beanOne;
}
public void setBeanTwo(YetAnotherBean beanTwo) {
this.beanTwo = beanTwo;
}
public void setIntegerProperty(int i) {
this.i = i;
}
}
对于上面那个类,其bean的set配置法:通过set方法注入。
<bean id="anotherExampleBean" class="examples.AnotherBean"/>
<bean id="yetAnotherBean" class="examples.YetAnotherBean"/>
<bean id="exampleBean" class="examples.ExampleBean">
<!-- setter injection using the nested ref element -->
<property name="beanOne">
<ref bean="anotherExampleBean"/>
</property>
<!-- setter injection using the neater ref attribute -->
<property name="beanTwo" ref="yetAnotherBean"/>
<property name="integerProperty" value="1"/>
</bean>
2.2.3 依赖注入的详细配置。
对于9种类型的注入:
bean | ref | idref | list | set | map | props | value | null
-
配置默认bean
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close"> <!-- results in a setDriverClassName(String) call --> <property name="driverClassName" value="com.mysql.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mydb"/> <property name="username" value="root"/> <property name="password" value="masterkaoli"/> </bean>
-
使用ref:我们上面一直在用,引用类型
-
使用idref
<bean id="theTargetBean" class="..."/> <!--下面两个的效果相同--> <bean id="theClientBean" class="..."> <property name="targetName"> <idref bean="theTargetBean"/> </property> </bean> <bean id="client" class="..."> <property name="targetName" value="theTargetBean"/> </bean>
-
使用list
<bean id="moreComplexObject" class="example.ComplexObject"> <!-- results in a setSomeList(java.util.List) call --> <property name="someList"> <list> <value>a list element followed by a reference</value> <ref bean="myDataSource" /> </list> </property> </bean>
-
使用set:
<bean id="moreComplexObject" class="example.ComplexObject"> <!-- results in a setSomeSet(java.util.Set) call --> <property name="someSet"> <set> <value>just some string</value> <ref bean="myDataSource" /> </set> </property> </bean>
-
使用map:entry种设置key和value
<bean id="moreComplexObject" class="example.ComplexObject"> <!-- results in a setSomeMap(java.util.Map) call --> <property name="someMap"> <map> <entry key="an entry" value="just some string"/> <entry key ="a ref" value-ref="myDataSource"/> </map> </property> </bean>
-
使用property:配置文件中key和value
<bean id="moreComplexObject" class="example.ComplexObject"> <!-- results in a setAdminEmails(java.util.Properties) call --> <property name="adminEmails"> <props> <prop key="administrator">administrator@example.org</prop> <prop key="support">support@example.org</prop> <prop key="development">development@example.org</prop> </props> </property> </bean>
-
使用value:上面的任意样例都使用了value
-
使用null:直接一个null标签就可以了
<bean class="ExampleBean"> <property name="email"> <null/> </property> </bean>
2.2.4 扩展依赖注入配置:c,p命名空间
首先导入对应的命名空间:
<beans xmlns:p="http://www.springframework.org/schema/p"
xmlns:c="http://www.springframework.org/schema/c"
...>
之后就可以使用了:
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:c="http://www.springframework.org/schema/c"
xmlns:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://www.springframework.org/schema/beans/spring-beans.xsd">
<!--普通的和使用命名空间的比较-->
<!--使用c-namespace比较-->
<bean id="beanTwo" class="x.y.ThingTwo"/>
<bean id="beanThree" class="x.y.ThingThree"/>
<bean id="beanOne" class="x.y.ThingOne">
<constructor-arg name="thingTwo" ref="beanTwo"/>
<constructor-arg name="thingThree" ref="beanThree"/>
<constructor-arg name="email" value="something@somewhere.com"/>
</bean>
<bean id="beanOne" class="x.y.ThingOne"
c:thingTwo-ref="beanTwo"
c:thingThree-ref="beanThree"
c:email="something@somewhere.com"/>
<!--使用p-namespace比较-->
<bean name="jane" class="com.example.Person">
<property name="name" value="Jane Doe"/>
</bean>
<bean name="john-classic" class="com.example.Person">
<property name="name" value="John Doe"/>
<property name="spouse" ref="jane"/>
</bean>
<bean name="john-modern" class="com.example.Person"
p:name="John Doe"
p:spouse-ref="jane"/>
</beans>
2.3 bean的作用域
官方给了一个完整的表格:
Scope | Description |
---|---|
singleton | (Default) Scopes a single bean definition to a single object instance for each Spring IoC container. |
prototype | Scopes a single bean definition to any number of object instances. |
request | Scopes a single bean definition to the lifecycle of a single HTTP request. That is, each HTTP request has its own instance of a bean created off the back of a single bean definition. Only valid in the context of a web-aware Spring ApplicationContext . |
session | Scopes a single bean definition to the lifecycle of an HTTP Session . Only valid in the context of a web-aware Spring ApplicationContext . |
application | Scopes a single bean definition to the lifecycle of a ServletContext . Only valid in the context of a web-aware Spring ApplicationContext . |
websocket | Scopes a single bean definition to the lifecycle of a WebSocket . Only valid in the context of a web-aware Spring ApplicationContext . |
后四个是和web有关的,在MVC处涉及。
前两个
- singleton:单例,就是一个bean被多个bean引用,这些对象是相同的。默认是单例状态
- prototype:原型,就是一个bean被多个bean引用,分别是不同的对象。
可以写一下xml和测试类测试一下:
pojo:
public class Address {
private String address;
}
public class Student {
private String name;
}
bean:
<bean id="Address" class="com.admin.pojo.Address" scope="prototype"/>
<bean id="Student" class="com.admin.pojo.Student" scope="singleton">
<property name="name" value="admin"/>
</bean>
test:
import com.admin.pojo.Address;
import com.admin.pojo.Student;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
Student student = context.getBean("Student",Student.class);
Student student1 = context.getBean("Student",Student.class);
Address address1 = context.getBean("Address",Address.class);
Address address2 = context.getBean("Address",Address.class);
System.out.println(student==student1);
System.out.println(address1==address2);
}
}
可以看出他们的异同。
2.4 bean的自动装配
2.4.1 基于xml的自动装配
我们可以通过配置autowire属性来减少配置文件的书写。
bean的自动装配主要分为byName和byType。
- byName:通过名字自动配置。名字必须相同,大小写也要相同。
- byType:通过类型自动装配。类型必须保证唯一。
<bean id="Address" class="com.admin.pojo.Address">
<property name="address" value="123"/>
</bean>
<bean id="Student" class="com.admin.pojo.Student" autowire="byType">
<property name="name" value="admin"/>
</bean>
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
Student student = context.getBean("Student",Student.class);
System.out.println(student);//输出:Student{name='admin', address=Address{address='123'}}
}
}
2.4.2 基于注解的自动装配
jdk1.5支持注解,spring2.5支持注解。
配置注解环境:导入约束,配置注解的支持。
<?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
https://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/context
https://www.springframework.org/schema/context/spring-context.xsd">
<context:annotation-config/>
</beans>
之后直接可以在属性上使用@Autowired来进行自动装配。多个同类型时,可以使用Qualifier指定装配id(需要同类型)
public class Student {
private String name;
@Autowired
@Qualifier(value = "Address2")
private Address address;
}
<bean id="Address" class="com.admin.pojo.Address">
<property name="address" value="123"/>
</bean>
<bean id="Address2" class="com.admin.pojo.Address">
<property name="address" value="123321"/>
</bean>
<bean id="Student" class="com.admin.pojo.Student">
<property name="name" value="admin"/>
</bean>
在测试类中输出测试:
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("bean.xml");
Student student = context.getBean("Student",Student.class);
System.out.println(student);//Student{name='admin', address=Address{address='123321'}}
}
}
还有一种直接一个注解Resource就可以的。在javax.annotation.Resource中。
jdk中没有的,取maven仓库导一下javax.annotation就可以了。
public class Student {
private String name;
// @Autowired
// @Qualifier(value = "Address2")
@Resource(name = "Address2")
private Address address;
}
3. Spring配置
3.1 别名
<alias name="user" alias="userNew"/>
在调用中,userNew和user都会返回user对象,就是起一个其他称呼。
3.2 bean配置
<bean id="UserDaoImpl" class="com.admin.dao.UserDaoImpl" name="dao dao1,dao2;dao3"/>
id和class之前我们题到了,这里多了一个name,也是别名的意思,还可以同时取多个别名,中间可以用",“或者” “或者”;"等符号分割。
3.3 import
团队开发一个项目,分工不同的人写不同的类,有不同的bean我们需要将其汇总到一个总的bean中,然后只获得总的bean就好了。
官方文档:1.2.1中提到了如果多个xml,我们如何将其整到一块。
- A:bean1.xml
- B:bean2.xml
- C:bean3.xml
在总的bean中就可以了:
<import resource="bean1.xml"/>
<import resource="bean2.xml"/>
<import resource="bean3.xml"/>
4. 使用注解实现bean
在官方文档1.9和1.10处将的非常详细,我就不抄了,把我认为的终点总结一下吧。
官方文档:https://docs.spring.io/spring/docs/5.2.4.RELEASE/spring-framework-reference/core.html
可以现在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
https://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.admin.pojo"/>
<context:annotation-config/>
</beans>
之后就可以自由使用注解来进行操作了。
4.1 自动装配的注解@Autowired和@Required
在xml中配置扫描的包,就可以直接使用注解来实现自动装配了。
上面已经介绍过了,下面把例子再抄一份。
-
@Autowired:spring的自动配置bean,配合@Qualifier和@Primary(主要自动添装)使用
该注解可以在属性,构造函数,set上使用。public class Student { private String name; @Autowired @Qualifier(value = "Address2") private Address address; }
-
@Resource:java自带的自动配置。
public class Student {
private String name;
@Resource(name = "Address2")
private Address address;
}
4.2 属性的注入@Component
在类和属性上加一些注解就可以实现注解的注入。
-
@Component:将一个类装配到spring容器中,效果等同配置中的:
<bean id = "..." class = "..." />
在MVC三层架构中,不同层使用的注解有不同的标识,作用都是一样的(将某个类装配到spring容器中)目的只是为了区分不同的层。
- @Respository:用在dao层的注解
- @Service:用在Service层的注解
- @Controller:用在controller层的注解
-
@Value(String key):效果等同
<property name = "name" value = "value"/>
使用样例:
@Component
public class User {
@Value("aaa")
public String name;
}
4.2 作用域的配置@Scope
可以直接使用@Scope注解进行作用域的配置
我们上面说的单例和原型。是可以通过这个注解来配置的。@Scope(String);
官方给了一个原型的作用域。
@Scope("prototype")
@Repository//表示是一个dao层的注解效果等同@Component
public class MovieFinderImpl implements MovieFinder {
// ...
}
4.3 使用配置类代替xml的注解
也可以完全抛弃xml,直接使用注解配置。
4.3.1 创建配置类@Configuration和@Bean
在一个主配置类中配置@Configuration和@Bean相当于再xml中配置了spring容器。
- @Configuration:表示这是一个配置类,就和我们之前看的beas.xml一样。这个类也会被spring容器托管,本来就是@Component。
- @Bean:注册一个bean,相当于我们写的bean标签。
import com.admin.pojo.User;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class MyConfig {
@Bean
public User getUser(){
return new User();
}
}
4.3.2 配置扫描的包@ComponentScan
如果要扫描包,可以在配置类中使用@ComponentScan来描述扫描的包。
@Configuration
@ComponentScan(basePackages = "org.example",
includeFilters = @Filter(type = FilterType.REGEX, pattern = ".*Stub.*Repository"),
excludeFilters = @Filter(Repository.class))
public class AppConfig {
...
}
效果等同:
<beans>
<context:component-scan base-package="org.example">
<context:include-filter type="regex"
expression=".*Stub.*Repository"/>
<context:exclude-filter type="annotation"
expression="org.springframework.stereotype.Repository"/>
</context:component-scan>
</beans>
4.3.3 导入其他配置类@Import
创建另一个配置类MyConfig2.java
@Configuration
public class MyConfig2 {
//...
}
在之前的主配置类中使用@Import来调用
@Configuration
@ComponentScan("com.admin.pojo")
@Import(MyConfig2.class)
public class MyConfig {
@Bean
public User getUser(){
return new User();
}
}
4.4 测试类中获取容器
由于我们没有通过xml配置spring容器,而是注解,因此我们就不能再用ClassPathXmlApplicationContext类获取容器了,而是与之相应的注解获取容器AnnotationConfigApplicationContext。
public class MyTest {
public static void main(String[] args) {
//ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
ApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
User user = context.getBean("getUser",User.class);
System.out.println(user);
}
}
5. AOP
5.1 使用Spring实现AOP
实现spring的AOP操作有很多种,首先先准备一些不变的东西
首先需要导入包
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.9.5</version>
</dependency>
创建一个简单的接口和其实现类.写完之后就不用管它了,就像我们之前学过的动态代理一样,我们不需要对原有的代码做一丝修改.
package com.admin.service;
public interface UserService {
void add();
void delete();
void update();
void select();
}
public class UserServiceImpl implements UserService {
public void add() {
System.out.println("增加了一个用户");
}
public void delete() {
System.out.println("删除了一个用户");
}
public void update() {
System.out.println("修改了一个用户");
}
public void select() {
System.out.println("查找了一个用户");
}
}
通过测试类来查看是否完成想要的操作
public class MyTest {
public static void main(String[] args) {
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserService service = context.getBean("UserService",UserService.class);
service.add();
}
}
5.1.1 方法一:通过springAPI来实现动态代理
写一些日志实现:用来做切入操作.这里需要实现spring的代理接口.
package com.admin.log;
import org.springframework.aop.MethodBeforeAdvice;
import java.lang.reflect.Method;
public class Log implements MethodBeforeAdvice {
public void before(Method method, Object[] objects, Object o) throws Throwable {
System.out.println(method.getClass().getName()+"类调用了"+method.getName()+"接口");
}
}
public class AfterLog implements AfterReturningAdvice {
public void afterReturning(Object o, Method method, Object[] objects, Object o1) throws Throwable {
System.out.println(method.getClass().getName()+"调用了"+method.getName()+"方法");
}
}
配置applicationConftext.xml来注册到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"
xmlns:context="http://www.springframework.org/schema/context"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://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">
<context:component-scan base-package="com.admin"/>
<context:annotation-config/>
<bean id="UserService" class="com.admin.service.UserServiceImpl"/>
<bean id="Log" class="com.admin.log.Log"/>
<bean id="AfterLog" class="com.admin.log.AfterLog"/>
<!--这里就是配置spring的AOP的核心配置-->
<aop:config>
<!--配置切入点,注意expression表达式 类型 包.类.方法名(参数)-->
<aop:pointcut id="PointCut" expression="execution(* com.admin.service.UserServiceImpl.*(..))"/>
<!--配置动态代理-->
<!--advice-ref指的是添加的方法,pointcut-ref指的是切入点位置-->
<aop:advisor advice-ref="Log" pointcut-ref="PointCut"/>
<aop:advisor advice-ref="AfterLog" pointcut-ref="PointCut"/>
</aop:config>
</beans>
测试类种可以测试成功
java.lang.reflect.Method类调用了add接口
增加了一个用户
java.lang.reflect.Method调用了add方法
这种方法的好处就是能够更加随意地操作我们的类.只需要实现对应的接口就好了.
我们可以在类中放入更多的东西.
5.1.2 方法二:通过spring自定义切面来实现动态代理
新加入一个自定义切面类,方法名什么都随便写,我这是方便自己些的before和after.
package com.admin.aspect;
public class MyAspect {
public void before(){
System.out.println("这是一个前置通知");
}
public void after(){
System.out.println("这是一个后置通知");
}
}
再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"
xmlns:aop="http://www.springframework.org/schema/aop"
xsi:schemaLocation="http://www.springframework.org/schema/beans
https://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">
<context:component-scan base-package="com.admin"/>
<context:annotation-config/>
<bean id="UserService" class="com.admin.service.UserServiceImpl"/>
<bean id="Log" class="com.admin.log.Log"/>
<bean id="AfterLog" class="com.admin.log.AfterLog"/>
<bean id="MyAspect" class="com.admin.aspect.MyAspect"/>
<aop:config>
<!--自定义切面,ref表示要引用的类-->
<aop:aspect ref="MyAspect">
<aop:pointcut id="pointcut" expression="execution(* com.admin.service.UserService.*(..))"/>
<!--配置方法出现的位置-->
<aop:after method="after" pointcut-ref="pointcut"/>
<aop:before method="before" pointcut-ref="pointcut"/>
</aop:aspect>
</aop:config>
</beans>
测试输出:
这是一个前置通知
增加了一个用户
这是一个后置通知
这种方法的好处就是简单,将所有的东西都放入一个普通类中了
而且一个切面下去,东西全在里面.
不过缺点就是由于是普通类,所以可能支持的操作就变少了.
5.1.3 方法三:通过注解来实现动态代理
新建一个代理类,其中标上注解
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
@Aspect//使用注解定义了切面
public class AnnoPointCut {
@Before("execution(* com.admin.service.UserService.*(..))")
public void before(){
System.out.println("前置通知");
}
@After("execution(* com.admin.service.UserService.*(..))")
public void after(){
System.out.println("后置通知");
}
@Around("execution(* com.admin.service.UserService.*(..))")
public void around(ProceedingJoinPoint jp) throws Throwable {
System.out.println("环绕前");
Signature signature = jp.getSignature();
System.out.println("signatuer:"+signature);
Object proceed = jp.proceed();
System.out.println("环绕后");
}
}
xml中需要添加支持:
输出结果:
环绕前
signatuer:void com.admin.service.UserService.add()
前置通知
增加了一个用户
环绕后
后置通知
其实注解操作只需要几个简单的点就好了:
- @Aspect:定义该类为切面类.
- @Before:定义该方法是前置通知
- @After:定义该方法为后置通知.
- @Around:定义该方法为环绕通知.
- 其中根据ProceedingJoinPoint.proceed()来确定环绕前后.
- 需要抛出异常.
还有其他方法都是一样的.
注意一下环绕通知被前置和后置包括在里面.
和直接使用JDK来实现动态代理相比,其实用spring的AOP的话还是很简单的.
6.整合Mybatis
首先需要导包:
- Mybatis的所有包:MySql,mybatis
- 导入spring的所有包:spring-jdbc,spring-webmvc,spring-test,aspectjweaver
- 导入mybatis和spring整合包:mybatis-spring
- 导入单元测试和lombok方便:junit,lombok
6.1 Mybatis-Spring简介
Mybatis-Spring有官方文档的:http://mybatis.org/spring/zh/index.html
6.1.1 什么是Mybatis-Spring?
简介中简要的介绍了什么是Mybatis-Spring:
- MyBatis-Spring 会帮助你将 MyBatis 代码无缝地整合到 Spring 中.
- 它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器
mapper
和SqlSession
并注入到bean
中 - 可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring
6.1.2 版本情况
Mybatis-Spring有两个版本,分别是2.0和1.3,注意一下使用的区别.
6.2 回顾Mybatis
导完包后,我们简单的写一个Mybatis的crud操作,然后通过这个简单的样例来修改整合到我们的Mybatis-Spring中.
-
首先确定接口和实体类
package com.admin.dao; import com.admin.pojo.User; import java.util.List; public interface UserMapper { // void add(); // void delete(); // void update(); List<User> selectAll(); }
实体类:用来
limbok
来简化代码package com.admin.pojo; import lombok.Data; @Data public class User { private String name; }
-
然后是配置
Mybatis-Config.xml
和mapper.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>
<properties resource="db.properties"/>
<settings>
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
<typeAliases>
<package name="com.admin.pojo"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="com/admin/dao/UserMapper.xml"/>
</mappers>
</configuration>
其中我用了db.properties
资源文件来引入数据库的变量
driver=com.mysql.cj.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC
username=root
password=123456
根据上面的接口,我们在resoueces
中创建目录结构相同的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.admin.dao.UserMapper">
<select id="selectAll" resultType="user">
select * from user;
</select>
</mapper>
-
写一个测试类来测试是否成功连接
先写一个工具类来简化获取
SqlSession
代码package com.admin.utils; import org.apache.ibatis.io.Resources; import org.apache.ibatis.session.SqlSession; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.session.SqlSessionFactoryBuilder; import java.io.IOException; import java.io.InputStream; public class MybatisUtils { private static SqlSessionFactory factory; static { String resource = "mybatis-config.xml"; try { InputStream inputStream = Resources.getResourceAsStream(resource); factory = new SqlSessionFactoryBuilder().build(inputStream); } catch (IOException e) { e.printStackTrace(); } } public static SqlSession getSqlSession(){ return factory.openSession(); } }
然后测试类中调用测试即可.
package com.admin; import com.admin.dao.UserMapper; import com.admin.pojo.User; import com.admin.utils.MybatisUtils; import org.apache.ibatis.session.SqlSession; import org.junit.Test; import java.util.List; public class MyTest { @Test public void test1(){ SqlSession sqlSession = MybatisUtils.getSqlSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); List<User> userList = userMapper.selectAll(); for (User user : userList) { System.out.println(user); } sqlSession.close(); } }
输出结果:由于我们配置的有简单的日志,因此可以看到获取数据的过程.
Checking to see if class com.admin.pojo.User matches criteria [is assignable to Object] PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. PooledDataSource forcefully closed/removed all connections. Opening JDBC Connection Created connection 1906879951. Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@71a8adcf] ==> Preparing: select * from user; ==> Parameters: <== Columns: id, name <== Row: 1, a <== Row: 2, b <== Row: 3, c <== Total: 3 User(name=a) User(name=b) User(name=c) Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@71a8adcf] Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@71a8adcf] Returned connection 1906879951 to pool.
Nice~下面我们就参考官方的入门来改造一下这个Mybatis.
6.3 快速上手
按官方的来.
-
首先要再Spring中配置一个
SqlSessionFactory
和至少一个数据映射器类我们可以直接使用
SqlSessionFactoryBean
来创建SqlSessionFactory
不过需要先配置好数据源dataSource
.顺便创建出我们的
sqlSession
.<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource"> <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://localhost:3306/mybatis?useSSL=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC"/> <property name="username" value="root"/> <property name="password" value="123456"/> </bean> <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"/> <!-- 绑定mybatis配置文件 --> <property name="configLocation" value="classpath:mybatis-config.xml"/> <!-- 配置映射器mapper 当然也可以在mybatis-config中配置 --> <property name="mapperLocations" value="classpath:com/admin/dao/UserMapper.xml"/> </bean> <!--SqlSessionTemplate 就是我们使用的sqlSession--> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory"/> </bean>
此时,我们已经不需要工具类了,因为我们已经可以获得
SqlSession了
.就可以删掉获得SqlSession
的工具类了. -
创建
UserMapperImpl
实现类由于Spring中所有东西都在容器中被托管.为了我们能够直接调用接口中的方法,我们需要一个实现类来实现它
直接调用
sqlSession
中的getMapper
方法来通过Mybatis
的Mapper.xml
来实现具体的功能,我们要做的就是把功能绑定起来罢了.package com.admin.dao.Impl; import com.admin.dao.UserMapper; import com.admin.pojo.User; import org.mybatis.spring.SqlSessionTemplate; import java.util.List; public class UserMapperImpl implements UserMapper { private SqlSessionTemplate sqlSession; public void setSqlSession(SqlSessionTemplate sqlSession) { this.sqlSession = sqlSession; } public List<User> selectAll() { UserMapper mapper = sqlSession.getMapper(UserMapper.class); return mapper.selectAll(); } }
将这个
Impl
实现类注册到bean中<bean id="userMapper" class="com.admin.dao.Impl.UserMapperImpl"> <property name="sqlSession" ref="sqlSession"/> </bean>
-
测试类中通过Spring来调用方法
package com.admin; import com.admin.dao.UserMapper; import com.admin.pojo.User; import org.junit.Test; import org.springframework.context.ApplicationContext; import org.springframework.context.support.ClassPathXmlApplicationContext; public class MyTest { @Test public void test1(){ ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml"); UserMapper userMapper = context.getBean("userMapper", UserMapper.class); for (User user : userMapper.selectAll()) { System.out.println(user); } } }
输出结果:
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@7642df8f] will not be managed by Spring ==> Preparing: select * from user; ==> Parameters: <== Columns: id, name <== Row: 1, a <== Row: 2, b <== Row: 3, c <== Total: 3 Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@791d1f8b] User(name=a) User(name=b) User(name=c)
可以看出来,改造还是挺成功的,我们进行了以下的修改
- 新加入一个
applicationContext.xml
的Spring配置类 mybatis-config.xml
中的数据源,mappers
删掉了.- 获取
SqlSession
的工具类删掉了. - 新增了一个接口的实现类
6.4 快速小结
但是我们调用的时候就不再需要sqlSession
这些东西了,spring都在后面帮我们做好了,我们显式的使用方法只需要通过IoC
容器就好了.
大家可能此处有些小疑问:为什么要多写一个impl
呢,我们在测试类中一样可以用sqlsession
来获取mapper
来使用方法啊.
(其实这就是我当时想到的一个小问题,不过这东西稍微想一下就清楚了)
我们来看一下如果不写impl
会表现出什么效果:
我们也不需要多改什么,只是不用impl
罢了,修改以下测试类,使用sqlSession
来获取方法.
public class MyTest {
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
//UserMapper userMapper = context.getBean("userMapper", UserMapper.class);
SqlSessionTemplate sqlSession = context.getBean("sqlSession",SqlSessionTemplate.class);
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
for (User user : userMapper.selectAll()) {
System.out.println(user);
}
}
}
和之前的比较以下,是不是多了很多东西,我们在测试类中还要调用dao
层的东西.这非常不利于我们的开发.
如果将他们分开,dao
专注dao
的事情,测试类只用测试就好了,其他层各司其职.可以更加简化.而且impl
中也没有多写什么,只是把测试类中的代码放到了impl
里面,我们以后测试类中就不用重复写了.这也方便了很多.
总之,尽量减少耦合.
6.5 通过继承SqlSessionDaoSupport来实现impl
使用SqlSession
这一个标签中提到了另一种使用方法:http://mybatis.org/spring/zh/sqlsession.html
直接在impl
中继承一个SqlSessionDaoSupport
,然后就可以直接通过getSqlSession()
方法获得SqlSessionTemplate
了.然后就是正常的方法了.
这个类中不需要显式的注入SqlSession
.
package com.admin.dao.Impl;
import com.admin.dao.UserMapper;
import com.admin.pojo.User;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import java.util.List;
public class UserMapperImpl2 extends SqlSessionDaoSupport implements UserMapper {
public List<User> selectAll() {
return getSqlSession().getMapper(UserMapper.class).selectAll();
}
}
同样写完之后需要到Ioc
中去配置对应的bean
.虽然这个类中没有构造和set方法,但是继承的SqlSessionDaoSupport
中有一个set方法.
通过这个set方法我们可以知道,传入Factory
和Template
都是可以的.
<bean id="userMapper2" class="com.admin.dao.Impl.UserMapperImpl2">
<property name="sqlSessionFactory" ref="sqlSessionFactory"/>
</bean>
在测试类中测试效果相同
package com.admin;
import com.admin.dao.UserMapper;
import com.admin.pojo.User;
import org.junit.Test;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
public class MyTest {
@Test
public void test1(){
ApplicationContext context = new ClassPathXmlApplicationContext("applicationContext.xml");
UserMapper userMapper = context.getBean("userMapper2", UserMapper.class);
for (User user : userMapper.selectAll()) {
System.out.println(user);
}
}
}
/*
JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@68be8808] will not be managed by Spring
==> Preparing: select * from user;
==> Parameters:
<== Columns: id, name
<== Row: 1, a
<== Row: 2, b
<== Row: 3, c
<== Total: 3
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@3336e6b6]
User(name=a)
User(name=b)
User(name=c)
*/
7.声明式事务
7.1 回顾事务
事务是什么:
- 把一组业务当成一个业务来做,要么都成功要么都失败
- 事务在项目中非常重要,涉及到数据的一致性问题!非常重要!
- 确保完整性和一致性.
事务的ACID原则
- 原子性(Atomicity):操作这些指令时,要么全部执行成功,要么全部不执行。只要其中一个指令执行失败,所有的指令都执行失败,数据进行回滚,回到执行指令前的数据状态.
- **一致性(Consistency)😗*事务的执行使数据从一个状态转换为另一个状态,但是对于整个数据的完整性保持稳定.
- 隔离性(Isolation):隔离性是当多个用户并发访问数据库时,比如操作同一张表时,数据库为每一个用户开启的事务,不能被其他事务的操作所干扰,多个并发事务之间要相互隔离.
- **持久性(Durability)😗*当事务正确完成后,它对于数据的改变是永久性的.无论发生什么都不会再改变了
7.2 Spring中的事务管理
7.2.1 声明式事务
spring给我们已经整理好了所有的操作.我们只需要再xml中配置就行了.
使用Spring的事务管理,直接调用.我们不需要编写事务操作.
7.2.1.1 方法一
<!--配置声明式事务-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource" />
</bean>
<!--结合AOP实现事务织入-->
<!--配置事务通知-->
<tx:advice id="txAdvice" transaction-manager="transactionManager">
<!--给那些方法配置事务配置事务的-->
<tx:attributes>
<tx:method name="add"/>
<tx:method name="delete"/>
<tx:method name="update" propagation="REQUIRED"/>
<tx:method name="query" read-only="true"/>
<tx:method name="*"/>
</tx:attributes>
</tx:advice>
<!--配置事务的切入-->
<aop:config>
<aop:pointcut id="txPointCut" expression="execution(* com.admin.dao.*.*(..))"/>
<aop:advisor advice-ref="txAdvice" pointcut-ref="txPointCut"/>
</aop:config>
修改以下方法测试一下:
package com.admin.dao.Impl;
import com.admin.dao.UserMapper;
import com.admin.pojo.User;
import org.apache.ibatis.session.SqlSession;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import java.util.List;
public class UserMapperImpl extends SqlSessionDaoSupport implements UserMapper {
public List<User> selectAll() {
UserMapper userMapper = getSqlSession().getMapper(UserMapper.class);
userMapper.deleteUserById(4);
userMapper.addUser(new User(5,"e"));
return getSqlSession().getMapper(UserMapper.class).selectAll();
}
public void addUser(User user) {
getSqlSession().getMapper(UserMapper.class).addUser(user);
}
public void deleteUserById(int id) {
getSqlSession().getMapper(UserMapper.class).deleteUserById(id);
}
}
修改一下再看看.发现已经被事务管理了.
7.2.1.2 方法二:
而官方的操作是另一种方法,通过直接配置在方法中.
-
创建
DataSourceTransactionManager
对象. -
使用Spring的事务名命空间或者
JtaTransactionManagerFactoryBean
.使用一个子类或者由容器指定一个子类作为事务管理器
-
在这个配置中,MyBatis 将会和其它由容器管理事务配置的 Spring 事务资源一样。Spring 会自动使用任何一个存在的容器事务管理器,并注入一个
SqlSession
。如果没有正在进行的事务,而基于事务配置需要一个新的事务的时候,Spring 会开启一个新的由容器管理的事务。注意,如果你想使用由容器管理的事务,而不想使用 Spring 的事务管理,你就不能配置任何的 Spring 事务管理器。并必须配置
SqlSessionFactoryBean
以使用基本的 MyBatis 的ManagedTransactionFactory
:
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<constructor-arg ref="dataSource" />
</bean>
<tx:jta-transaction-manager />
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="transactionFactory">
<bean class="org.apache.ibatis.transaction.managed.ManagedTransactionFactory" />
</property>
</bean>
7.2.2 编程式事务
用的似乎不多,多少抄一下把.留个印象.
MyBatis 的 SqlSession
提供几个方法来在代码中处理事务。但是当使用 MyBatis-Spring 时,你的 bean 将会注入由 Spring 管理的 SqlSession
或映射器。也就是说,Spring 总是为你处理了事务。
你不能在 Spring 管理的 SqlSession
上调用 SqlSession.commit()
,SqlSession.rollback()
或 SqlSession.close()
方法。如果这样做了,就会抛出 UnsupportedOperationException
异常。在使用注入的映射器时,这些方法也不会暴露出来。
无论 JDBC 连接是否设置为自动提交,调用 SqlSession
数据方法或在 Spring 事务之外调用任何在映射器中方法,事务都将会自动被提交。
如果你想编程式地控制事务,请参考 the Spring reference document(Data Access -Programmatic transaction management-)。下面的代码展示了如何使用 PlatformTransactionManager
手工管理事务。
TransactionStatus txStatus =
transactionManager.getTransaction(new DefaultTransactionDefinition());
try {
userMapper.insertUser(user);
} catch (Exception e) {
transactionManager.rollback(txStatus);
throw e;
}
transactionManager.commit(txStatus);
在使用 TransactionTemplate
的时候,可以省略对 commit
和 rollback
方法的调用。
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.execute(txStatus -> {
userMapper.insertUser(user);
return null;
});
注意:虽然这段代码使用的是一个映射器,但换成 SqlSession 也是可以工作的。