2017-01-22 177 views
1

我有一個關於@Transactional註釋的問題。 沒有什麼特別的定義,所以據我所知PROPAGATION_REQUIRED 假設我有一個在服務和dao層上的事務註釋。交易在服務和道服務層

服務

@Transactional 
public long createStudentInDB(Student student) { 
    final long id = addStudentToDB (student); 
    addStudentToCourses (id, student.getCourseIds()); 
    return id; 
} 

private long addStudentToDB (Student student) { 
    StudentEntity entity = new StudentEntity(); 
    convertToEntity(student, entity); 
    try { 
     final id = dao.create(entity); 
    } catch (Exception e){ 
     // 
     } 
    return id; 
} 

private void addStudentToCourses (long studentId, List<String> coursesIds){ 
    //add user to group 
    if(coursesIds!= null){ 
     List<StudentCourseEntity> studentCourses = new ArrayList<>(); 
     for(String coursesId: coursesIds){ 
      StudentCourseEntity entity = new StudentCourseEntity(); 
      entity.setCourseId(coursesId); 
      entity.setStudentId(userId); 
      studentCourses.add(studentId); 
     } 
     anotherDao.saveAll(studentCourses); 
    } 
} 

DAO

@Transactional 
public UUID create(StudentEntity entity) { 

    if (entity == null) { throw new Exception(//…); } 

    getCurrentSession().save(entity); 
    return entity.getId(); 
} 

ANOTHERDAO

@Transactional 
public void saveAll(Collection<StudentCourseEntity> studentCourses) { 
    List<StudentCourseEntity> result = new ArrayList<>(); 
    if(studentCourses!= null) { 
     for (StudentCourseEntity studentCourse : studentCourses) { 
      if (studentCourse!= null) { 
       save(studentCourse); 
      } 
     } 
    } 

} 

儘管這不是最優的事實,現在看來,這導致死鎖。 假設我有最多2個連接到數據庫。 而我正在使用3個不同的線程來運行相同的代碼。 線程1和線程2接收連接,線程3沒有獲得任何連接。 除此之外,看起來thread-1在嘗試獲取dao級別的連接時變得卡住,與thread-2相同。造成僵局。

我確信通過使用propagation_required這不會發生。 我錯過了什麼嗎? 這是什麼建議?有沒有一種方法可以在兩個層上都有@transactional?如果不是哪個是首選? 感謝 法布里奇奧

+0

如果您的連接不夠用,您可能沒有正確設置事務,或者自己搞亂連接,而不是讓Spring管理事務。只有在沒有實現的情況下添加方法簽名才能解決您的問題。首先檢查你的設置(確保你有正確的tx設置)並檢查你的實現。 –

回答

0

由於dao.doSomeStuff預計將調用來自其他交易中,我建議您配置此方法爲:

@Transaction(propagation=REQUIRES_NEW) 

多虧了這是調用此方法將交易停止,直到REQUIRES_NEW的那一個完成。

不確定這是否是針對您的特定死鎖案例的修復,但您的示例適用於此特定設置。

+0

感謝您的回答。我確實需要完全相反的。我不想被制止。我只是想知道如果我在服務級別接收到連接,現在我正在'重用'該連接。我爲什麼卡在那? – fabriziomieli

+0

嘗試添加方法的實現。 LEts看看發生了什麼 –

+0

嗨,我編輯了我的問題。這可能與我使用2種不同的daos的事實有關? – fabriziomieli

0

你說得對,Propagation.REQUIRED是默認值。但是這也意味着dao上的第二個(嵌套)調用會加入/重用在服務級別創建的事務。所以不需要爲嵌套調用創建另一個事務。

的首選方法是使用Spring的最高水平的基於模板的 持久化API或使用方法:

一般春季(高級別使用)應該由它轉發給底層的ORM層管理資源處理本地ORM API與 事務感知工廠bean或用於管理本地 資源工廠的代理。這些事務感知解決方案在內部處理資源創建和重用,清理,可選事務 資源同步和異常映射。因此,用戶數據訪問代碼不必解決這些任務,但可以將 純粹集中在非模板化持久性邏輯上。

即使你處理一下你自己(在低級別的API使用)的連接應該被重複使用:

當你需要的應用程序代碼直接與資源處理 類型的本土持久性API,您可以使用這些類來確保 獲得正確的Spring Framework託管實例, 事務(可選)同步,並且在此過程中發生的 的異常正確映射到一致的API。

...

如果現有的交易已經同步 的連接(鏈接)到它,則返回該實例。否則,方法調用 將觸發創建一個新連接,該連接是(可選)同步到任何現有事務的(可選) ,並且可用於在同一事務中隨後重用。

Source

也許你必須找到發生了什麼事情在那裏。

每個會話/工作單元將綁定到一個線程,並在事務結束後釋放(連同分配的連接)。當然,當你的線程卡住它不會釋放連接。

你確定這個「死鎖」是由這個嵌套造成的嗎?也許這是另一個原因。你有這個例子的測試代碼嗎?或者一個線程轉儲或其他東西?

+0

謝謝你的回答。我明白了。但是,如果情況是這樣,並且嵌套調用重用了在服務級別創建的事務,那麼它是如何影響獲得連接的呢?我讀到他們正在使用相同的「物理」連接,但有一個不同的「邏輯」連接......但也許我錯過了一些東西! – fabriziomieli

+0

編輯我的答案。也許你可以在這裏發佈你的測試代碼。 –

+0

嗨,我編輯了我的問題。這可能與我使用2種不同的daos的事實有關? – fabriziomieli

0

@Transactional通過保持ThreadLocal狀態來工作,該狀態可由(Spring管理的)代理EntityManager訪問。如果您使用的是Propagation.REQUIRED(默認值),並且您有一個調用兩個不同DAO(或同一個DAO上的兩個事務方法)的非事務性方法,您將獲得兩個事務,並且兩個調用獲取一個池連接。您可能會獲得相同的連接兩次或兩次不同的連接,但您應該只使用一次連接。

如果您從@Transactional方法中調用兩個DAO,則只會有一個事務,因爲DAO將查找並加入在ThreadLocal狀態中找到的現有事務,同樣,您只需要一個來自池的連接。

如果遇到死鎖,那麼有些東西是非常錯誤的,您可能想要在創建連接和事務時進行調試。通過調用Connection.setAutoCommit(false)開始一個事務,在Hibernate中這發生在org.hibernate.resource.jdbc.internal.AbstractLogicalConnectionImplementor#begin()。連接由延伸org.hibernate.resource.jdbc.internal.AbstractLogicalConnectionImplementor的類進行管理,因此這些是放置斷點的好地方,並將調用堆棧追溯回代碼,以查看哪些線路創建了連接。