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

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

我正在使用Spring3+JPA+Hibernate。我试图保持示例的结构与实际的代码结构相似。请滚动到底部查看实际问题。Zipped 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);
    }
}
下面是正在使用的
数据访问器的实现

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