Spring 多表事务和回滚
Spring transaction and rollback on multiple tables
我正在努力使用 DAO 进行事务管理。该方案是创建包含 quote_line 列表和客户的新报价。如果客户不存在,它将插入到 table 客户中。
我的代码架构如下:
@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;
}
}
在调试模式下我只有两个变量:
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>)
...
如您所见,打印了消息“°°°°°°°°°°°°°°°°°°第 44 行已删除”,但没有 DELETE FROM hibernate 查询的踪迹。
此代码不起作用:使用 customerDao 和 quoteLineDao 的交易不会将对象保存在数据库中。
我认为 propagation=Propagation.REQUIRED 将强制所有 DAO 使用相同的会话,因此不同的事务将
被执行,如果发生错误,将全部回滚。
我找到的唯一解释(根据结果)是自动装配的 DAO 使用不同的会话。
我尝试了 propagation=Propagation.SUPPORTS --> 事务已执行但我无法回滚,因为 SUPPORTS 强制使用不同的会话。
你能解释一下为什么这不起作用吗?我该如何纠正?
任何帮助将不胜感激!!!
谢谢!
更新我的回答:
- 您希望 "public Quote save(Quote quote)" 方法是事务性的。
- 调用此方法时...事务在 TransactionInterceptor 中开始并从代理 "public Quote save(Quote quote)" 被调用
- 第 "quoteLineDao.delete(new Long(44));" 行工作正常
- 第"System.out.println("°°°°°°°°°°°°°°°°°°°°第44行已删除");"工作正常
- 行"quoteDao.save(quote);"给出了违反约束的异常。交易被标记为回滚
- 您正在捕获此异常并使用它,而不是传播异常
- 方法 "public Quote save(Quote quote) " 将 return 为空,因为行 "return null;"
- 现在代码到达事务拦截器,由于该拦截器没有异常,它尝试提交,但事务已标记为回滚,因此失败。
解决方案:- 由于您的事务需要,您不能使用异常而是传播异常。
改为关注。添加了 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;
}
此 link 中提供了分步说明:
Could not commit JPA transaction: Transaction marked as rollbackOnly
我正在努力使用 DAO 进行事务管理。该方案是创建包含 quote_line 列表和客户的新报价。如果客户不存在,它将插入到 table 客户中。 我的代码架构如下:
@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;
}
}
在调试模式下我只有两个变量: 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>)
...
如您所见,打印了消息“°°°°°°°°°°°°°°°°°°第 44 行已删除”,但没有 DELETE FROM hibernate 查询的踪迹。
此代码不起作用:使用 customerDao 和 quoteLineDao 的交易不会将对象保存在数据库中。 我认为 propagation=Propagation.REQUIRED 将强制所有 DAO 使用相同的会话,因此不同的事务将 被执行,如果发生错误,将全部回滚。 我找到的唯一解释(根据结果)是自动装配的 DAO 使用不同的会话。 我尝试了 propagation=Propagation.SUPPORTS --> 事务已执行但我无法回滚,因为 SUPPORTS 强制使用不同的会话。
你能解释一下为什么这不起作用吗?我该如何纠正?
任何帮助将不胜感激!!!
谢谢!
更新我的回答:
- 您希望 "public Quote save(Quote quote)" 方法是事务性的。
- 调用此方法时...事务在 TransactionInterceptor 中开始并从代理 "public Quote save(Quote quote)" 被调用
- 第 "quoteLineDao.delete(new Long(44));" 行工作正常
- 第"System.out.println("°°°°°°°°°°°°°°°°°°°°第44行已删除");"工作正常
- 行"quoteDao.save(quote);"给出了违反约束的异常。交易被标记为回滚
- 您正在捕获此异常并使用它,而不是传播异常
- 方法 "public Quote save(Quote quote) " 将 return 为空,因为行 "return null;"
- 现在代码到达事务拦截器,由于该拦截器没有异常,它尝试提交,但事务已标记为回滚,因此失败。
解决方案:- 由于您的事务需要,您不能使用异常而是传播异常。
改为关注。添加了 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;
}
此 link 中提供了分步说明: Could not commit JPA transaction: Transaction marked as rollbackOnly