2011-10-06 37 views
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): expected:<1> but was:<0>上面给出的形式。如果我修改ServiceImpl类中的FooifyBar方法并删除对getFoogetBar的调用,则测试用例成功无错。这意味着如果getFoo发生在fooifyBar之前,则fooifyBar对测试方法不可见。这是为什么?

回答

1

REQUIRES_NEW并不意味着嵌套事务,spring会启动另一个事务来挂起当前活动的事务。就DB而言,它们是两次独立的交易。

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

+0

我不是说嵌套事务,修正了标题。正如你所说,这两个是单独的交易,所以内部交易的变化必须对外部交易可见。更奇怪的是,如果我将调用移动到insertFooRelationAndUpdateBar中的getFoo,则测试用例会成功。 –

+0

什么是隔离级别 - 事务启动后对数据库所做的更改是否可见取决于隔离级别。在你的例子中,还不清楚你测试失败的意思 - 哪些是内部和外部事务? – gkamal

+0

隔离不变,即它是默认值。外部事务从测试方法开始,内部事务以insertFooRelationAndUpdateBar方法开始,该方法具有传播= REQUIRES_NEW的“事务性”注释。通过测试失败,我的意思是断言失败,因为在内部事务中添加的关系对测试方法是不可见的。 –

1

您在问为什么在一个事务中所做的更改在第二个事务中不可见。这是使用交易的主要原因:到keep changes isolated直到提交。所以你有点问为什么关系数据库以他们的方式工作。

+0

对不起,我没有更清楚。内部事务在方法insertFooRelationAndUpdateBar结束后被提交(至少日志会这样说)。如果getFoo被带入这个方法中,那么同样的事情就会起作用。 –

+0

正确:一项交易承诺,但另一项交易正在进行中。根据隔离级别,正在进行的第一个事务不会看到第二个事务完成的事情。承认可能有其他几件事情可能是错误的。如果您正在寻找更具体的答案,请提出更具体的问题 - 即。提供[SSCCE](http://sscce.org/)。 –

+0

我用一个具体的例子编辑了这个问题。我想我明白你的意思。我将不得不将'getFoo'的传播更改为REQUIRES_NEW,以便检索还会启动一个新的事务,该事务将知道前一个事务所做的更改。还有其他解决方案吗? –

相关问题