2011-10-06 56 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,以便檢索還會啓動一個新的事務,該事務將知道前一個事務所做的更改。還有其他解決方案嗎? –