Hibernate 内部事务更改对外部事务不可见
我正在使用Spring3+JPA+Hibernate。我试图保持示例的结构与实际的代码结构相似。请滚动到底部查看实际问题。Zipped maven项目可从www.esnips.com/nsdoc/da7a09c0-ce5a-4dbf-80a2-f414ea3bf333/?action=forceDL下载 下面是正在测试的类Hibernate 内部事务更改对外部事务不可见,hibernate,spring,jpa,transactions,Hibernate,Spring,Jpa,Transactions,我正在使用Spring3+JPA+Hibernate。我试图保持示例的结构与实际的代码结构相似。请滚动到底部查看实际问题。Zipped maven项目可从www.esnips.com/nsdoc/da7a09c0-ce5a-4dbf-80a2-f414ea3bf333/?action=forceDL下载 下面是正在测试的类 public class ServiceImpl implements Service { @Autowired private DataAccessor dataAcce
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);
}
}
下面是正在使用的数据访问器的实现
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):应为:但为:
,格式如上所示。如果我在ServiceImpl
类中修改FooifyBar
方法,并删除对getFoo
和getBar
的调用,测试用例将成功而无误。这意味着如果getFoo
发生在fooifyBar
之前,则fooifyBar
所做的更改对测试方法不可见。为什么会这样?需要\u NEW并不意味着嵌套事务,spring启动另一个事务,挂起当前活动的事务。就争议裁决委员会而言,它们是两项独立的交易
如果需要嵌套事务,则应使用嵌套属性。为此,数据库和驱动程序需要支持某些功能—我认为这些功能并没有得到广泛支持。需要\u NEW并不意味着嵌套事务,spring启动另一个事务,挂起当前活动的事务。就争议裁决委员会而言,它们是两项独立的交易
如果需要嵌套事务,则应使用嵌套属性。为此,数据库和驱动程序需要支持某些功能—我认为这些功能没有得到广泛支持。您的问题是,为什么在一个事务中所做的更改在第二个事务中不可见。这是使用事务的主要原因:直到提交。因此,您会问为什么关系数据库会以这种方式工作。您会问为什么在一个事务中所做的更改在第二个事务中不可见。这是使用事务的主要原因:直到提交。所以你会问为什么关系数据库会这样工作。我不是指嵌套事务,而是修复了标题。正如您所说,这两个事务是独立的,因此内部事务的更改必须对外部事务可见。更奇怪的事实是,如果我在insertFooRelationAndUpdateBar中移动对getFoo的调用,测试用例就会成功。隔离级别是什么?事务启动后对DB所做的更改对它是否可见取决于隔离级别。另外,在您的示例中,不清楚测试失败是什么意思-这是内部和外部事务?隔离没有改变,即它是默认的。外部事务从测试方法开始,内部事务从insertFooRelationAndUpdateBar方法开始,该方法具有“transactional”注释,其传播=REQUIRES\u NEW。所谓测试失败,我的意思是断言失败,因为添加到内部事务中的关系对测试方法不可见。这种行为是通过隔离可重复读取得到的。尝试将测试中@Transactional的隔离级别更改为READ_Committed。使用隔离可重复读取时,事务中执行的任何查询都需要返回相同的数据,这就是为什么当您将getFoo移动到另一个事务中时,它可以工作的原因。@gkamal在其最后一条评论中正确回答了问题-隔离READ\u committed
是解决此问题的方法我不是指嵌套事务,修正了标题。正如您所说,这两个事务是独立的,因此内部事务的更改必须对外部事务可见。更奇怪的事实是,如果我在insertFooRelationAndUpdateBar中移动对getFoo的调用,测试用例就会成功。隔离级别是什么?事务启动后对DB所做的更改对它是否可见取决于隔离级别。另外,在您的示例中,不清楚测试失败是什么意思-这是内部和外部事务?隔离没有改变,即它是默认的。外部事务从测试方法开始,内部事务从insertFooRelationAndUpdateBar方法开始,该方法具有“transactional”注释,其传播=REQUIRES\u NEW。所谓测试失败,我的意思是断言失败,因为添加到内部事务中的关系对测试方法不可见。这种行为是通过隔离可重复读取得到的。尝试将测试中@Transactional的隔离级别更改为READ_Committed。通过隔离可重复读取,事务中执行的任何查询都需要返回相同的数据,这就是为什么当你将getFoo移动到另一个事务中时它会工作的原因。@gkamal在他的最后一条评论中正确地回答了这个问题-隔离READ\u committed
是解决这个问题的方法,很抱歉我没有更清楚地说明这一点。内部事务在insertFooRelationAndUpdateBar方法结束后提交(至少日志上这么说)。如果getFoo被带到这个方法中,同样的事情也会发生。对:一个事务被提交,但另一个事务(首先启动)正在进行。根据隔离级别,正在进行的第一个事务将看不到第二个事务完成的操作。当然,可能还有其他一些事情是错误的。如果你想找到更具体的答案,可以问一个更具体的问题——例如,提供一个答案。我用一个具体的例子编辑了这个问题。我想我明白你的意思了。我必须将getFoo
的传播更改为REQUIRES\u NEW,以便检索也启动一个n