内部事务更改对外部事务不可见

发布于 2024-12-08 14:45:24 字数 3914 浏览 0 评论 0原文

我正在使用 Spring3+JPA+Hibernate。我试图保持示例的结构与我的实际代码结构相似。请滚动到底部查看实际问题。压缩的maven项目可以从www.esnips.com/nsdoc/da7a09c0-ce5a-4dbf-80a2-f414ea3bf333/?action=forceDL下载

以下是正在测试的类。

public class ServiceImpl implements Service {

@Autowired
private DataAccessor dataAccessor;

@Autowired
private ServiceTransactions serviceTransactions;

public Foo getFoo(long id) {
    return dataAccessor.getFoo(id);
}

public Foo createFoo(Foo foo) {
    return dataAccessor.createFoo(foo);
}

public Bar createBar(Bar bar) {
    return dataAccessor.createBar(bar);
}

@SuppressWarnings("unused")
public Foo FooifyBar(long fooId, long barId) {
    Foo foo = dataAccessor.getFoo(fooId);
    Bar bar = dataAccessor.getBar(barId);
    return serviceTransactions.fooifyBar(fooId, barId, "Error");
}

}

以下是 ServiceTransactions 类。

public class ServiceTransactions {
    @Autowired
    private DataAccessor dataAccessor;

    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public Foo fooifyBar(long fooId, long barId, String error) {
    Foo foo = dataAccessor.getFoo(fooId);
    Bar bar = dataAccessor.getBar(barId);
    return dataAccessor.fooifyBar(foo, bar, error);
    }
}

以下是使用中的DataAccessor 的实现。

public class DataAccessorImpl implements DataAccessor {

@Autowired
private DBController controller;

@Transactional
public Foo getFoo(long id) {
    FooDao food = controller.getFoo(id);
    return convertFoodToFoo(food);
}

@Transactional
public Foo createFoo(Foo foo) {
    FooDao food = new FooDao();
    food.setName(foo.getName());
    return convertFoodToFoo(controller.createFoo(food));
}

@Transactional
public Bar getBar(long id) {
    return convertBardToBar(controller.getBar(id));
}

@Transactional
public Bar createBar(Bar bar) {
    BarDao bard = new BarDao();
    bard.setName(bar.getName());
    return convertBardToBar(controller.createBar(bard));
}

@Transactional
public Foo fooifyBar(Foo foo, Bar bar, String error) {
    return convertFoodToFoo(controller.fooBar(foo.getId(), bar.getId(), error));
}

以下是DBController的实现

public class DBControllerImpl implements DBController {

@PersistenceContext 
private EntityManager em;

public FooDao getFoo(long id) {
    return em.find(FooDao.class, id);
}

public FooDao createFoo(FooDao foo) {
    em.persist(foo);
    return foo;
}

public BarDao getBar(long id) {
    return em.find(BarDao.class, id);
}

public BarDao createBar(BarDao bar) {
    em.persist(bar);
    return bar;
}

public FooDao fooBar(long fooId, long barId, String error) {
    FooDao foo = em.find(FooDao.class, fooId);
    FooedBarDao fb = new FooedBarDao();
    fb.setFoo(foo);
    fb.setBar(em.find(BarDao.class, barId));
    fb.setError(error);
    em.persist(fb);

    foo.getFooedBars().add(fb);

    em.merge(foo);
    return foo;
}

,最后是测试类

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="/testContext.xml")
public class TestFooBar {

@Autowired
private Service service;
Foo foo;
Bar bar;

@BeforeTransaction
public void before() {
    foo = new Foo();
    foo.setName("foo");
    foo = service.createFoo(foo);
    bar = new Bar();
    bar.setName("bar");
    bar = service.createBar(bar);
}

@Test
@Transactional
public void testFooingBar() {
    service.FooifyBar(foo.getId(), bar.getId());
    Foo foo2 = service.getFoo(foo.getId());
    Assert.assertEquals(1, foo2.getFooedBars().size());
}

现在的问题是测试用例失败并出现错误testFooingBar(com.test.sscce.server.TestFooBar) :预期:<1>但在上面给出的形式中是:<0>。如果我修改 ServiceImpl 类中的 FooifyBar 方法并删除对 getFoogetBar 的调用,则测试用例成功没有错误。这意味着如果 getFoo 发生在 fooifyBar 之前,则 fooifyBar 所做的更改对测试方法不可见。这是为什么?

I'm using Spring3+JPA+Hibernate. I've tried to keep structure of the example similar to my actual code structure. Please scroll to the bottom for the actual question. Zipped maven project can be downloaded from www.esnips.com/nsdoc/da7a09c0-ce5a-4dbf-80a2-f414ea3bf333/?action=forceDL

Following is the class under test.

public class ServiceImpl implements Service {

@Autowired
private DataAccessor dataAccessor;

@Autowired
private ServiceTransactions serviceTransactions;

public Foo getFoo(long id) {
    return dataAccessor.getFoo(id);
}

public Foo createFoo(Foo foo) {
    return dataAccessor.createFoo(foo);
}

public Bar createBar(Bar bar) {
    return dataAccessor.createBar(bar);
}

@SuppressWarnings("unused")
public Foo FooifyBar(long fooId, long barId) {
    Foo foo = dataAccessor.getFoo(fooId);
    Bar bar = dataAccessor.getBar(barId);
    return serviceTransactions.fooifyBar(fooId, barId, "Error");
}

}

Following is the ServiceTransactions class.

public class ServiceTransactions {
    @Autowired
    private DataAccessor dataAccessor;

    @Transactional(propagation=Propagation.REQUIRES_NEW)
    public Foo fooifyBar(long fooId, long barId, String error) {
    Foo foo = dataAccessor.getFoo(fooId);
    Bar bar = dataAccessor.getBar(barId);
    return dataAccessor.fooifyBar(foo, bar, error);
    }
}

Following is the implementation of DataAccessor in use.

public class DataAccessorImpl implements DataAccessor {

@Autowired
private DBController controller;

@Transactional
public Foo getFoo(long id) {
    FooDao food = controller.getFoo(id);
    return convertFoodToFoo(food);
}

@Transactional
public Foo createFoo(Foo foo) {
    FooDao food = new FooDao();
    food.setName(foo.getName());
    return convertFoodToFoo(controller.createFoo(food));
}

@Transactional
public Bar getBar(long id) {
    return convertBardToBar(controller.getBar(id));
}

@Transactional
public Bar createBar(Bar bar) {
    BarDao bard = new BarDao();
    bard.setName(bar.getName());
    return convertBardToBar(controller.createBar(bard));
}

@Transactional
public Foo fooifyBar(Foo foo, Bar bar, String error) {
    return convertFoodToFoo(controller.fooBar(foo.getId(), bar.getId(), error));
}

Following is the implementation of DBController

public class DBControllerImpl implements DBController {

@PersistenceContext 
private EntityManager em;

public FooDao getFoo(long id) {
    return em.find(FooDao.class, id);
}

public FooDao createFoo(FooDao foo) {
    em.persist(foo);
    return foo;
}

public BarDao getBar(long id) {
    return em.find(BarDao.class, id);
}

public BarDao createBar(BarDao bar) {
    em.persist(bar);
    return bar;
}

public FooDao fooBar(long fooId, long barId, String error) {
    FooDao foo = em.find(FooDao.class, fooId);
    FooedBarDao fb = new FooedBarDao();
    fb.setFoo(foo);
    fb.setBar(em.find(BarDao.class, barId));
    fb.setError(error);
    em.persist(fb);

    foo.getFooedBars().add(fb);

    em.merge(foo);
    return foo;
}

And finally the test class

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations="/testContext.xml")
public class TestFooBar {

@Autowired
private Service service;
Foo foo;
Bar bar;

@BeforeTransaction
public void before() {
    foo = new Foo();
    foo.setName("foo");
    foo = service.createFoo(foo);
    bar = new Bar();
    bar.setName("bar");
    bar = service.createBar(bar);
}

@Test
@Transactional
public void testFooingBar() {
    service.FooifyBar(foo.getId(), bar.getId());
    Foo foo2 = service.getFoo(foo.getId());
    Assert.assertEquals(1, foo2.getFooedBars().size());
}

Now the question is the test case fails with error testFooingBar(com.test.sscce.server.TestFooBar): expected:<1> but was:<0> in the form given above. If I modify the FooifyBar method in ServiceImpl class and remove the calls to getFoo and getBar, the test case succeeds without error. This means changes made by fooifyBar are not visible to the test method, if getFoo occurs before fooifyBar. Why is that?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

⒈起吃苦の倖褔 2024-12-15 14:45:24

REQUIRES_NEW 并不意味着嵌套事务,spring 启动另一个事务,暂停当前活动的事务。就数据库而言,它们是两个独立的事务。

如果您需要嵌套事务,您应该使用属性 NESTED。为此,数据库和驱动程序需要支持某些功能 - 我认为这些功能并未得到广泛支持。

REQUIRES_NEW doesn't mean nested transaction, spring starts another transaction suspending the one currently active. As far as the DB is concerned they are two independent transactions.

If you need a nested transaction you should use the attribute NESTED. For this to work the database and driver need to support certain features - which I don't think is widely supported.

往事风中埋 2024-12-15 14:45:24

您问为什么在一笔交易中所做的更改在第二笔交易中不可见。这是使用事务的主要原因:保持更改隔离直到提交。所以你有点想问为什么关系数据库会这样工作。

You're asking why changes made in one transaction aren't visible in a second transaction. This is a primary reason that transactions are used: to keep changes isolated until commit. So you're kind of asking why relational databases work the way they do.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文