Java 多表上的Spring事务和回滚

Java 多表上的Spring事务和回滚,java,mysql,spring,hibernate,transactional,Java,Mysql,Spring,Hibernate,Transactional,我正在使用DAO进行事务处理。该场景是创建包含报价行和客户列表的新报价。如果客户不存在,它会将其插入customer表中。 我的代码如下所示: @Entity @Table(name = "quote") public class Quote { @Id @GeneratedValue(strategy = GenerationType.AUTO) private Long id; //....properties

我正在使用DAO进行事务处理。该场景是创建包含报价行和客户列表的新报价。如果客户不存在,它会将其插入customer表中。 我的代码如下所示:

    @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;
    }
}
@实体
@表(name=“quote”)
公开课报价{
@身份证
@GeneratedValue(策略=GenerationType.AUTO)
私人长id;
//……财产
@manytone(fetch=FetchType.EAGER)
@JoinColumn(name=“customer\u id”,nullable=true)
私人客户;
@OneToMany(cascade={CascadeType.PERSIST,CascadeType.MERGE,CascadeType.REFRESH},mappedBy=“quote”)
@级联({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})
私有集引用线;
//…方法
}
@实体
@表(name=“quote_行”)
公共类报价线{
@身份证
@GeneratedValue(策略=GenerationType.AUTO)
私人长id;
//……财产
@manytone(fetch=FetchType.EAGER)
@JoinColumn(name=“quote\u id”,nullable=false)
私人报价;
//…方法
}
引用的公共接口ao扩展了crudepository{
//…方法
}
公共接口QuoteLineDao扩展了CrudePository{
//…方法
}
公共接口CustomerDao扩展了Crudepository{
//…方法
}
@服务
公共类报价服务{
@自动连线
私人引述道引述道;
@自动连线
私人报价单报价单报价单报价单;
@自动连线
私人客户道客户道;
@事务性(传播=propagation.REQUIRED,rollboor=Exception.class)
公共报价保存(报价){
试一试{
删除(新长(44));
系统输出打印项次(“删除第44行”);
返回报价保存(报价);
}捕获(例外e){
Logger.getLogger(QuoteService.class).log(Logger.Level.FATAL,e);
}
返回null;
}
}
//Application.java
@启用自动配置
@配置
@启用事务管理
@组件扫描
公共类应用程序{
公共静态void main(字符串[]args){
SpringApplication.run(Application.class,args);
}
}
//无状态身份验证SecurityConfig.java
@启用Web安全性
@配置
@启用事务管理
@订单(1)
公共类无状态身份验证SecurityConfig扩展了WebSecurity配置适配器{
@自动连线
私有用户详细信息服务用户详细信息服务;
@自动连线
专用TokenAuthenticationService TokenAuthenticationService;
公共无状态身份验证SecurityConfig(){
超级(真);
}
@凌驾
受保护的无效配置(HttpSecurity http)引发异常{
http
.exceptionHandling()和()
.anonymous()和()
.servletApi()和()
.headers().cacheControl()和()
.授权请求()
//允许匿名资源请求
.antMatchers(“/”).permitAll()
.antMatchers(“/favicon.ico”).permitAll()
.antMatchers(“/resources/**”).permitAll()
//允许匿名帖子登录
.antMatchers(HttpMethod.POST,“/api/login”).permitAll()
//允许向客户发送匿名帖子
//.antMatchers(HttpMethod.POST,“/api/customer/**”).permitAll()
.antMatchers(“/api/**”).hasRole(“用户”)
.antMatchers(“/api/invoice/**”).permitAll()
//定义的仅管理API区域
.antMatchers(“/api/admin/**”).hasRole(“admin”)
//定义的仅管理API区域
.antMatchers(“/api/superadmin/**”).hasRole(“超级管理员”)
//允许匿名访问API
//.antMatchers(HttpMethod.GET,“/api/**”).permitAll()
//所有其他请求都需要经过身份验证
.anyRequest().hasRole(“用户”)和()
//通过发布{“用户名”:“,“密码”:”}自定义基于JSON的身份验证,在身份验证时设置令牌头
.addFilterBefore(新的无状态LoginFilter(“/api/login”、tokenAuthenticationService、userDetailsService、authenticationManager())、UsernamePasswordAuthenticationFilter.class)
//基于先前提供给客户端的头的自定义令牌身份验证
.addFilterBefore(新的无状态AuthenticationFilter(tokenAuthenticationService),UsernamePasswordAuthenticationFilter.class);
}
@豆子
@凌驾
公共AuthenticationManager authenticationManagerBean()引发异常{
返回super.authenticationManagerBean();
}
@凌驾
受保护的无效配置(AuthenticationManagerBuilder auth)引发异常{
auth.userDetailsService(userDetailsService).passwordEncoder(新的BCryptPasswordEncoder());
//auth.jdbc authentication().dataSource(null).usersByUsernameQuery(“”).authoritiesByUsernameQuery(“”);
}
@凌驾
受保护的UserDetailsService UserDetailsService(){
返回userDetailsService;
}
}
在调试模式下,我只有两个变量: 1-本(报价服务) 2-报价

这是我的日志:

---------------------------------
==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>)
    ...
---------------------------------
==授予角色管理员
==授予用户角色
休眠:选择quoteline0.id作为id1_6_0,quoteline0.position作为p
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;
}