Miguel Martins
Miguel Martins

Reputation: 93

JPA - How to avoid getting an empty list?

I'm creating a sort of a social networking site, like Facebook, as a university project. Users can upload photos, but I'm somehow unable to retrieve the list of photos for a particular user.

Here's how I'm doing it right now:

@Entity
@Table(name = "users")
public class User implements Serializable {

@Id
private String emailAddress;
private String password;
private String firstName;
private String lastName;

(...)

@OneToMany(mappedBy = "owner", fetch = FetchType.EAGER)
private List<Photo> photos;

public User() {
}

(...)

public void addPhoto( Photo photo){
    photos.add(photo);
}

public List<Photo> getPhotos() {
    return photos;
}
}

And here's the Photo entity:

@Entity
public class Photo implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String url;
private String label;     
@ManyToOne
private User owner;

public Photo() {
}

(...)

public User getOwner() {
    return owner;
}    
}

Each photo is uploaded by creating a post that contains it. Here's the EJB that does it:

@Stateless
public class PublicPost implements PublicPostRemote {

@PersistenceContext
EntityManager em;

@Override
public void createPost(LoginUserRemote loginUserBean, String targetEmail, final String content, final String photoURL) {
    if (loginUserBean.isLoggedIn()) {
        final User author = loginUserBean.getLoggedUser();
        System.out.println(targetEmail);
        final User target = em.find(User.class, targetEmail);
        if (author != null && target != null) {
            //See if there's a photo to post as well
            Photo photo = null;
            if (photoURL != null) {
                photo = new Photo(photoURL, author, content);
                em.persist(photo);
            }

            MessageBoard publicMessageBoard = target.getPublicMessageBoard();
            Post post = new Post(author, content);
            post.setMessageBoard(publicMessageBoard);
            if (photo != null) {
                post.setPostPhoto(photo);
            }
            em.persist(post);
            em.refresh(publicMessageBoard);
            //Send an e-mail to the target (if the author and the target are different)
            if (!author.getEmailAddress().equals(target.getEmailAddress())) {
                final String subject = "[PhaseBook] " + author.getEmailAddress() + " has posted on your public message board.";
                Thread mailThread = new Thread() {

                    @Override
                    public void run() {
                        try {
                            GMailSender.sendMessage(target.getEmailAddress(), subject, content);
                        } catch (MessagingException ex) {
                            Logger.getLogger(PublicPost.class.getName()).log(Level.SEVERE, null, ex);
                        }
                    }
                };
                mailThread.start();
            }
        }
    }
}
}

So what happens is: I create a new post that contains a photo, yet later, when I use this, on the web tier...

LoginUserRemote lur = (LoginUserRemote)session.getAttribute("loginUserBean");
User user = lur.getLoggedUser();
List<Photo> photos = user.getPhotos();
System.out.println();
System.out.println("This user has this many photos: " + photos.size());

...it always tells me that the user has 0 photos. Why is this? Am I defining the relationship between user and photo incorrectly? Am I forgetting to persist/refresh anything? Or does the problem lie somewhere else?

Upvotes: 1

Views: 628

Answers (2)

Tomasz Nurkiewicz
Tomasz Nurkiewicz

Reputation: 340903

There is a series of issues here:

  • Are photos actually stored in the database? Maybe you don't have a transaction open?

  • You are not updating both sides of the association.

Theoretically you only need to update the owning side, but better be safe than sorry:

photo = new Photo(photoURL, author, content);
em.persist(photo);
author.addPhoto(photo);
  • You are fetching the user from a session and then retrieving associated collection of photos. Do you really know what this means? If the user has hundreds of photos, do you really want to store them in HTTP session along with the user all the time? This is not how Facebook works ;-).

I think refreshing your entity (with em.refresh(lur.getLoggedUser())) might work, but only at university, not in real life. Loading all the user photos at once into memory is an overkill. Personally I would even remove photos association from user to avoid this. Load one page at a time and only on demand.

  • Even if you know what you are doing or such a behaviour is acceptable, objects stored in HTTP session are so called detached from persistence context, meaning your persistence provider does no longer keep track of them. So adding a photo does not mean that the photos collection will be magically updated in every object. I think about carefully, this would be even worse.

  • Last but not least, your createPost() really needs some code review. It does at least 4 things at once, System.out, one time threads created on demand, silently doing nothing when preconditions are not met (like user not being logged in, missing parameters), mixing concerns on different level of abstraction. Don't want to be too meticulous, but your grade might be influenced by the quality of code.

Upvotes: 0

JB Nizet
JB Nizet

Reputation: 692023

If you store a detached User object (the logged in user) in the HTTP session, and then create and persists photos having this detached user as owner, JPA won't automatically add the photo to the detached user. For the entity manager, this detached user doesn't exist: it's not under its responsibility anymore.

Even if User was still attached, it's your responsibility to maintain the coherence of the object graph. If you modify one side of the association (by setting the user as owner of the photo), you should also modify the other side (by adding the photo to the list of photos of the owner).

I'm not absolutely sure this is the cause of the problem, because you haven't shown us what the loginUserBean was and did to get the logged in user, but it might be the answer.

Upvotes: 1

Related Questions