Spring事务和多表回滚

9 浏览
0 Comments

Spring事务和多表回滚

我在使用DAO进行事务管理方面遇到了困难。情景是创建一个包含报价行列表和一个客户的新报价。如果客户不存在,它将在客户表中插入。我的代码结构如下:

@Entity
@Table(name = "quote")
public class Quote {
  @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
   //....属性
   @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "customer_id", nullable = true)
    private Customer customer;
    @OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE, CascadeType.REFRESH}, mappedBy = "quote")
    @Cascade({org.hibernate.annotations.CascadeType.SAVE_UPDATE,
            org.hibernate.annotations.CascadeType.DELETE,
            org.hibernate.annotations.CascadeType.MERGE,
            org.hibernate.annotations.CascadeType.PERSIST,
            org.hibernate.annotations.CascadeType.DELETE_ORPHAN})
    private Set quoteLines;
    //... 方法
}
@Entity
@Table(name = "quote_line")
public class QuoteLine {
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    //....属性
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "quote_id", nullable = false)
    private Quote quote;
    //... 方法
}
public interface QuoteDao extends CrudRepository {
    //... 方法
}
public interface QuoteLineDao extends CrudRepository {
    //... 方法
}
public interface CustomerDao extends CrudRepository {
    //... 方法
}
@Service
public class QuoteService{
    @Autowired
    private QuoteDao quoteDao;
    @Autowired
    private QuoteLineDao quoteLineDao;
    @Autowired
    private CustomerDao customerDao;
    @Transactional(propagation=Propagation.REQUIRED, rollbackFor=Exception.class)
    public Quote save(Quote quote) {
        try{
            quoteLineDao.delete(new Long(44));
            System.out.println("°°°°°°°°°°°°°°°°°°Line 44 deleted");
            return  quoteDao.save(quote); 
        } catch(Exception e){
            Logger.getLogger(QuoteService.class).log(Logger.Level.FATAL, e);
        }
        return null;
    }
}
@EnableAutoConfiguration
@Configuration
@EnableTransactionManagement
@ComponentScan
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}
@EnableWebSecurity
@Configuration
@EnableTransactionManagement
@Order(1)
public class StatelessAuthenticationSecurityConfig extends WebSecurityConfigurerAdapter {
    @Autowired
    private UserDetailsService userDetailsService;
    @Autowired
    private TokenAuthenticationService tokenAuthenticationService;
    public StatelessAuthenticationSecurityConfig() {
        super(true);
    }
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
                .exceptionHandling().and()
                .anonymous().and()
                .servletApi().and()
                .headers().cacheControl().and()
                .authorizeRequests()
                //允许匿名资源请求
                .antMatchers("/").permitAll()
                .antMatchers("/favicon.ico").permitAll()
                .antMatchers("/resources/**").permitAll()
                //允许匿名登录POST请求
                .antMatchers(HttpMethod.POST, "/api/login").permitAll()
                                //允许匿名客户POST请求
                //.antMatchers(HttpMethod.POST, "/api/customer/**").permitAll()
                                .antMatchers("/api/**").hasRole("USER")
                                .antMatchers("/api/invoice/**").permitAll()
                                //定义只有管理员才能访问的API区域
                .antMatchers("/api/admin/**").hasRole("ADMIN")
                                //定义只有超级管理员才能访问的API区域
                .antMatchers("/api/superadmin/**").hasRole("SUPER_ADMIN")
                //允许匿名GET请求的API
                //.antMatchers(HttpMethod.GET, "/api/**").permitAll()
                //其他请求需要身份验证
                .anyRequest().hasRole("USER").and()             
                //自定义基于JSON的身份验证
                .addFilterBefore(new StatelessLoginFilter("/api/login", tokenAuthenticationService, userDetailsService, authenticationManager()), UsernamePasswordAuthenticationFilter.class)
                //自定义基于令牌的身份验证
                .addFilterBefore(new StatelessAuthenticationFilter(tokenAuthenticationService), UsernamePasswordAuthenticationFilter.class);
    }
    @Bean
    @Override
    public AuthenticationManager authenticationManagerBean() throws Exception {
        return super.authenticationManagerBean();
    }
    @Override
    protected void configure(AuthenticationManagerBuilder auth) throws Exception {
        auth.userDetailsService(userDetailsService).passwordEncoder(new BCryptPasswordEncoder());
                //auth.jdbcAuthentication().dataSource(null).usersByUsernameQuery("").authoritiesByUsernameQuery("");
    }
    @Override
    protected UserDetailsService userDetailsService() {
        return userDetailsService;
    }
}

在调试模式下,我只有两个变量:

1- this(QuoteService)

2- quote

以下是日志:

---------------------------------

==授予角色ADMIN

==授予角色USER

Hibernate: select quoteline0_.id as id1_6_0_, quoteline0_.position as position2_6_0_, quoteline0_.quote_id as quote_id4_6_0_, quoteline0_.line_id as line_5_6_0_, quoteline0_.title as title3_6_0_, quote1_.id as id1_4_1_, quote1_.account_id as account20_4_1_, quote1_.address_line1 as address_2_4_1_, quote1_.address_line2 as address_3_4_1_, quote1_.address_line3 as address_4_4_1_, quote1_.address_line4 as address_5_4_1_, quote1_.city as city6_4_1_, quote1_.company_name as company_7_4_1_, quote1_.country as country8_4_1_, quote1_.customer_id as custome21_4_1_, quote1_.date_accepted as date_acc9_4_1_, quote1_.date_created as date_cr10_4_1_, quote1_.date_validity as date_va11_4_1_, quote1_.email as email12_4_1_, quote1_.fax as fax13_4_1_, quote1_.name as name14_4_1_, quote1_.phone as phone15_4_1_, quote1_.postal_code as postal_16_4_1_, quote1_.reference as referen17_4_1_, quote1_.subject as subject18_4_1_, quote1_.total as total19_4_1_, customer2_.id as id1_1_2_, customer2_.account_id as account15_1_2_, customer2_.address_line1 as address_2_1_2_, customer2_.address_line2 as address_3_1_2_, customer2_.address_line3 as address_4_1_2_, customer2_.address_line4 as address_5_1_2_, customer2_.city as city6_1_2_, customer2_.company_name as company_7_1_2_, customer2_.country as country8_1_2_, customer2_.email as email9_1_2_, customer2_.fax as fax10_1_2_, customer2_.name as name11_1_2_, customer2_.phone as phone12_1_2_, customer2_.postal_code as postal_13_1_2_, customer2_.url as url14_1_2_, line3_.id as id1_9_3_, line3_.account_id as account_3_9_3_, line3_.title as title2_9_3_ from quote_line quoteline0_ inner join quote quote1_ on quoteline0_.quote_id=quote1_.id left outer join customer customer2_ on quote1_.customer_id=customer2_.id left outer join line line3_ on quoteline0_.line_id=line3_.id where quoteline0_.id=?

°°°°°°°°°°°°°°°°°°Line 44 deleted

Hibernate: insert into quote (account_id, address_line1, address_line2, address_line3, address_line4, city, company_name, country, customer_id, date_accepted, date_created, date_validity, email, fax, name, phone, postal_code, reference, subject, total) values (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)

Hibernate: insert into quote_line (position, quote_id, line_id, title) values (?, ?, ?, ?)

2015-12-22 13:40:46.068 WARN 3807 --- [nio-8080-exec-1] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: 1048, SQLState: 23000

2015-12-22 13:40:46.068 ERROR 3807 --- [nio-8080-exec-1] o.h.engine.jdbc.spi.SqlExceptionHelper : Column 'quote_id' cannot be null

2015-12-22 13:40:46.079 ERROR 3807 --- [nio-8080-exec-1] c.e4ms.artin.service.impl.QuoteService : org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement

2015-12-22 13:40:46.103 ERROR 3807 --- [nio-8080-exec-1] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.transaction.TransactionSystemException: Could not commit JPA transaction; nested exception is javax.persistence.RollbackException: Transaction marked as rollbackOnly] with root cause

javax.persistence.RollbackException: Transaction marked as rollbackOnly

at org.hibernate.jpa.internal.TransactionImpl.commit(TransactionImpl.java:74)

at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:515)

at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:757)

at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:726)

at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:496)

at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:276)

at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95)

at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)

at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:653)

at com.e4ms.artin.service.impl.QuoteService$$EnhancerBySpringCGLIB$$b74b9c5.save()

...

正如您所注意到的,消息“°°°°°°°°°°°°°°°°°°Line 44 deleted”被打印出来,但没有关于DELETE FROM hibernate查询的痕迹。

这段代码无法工作:使用customerDao和quoteLineDao的事务不会将对象持久化到数据库中。

我认为propagation=Propagation.REQUIRED会强制所有DAO使用相同的会话,因此不同的事务将被执行,如果发生错误,所有事务都将被回滚。

我找到的唯一解释(关于结果)是自动装配的DAO使用不同的会话。

我尝试了propagation=Propagation.SUPPORTS --> 事务被执行,但我无法回滚,因为SUPPORTS强制使用不同的会话。

您能解释为什么这不起作用,以及如何更正这个问题吗?

非常感谢!

0
0 Comments

(Spring事务和多表回滚)

在更新我的答案时:

  1. 您希望"public Quote save(Quote quote)"方法具有事务性。
  2. 当调用此方法时...事务在TransactionInterceptor中开始,并从代理中调用"public Quote save(Quote quote)"。
  3. 行"quoteLineDao.delete(new Long(44));"工作正常。
  4. 行"System.out.println("°°°°°°°°°°°°°°°°°°Line 44 deleted");"工作正常。
  5. 行"quoteDao.save(quote);"出现约束违规异常。事务标记为回滚。
  6. 您捕获并消耗了此异常,而不是传播异常
  7. 方法"public Quote save(Quote quote)"将返回null,因为行"return null;"
  8. 现在代码到达事务拦截器,因为此拦截器没有异常,它尝试提交,但事务已标记为回滚,因此失败。

解决方案:您不应该消耗异常,而应传播异常,因为您的事务需求。

更改如下,添加了throw语句。

try{
      quoteLineDao.delete(new Long(44));
      System.out.println("°°°°°°°°°°°°°°°°°°Line 44 deleted");
      return  quoteDao.save(quote); 
} catch(Exception e){
      Logger.getLogger(QuoteService.class).log(Logger.Level.ERROR, e);
      throw e;
}

逐步解释请参见此链接:

Could not commit JPA transaction: Transaction marked as rollbackOnly

感谢您的回答。我尝试过了,但仍然不起作用。我编辑了我的问题,添加了有关application.java和StatelessAuthenticationSecurityConfig.java的更多说明,以及日志。谢谢。

您能贴出完整的堆栈跟踪吗?

是的,它正在工作。注意。在类被**注释时,没有必要添加**。我认为这是在内部方法调用时自动完成的(!需要检查!)。再次感谢。

是的,由于Spring Boot的原因,我之前没有注意到。如果这个答案对您有用,请接受并点赞吗?

我正在使用Spring Boot创建一个API,用于更新两个表,因此如果更新之一失败,我需要一个回滚的方法。然而,如果在异常处理程序中消耗异常,意味着事务失败,我还需要API向用户返回错误消息。在这种情况下,我该怎么办?

我的问题的解决方案可以在这里找到。它与异常处理程序无关,而是与Spring无法拦截内部方法调用有关。

0