Spring transaction and rollback on multiple tables

走远了吗. 提交于 2021-02-07 19:50:39

问题


I'm struggling on transaction magement using DAO. The scenario is to create new quote that contains a list of quote_line and a customer. If the customer doesn't exist, it will insert it in the table customer. My code is architectures as follow :

    @Entity
    @Table(name = "quote")
    public class Quote {
      @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;
       //....properties
       @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<QuoteLine> quoteLines;

        //... methods
    }

    @Entity
    @Table(name = "quote_line")
    public class QuoteLine {

        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        private Long id;

        //....properties


        @ManyToOne(fetch = FetchType.EAGER)
        @JoinColumn(name = "quote_id", nullable = false)
        private Quote quote;
        //... methods
    }

    public interface QuoteDao extends CrudRepository<Quote, Long> {
        //... methods
    }

    public interface QuoteLineDao extends CrudRepository<QuoteLineDao, Long> {
        //... methods
    }

    public interface CustomerDao extends CrudRepository<CustomerDao, Long> {
        //... methods
    }

    @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;
        }
    }

//Application.java
@EnableAutoConfiguration
@Configuration
@EnableTransactionManagement
@ComponentScan
public class Application {

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

}

//StatelessAuthenticationSecurityConfig.java
@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()

                //allow anonymous resource requests
                .antMatchers("/").permitAll()
                .antMatchers("/favicon.ico").permitAll()
                .antMatchers("/resources/**").permitAll()

                //allow anonymous POSTs to login
                .antMatchers(HttpMethod.POST, "/api/login").permitAll()

                                //allow anonymous POSTs to customer
                //.antMatchers(HttpMethod.POST, "/api/customer/**").permitAll()
                                .antMatchers("/api/**").hasRole("USER")
                                .antMatchers("/api/invoice/**").permitAll()

                                //defined Admin only API area
                .antMatchers("/api/admin/**").hasRole("ADMIN")

                                //defined Admin only API area
                .antMatchers("/api/superadmin/**").hasRole("SUPER_ADMIN")

                //allow anonymous GETs to API
                //.antMatchers(HttpMethod.GET, "/api/**").permitAll()



                //all other request need to be authenticated
                .anyRequest().hasRole("USER").and()             

                // custom JSON based authentication by POST of {"username":"<name>","password":"<password>"} which sets the token header upon authentication
                .addFilterBefore(new StatelessLoginFilter("/api/login", tokenAuthenticationService, userDetailsService, authenticationManager()), UsernamePasswordAuthenticationFilter.class)

                // custom Token based authentication based on the header previously given to the client
                .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;
    }
}

In a debug mode I have only two vars : 1- this (the QuoteService) 2- quote

And this is th logs:

---------------------------------
==Granting role ADMIN
==Granting role 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(<generated>)
    ...

As you can notice, the message "°°°°°°°°°°°°°°°°°°Line 44 deleted" is printed but there is no trace for the DELETE FROM hibernate query.

This code Doesn't work : transactions using customerDao and quoteLineDao don't persist the objects in the database. I thought that propagation=Propagation.REQUIRED will force all the DAOs to use the same session and so the different transactions will be executed and if an error occurs, all will be rolled-back. The only explanation that I found (wrt the result) is the autowired DAOs use different sessions. I tried propagation=Propagation.SUPPORTS --> the transactions are executed but I can't rollback since SUPPORTS force the use of different sessions.

Could you explain why this doesn't work, and how can I corect this ?

Any helps would be appreciated!!!

Thank you!


回答1:


Updating my answer:

  1. You want your "public Quote save(Quote quote)" method to be transactional.
  2. when this method is invoked ... transaction begins in the TransactionInterceptor and from the proxy "public Quote save(Quote quote)" is invoked
  3. Line "quoteLineDao.delete(new Long(44));" works fine
  4. Line "System.out.println("°°°°°°°°°°°°°°°°°°Line 44 deleted");" works fine
  5. Line "quoteDao.save(quote);" gives constraint violation exception. Transaction is marked as rollback
  6. you are catching this exception and consuming it and not propagating the exception
  7. method "public Quote save(Quote quote) " will return null because of line "return null;"
  8. Now code reaches transaction interceptor and as there was no exception at this interceptor, it tries to commit but the transaction is already marked as rollback and therefore fails.

Solution:- You must not consume the exception and instead propagate the exception because of your transactional need.

Change to following. have added throw statement.

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;
}

Step by step explanation is available in this link: Could not commit JPA transaction: Transaction marked as rollbackOnly



来源:https://stackoverflow.com/questions/34413313/spring-transaction-and-rollback-on-multiple-tables

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!