【问题标题】:LazyInitialization Exception with Spring and HibernateSpring 和 Hibernate 的 LazyInitialization 异常
【发布时间】:2010-01-11 05:06:33
【问题描述】:

我认为我缺少有关 Hibernate 工作原理的一些基本知识,特别是延迟加载。我的问题是调试,因为我不确定这是 Hibernate 问题还是变相的 Spring 问题。我想在进行一些重大重构之前我会在这里问。

我有两个实体。一个以 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 -->

【问题讨论】:

    标签: java hibernate spring


    【解决方案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 调用。但在这种情况下,这可能不适合这里。

    【讨论】:

    • 啊,所以如果我想获取与给定 LearningEntry 相关的一组 Sample 实体,我需要对数据库进行全新的调用吗?这很有意义,尽管它比我希望的要多。也许 LearningEntryDAO 中的方法,例如 getSampleForLearningEntry(LearningEntry le) 是合适的?
    • 是的,但是现在通过从 LearningEntryDao 询问 Samples,尽管您有一个 SampleDAO,但现在看看您如何将 2 个表混合到 1 个 DAO。 IMO,最好在用例驱动设计中做到这一点。您可能不需要 2 个单独的 DAO。
    • 懒加载也意味着DB之旅。
    【解决方案2】:

    感谢 Vinegar 提供有效的答案(已投票)。

    我决定添加这个对我也有用的答案。我采用这种方法是因为我将来可能想进行单独的 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();      
    }
    

    【讨论】:

    • 是的,没错,这就是我在第三点中试图提到的内容。
    【解决方案3】:

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

    解决方案,在两层系统中, 随着动作执行,数据访问 通过 Session 和渲染 的视图都在同一个虚拟 机器,就是保持Session打开 直到视图被渲染。

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

    【讨论】:

      猜你喜欢
      • 2021-12-17
      • 2014-11-02
      • 2019-05-03
      • 1970-01-01
      • 2014-07-27
      • 2018-07-24
      • 1970-01-01
      • 1970-01-01
      • 2015-01-03
      相关资源
      最近更新 更多