我有一个注释为@Transactional的eventhandler方法,该方法在同一类中调用事件的实现。
此事件进行一些检查,并根据结果将执行某些操作,或者更改状态并引发RuntimeException。
万一由于检查而导致状态更改,我需要状态保持不变,但事件无法重试。
状态更改方法在另一个类中,并用@Transactional(propagation = Propagation.REQUIRES_NEW)进行注释。
我希望因为内部事务完成,所以状态更改将保持不变,并且事件事务将回滚。
我看到的是状态更改也已回滚,但是当我明确告诉它为状态更改创建新交易时,我不明白为什么它会回滚所有内容。
请记住,这是一个遗留项目,因此无法对体系结构进行重大更改。
我尝试调试事务更改,调试器确实跳入了新事务的提交,但是由于某种原因,它没有持久化到数据库中。
public class t implements it {
// Do initialisation and class injection. Y is constructor injected
private final Y y;
public t(Y y) {
this.y = y;
}
@Override
@Transactional
public void handleEvent(EventContext context) {
switch (context.getEventType()) {
case event:
validate(context);
break;
}
}
private void validate(EventContext context) {
Object o = crudService.findByProperty(context.getObjectUuid());
if (!o.check) {
y.changeStatus(ERROR);
// break for retry
throw new RuntimeException("Some serious message log");
} else {
// do some stuff
}
}
}
public class Y implements IY {
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void changeStatus(Object o, String status) {
// We do a lot more here then just change this status because of inheriting objects but for the sake of the argument, change status
o.status = status;
}
}
这是代码工作的粗略草稿。
我希望状态更改能够保留下来,因为在propogation_new事务开始时外部事务会暂停。我还可以看到该提交在Spring的事务代码中被调用,但是由于某种原因它没有持久化到数据库中。
如果我删除了运行时异常引发,则可以正常运行,但事件完成,这是不希望的。
此图片中我缺少什么?希望您能提供帮助。
谢谢!
我认为我发现了问题,对示例代码进行了一些更改以使其更加清晰。
changeStatus更改由crudService返回的对象的状态。在实际的应用程序中,由于依赖对象的对象o还需要更改状态更改,因此我们还要进行很多更改。
因为外部事务的状态为o,这是否意味着如果我在内部事务内进行更改,因为外部事务拥有引用,它将回滚到该状态,而不是保留内部事务的更改?
答案 0 :(得分:0)
由于第一个事务持有对状态更改的对象的引用而导致问题。
当我们在新交易中更改状态时,我们提交该状态更改并返回。当我们返回时,外部事务将继续并引发RuntimeException,这将导致回滚。由于事务持有状态已更改的对象的状态,因此该对象将回滚到外部事务所具有的状态,即旧状态。
要解决此问题,我将所有逻辑移至其自己的事务中,而不是仅在新事务中进行状态更改,而是在状态更改时删除了该事务。
然后,我实现了一个已检查的异常,该异常在状态更改时引发,该状态更改被捕获然后抛出给父级。其他所有异常都会被捕获并发送RuntimeException来中断。
异常被父级捕获,服务将抛出RuntimeException。由于内部事务已完成并已提交(在检查到异常的情况下),因此状态保持不变,并且事件无法重试。
在我的场景中,我将逻辑移到了自己的类/方法中,您也可以将代码留在同一类中,但是您将必须实现自身的代理并使用该代理进行调用,以便Spring通过您的方法进行代理,否则它将忽略该方法上的事务性语句。
下面是外观的最终草案。
public class t implements it {
// Do initialisation and class injection. Y is constructor injected
private final B b;
public t(B b) {
this.b = b;
}
@Override
@Transactional
public void handleEvent(EventContext context) {
switch (context.getEventType()) {
case event:
validate(context);
break;
}
}
// You can skip this method and simply call b, but in my scenario we do a couple of other things that do not have to be part of the transaction
private void validate(EventContext context) {
try {
b.allLogicMethod(context.getObjectUuid());
} catch(Exception e) {
// Here we break the event so we can retry it, but the transaction succeeded in case it was a checked Exception
throw new RuntimeException(e);
}
}
}
public class b implements IB {
private final Y y;
Public B(Y y) {
this.Y = y;
}
@Override
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void allLogicMethod(String uuid) {
try {
Object o = crudService.findByProperty(context.getObjectUuid());
if (!o.check) {
y.changeStatus(o, ERROR);
// break for retry
throw new CheckedException("Some serious message log");
} else {
// do everything else
}
} catch(CheckedException ce) {
throw ce;
} catch(Exception e) {
throw new RuntimeException("some message", e);
}
}
}
public class Y implements IY {
@Override
public void changeStatus(Object o, String status) {
// We do a lot more here then just change this status because of inheriting objects but for the sake of the argument, change status
o.status = status;
}
}