Spring中事务你用对了吗

Spring中事务你用对了吗

背景

Spring中为JTA,JPA,Hibernate等事务API提供了一致性的编程模型,但是编程式事务需要编码支持,在实际中很少使用。所以Spring提供了声明式事务,
配合SpringBoot,我们可以通过@Transactional注解,轻松地实现事务的控制,让事务控制达到极简。注解事务固然方便,但是如果对它不够了解,很容易
留下坑,就我目前的项目中,有一些事务根本就没有生效。

开始

新建工程

引入H2 Database

spring.datasource.url=jdbc:h2:mem:testdb;DB_CLOSE_ON_EXIT=FALSE
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=root
spring.datasource.password=root
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.jpa.properties.show_sql=true
spring.jpa.properties.format_sql=true
spring.jpa.properties.use_sql_comments=true
spring.h2.console.enabled=true
spring.h2.console.path=/console
logging.level.org.springframework.orm.jpa=debug

都是一些基础的配置,这里使用了jpa,并且把日志级别设置成debug,为了更方便的观察事务的执行情况。

编写一个业务类

@Service
public class DataService {

    @Autowired
    private UserRepository userRepository;

    private final Logger logger = LoggerFactory.getLogger(DataService.class);

    @Transactional(rollbackFor = RuntimeException.class)
    public void saveA(String user) {
        logger.info("invoke saveA {}", user);
        User u = new User();
        u.setName(user);
        userRepository.save(u);
    }

    public void saveB(String user) {
        logger.info("invoke saveB {}", user);
        try {
            User u = new User();
            u.setName(user);
            this.saveAndRollback(user);
        } catch (RuntimeException e) {
            logger.warn("catch an exception in saveB()");
        }
    }

    public void findAll() {
        logger.info("print data:");
        userRepository.findAll().forEach(user -> logger.info(user.toString()));
    }

    @Transactional(rollbackFor = RuntimeException.class)
    public void saveAndRollback(String user) {
        logger.info("invoke saveAndRollback {}", user);
        User u = new User();
        u.setName(user);
        userRepository.save(u);
        throw new RuntimeException();
    }

}

验证栗子

@SpringBootApplication
@EnableTransactionManagement
public class Application implements ApplicationRunner {

    @Autowired
    private DataService dataService;
    private final Logger logger = LoggerFactory.getLogger(Application.class);

    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }

    @Override
    public void run(ApplicationArguments args) throws Exception {
        demo1();
    }

    private void demo1() {
        dataService.saveA("frank");//1
        dataService.findAll();
        try {
            dataService.saveAndRollback("frank");//2
        } catch (Exception e) {
            logger.warn("catch an save exception");
        }
        dataService.findAll();
        dataService.saveB("jack");//3
        dataService.findAll();
    }
}

代码1执行结果,上面是insert,下面是查询操作

2020-03-31 14:14:14.733 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [org.boot.transaction.service.DataService.saveA]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,-java.lang.RuntimeException
2020-03-31 14:14:14.733 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Opened new EntityManager [SessionImpl(1955991197<open>)] for JPA transaction
2020-03-31 14:14:14.740 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@4e4162bc]
2020-03-31 14:14:14.754  INFO 17477 --- [           main] o.boot.transaction.service.DataService   : invoke saveA frank
2020-03-31 14:14:14.758 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Found thread-bound EntityManager [SessionImpl(1955991197<open>)] for JPA transaction
2020-03-31 14:14:14.759 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Participating in existing transaction
2020-03-31 14:14:14.804 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Initiating transaction commit
2020-03-31 14:14:14.805 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Committing JPA transaction on EntityManager [SessionImpl(1955991197<open>)]
2020-03-31 14:14:14.822 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Closing JPA EntityManager [SessionImpl(1955991197<open>)] after transaction
print data:
2020-03-31 14:14:14.823 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly
2020-03-31 14:14:14.824 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Opened new EntityManager [SessionImpl(2045560071<open>)] for JPA transaction
2020-03-31 14:14:14.841 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@7e5efcab]
2020-03-31 14:14:15.059 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Initiating transaction commit
2020-03-31 14:14:15.060 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Committing JPA transaction on EntityManager [SessionImpl(2045560071<open>)]
2020-03-31 14:14:15.060 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Closing JPA EntityManager [SessionImpl(2045560071<open>)] after transaction
2020-03-31 14:14:15.061  INFO 17477 --- [           main] o.boot.transaction.service.DataService   : User{id=1, name='frank'}

这里可以看到插入成功了

代码2执行结果

2020-03-31 14:14:15.062  INFO 17477 --- [           main] o.boot.transaction.service.DataService   : invoke saveAndRollback frank
2020-03-31 14:14:15.062 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Found thread-bound EntityManager [SessionImpl(1796154990<open>)] for JPA transaction
2020-03-31 14:14:15.062 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Participating in existing transaction
2020-03-31 14:14:15.063 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Initiating transaction rollback
2020-03-31 14:14:15.063 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Rolling back JPA transaction on EntityManager [SessionImpl(1796154990<open>)]
2020-03-31 14:14:15.067 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Closing JPA EntityManager [SessionImpl(1796154990<open>)] after transaction
2020-03-31 14:14:15.068  WARN 17477 --- [           main] org.boot.transaction.Application         : catch an save exception
2020-03-31 14:14:15.068  INFO 17477 --- [           main] o.boot.transaction.service.DataService   : print data:
2020-03-31 14:14:15.068 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly
2020-03-31 14:14:15.069 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Opened new EntityManager [SessionImpl(1138190994<open>)] for JPA transaction
2020-03-31 14:14:15.069 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@4a2bf50f]
2020-03-31 14:14:15.076 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Initiating transaction commit
2020-03-31 14:14:15.076 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Committing JPA transaction on EntityManager [SessionImpl(1138190994<open>)]
2020-03-31 14:14:15.076 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Closing JPA EntityManager [SessionImpl(1138190994<open>)] after transaction
2020-03-31 14:14:15.076  INFO 17477 --- [           main] o.boot.transaction.service.DataService   : User{id=1, name='frank'}

从这里的结果可以看出,这时候数据没有插入,还是我们第一次添加的数据,说明事务生效了。

代码3执行结果

2020-03-31 14:14:15.076  INFO 17477 --- [           main] o.boot.transaction.service.DataService   : invoke saveB jack
2020-03-31 14:14:15.076  INFO 17477 --- [           main] o.boot.transaction.service.DataService   : invoke saveAndRollback jack
2020-03-31 14:14:15.077 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.save]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT
2020-03-31 14:14:15.077 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Opened new EntityManager [SessionImpl(1683617002<open>)] for JPA transaction
2020-03-31 14:14:15.077 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@740b9a50]
2020-03-31 14:14:15.077 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Initiating transaction commit
2020-03-31 14:14:15.077 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Committing JPA transaction on EntityManager [SessionImpl(1683617002<open>)]
2020-03-31 14:14:15.078 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Closing JPA EntityManager [SessionImpl(1683617002<open>)] after transaction
2020-03-31 14:14:15.078  WARN 17477 --- [           main] o.boot.transaction.service.DataService   : catch an exception in saveB()
2020-03-31 14:14:15.078  INFO 17477 --- [           main] o.boot.transaction.service.DataService   : print data:
2020-03-31 14:14:15.078 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly
2020-03-31 14:14:15.079 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Opened new EntityManager [SessionImpl(589094312<open>)] for JPA transaction
2020-03-31 14:14:15.079 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@6b70d1fb]
2020-03-31 14:14:15.080 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Initiating transaction commit
2020-03-31 14:14:15.080 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Committing JPA transaction on EntityManager [SessionImpl(589094312<open>)]
2020-03-31 14:14:15.081 DEBUG 17477 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Closing JPA EntityManager [SessionImpl(589094312<open>)] after transaction
2020-03-31 14:14:15.081  INFO 17477 --- [           main] o.boot.transaction.service.DataService   : User{id=1, name='frank'}
2020-03-31 14:14:15.081  INFO 17477 --- [           main] o.boot.transaction.service.DataService   : User{id=3, name='jack'}

这里看到最后的查询结果有两条数据,说明这里虽然产生了异常,但是数据没有回滚,说明事务没有生效。

分析原因

从代码层面上看,代码2和代码3的区别是:代码2是直接调用的带@Transactional注解的方法(saveAndRollback),而代码3调用的方法(saveB)没有@Transactional注解,在saveB中调用了saveAndRollback方法,这属于内部调用,也就是通过this去调用对象的方法。而Spring的事务是通过AOP实现的,AOP会在加了事务注解的方法上进行增强,而Spring实现AOP主要是通过动态代理的方式,所以Spring做事务增强是在代理类上面做的增强,而我们用this去调用原来的方法,是没有做增强的,所以事务也就不会生效。

解决方法也很简单,只有被AOP增强过的类事务才会生效,有三种:

  1. 注入DataService本身调用
  2. 通过ApplicationContext拿到bean之后调用
  3. 使用AopContext获取到代理类调用

所以我们在DataService中增加如下方法:

@Transactional(rollbackFor = RuntimeException.class)
public void saveAndRollback(String user) &#123;
    logger.info("invoke saveAndRollback &#123;&#125;", user);
    User u = new User();
    u.setName(user);
    userRepository.save(u);
    throw new RuntimeException();
&#125;

public void invokeSelf(String user) &#123;
    try &#123;
        dataService.saveAndRollback(user);
    &#125; catch (RuntimeException e) &#123;
        logger.warn("catch an exception in invokeSelf()");
    &#125;
&#125;

public void invokeWithApplicationContext(String user) &#123;
    try &#123;
        ((DataService) applicationContext.getBean("dataService")).saveAndRollback(user);
    &#125; catch (RuntimeException e) &#123;
        logger.warn("catch an exception in invokeWithApplicationContext()");
    &#125;
&#125;

public void invokeWithAop(String user) &#123;
    try &#123;
        // 需要把@EnableAspectJAutoProxy注解中的(exposeProxy = true)
        ((DataService) AopContext.currentProxy()).saveAndRollback(user);
    &#125; catch (RuntimeException e) &#123;
        logger.warn("catch an exception in invokeWithAop()");
    &#125;
&#125;

再来运行看看结果

dataService.saveA("frank");
dataService.invokeSelf("frank");
dataService.findAll();
dataService.invokeWithApplicationContext("tom");
dataService.findAll();
dataService.invokeWithAop("jack");
dataService.findAll();

可以看到日志中,出现异常的地方出现了rollback字样,说明事务都生效了

2020-03-31 18:02:09.622 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Opened new EntityManager [SessionImpl(1779787990<open>)] for JPA transaction
2020-03-31 18:02:09.645 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@411fa0ce]
2020-03-31 18:02:10.011 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Initiating transaction commit
2020-03-31 18:02:10.012 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Committing JPA transaction on EntityManager [SessionImpl(1779787990<open>)]
2020-03-31 18:02:10.012 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Closing JPA EntityManager [SessionImpl(1779787990<open>)] after transaction
2020-03-31 18:02:10.013  INFO 20635 --- [           main] o.boot.transaction.service.DataService   : User{id=1, name='frank'}
2020-03-31 18:02:10.013 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [org.boot.transaction.service.DataService.saveAndRollback]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,-java.lang.RuntimeException
2020-03-31 18:02:10.013 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Opened new EntityManager [SessionImpl(1478269879<open>)] for JPA transaction
2020-03-31 18:02:10.013 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@138f0661]
2020-03-31 18:02:10.014  INFO 20635 --- [           main] o.boot.transaction.service.DataService   : invoke saveAndRollback tom
2020-03-31 18:02:10.014 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Found thread-bound EntityManager [SessionImpl(1478269879<open>)] for JPA transaction
2020-03-31 18:02:10.014 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Participating in existing transaction
2020-03-31 18:02:10.015 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Initiating transaction rollback
2020-03-31 18:02:10.016 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Rolling back JPA transaction on EntityManager [SessionImpl(1478269879<open>)]
2020-03-31 18:02:10.018 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Closing JPA EntityManager [SessionImpl(1478269879<open>)] after transaction
2020-03-31 18:02:10.018  WARN 20635 --- [           main] o.boot.transaction.service.DataService   : catch an exception in invokeWithApplicationContext()
2020-03-31 18:02:10.018  INFO 20635 --- [           main] o.boot.transaction.service.DataService   : print data:
2020-03-31 18:02:10.018 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly
2020-03-31 18:02:10.019 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Opened new EntityManager [SessionImpl(321795476<open>)] for JPA transaction
2020-03-31 18:02:10.019 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@4f235107]
2020-03-31 18:02:10.020 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Initiating transaction commit
2020-03-31 18:02:10.020 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Committing JPA transaction on EntityManager [SessionImpl(321795476<open>)]
2020-03-31 18:02:10.020 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Closing JPA EntityManager [SessionImpl(321795476<open>)] after transaction
2020-03-31 18:02:10.020  INFO 20635 --- [           main] o.boot.transaction.service.DataService   : User{id=1, name='frank'}
2020-03-31 18:02:10.020 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [org.boot.transaction.service.DataService.saveAndRollback]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,-java.lang.RuntimeException
2020-03-31 18:02:10.020 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Opened new EntityManager [SessionImpl(977952572<open>)] for JPA transaction
2020-03-31 18:02:10.021 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@4f3356c0]
2020-03-31 18:02:10.021  INFO 20635 --- [           main] o.boot.transaction.service.DataService   : invoke saveAndRollback jack
2020-03-31 18:02:10.021 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Found thread-bound EntityManager [SessionImpl(977952572<open>)] for JPA transaction
2020-03-31 18:02:10.021 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Participating in existing transaction
2020-03-31 18:02:10.022 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Initiating transaction rollback
2020-03-31 18:02:10.022 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Rolling back JPA transaction on EntityManager [SessionImpl(977952572<open>)]
2020-03-31 18:02:10.023 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Closing JPA EntityManager [SessionImpl(977952572<open>)] after transaction
2020-03-31 18:02:10.023  WARN 20635 --- [           main] o.boot.transaction.service.DataService   : catch an exception in invokeWithAop()
2020-03-31 18:02:10.023  INFO 20635 --- [           main] o.boot.transaction.service.DataService   : print data:
2020-03-31 18:02:10.023 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findAll]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly
2020-03-31 18:02:10.023 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Opened new EntityManager [SessionImpl(1367900185<open>)] for JPA transaction
2020-03-31 18:02:10.023 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@6f50d55c]
2020-03-31 18:02:10.024 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Initiating transaction commit
2020-03-31 18:02:10.024 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Committing JPA transaction on EntityManager [SessionImpl(1367900185<open>)]
2020-03-31 18:02:10.025 DEBUG 20635 --- [           main] o.s.orm.jpa.JpaTransactionManager        : Closing JPA EntityManager [SessionImpl(1367900185<open>)] after transaction
2020-03-31 18:02:10.025  INFO 20635 --- [           main] o.boot.transaction.service.DataService   : User{id=1, name='frank'}

小结

在 Spring 的 AOP 代理下,只有目标方法由外部调用,目标方法才由 Spring 生成的代理对象来管理,这会造成自调用问题。若同一类中的其他没有@Transactional 注解的方法内部调用有@Transactional 注解的方法,有@Transactional 注解的方法的事务被忽略,不会发生回滚。解决方法上面已经列出,并且放在了github上。所以平时在内部调用带有事务的方法时,要小心一点。

还有以下常见场景事务也会失效

  1. @Transactional注解到非public方法上
  2. 如果在事务中抛出了未检查异常(继承自 RuntimeException的异常,也就是说如果有IO操作,抛出了IOException,事务是不会回滚的)或者Error,则 Spring 将回滚事务;除此之外,Spring 不会回滚事务。注意,这里说的是需要抛出,如果没有抛出,比如异常被catch吞了,事务是不会回滚的。

   转载规则

本文不允许转载。
 上一篇
SpringBoot中的参数校验 SpringBoot中的参数校验
SpringBoot中的参数校验 背景 为了保证数据的正确性, 避免埋坑, 参数校验在日常业务开发中用得非常多, 在Spring中用得最多的就是使用JSR303– Bean Validation规范提供的校验, Hibernate Val
下一篇 
在SpringMVC中优雅的拼接URL 在SpringMVC中优雅的拼接URL
在SpringMVC中优雅的拼接URL背景 在日常开发中常常会遇到拼接URL的情况,大多数时候可以手动拼接字符串来达到目的,但是这样的方式不够优雅,同时容易出错。其实SpringMVC中已经给我们提供好了工具,这个工具就是UriCompon