2012-11-27 42 views
2

我有一個基於Spring的web應用程序,我的問題是在我的代碼更改後,我開始得到延遲加載異常。下面我將介紹詳細情況:奇怪的懶加載異常

一開始

我有一個帳戶和Word實體。一個帳戶可以有多個單詞,一個單詞可以分配給多個帳戶。

Account.class

@ManyToMany(targetEntity = Word.class, fetch = FetchType.LAZY) 
@JoinTable(name = "account_word", joinColumns = {@JoinColumn(name="account_id")}, inverseJoinColumns = {@JoinColumn(name="word_id")}) 
@OrderBy("word") 
private List<Word> words; 

Word.class

@ManyToMany(targetEntity = Account.class, fetch = FetchType.LAZY, mappedBy = "words") 
@JsonIgnore 
private List<Account> accounts; 

除了每個帳戶可以有其主要原因是Account.class映射字實體代表只有一個 「WordForToday」像這樣:

@OneToOne 
@JoinColumn(name="word_for_today") 
private Word wordForToday; 

一切工作正常。特別是我有一個@Scheduled方法,該方法被調用每天一次改變 「WordForToday」 爲每個帳戶:

WordServiceImpl.class

@Transactional 
@Service 
public class WordServiceImpl implements WordService { 

@Autowired 
AccountDao accountDao; 

@PersistenceContext 
EntityManager entityManager; 

@Override 
@Scheduled(cron="0 0 0 * * ?") 
public void setNewWordsForToday() { 
    logger.info("Starting setting new Words For Today"); 
    List<Account> allAccounts = accountDao.listAccounts(); 
    for(Account account : allAccounts) {  
     if(hasListAtLeastOneWordWithDefinitionWhichIsNotSetAsWordForToday(account.getWords(), account.getUsername())) { 
      account.setWordForToday(getUserRandomWordWithDefinition(account.getUsername())); 
      entityManager.persist(account); 
     } 
    } 
    logger.info("Setting new Words For Today ended"); 
} 

@Override 
@Transactional 
public List<Word> listUserWords(String username) { 
    try { 
     Account foundAccount = accountDao.findUserByUsername(username); 
     List<Word> userWords = foundAccount.getWords(); 
     userWords.size(); 
     return userWords; 
    } catch (UserNotFoundException unf) { 
     logger.error("User not found: " + username, unf.getMessage()); 
     return Collections.emptyList(); 
    } 
} 
} 

AccountDaoImpl.class

@Override 
public Account findUserByUsername(String username) throws UserNotFoundException { 
    CriteriaQuery<Account> c = cb.createQuery(Account.class); 
    Root<Account> r = c.from(Account.class); 
    try { 
     c.select(r).where(cb.equal(r.get("username"), username)); 
     Account foundAccount = entityManager.createQuery(c).getSingleResult(); 
     return foundAccount; 
    } catch(NoResultException nre){ 
     throw new UserNotFoundException(); 
    } 
} 

@Override 
public List<Account> listAccounts() { 
    CriteriaQuery<Account> cq = cb.createQuery(Account.class); 
    Root<Account> account = cq.from(Account.class); 
    cq.select(account); 
    TypedQuery<Account> q = entityManager.createQuery(cq); 
    List<Account> accounts = q.getResultList(); 
    return accounts; 
} 

上面的代碼沒有延遲加載異常。懶惰提取正確的詞。


所以後來

我不得不實施字每個帳戶的團體,所以我在我的項目增加了新的集團實體。現在除了沒有改變的「WordForToday」之外,賬戶和Word之間沒有直接關係。現在一個帳戶可以有多個組,並且只有一個組可以分配給一個帳戶[單向一對多連接表]。

Account.class

@OneToMany(fetch = FetchType.LAZY) 
@JoinTable(name = "account_wordgroup", joinColumns = {@JoinColumn(name="account_id")}, inverseJoinColumns = {@JoinColumn(name="wordgroup_id")}) 
@OrderBy("name") 
private List<Group> groups; 

另外一個組可以有許多字一個字可以被分配到多個組。

其使用上述這個實體Group.class

@ManyToMany(targetEntity = Word.class, fetch = FetchType.EAGER) 
@OrderBy(value="word") 
@JoinTable(name = "wordgroup_word", joinColumns = {@JoinColumn(name="wordgroup_id")}, inverseJoinColumns = {@JoinColumn(name="word_id")}) 
private List<Word> words; 

Word.class

@ManyToMany(targetEntity = Group.class, fetch = FetchType.LAZY, mappedBy = "words") 
@JsonIgnore 
private List<Group> groups; 

和每一個CRUD方法是否工作正常。我只有問題setNewWordsForToday()高於其現在看起來像這樣提及方法(I重構碼的位):

WordServiceImpl.class

@Transactional 
@Service 
public class WordServiceImpl implements WordService { 

@Autowired 
AccountDao accountDao; 

@PersistenceContext 
EntityManager entityManager; 

@Autowired 
GroupService groupService; 

@Override 
@Scheduled(cron="0 0 0 * * ?") 
@Transactional 
public void setNewWordsForToday() { 
    logger.info("Starting setting new Words For Today"); 
    List<Account> allAccounts = accountDao.listAccounts(); 
    for(Account account : allAccounts) {  
     if(hasListAtLeastOneWordWithDefinitionWhichIsNotSetAsWordForToday(listUserWords(account), account)) { 
      account.setWordForToday(getUserRandomWordWithDefinition(account)); 
      entityManager.persist(account); 
     } 
    } 
    logger.info("Setting new Words For Today ended"); 
} 

@Override 
@Transactional 
public List<Word> listUserWords(Account account) { 
    List<Group> userGroups = groupService.listUserGroups(account); 
    List<Word> userWords = new ArrayList<Word>(); 
    for(Group userGroup : userGroups) { 
     userWords.addAll(userGroup.getWords()); 
    } 
    return userWords; 
} 
} 

GroupServiceImpl.class

@Transactional 
@Service 
public class GroupServiceImpl implements GroupService { 

@Override 
@Transactional 
public List<Group> listUserGroups(Account account) { 
    List<Group> userGroups = account.getGroups(); 
    userGroups.size(); 
    return userGroups; 
} 

}

AccountDaoImpl.class沒有更改。現在我已經被調用@Scheduled方法時,這個延遲加載異常:

ERROR [org.springframework.scheduling.support.MethodInvokingRunnable] - Invocation of method 'setNewWordsForToday' on target class [class pl.net.grodek.snd.service.WordServiceImpl] failed 
org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: pl.net.grodek.snd.model.Account.groups, no session or session was closed 
at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationException(AbstractPersistentCollection.java:394) 
at org.hibernate.collection.internal.AbstractPersistentCollection.throwLazyInitializationExceptionIfNotConnected(AbstractPersistentCollection.java:386) 
at org.hibernate.collection.internal.AbstractPersistentCollection.readSize(AbstractPersistentCollection.java:126) 
at org.hibernate.collection.internal.PersistentBag.size(PersistentBag.java:242) 
at pl.net.grodek.snd.service.GroupServiceImpl.listUserGroups(GroupServiceImpl.java:63) 
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) 
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) 
at java.lang.reflect.Method.invoke(Method.java:597) 
at org.springframework.aop.support.AopUtils.invokeJoinpointUsingReflection(AopUtils.java:309) 
at org.springframework.aop.framework.ReflectiveMethodInvocation.invokeJoinpoint(ReflectiveMethodInvocation.java:183) 
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:150) 
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:110) 
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:172) 
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:202) 
at $Proxy48.listUserGroups(Unknown Source) 
at pl.net.grodek.snd.service.WordServiceImpl.listUserWords(WordServiceImpl.java:83) 
at pl.net.grodek.snd.service.WordServiceImpl.setNewWordsForToday(WordServiceImpl.java:333) 
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) 
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39) 
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25) 
at java.lang.reflect.Method.invoke(Method.java:597) 
at org.springframework.util.MethodInvoker.invoke(MethodInvoker.java:273) 
at org.springframework.scheduling.support.MethodInvokingRunnable.run(MethodInvokingRunnable.java:65) 
at org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(DelegatingErrorHandlingRunnable.java:51) 
at org.springframework.scheduling.concurrent.ReschedulingRunnable.run(ReschedulingRunnable.java:81) 
at java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:441) 
at java.util.concurrent.FutureTask$Sync.innerRun(FutureTask.java:303) 
at java.util.concurrent.FutureTask.run(FutureTask.java:138) 
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$301(ScheduledThreadPoolExecutor.java:98) 
at java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(ScheduledThreadPoolExecutor.java:206) 
at java.util.concurrent.ThreadPoolExecutor$Worker.runTask(ThreadPoolExecutor.java:886) 
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:908) 
at java.lang.Thread.run(Thread.java:662) 

我覺得我什麼都試過,我一點也不知道該怎麼辦。它阻止我幾天,現在,請人幫我這個:(

PS:我用的當然OpenEntityManagerInViewFilter

+0

hasListAtLeastOneWordWithDefinitionWhichIsNotSetAsWordForToday:懶讀書例外:) – HRgiger

+0

我知道我知道:P,但它是非常豐富的,我因爲這種方法的邏輯並不明顯 –

回答

5

任務調度程序直接執行該方法對服務實現,而不是通過代理服務器,所以它沒有選擇@Transactional並在該範圍內啓動一個休眠會話。

嘗試創建另一個在WordService中連接的bean,並將調度放在那裏,以便調用該方法WordService代理,應該清除它

+0

爲了澄清我要創建一個bean,只放在那裏@Scheduled方法,然後將它自動裝配到WordService bean中? –

+0

不,自動將WordService bean引入新bean並調用wordService.setNewWordsForToday();從新bean中的預定方法內部。 – Affe

+0

神聖的煙!它的工作,你做我的...周! Huh –

0

如果是Affe解決方案不工作,我看到你的方法實現不同的是:

getUserRandomWordWithDefinition(account.getUsername()); 

取代實體:

getUserRandomWordWithDefinition(account); 

是可可能你當你關閉你的交易正在調用隨機詞?由於返回單詞或發送帳戶參數?

+1

感謝您的幫助,但在我寫這個問題之前,我嘗試了它 –