0

I'm trying to persist a User-class, which contains 2 lists: one with users to see which are following that specific user, and one with users to see which users that specific user follows.

User.java:

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

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;
    private String name;
    private String web;
    private String bio;
    @OneToMany(cascade=CascadeType.ALL)
    private Collection<User> followers = new ArrayList();
    @OneToMany(cascade=CascadeType.ALL)
    private Collection<User> following = new ArrayList();

I have a class named UserDAOJPA.java which creates and modifies the users:

@Alternative
@Stateless
public class UserDAOJPA implements Serializable, IUserDAO {

    @PersistenceContext(unitName = "KwetterSOAPPU")
    private EntityManager em;

    @Override
    public void create(User user) {
        em.persist(user);
    }
@Override
    public List<User> getFollowers(User user) {
        List<User> followers;
        followers = (List<User>) user.getFollowers();
        return followers;
    }

    @Override
    public void addFollower(User user, User follower)
    {
        user.addFollower(follower);
        em.merge(user);
    }

    @Override
    public List<User> getFollowing(User user) {
        List<User> following;
        following = (List<User>) user.getFollowing();
        return following;
    }

    @Override
    public void addFollowing(User user, User following) {
        user.addFollower(following);
        em.merge(user);
    }
    @PostConstruct
    private void initUsers() {
        User u1 = new User("Hans", "http", "geboren 1");
        User u2 = new User("Frank", "httpF", "geboren 2");
        User u3 = new User("Tom", "httpT", "geboren 3");
        User u4 = new User("Sjaak", "httpS", "geboren 4");

        this.create(u1);
        this.create(u2);
        this.create(u3);
        this.create(u4);

        this.addFollowing(u1, u2);
        this.addFollower(u2, u1);
        this.addFollowing(u1, u3);
        this.addFollower(u3, u1);
        this.addFollowing(u1, u4);
        this.addFollower(u4, u1);

My own guess is that I'm missing a correct annotation in the User.java class, when looking at the Collection of User's.

The error message:

Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.3.2.v20111125-r10461): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.SQLIntegrityConstraintViolationException: Column 'FOLLOWING_ID'  cannot accept a NULL value.
Error Code: -1
Call: INSERT INTO Users_Users (followers_ID, User_ID) VALUES (?, ?)
    bind => [2 parameters bound]
Query: DataModifyQuery(name="followers" sql="INSERT INTO Users_Users (followers_ID, User_ID) VALUES (?, ?)")
Joetjah
  • 6,292
  • 8
  • 55
  • 90
  • there may to be sth wrong with your `addFollowing` and `adFollower` methods - they are identical (just the second arg is named differently) - you may have had sth different in mind? – kostja Jan 24 '13 at 11:23
  • No that should be correct, I'm trying to create two lists: One containing the users following them, and one containing the users he follows. – Joetjah Jan 24 '13 at 11:28

2 Answers2

2

Relationship between users and their followers is a bidirectional many-to-many relationship, not two one-to-many relationships, therefore you need to map it as such:

@ManyToMany(mappedBy = "following")
private Collection<User> followers = new ArrayList<>();

@ManyToMany
@JoinTable(name = "followers",
    joinColumn = @Column(name = "follower_id"),
    inverseJoinColumn = @Column(name = "following_id"))
private Collection<User> following = new ArrayList<>();

Also note that cascading usually should be used when referring to logically "owned" entities. In your case it makes no sense because Users don't "own" each other.

Kalle Richter
  • 8,008
  • 26
  • 77
  • 177
axtavt
  • 239,438
  • 41
  • 511
  • 482
  • I changed the relationship, and now I get the following error: ` A cycle is detected in the object graph. This will cause infinitely deep XML: domain.User[naam=Hans] -> domain.User[naam=Frank] -> domain.User[naam=Hans]]`. Can this be caused because of the relationships? – Joetjah Jan 24 '13 at 12:11
  • See http://stackoverflow.com/questions/4944506/jaxb-a-cycle-is-detected-in-the-object-graph – axtavt Jan 24 '13 at 12:12
1

Just guessing, as I don't have an environment to test the code, but I think you need to change the create method to return the return value from the EntityManager.merge class and use the result, like this:

...

public User create(User user) {
    return em.merge(user);
}

...

User user = this.create(new User(...));
User follower = this.create(new User(...));
this.addFollower(user, follower);

The reason for the error is that the reference to the Follower's id is undefined, as you don't return the merged object state one you've persisted it.

Peter Hägg
  • 158
  • 1
  • 9
  • Actually, I first created the users, and then I've set their following data. Look at `initUsers()` in `UserDAOJPA.java` – Joetjah Jan 24 '13 at 12:12
  • Yes you do, but your generated ids get lost as create() method doesn't return the persisted state of your User objects. I included the last three lines of code just to illustrate, that you need to change the initUsers() method as well. Sorry if this was a tad unclear. This is assuming that EntityManager handles the objects in a mutable way. As I said, this is just a guess and it is higly possible that axtavt's answer solves the issue. – Peter Hägg Jan 25 '13 at 08:45
  • Isn't it true that when you create an object, and then persist it, that object automatically gains an ID? Using `@ID @GeneratedValue` and using `@ManyToMany` automatically creates that link, doesn't it? I could be mistaken though. – Joetjah Jan 25 '13 at 20:17
  • 1
    It does, but the thing that I can't test, as I don't have a development environment is, that does EntityManager handle the entity classes as mutable classes, i.e. is the id set in the actual object passed to create, or is is set in the object returned by the merge method call. Looking at the EntityManager API reference, it appears that the latter is the case, thus you should return the persisted object (the User) as it is returned from the merge method call. – Peter Hägg Jan 28 '13 at 13:05