Spring 启动@Transactional 不回滚数据库插入
Spring boot @Transactional not rolling back the database inserts
这里需要一些帮助,我不明白为什么我的交易在发生异常时没有回滚。
我会尽量把我的代码放在项目上(不能在互联网上分享)
这是我的服务
@Service
@SL4j
@Transactional(propagation = propagation.SUPPORTS, readOnly=true)
public class PublicationServiceImpl implements PublicationService{
@Autowired
private PartnerRepo partnerRepo;
@Autowired
private FlowRepo flowRepo;
@Autowired
private PubRepo pubRepo;
@Override
@Transactional(propagation = propagation.REQUIRED, rollbackFor=Exception.class)
public int save(Request request) {
try{
int pk_id_partner = partnerRepo.save(request);
int pk_id_flow = flowRepo.save(request);
String publicationCode = generatePubCode(request);
int publicationCode= pubRepo.save(pk_id_partner, pk_id_flow, request);
}
catch(Exception e){
log.error("Exception in saving");
}
return 0;
}
}
这是我的存储库(示例 1,所有 3 个存储库都遵循相同的编码标准)
@Repository
@Slf4j
public class PartnerRepo implemets PartnerRepo{
@Autowired
private NamedParamaterJDBCTemplate namedParamaterJDBCTemplate;
//String Declarations .....
private MapSqlParameterSource sqlParameterSource;
@Override
public int save(Request request){
sqlParamatersSource = new MapSqlParameterSource();
//sqlParamatersSource.addValue(.....)
//sqlParamatersSource.addValue(.....)
//sqlParamatersSource.addValue(.....)
return executeQuery();
}
private int executeQuery(){
try{
keyHolder = new GenerateKeyHolder();
namedParamaterJDBCTemplate.update(getInsertQuery(), sqlParamaterSource , kekHolder, new String[]{"pk_id"})
return keyHolder.getKey().intValue();
}catch(Exception e){
log.error("Exception while saving");
return 0;
}
}
}
所以问题是,考虑在方法generatePubCode(request);
中存在异常,理想情况下因为我在class级别和方法级别使用了@Transactional
,之前的2个回购交易() 应该回滚吧?然而它并没有发生,即使在代码执行完成后我也可以看到数据库中的记录(Postgres DB v10)。
请帮忙解决这个问题,我是不是做错了什么?
如果您需要更多可能对此处有帮助的信息,请告诉我!
P.S:我已经尝试了 @Transactional
的所有排列,没有任何效果:只有在 catch 块中有这个才有效! TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
我想知道它是否是 springBoot 项目的正确方法
在此先感谢您的帮助!
编辑:根据建议 PublicationServiceSaverImpl.save()
public
此致,
巴尔加夫
如果要使用@Transactional
,方法PublicationServiceImpl.save
必须是public
。
根据 Spring 文档:
When you use transactional proxies with Spring’s standard configuration, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private, or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings.
由于在 Spring.
中创建代理的方式,注释通常不会对从同一个 class 调用的方法起作用
它与 @Transaction 无关,特别是因为您的方法是 私有的 并且 从同一对象中调用.
请制作方法 public 并将 @Transactional 方法移动到一个单独的 class 中,用 @Service 注释并从实例外部调用它的 class
@Service
public class PublicationServiceSaverImpl {
@Transactional
**public** int save(Request request) {
...
}
}
您必须从 class PublicationServiceSaverImpl 外部调用保存方法,可能来自 PublicationServiceImpl。
代替@Transactional(propagation = propagation.REQUIRED提供@Transactional(propagation = propagation.REQUIRED_NEW
如果你使用后者,它将使用父事务边界,它在class级别。
并且您不需要显式状态 rollbackFor=Exception.class。默认情况下 spring 将回滚异常
并将私有更改为 public
试试这个
在 Spring
中有几件事会破坏正常的交易
- 您的服务方式是
private
- 你正在捕捉并吞噬异常
private
方法
事实上,您的 PublicationServiceImpl
save
方法是 private
基本上使得该方法上的 @Transactional
无用。由于无法代理 private
方法,因此不会应用任何事务。即使它是 public
它也不会工作,因为您从同一对象中调用该方法,因此该方法的事务性适用。
要修复,使您的方法 public
并从另一个 class 调用 save
方法(或者使调用 save
的实际方法具有正确的@Transactional
.
事实上这是行不通的,因为正在使用类型操作 AOP,默认情况下 Spring 将使用代理,这是使用基于代理的 AOP 的缺点。
另一种使其与 private
方法一起工作的解决方案是切换到具有 classes 的编译时或加载时编织的成熟的 AspectJ。两者都需要额外的设置,而且可能很乏味。
捕获并吞下异常
您的存储库和服务中都有一个 try/catch
块。这些中的每一个都捕获并吞下了异常(它们被记录但没有被重新抛出)。
为了使事务正常工作,它需要查看异常。您捕获并吞下它们的事实使事务方面看不到它们,而不是进行回滚,而是进行提交。对于交易方面,一切都很好,因为没有例外。
要修复,请删除 try/catch
或重新抛出异常。
首先:制定你的方法public。
其次:你必须抛出异常。如果你捕获而不重新抛出它,你如何期望事务处理知道发生错误然后回滚?
您有两个选择:抛出 Exception
而不是捕获它,或者捕获,做一些进一步的处理然后重新抛出它。
因此在您的存储库中,只需添加一个 throws 关键字,然后在日志语句后重新抛出异常:
public int executeQuery() throws Exception {
try {
keyHolder = new GenerateKeyHolder();
namedParamaterJDBCTemplate.update(getInsertQuery(), sqlParamaterSource, kekHolder, new String[] {
"pk_id"
})
return keyHolder.getKey().intValue();
} catch(Exception e) {
log.error("Exception while saving");
throw e;
}
}
现在,为您服务:
示例 1 - 使用 throws 关键字传播已检查的异常:
@Override
@Transactional(propagation = propagation.REQUIRED, rollbackFor = Exception.class)
public int save(Request request) throws Exception {
int pk_id_partner = partnerRepo.save(request);
int pk_id_flow = flowRepo.save(request);
String publicationCode = generatePubCode(request);
int publicationCode = pubRepo.save(pk_id_partner, pk_id_flow, request);
return 0;
}
示例 2 - 捕获并将其作为未检查的 RuntimeException 重新抛出。
@Override
@Transactional(propagation = propagation.REQUIRED)
public int save(Request request) {
try {
int pk_id_partner = partnerRepo.save(request);
int pk_id_flow = flowRepo.save(request);
String publicationCode = generatePubCode(request);
int publicationCode = pubRepo.save(pk_id_partner, pk_id_flow, request);
} catch(Exception ex) {
throw new RuntimeException(ex);
}
return 0;
}
请注意,第二个示例不需要 @Transactional
的 rollbackFor
参数。默认情况下,如果发生未经检查的异常,事务将回滚,因此在 RuntimeException
的情况下无需显式使用 rollbackFor
。
如果解决方案不起作用,则必须进行另一项验证。它是为了验证数据库表是否允许回滚。为此,引擎必须在 InnoDB 中,而不是在 MyISAM 和其他中。
这里需要一些帮助,我不明白为什么我的交易在发生异常时没有回滚。
我会尽量把我的代码放在项目上(不能在互联网上分享)
这是我的服务
@Service
@SL4j
@Transactional(propagation = propagation.SUPPORTS, readOnly=true)
public class PublicationServiceImpl implements PublicationService{
@Autowired
private PartnerRepo partnerRepo;
@Autowired
private FlowRepo flowRepo;
@Autowired
private PubRepo pubRepo;
@Override
@Transactional(propagation = propagation.REQUIRED, rollbackFor=Exception.class)
public int save(Request request) {
try{
int pk_id_partner = partnerRepo.save(request);
int pk_id_flow = flowRepo.save(request);
String publicationCode = generatePubCode(request);
int publicationCode= pubRepo.save(pk_id_partner, pk_id_flow, request);
}
catch(Exception e){
log.error("Exception in saving");
}
return 0;
}
}
这是我的存储库(示例 1,所有 3 个存储库都遵循相同的编码标准)
@Repository
@Slf4j
public class PartnerRepo implemets PartnerRepo{
@Autowired
private NamedParamaterJDBCTemplate namedParamaterJDBCTemplate;
//String Declarations .....
private MapSqlParameterSource sqlParameterSource;
@Override
public int save(Request request){
sqlParamatersSource = new MapSqlParameterSource();
//sqlParamatersSource.addValue(.....)
//sqlParamatersSource.addValue(.....)
//sqlParamatersSource.addValue(.....)
return executeQuery();
}
private int executeQuery(){
try{
keyHolder = new GenerateKeyHolder();
namedParamaterJDBCTemplate.update(getInsertQuery(), sqlParamaterSource , kekHolder, new String[]{"pk_id"})
return keyHolder.getKey().intValue();
}catch(Exception e){
log.error("Exception while saving");
return 0;
}
}
}
所以问题是,考虑在方法generatePubCode(request);
中存在异常,理想情况下因为我在class级别和方法级别使用了@Transactional
,之前的2个回购交易() 应该回滚吧?然而它并没有发生,即使在代码执行完成后我也可以看到数据库中的记录(Postgres DB v10)。
请帮忙解决这个问题,我是不是做错了什么?
如果您需要更多可能对此处有帮助的信息,请告诉我!
P.S:我已经尝试了 @Transactional
的所有排列,没有任何效果:只有在 catch 块中有这个才有效! TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
我想知道它是否是 springBoot 项目的正确方法
在此先感谢您的帮助!
编辑:根据建议 PublicationServiceSaverImpl.save()
public
此致, 巴尔加夫
如果要使用@Transactional
,方法PublicationServiceImpl.save
必须是public
。
根据 Spring 文档:
When you use transactional proxies with Spring’s standard configuration, you should apply the @Transactional annotation only to methods with public visibility. If you do annotate protected, private, or package-visible methods with the @Transactional annotation, no error is raised, but the annotated method does not exhibit the configured transactional settings.
由于在 Spring.
中创建代理的方式,注释通常不会对从同一个 class 调用的方法起作用它与 @Transaction 无关,特别是因为您的方法是 私有的 并且 从同一对象中调用.
请制作方法 public 并将 @Transactional 方法移动到一个单独的 class 中,用 @Service 注释并从实例外部调用它的 class
@Service
public class PublicationServiceSaverImpl {
@Transactional
**public** int save(Request request) {
...
}
}
您必须从 class PublicationServiceSaverImpl 外部调用保存方法,可能来自 PublicationServiceImpl。
代替@Transactional(propagation = propagation.REQUIRED提供@Transactional(propagation = propagation.REQUIRED_NEW
如果你使用后者,它将使用父事务边界,它在class级别。
并且您不需要显式状态 rollbackFor=Exception.class。默认情况下 spring 将回滚异常
并将私有更改为 public
试试这个
在 Spring
中有几件事会破坏正常的交易- 您的服务方式是
private
- 你正在捕捉并吞噬异常
private
方法
事实上,您的 PublicationServiceImpl
save
方法是 private
基本上使得该方法上的 @Transactional
无用。由于无法代理 private
方法,因此不会应用任何事务。即使它是 public
它也不会工作,因为您从同一对象中调用该方法,因此该方法的事务性适用。
要修复,使您的方法 public
并从另一个 class 调用 save
方法(或者使调用 save
的实际方法具有正确的@Transactional
.
事实上这是行不通的,因为正在使用类型操作 AOP,默认情况下 Spring 将使用代理,这是使用基于代理的 AOP 的缺点。
另一种使其与 private
方法一起工作的解决方案是切换到具有 classes 的编译时或加载时编织的成熟的 AspectJ。两者都需要额外的设置,而且可能很乏味。
捕获并吞下异常
您的存储库和服务中都有一个 try/catch
块。这些中的每一个都捕获并吞下了异常(它们被记录但没有被重新抛出)。
为了使事务正常工作,它需要查看异常。您捕获并吞下它们的事实使事务方面看不到它们,而不是进行回滚,而是进行提交。对于交易方面,一切都很好,因为没有例外。
要修复,请删除 try/catch
或重新抛出异常。
首先:制定你的方法public。
其次:你必须抛出异常。如果你捕获而不重新抛出它,你如何期望事务处理知道发生错误然后回滚?
您有两个选择:抛出 Exception
而不是捕获它,或者捕获,做一些进一步的处理然后重新抛出它。
因此在您的存储库中,只需添加一个 throws 关键字,然后在日志语句后重新抛出异常:
public int executeQuery() throws Exception {
try {
keyHolder = new GenerateKeyHolder();
namedParamaterJDBCTemplate.update(getInsertQuery(), sqlParamaterSource, kekHolder, new String[] {
"pk_id"
})
return keyHolder.getKey().intValue();
} catch(Exception e) {
log.error("Exception while saving");
throw e;
}
}
现在,为您服务:
示例 1 - 使用 throws 关键字传播已检查的异常:
@Override
@Transactional(propagation = propagation.REQUIRED, rollbackFor = Exception.class)
public int save(Request request) throws Exception {
int pk_id_partner = partnerRepo.save(request);
int pk_id_flow = flowRepo.save(request);
String publicationCode = generatePubCode(request);
int publicationCode = pubRepo.save(pk_id_partner, pk_id_flow, request);
return 0;
}
示例 2 - 捕获并将其作为未检查的 RuntimeException 重新抛出。
@Override
@Transactional(propagation = propagation.REQUIRED)
public int save(Request request) {
try {
int pk_id_partner = partnerRepo.save(request);
int pk_id_flow = flowRepo.save(request);
String publicationCode = generatePubCode(request);
int publicationCode = pubRepo.save(pk_id_partner, pk_id_flow, request);
} catch(Exception ex) {
throw new RuntimeException(ex);
}
return 0;
}
请注意,第二个示例不需要 @Transactional
的 rollbackFor
参数。默认情况下,如果发生未经检查的异常,事务将回滚,因此在 RuntimeException
的情况下无需显式使用 rollbackFor
。
如果解决方案不起作用,则必须进行另一项验证。它是为了验证数据库表是否允许回滚。为此,引擎必须在 InnoDB 中,而不是在 MyISAM 和其他中。