2

Here is my dilemma, I know in JSF the accessor method will get call mutilple times, therefore I know not to put expensive business logic (like DB access) in accessor method. What if I absolutely have to put business logic into my accessor. What should I do in this case? Below are a high level layout of my dilemma. (Mojarra 2.1, GF 3.1)

<h:dataTable value="#{myBean.comments}" var="item1">
    <h:column>
          #{item1.name} says: #{item1.comment}
          <h:dataTable value="#{myBean.handleReplies(item1)}" var="item2">
               <h:column>
                   #{item2.name} replies: #{item2.comment}
               </h:column>
          </h:dataTable>    
    </h:column>
</h:dataTable>    

@ManagedBean
@ViewScoped
public void myBean(){
    private List<Comment> comments;

    @EJB
    private MyEJB myEJB;

    @PostConstruct
    public void init(){
        comments = myEJB.getAllComments();
    }

    //getters and setters for List<Comment> comments

    public List<Comment> handleReplies(Comment comment){
         //Return a List of replies of the comment
         return myEJB.getRepliesFromComment(comment); 
    }
}

As you can see, the inner dataTable take in the item of the outer dataTable to generate its List. Is there a way for somehow stop handleReplies() to be called multiple times, since this accessor method access DB.

Thang Pham
  • 38,125
  • 75
  • 201
  • 285
  • Is it not an option to have a `#{item1.comments}` where lazy loading is just handled by JPA? – BalusC May 16 '11 at 19:39
  • @BalusC: Can u explain a bit more, BalusC? I am not sure I understand what u mean – Thang Pham May 16 '11 at 19:48
  • I'd naturally expect that `Comment` is a JPA entity which has a `@OneToMany List` property which is lazily fetched by `FetchType.LAZY`. Or did you have reasons to not do so? – BalusC May 16 '11 at 19:55
  • @BalusC: `Comment` is in fact JPA entity, which does has @OneToMany List, but I dont have `FetchType.LAZY` to it. What does this `FetchType.LAZY` does, BalusC? – Thang Pham May 16 '11 at 19:58
  • 1
    It will let JPA lazily load (and eventually cache) the children once the returned list is been iterated for the first time. They won't be loaded directly when you get only the parent(s). But since you already have .. Why don't you just use `#{item1.comments}` instead of `#{myBean.handleReplies(item1)}`? – BalusC May 16 '11 at 19:59
  • @BalusC: I see what you are saying. Thank you. That is in fact a great idea as well. Thanks again. – Thang Pham May 16 '11 at 20:07
  • You're welcome. I've posted it as an answer as well. – BalusC May 16 '11 at 20:22

2 Answers2

2

How about using a HashMap to create a view-scoped cache?

Something like:

private Map<Comment, List<Comment>> replies = new HashMap<Comment, List<Comment>>();

public List<Comment> handleReplies(Comment comment){
    if (!replies.containsKey(comment)) {
        replies.put(comment, myEJB.getRepliesFromComment(comment));
    }

    return replies.get(comment);
}

This way, your view-scoped bean store previous request results, and returns them if the request has already been done. If it hasn't, the request is made. In the end, no duplicate request!

Vivien Barousse
  • 20,555
  • 2
  • 63
  • 64
2

You can also just let JPA do the lazy loading and caching job (with a proper second level cache).

Assuming that your Comment entity look like this

@Entity
@NamedQuery(name="Comment.list", query="SELECT c FROM Comment c WHERE c.parent IS NULL")
public class Comment implements Serializable {

    @ManyToOne(optional=false)
    private Comment parent;

    @OneToMany(mappedBy="parent", fetch=LAZY, cascade=ALL);
    private List<Comment> children;

    // ...
}

You could just use #{comment.children} to (lazily) get the children in a <h:dataTable>.

BalusC
  • 1,082,665
  • 372
  • 3,610
  • 3,555