开发者

Inner transaction changes not visible to outer transaction

开发者 https://www.devze.com 2023-04-11 17:54 出处:网络
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 dow

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 DBControlle开发者_StackOverflowr

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?


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.


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.

0

精彩评论

暂无评论...
验证码 换一张
取 消