banner
NEWS LETTER

Spring笔记

Scroll down

image-20230222231348077

.Spring

Spring Framework系统架构

核心概念

  • IoC( Inversion of Control )控制反转:使用对象时,不通过程序创建,而是通过在外部的IoC容器直接创建,这种行为叫做控制反转

​ IoC负责对象的创建、初始化等一一系列的工作,被创建或被管理的对象在IoC容器中被通称为“Bean”

  • DI(Dependency Injection)依赖注入

​ 在容器中建立bean与bean之间的依赖关系的整个过程,称为依赖注入

​ 就是说两个bean之间有依赖关系DI会自动绑好这个关系

最终效果

​ 使用对象时不仅可以直接从IoC容器中获取,而且获取到的bean已经绑定好了所有依赖关系

IoC和DI基本使用

使用“详情见

Spring使用

  1. 在pom.xml中配置
1
2
3
4
5
6
7
<dependencies>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>5.2.1.RELEASE</version>
</dependency>
</dependencies>
  1. 在resources中右击创建applicationContext.xml

IoC使用

  1. 在dao.impl和service.impl中分别创建java类BookDaoImpl.java和BookServiceImpl.java

    BookDaoImpl.java

    1
    2
    3
    4
    5
    6
    public class BookDaoImpl implements BookDao {
    @Override
    public void save() {
    System.out.println("book dao save ...");
    }
    }

    BookServiceImpl.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    public class BookServiceImpl implements BookService {
    // 5.删除业务层中使用new的方法创建的dao对象
    public BookDao bookDao;
    @Override
    public void save() {
    System.out.println("book service save ...");
    bookDao.save();
    }
    // 6.提供对应的set方法
    public void setBookDao(BookDao bookDao){
    this.bookDao=bookDao;
    }
    }
  2. 创建接口BookDao和BookService

    BookDao

    1
    2
    3
    public interface BookDao {
    public abstract void save();
    }

    BookService

    1
    2
    3
    public interface BookService {
    public abstract void save();
    }
  3. 在applicationContext.xml中添加

配置bean

bean标签表示配置bean

id属性表示给bean起名字

class属性表示给bean定义类型

1
2
3
4
<bean id="bookDao" class="dao.impl.BookDaoImpl"/>
<bean id="bookService" class="service.impl.BookServiceImpl">

</bean>
  1. 在运行界面写获取IoC容器
1
ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");
  1. 获取&使用bean
1
2
BookDao bookDao=(BookDao) ctx.getBean("bookDao");
bookDao.save();

返回值:book dao save …

DI使用

1
2
3
4
5
6
7
8
9
10
11
//    5.删除业务层中使用new的方法创建的dao对象
public BookDao bookDao;
@Override
public void save() {
System.out.println("book service save ...");
bookDao.save();
}
// 6.提供对应的set方法
public void setBookDao(BookDao bookDao){
this.bookDao=bookDao;
}

在applicationContext.xml中设置,将bookDao注入bookService

1
2
3
4
5
6
7
8
9

<bean id="bookDao" class="dao.impl.BookDaoImpl"/>
<bean id="bookService" class="service.impl.BookServiceImpl">
<!-- 7.配置service和dao的关系-->
<!-- property标签表示配置当前bean的属性-->
<!-- name属性表示配置哪个具体的属性-->
<!-- ref属性表示参照哪一个bean-->
<property name="bookDao" ref="bookDao" />
</bean>

bean配置

bean的基础配置

  1. 给bean起个别名

name属性:别名,可以定义多个,使用逗号、分号或空格分隔

1
2
3
4
<bean id="bookDao" name="dao bookDaoiml" class="dao.impl.BookDaoImpl"/>
<bean id="bookService" class="service.impl.BookServiceImpl">
<property name="bookDao" ref="dao" />
</bean>
  1. bean作用范围

scope属性:定义bean的作用范围,可选范围如下

singleton:单例(默认) 一个类只有一个实例

prototype:非单例

1
<bean id="bookDao" name="dao bookDaoiml" class="dao.impl.BookDaoImpl" scope="prototype"/>

bean实例化方式

通过构造方法实例化

bean本质上就是对象,String创建bean其实是使用构造方法完成的

bean只能默认调用无参构造方法

通过静态工厂实例化bean

静态工厂

1
2
3
4
5
6
public class OrderDaoFactory implements OrderDao{
public static OrderDao getOrderDao(){
System.out.println("factory setup...");
return new OrderDaoFactory();
}
}

配置

1
<bean id="orderDao" factory-method="getOrderDao" class="com.demo.factory.OrderDaoFactory"/>
实例工厂实例化bean

实例工厂

1
2
3
4
5
6
public class OrderDaoFactory implements OrderDao{
public OrderDao getOrderDao(){
System.out.println("factory setup...");
return new OrderDaoFactory();
}
}

配置

1
2
3
4
<bean id="orderDao" class="com.demo.factory.OrderDaoFactory"/>
=======================================================﹀

<bean id="userFactory" factory-method="getOrderDao" factory-bean="orderDao"/>

第二种实例

实力工厂

1
2
3
4
5
6
7
8
9
10
11
12
13
public class OrderDaoFactory implements OrderDao{
//getObject是spring提供的固定写法
public OrderDao getObject(){
return new OrderDaoFactory();
}
public Class<?> getObiectType(){
return OrderDao.class;
}
//设置是否为单例
public boolean isSingleton(){
return true
}
}

配置

1
<bean id="orderDao" class="com.demo.factory.OrderDaoFactory"/>

bean生命周期

  • 初始化容器

    1. 创建对象(分配地址)

    2. 执行构造方法

    3. 执行属性注入(set操作)

    4. 执行bean初始化方法

      第一种:通过属性值设置生命周期钩子

      BookDaoImpl.java

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      public class BookDaoImpl implements BookDao{
      @Override
      public void save() {
      System.out.println("book dao save ...");
      }
      public void init(){
      System.out.println("init...");
      }
      public void destroy(){
      System.out.println("destroy...");
      }
      }

      applicationContext.xml

      1
      <bean id="bookDao" class="dao.impl.BookDaoImpl" init-method="init" destroy-method="destroy"/>

      第二种:通过Spring接口设置生命周期钩子

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      public class BookDaoImpl implements BookDao,InitializingBean, DisposableBean {
      public BookDaoImpl(){

      }
      @Override
      public void save() {
      System.out.println("book dao save ...");
      }

      @Override
      public void afterPropertiesSet() throws Exception {
      System.out.println("init...");
      }

      @Override
      public void destroy() throws Exception {
      System.out.println("destroy...");
      }
      }

属性值注入

在注入时,ref用于引用类型,value用于简单类型

setting注入

为私有属性注入值有两种方式,分别是setting的简单注入,另一个就是使用构造方法

applicationContext.xml

1
2
3
<bean id="bookDao" class="dao.impl.BookDaoImpl">
<property name="connectionNumber" value="sql"/>
</bean>

BookDaoImpl.java

1
2
3
4
5
6
7
8
   private String connectionNumber;

public void setConnectionNumber(String connectionNumber) {
this.connectionNumber = connectionNumber;
}
public void save() {
System.out.println(connectionNumber); //返回“sql”
}

构造方法注入

applicationContext.xml

index设置构造函数第几个参数

1
2
3
4
5
6
7
<bean id="bookDao" class="dao.impl.BookDaoImpl">
<constructor-arg index="0" value="sql"/>
<constructor-arg index="1" value="100"/>
</bean>
<bean id="bookService" class="service.impl.BookServiceImpl">
<constructor-arg name="bookDao" ref="bookDao"/>
</bean>

BookDaoImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
private String connectionNumber;
private int num;

public BookDaoImpl(String s, int s2) {
this.connectionNumber=s;
this.num=s2;
}

@Override
public void save() {
System.out.println("book dao save ..."+connectionNumber+num);
}

属性值注入方法选择

  • 用setting注入

    • 可选依赖
    • 自己开发的推荐使用setting注入
  • 用构造器注入

    • 强制依赖
    • Spring框架推荐使用构造器方法,且第三方框架基本都是用Spring

自动装配

#所谓自动装配即不用指明两个对象的关系,由Spring自动完成

autowire属性设置使用什么方式进行装配,

byType:通过类型 byName:通过名字

例如:

applicationContext.xml

1
2
<bean id="bookDao" class="dao.impl.BookDaoImpl"/>
<bean id="bookService" class="service.impl.BookServiceImpl" autowire="byType"/>

注意:

  1. 自动装配只适用于引用类型,不能对简单类型进行操作
  2. 推荐使用按类型装配,必须保证容器中相同类型的bean唯一
  3. 不推荐使用byName,必须保证容器中的bean名称唯一,导致变量名和配置耦合度更高
  4. 自动装配优先级低于setting和构造方法

BookServiceImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
public class BookServiceImpl implements BookService {
// 5.删除业务层中使用new的方法创建的dao对象
private BookDao bookDao;

public void setBookDao(BookDao bookDao) {
this.bookDao = bookDao;
}
@Override
public void save() {
System.out.println("book service save...");
bookDao.save();
}
}

集合注入

applicationContext.xml

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
<bean id="bookDao" class="dao.impl.BookDaoImpl">
<property name="array">
<array>
<value>100</value>
<value>200</value>
<value>300</value>
</array>
</property>

<property name="list">
<list>
<value>itcase</value>
<value>itheima</value>
<value>boxuegu</value>
</list>
</property>

<property name="set">
<set>
<value>itcase</value>
<value>itheima</value>
<value>boxuegu</value>
</set>
</property>

<property name="map">
<map>
<entry key="country" value="china"/>
<entry key="province" value="henan"/>
<entry key="city" value="kaifeng"/>
</map>
</property>

<property name="properties">
<props>
<prop key="country" >china</prop>
<prop key="province" >henan</prop>
<prop key="city">kaifeng</prop>
</props>
</property>
</bean>

BookDaoImpl.java

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
public class BookDaoImpl implements BookDao {
private int[] array;

private List<String> list;

private Set<String> set;

private Map<String,String> map;

private Properties properties;

public void setArray(int[] array) {
this.array = array;
}

public void setList(List<String> list) {
this.list = list;
}

public void setSet(Set<String> set) {
this.set = set;
}

public void setMap(Map<String, String> map) {
this.map = map;
}

public void setProperties(Properties properties) {
this.properties = properties;
}

@Override
public void save() {
System.out.println("book dao save...");
System.out.println("遍历数组:"+ Arrays.toString(array));
System.out.println("遍历list"+list);
System.out.println("遍历set"+set);
System.out.println("遍历map"+map);
System.out.println("遍历properties"+properties);
}
}

容器

创建容器

方法一:类路径加载配置文件

1
ApplicationContext ctx=new ClassPathXmlApplicationContext("applicationContext.xml");

方法二:文件路径加载配置文件

1
ApplicationContext ctx=new FileSystemXmlApplicationContext("C:\\applicationContext.xml");

方法三:加载多个配置文件

1
ApplicationContext ctx=new FileSystemXmlApplicationContext("bean1.xml","bean2.xml");

获取bean

方法一:使用bean名称获取

1
BookDao bookDao=(BookDao) ctx.getBean("bookDao");

方法二:使用bean名获取并获取类型

1
BookDao bookDao= ctx.getBean("bookDao",BookDao.class);

方法三:使用bean类型获取

1
BookDao bookDao= ctx.getBean(BookDao.class);

核心容器总结

bean

1
2
3
4
5
6
7
8
9
10
11
12
<bean
id="bookDao" bean的Id
name="dao bookDaoImpl daoImpl" bean别名
class="com,itheima.dao,impl,BookDaoImp1" bean类型,静态工厂类,FactoryBean类
scope="singleton" 控制bean的实例数量
init-method="init" 生命周期初始化方法
destroy-method"destory" 生命周期销毁方法
autowire="byType" 自动装配类型
factory-method="getInstance"
factory-bean="com.itheima.factory.BookDaoFactory'lazy-init="true"
bean工厂方法,应用于静态工厂或实例工厂实例工厂bean控制bean延迟加载
/>

依赖注入相关

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
<bean id="bookService" class="com.itheima.service.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"/>
//setter注入引用类型
<property name="bookDao" ref="bookDao" />
<property name="userDao" ref="userDao" />
//setter注入简单类型
<property name="msg" value="WARN" />
//setter注入集合类型
<property name="names">
//list集合
<list>
//集合注入简单类型
<value>itcast< /value>
//集合注入引用类型
<ref bean="dataSource" / >
</list>
</ property>
</ bean>

注解开发定义bean

1
2
3
4
5
6
7
8
//使用@component定义bean
@Component("bookDao")
public class BookDaoIml implement BookDao{

}

//在核心配置文件中通过组件扫描加载bean
<context:component-scan base-package="com.itheima"/>

String为了方便开发提供了@component的三个衍生注解

  1. @Conteoller:用于表现层bean定义
  2. @Service:用于业务层bean定义
  3. @Repository:用于数据层bean定义
1
2
3
4
5
6
7
8
9
@Repository("bookDao")
public class BookDaoImpl implements BookDao{

}

@Service
public class BookServiceImpl implements BookService{

}

而当使用三个衍生注解时也不能用单纯的名称了,而是要通过实体类BookDao.class

纯注解开发

在Spring3.0中开启了纯注解开发模式,使用java类来代替配置文件

第一步:删除applcationContext.xml文件

第二步:新建SpringConfig.java配置类

1
2
3
4
5
6
7
8
//设置此类为spring配置类
@Configurable

//设置注解扫描地址
@ComponentScan({"com.ithm.service","com.ithm.dao"})
public class SpringConfig {

}

第三步:设置加载配置文件类初始化容器

1
ApplicationContext ctx=new AnnotationConfigApplicationContext(SpringConfig.class);

bean的作用范围和生命周期

单例和非单例

1
2
//带参数的是设置为非单例
@Scope("prototype")

生命周期

通过注释@PostConstruct设置初始化

1
2
3
4
5
6
7
8
9
@PostConstruct
public void init(){

}

@PreDestory
public void destroy(){

}

注解依赖注入

引用类型的依赖注入

Service中需要使用dao层:

在dao对象上添加注解@Autowired就会自动匹配

1
2
3
4
5
6
7
8
9
10
public class BookServiceImpl implements BookService {
@Autowired
private BookDao bookDao;

@Override
public void save() {
System.out.println("run Service ...");
bookDao.save();
}
}

指定匹配注解:@Qualifier(“bookDao”)

注意:@Qualifier要配合@Autowired使用

简单数据类型的依赖注入

1
2
3
4
5
@Value("三国演义")
private String book;

@Value("15")
private int price;

使用资源文件为数据添加依赖

资源文件(放置在resources中):

1
name=ithm888;

配置类:

1
2
3
4
5
6
7
8
9
//设置此类为Spring配置类
@Configurable
//设置注解扫描地址
@ComponentScan("com.ithm")
//引入资源文件
@PropertySource("classpath:jdbc.properties")
public class SpringConfig {

}

Service层:

1
2
3
//将资源文件中的指定值注入    
@Value("${name}")
private String book;

注解开发管理第三方bean

在开发时时常会用到第三方的bean,而每一个bean都会有对应的一个专用类管理

例如:

JdbcConfig.java

1
2
3
4
5
6
7
8
public DataSource dataSource(){
DruidDataSource ds=new DruidDataSource();
ds.setDriverClassName("com.mysql.jdbc.Driver");
ds.setUrl("jdbc:mysql://localhost:3306/Spring_db");
ds.setUsername("root");
ds.setPassword("root");
return ds;
}

配置类:

1
2
3
4
5
6
7
8
9
10
//设置此类为Spring配置类
@Configurable
//设置注解扫描地址
@ComponentScan("com.ithm")
@PropertySource("jdbc.properties")
//将第三方bean的配置类引入
@Import(JdbcConfig.class)
public class SpringConfig {

}

运行:

1
2
3
4
5
6
7
8
public class run {
public static void main(String[] args) {
ApplicationContext ctx=new AnnotationConfigApplicationContext(SpringConfig.class);

DataSource dataSource=ctx.getBean(DataSource.class);
System.out.println(dataSource);
}
}

注解开发第三方Bean注入

简单类型

专属配置类中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class JdbcConfig {
@Value("com.mysql.jdbc.Driver")
private String driver;
@Value("jdbc:mysql://localhost:3306/Spring_db")
private String url;
@Value("root")
private String userName;
@Value("root")
private String passWord;

@Bean
public DataSource dataSource(){
DruidDataSource ds=new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(passWord);
return ds;
}
}

引用类型

在注入引用类型时只需要为bean定义方法设置形参

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public class JdbcConfig {
@Value("com.mysql.jdbc.Driver")
private String driver;
@Value("jdbc:mysql://localhost:3306/Spring_db")
private String url;
@Value("root")
private String userName;
@Value("root")
private String passWord;

@Bean
public DataSource dataSource(BookDao bookDao){
DruidDataSource ds=new DruidDataSource();
ds.setDriverClassName(driver);
ds.setUrl(url);
ds.setUsername(userName);
ds.setPassword(passWord);
return ds;
}
}

整合——XML和注解开发

image-20230301163310820

AOP

AOP简介

AOP:面向切面编程,一种编程范式,指导开发者如何组织程序结构

作用:在不惊动原始设计的情况下为其进行功能增强

AOP核心概念

image-20230302115437654

AOP入门案例

  1. 导入AOP相关依赖

    1
    2
    3
    4
    5
    <dependency>
    <groupId>org.aspectj</groupId>
    <artifactId>aspectjweaver</artifactId>
    <version>1.9.4</version>
    </dependency>
  2. 定义dao接口和实现类

    1
    2
    3
    4
    5
    6
    7
    //dao接口
    public interface BookDao {
    void save();
    void update();
    void delete();
    void select();
    }
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    //实现类
    @Repository
    public class BookDaoImpl implements BookDao {
    @Override
    public void save() {
    System.out.println("book dao save...");
    }

    @Override
    public void update() {
    System.out.println("book dao update...");
    }

    @Override
    public void delete() {
    System.out.println("book dao delete...");
    }

    @Override
    public void select() {
    System.out.println("book dao select...");
    }
    }
  3. 定义通知类

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    @Component
    @Aspect
    public class MyAdvice {
    //定义切入点,当运行到updata这个方法的时候加功能
    @Pointcut("execution(void com.itheima.dao.BookDao.update())")
    private void pt() {
    }

    //将method添加到上面的切入点pt()中
    @Before("pt()")
    public void method() {
    Long startTime = System.currentTimeMillis();
    for (int i = 0; i < 10000; i++) {
    System.out.println("success");
    }
    Long endTime = System.currentTimeMillis();

    Long totalTime = endTime - startTime;
    System.out.println("执行万次消耗时间" + totalTime + "ms");
    }
    }
  4. 定义通知类受Spring容器管理,并定义当前类为切面类

​ 注解@Aspect(上面代码的第二行)

  1. 设置Spring对AOP的支持

    1
    2
    3
    4
    5

    @EnableAspectJAutoProxy
    public class SpringConfig {

    }

    AOP切入点表达式

    可以使用通配符描述切入点,快速描述

      • :单个独立的任意符号,可以独立出现,已可以作为前缀或者后缀的匹配符出现
      1
      execture(public * com.itheima.*.UserService.find*(*))

      匹配com.itheima包下的任意包中的UserService类或接口中所有find开头的带有一个参数的方法

    • .. :多个连续的任意符号,可以独立出现,常用于简化包与参数的书写

      1
      execture(public User com...UserService.findById(..))

      匹配com包下的任意包中的UserService类或接口中所有名称为findById的方法

AOP通知类型

@Before:在原方法之前执行

1
2
3
4
@Before("pt()")
public void qian() {
System.out.println("我在前!!!");
}

@After:在原方法之后执行

1
2
3
4
@After("pt()")
public void hou(){
System.out.println("我在后!!!");
}

@Around

1
2
3
4
5
6
7
8
@Around("pt()")
public Object rao(ProceedingJoinPoint pjp) throws Throwable {
System.out.println("我将带头冲锋");
//用来区分原方法之前执行什么之后执行什么
Object ret=pjp.proceed();
System.out.println("我将掩护撤退");
return ret;
}

AOP通知获取数据

日常使用时,经常会用到给的数值需要通过加工或改变,因此AOP中可以通过切面来进行数据的更改和加工

@Before和@After

1
2
3
4
5
6
@Before("pt()")
public void qian(JoinPoint jp) { /*JoinPoint参数实现拿取参数*/
Object[] args=jp.getArgs();
System.out.println(Arrays.toString(args));
System.out.println("我在前!!!");
}

@Around

1
2
3
4
5
6
7
8
9
10
11
12
13
@Around("pt()")
//ProceedingJoinPoint继承于JoinPoint因此可以直接使用getArgs属性
public Object rao(ProceedingJoinPoint pjp) throws Throwable {
Object[] args= pjp.getArgs();
System.out.println(Arrays.toString(args));
args[0]=666;

System.out.println("我将带头冲锋");
Object ret=pjp.proceed(args);
System.out.println("我将掩护撤退");

return ret;
}

Spring 事务

事务的基本原理

Spring事务的本质其实就是数据库对于事务的支持,当我们使用jdbc时就是利用java.sql.Connection对象完成对事务的提交

事务是一系列的动作,一旦其中有一个动作出现了错误,必须全部回滚。系统将事务中对数据库的所有已完成的操作进行撤销,避免了出现数据不一致而导致的错误。

服务层接口

1
2
3
4
5
public interface AccountService {
// 1.使用@Transactional在业务层接口上添加Spring事务管理
@Transactional
void transfer(String out,String in,Double money);
}

JDBC配置类

1
2
3
4
5
6
7
/*2.在配置类中配置事务管理器*/
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource){
DataSourceTransactionManager transactionManager=new DataSourceTransactionManager();
transactionManager.setDataSource(dataSource);
return transactionManager;
}

Spring配置类

1
2
3
4
5
/*3.告诉配置类使用注解式事务管理*/
@EnableTransactionManagement
public class SpringConfig {

}

事务相关配置

在@Transactional()中写

属性 作用 示例
readOnly 设置是否为只读事务 readOnly=true 只读事务
timeout 设置事务超时时间 timeout=-1(永不超时)
rollbackFor 设置事务回滚异常(class) rollbackFor={NullPointException.class}
propagation 设置事务传播行为 ……

在使用默认是一个出错所有事务都回滚,而这不能满足我们的要求

例如:银行转账模式,当我们取消转账时仍要添加一个日志说明交易取消这就要保证在sql语句不运行的基础上继续通过service层来添加日志

因此,有了设置事务传播行为的枚举

传播属性事务管理员事务协调员
REQUIRED(默认)开启T加入T
新建T2
REQUIRES_NEW开启T新建T2
新建T2
SUPPORTS开启T加入T
NOT_SUPPORTED开启T
MANDATORY开启T加入T
<font color="red">ERROR</font>
NEVER开启T<font color="red">ERROR</font>
NESTED设置savePoint,一旦事务回滚,事务将回滚到savePoint处,交给客户响应提交/回滚

例如:@Transactional(propagation = Propagation.REQUIRES_NEW)

Other Articles