2010-01-11 72 views
0

我想我缺少一些有關Hibernate如何工作的基礎知識,特別是延遲加載。我的問題在於調試,因爲我不確定這是一個Hibernate問題還是一個變相的Spring問題。我想在做一些重大的重構之前,我會問這裏。Spring和Hibernate的懶惰初始化異常

我有兩個實體。一個擁有OneToMany關係的另一個集合。對於我的網頁,我希望抓住所有第一個實體,然後爲每個實體獲取一組關聯實體並顯示它們。

我相信我的問題是這樣的:我使用JpaTemplate來查找所有實體。這工作正常,但是因爲延遲加載,我沒有得到關聯的相關實體集。在我看來(jsp)我想訪問這個集合,但它當然是空的,因爲它正在被加載。現在,我得到一個LazyInitialization異常,指出事務已經結束。對我來說這是有道理的,當然交易現在應該結束了。事情是,如果交易結束,那麼如何延遲相關集呢?

實體類:

@Entity 
public class LearningEntry implements Serializable { 

private Long id; 
String imagePath = ""; 
Set<Sample> samples = null; 

//------------------------------ 
// Constructors 
//------------------------------ 
public LearningEntry(){ 
    imagePath = ""; 
    samples = new HashSet<Sample>(); 
} 

//------------------------------ 
// Instance Methods 
//------------------------------ 
public void addSample(Sample s){ 
    samples.add(s); 
} 

public void removeSample(Sample s){ 
    samples.remove(s); 
} 

//------------------------------ 
// Setters and Getters 
//------------------------------ 

@Id 
@GeneratedValue(strategy = GenerationType.IDENTITY) 
public Long getId() { 
    return id; 
} 

public void setId(Long id) { 
    this.id = id; 
} 

//@Column(name = "wisi_LE_IMAGEPATH", length = 100, nullable = false) 
public String getImagePath() { 
    return imagePath; 
} 

public void setImagePath(String imagePath) { 
    this.imagePath = imagePath; 
} 

// TODO - ONly works with fetch type EAGER 
//@OneToMany(fetch = FetchType.EAGER, cascade = {CascadeType.PERSIST, CascadeType.MERGE}) 
@OneToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE}) 
public Set<Sample> getSamples() { 
    return samples; 
} 

public void setSamples(Set<Sample> samples) { 
    this.samples = samples; 
} 
} 

樣品實體

@Entity 
public class Sample implements Serializable { 


private Long id; 
Date creationDate; 
String audioFileLocation; 
Integer votes; 
String description; 

public Sample(){ 
    creationDate = new Date(); 
    audioFileLocation = ""; 
    votes = 0; 
    description = ""; 
} 

@Id 
@GeneratedValue(strategy = GenerationType.IDENTITY) 
public Long getId() { 
    return id; 
} 

public void setId(Long id) { 
    this.id = id; 
} 

public String getAudioFileLocation() { 
    return audioFileLocation; 
} 

public void setAudioFileLocation(String audioFileLocation) { 
    this.audioFileLocation = audioFileLocation; 
} 

@Temporal(TemporalType.DATE) 
public Date getCreationDate() { 
    return creationDate; 
} 

public void setCreationDate(Date creationDate) { 
    this.creationDate = creationDate; 
} 

public String getDescription() { 
    return description; 
} 

public void setDescription(String description) { 
    this.description = description; 
} 

public Integer getVotes() { 
    return votes; 
} 

public void setVotes(Integer votes) { 
    this.votes = votes; 
} 
} 

DAO類: LearningEntryDAO

@Transactional 
public class JpaLearningEntryDAO implements LearningEntryDAO{ 

private JpaTemplate jpaTemplate; 

public JpaLearningEntryDAO(){ 
} 

public void setJpaTemplate(JpaTemplate jpaTemplate){ 
    this.jpaTemplate = jpaTemplate; 
} 

    @Override 
//@Transactional 
public void delete(Long leId) { 
    LearningEntry dp = jpaTemplate.find(LearningEntry.class, leId); 
    jpaTemplate.remove(dp); 
} 

    @Override 
@SuppressWarnings("unchecked") 
//@Transactional 
public List<LearningEntry> findAll() { 
     return jpaTemplate.find("from LearningEntry"); 
    } 

    @Override 
//@Transactional 
public LearningEntry findById(Long leId) { 
    return jpaTemplate.find(LearningEntry.class, leId); 
} 

    @Override 
//@Transactional 
public LearningEntry store(LearningEntry dp) { 
    return jpaTemplate.merge(dp); 
} 

    @Override 
@SuppressWarnings("unchecked") 
//@Transactional 
public void deleteAll(){ 
    throw new RuntimeException("deleteAll not implemented"); 
} 
} 

樣品DAO

@Transactional 
public class JpaSampleDAO implements SampleDAO{ 

private JpaTemplate jpaTemplate; 

public JpaSampleDAO(){} 

public void setJpaTemplate(JpaTemplate jpaTemplate){ 
    this.jpaTemplate = jpaTemplate; 
} 

    @Override 
//@Transactional 
public void delete(Long sampleId) { 
    Sample dp = jpaTemplate.find(Sample.class, sampleId); 
    jpaTemplate.remove(dp); 
} 

    @Override 
@SuppressWarnings("unchecked") 
public List<Sample> findAll() { 
    return jpaTemplate.find("from Sample"); 
} 

    @Override 
public Sample findById(Long sampleId) { 
    return jpaTemplate.find(Sample.class, sampleId); 
} 

    @Override 
public Sample store(Sample dp) { 
    return jpaTemplate.merge(dp); 
} 

    @Override 
@SuppressWarnings("unchecked") 
public void deleteAll(){ 
    throw new RuntimeException("deleteAll not implemented"); 
} 
} 

控制器

@RequestMapping(value = "/index.htm", method = RequestMethod.GET) 
public ModelAndView sayHello(HttpServletRequest request, 
     HttpServletResponse response) throws ServletException, IOException { 

    Map<String, Object> model = new HashMap<String, Object>(); 
    List<LearningEntry> le = learningEntryService.getLearningEntries(); 
    model.put("learningEntries", le); 
    return new ModelAndView("main", model); 
} 

查看

<section id="content" class="body"> 
    <ol id="posts-list" class="hfeed"> 
     <c:forEach items="${learningEntries}" var="learningEntry"> 
      <li> 
      <table class="wisiEntry"> 
       <tr> 
        <td class="pictureCell"> 
         <img class="wisiEntry-pic" src="${learningEntry.imagePath}" /> 
        </td> 
        <td class="previousNextCell" 
         <div class="wisiEntry-nextSampleButton">Next</div> 
         <div class="wisiEntry-previousSampleButton">Previous</div> 
         <br /> 
         <div class="wisiEntry-addTagButton">Tag</div> 
         <div class="wisiEntry-addCommentButton">Comment</div> 
         <br /> 
         <div class="wisiEntry-uploadButton">Upload</div> 
        </td> 
        <td> 
         <!-- ERROR HAPPENS HERE. Samples should not be null --> 
         <c:forEach items="${learningEntry.samples}" var="sample" varStatus = "status"> 
          <table class="sampleEntry" ${status.first ? '' : 'style = "display:none"'}> 
           <tr> 
            <td class="sampleCell"> 
             <p class="description"> 
              ${sample.description} 
             </p> 
             <audio src="${sample.audioFileLocation}" controls> 
              Your browser does not support the <code>audio</code> element. 
             </audio> 
            </td> 
            <td class="voteCell"> 
             <img class="upVote" src="/images/upArrow.jpeg" /> 
             <span class="voteNumber">${sample.votes}</span> 
             <img class="downVote" src="/images/downArrow.jpeg" /> 
            </td> 
           </tr> 
          </table> 
         </c:forEach> 
        </td> 
       </tr> 
      </table> 
      </li> 
     </c:forEach> 
    </ol><!-- /#posts-list --> 
</section><!-- /#content --> 

回答

0

感謝醋提供了一個工作答案(upvoted)。

我決定添加這個也適用於我的答案。我採取了這種方法,因爲我可能想在未來分別進行ajax調用。換句話說,我可以在一次交易中要求學習入學,而不是在一段時間內要求樣本。

@Transactional 
public Set<Sample> getSamplesForLearningEntry(LearningEntry le) { 
    // Reload the le from the database so it is not transient: 
    LearningEntry le = leDAO.store(le); 
    le.getSamples.size(); 
    return le.getSamples();  
} 
+0

是的,這正是我在第三點提到的。 – 2010-01-11 06:25:33

1

我希望你正在使用findAll()方法降低通話。您可以通過修改您的方法加載所有關聯的樣本,如下所示。

public List<LearningEntry> findAll() { 
    List<LearningEntry> entries = jpaTemplate.find("from LearningEntry"); 
    for(LearningEntry entry : entries){ 
     entry.getSamples().size(); 
    } 
    return entries; 
} 

或者,因爲你已經知道了,你還可以通過更改fetchFetchType.EAGER實現這一目標。但這可能不適合所有情況。所以,以前的方式比較好。

或者您可能不想在任何地方做任何更改,並定義另一種方法來獲取基於LearningEntry的所有示例,這樣您就可以在某些事件中觸發AJAX調用。但在這種情況下,這可能不適合。

+0

ahhh,所以如果我想獲得與給定的LearningEntry相關的Sample實體集,我需要對數據庫進行全新的調用?這很有道理,儘管它比我希望的要多得多。也許LearningEntryDAO中的一個方法,比如getSampleForLearningEntry(LearningEntryle)會是合適的嗎? – 2010-01-11 05:24:52

+0

是的,但現在看看如何通過從LearningEntryDao中提取樣本來混合2個表格到1個DAO,儘管您有一個SampleDAO。國際海事組織,它更好地在一個用例驅動的設計。您可能不需要2個獨立的DAO。 – 2010-01-11 06:23:03

+0

延遲加載也意味着到DB的旅程。 – 2010-01-11 06:23:59

0

大多數框架都提供'open session in view'模式。見https://www.hibernate.org/43.html

的溶液,在兩級系統中, 與動作執行,通過所述會話數據訪問 ,並且所有在相同的虛擬 機視圖的渲染 ,是保持會話打開 ,直到視圖被渲染。

對於經常讀取且幾乎沒有更新的數據,查詢緩存也可以提供幫助。這減少了數據庫的負載,但增加了內存使用量。 Hibernate可以配置爲你做這個。