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.
精彩评论