Spring和Hibernate的LazyInitialization异常

时间:2010-01-11 05:06:34

标签: java hibernate spring

我认为我缺少一些关于Hibernate如何工作的基本知识,尤其是延迟加载。我的问题是调试,因为我不确定这是一个Hibernate问题还是伪装的Spring问题。在做一些重大的重构之前,我想我会问这里。

我有两个实体。 One在OneToMany关系中拥有另一个的集合。对于我的网页,我希望获取所有第一个实体,然后为每个实体获取一组关联实体并显示它们。

我相信我的问题是:我使用JpaTemplate来查找所有实体。这工作正常,但由于延迟加载我没有得到相关的实体集。在我看来(jsp)我想访问这个集合,但当然它是null因为它是延迟加载的。现在,我得到一个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 -->

3 个答案:

答案 0 :(得分:1)

我希望您在通话中使用findAll()方法。您可以通过修改下面的方法来加载所有相关的样本。

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

或者,正如您所知,您也可以通过将fetch更改为FetchType.EAGER来实现此目的。但是在所有情况下这可能都不适合你。因此,以前的方式更好。

或者您可能不想在任何地方进行任何更改,并定义另一种方法来获取基于LearningEntry的所有示例,这样您就可以在某些事件上启动AJAX调用。但在这种情况下,这可能不适合。

答案 1 :(得分:0)

感谢Vinegar提供了一个有效的答案(upvoted)。

我决定添加这个对我有用的答案。我采用这种方法是因为我可能希望将来进行单独的ajax调用。换句话说,我可以在一次交易中要求LearningEntry,而不是在未来一段时间内询问其样本。

@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();      
}

答案 2 :(得分:0)

大多数框架都提供“视图中的开放会话”模式。见https://www.hibernate.org/43.html

  

解决方案,在双层系统中,   与动作执行,数据访问   通过Session和渲染   视图全部在同一虚拟中   机器,是保持会话打开   直到视图已呈现。

对于经常阅读但几乎不更新的数据,查询缓存也可以提供帮助。这减少了数据库的负载,但增加了内存使用量。可以配置Hibernate为您执行此操作。