0

I have News entity and comment entity. I want to create news with 0 comments and then add some comments to this news.

In DB I have table NEWS for news info and news_id column in COMMENTS table.

News.java:

@Entity
@Table(name = "NEWS")
public class News implements Serializable{
    /**
     * serialVersionUID
     */
    private static final long serialVersionUID = 883279937885116359L;
    /**
     * News id
     */
    @Id
    @GeneratedValue(generator = "seq")
    @SequenceGenerator(name="seq", sequenceName="NEWS_SEQ",allocationSize=1)
    @Column(name = "NEWS_ID", nullable = false, unique = true)
    private Long id;
    /**
     * News short text
     */
    @Column(name = "SHORT_TEXT")
    private String shortText;
    /**
     * News full text
     */
    @Column(name = "FULL_TEXT")
    private String fullText;
    /**
     * News title
     */
    @Column(name = "TITLE")
    private String title;
    /**
     * News creation date
     */
    @Column(name = "CREATION_DATE")
    private Date creationDate;
    /**
     * News modification date
     */
    /**
     * News comments
     */
    @OneToMany(cascade = CascadeType.ALL, orphanRemoval= true, fetch = FetchType.EAGER)
    @JoinColumn(name = "NEWS_ID")
    private List<Comment> comments;

Comment.java:

@Entity
@Table(name = "COMMENTS")
public class Comment implements Serializable{
    /**
     * serialVersionUID
     */
    private static final long serialVersionUID = -5697896094322498108L;
    /**
     * Comment id
     */
    @Id
    @GeneratedValue(generator = "seq")
    @SequenceGenerator(name="seq", sequenceName="COMMENTS_SEQ",allocationSize=1)
    @Column(name = "COMMENT_ID", nullable = false, unique = true)
    private Long id;
    /**
     * Comment text
     */
    @Column(name = "COMMENT_TEXT")
    private String commentText;
    /**
     * Comment creation date
     */
    @Column(name = "CREATION_DATE")
    private Date creationDate;
    /**
     * Id of the news which the comment is added to
     */
    @Column(name = "NEWS_ID")
    private Long newsId;

I have such code:

            News news = new News();

            news.setTitle("qwerty");
            news.setShortText("qwerty");
            news.setFullText("qwerty");

            jpaNewsDAO.add(news);
            news = jpaNewsDAO.findById(news.getId());//everything is fine here
                                                     // 0 comments
            Comment comment = new Comment(null,"qwerty",new Date(),news.getId());
            jpaCommentDAO.add(comment);
            news = jpaNewsDAO.findById(news.getId());

But after that news.comments have 3 items with copies(even with the same id's) of out comment and I can't understand why. There are only one such comment in DB.

Add comment:

@Override
    public Long add(Comment entity) throws DAOException {
        EntityManager manager = null;
        EntityTransaction transaction;
        try {
            manager = managerFactory.createEntityManager();
            transaction = manager.getTransaction();
            transaction.begin();
            entity.setId(null);
            manager.persist(entity);
            manager.flush();
            transaction.commit();
        } catch (PersistenceException e) {
            throw new DAOException(e);
        } finally {
            closeManager(manager);
        }
        return entity.getId();
    }

Add news:

@Override
    public Long add(News entity) throws DAOException {
        EntityManager manager = null;
        EntityTransaction transaction;
        try {
            manager = managerFactory.createEntityManager();
            transaction = manager.getTransaction();
            transaction.begin();
            entity.setId(null);

            List<Comment> comments = new ArrayList<Comment>();
            if (entity.getComments()!= null) {
                for (Comment comment : entity.getComments()) {
                    comments.add(manager.find(Comment.class, comment.getId()));
                }
            }
            entity.setComments(comments);

            manager.persist(entity);//add
            manager.flush();
            transaction.commit();
        } catch (PersistenceException e) {
            throw new DAOException(e);
        } finally {
            closeManager(manager);
        }
        return entity.getId();
    }

So why I have copies of comments?

  • Your add method looks really strange. Why are you re-adding the comments to the news? JPA will save the comments if you save the news. No additional work needed from your side. Also comment should have a reference to News not just newsid. That way you take full advantage of JPA – Konstantin Jun 11 '15 at 10:17
  • 1
    Post the code of the jpaNewsDAO findById() method because that is most likely where the issue is. – Alan Hay Jun 11 '15 at 11:31
  • and your flush() calls are needless, flush happens in commit (i.e the statement immediately after). – Neil Stockton Jun 11 '15 at 12:39
  • @AlanHay Nothing special: manager = managerFactory.createEntityManager(); transaction = manager.getTransaction(); transaction.begin(); result = manager.find(News.class, id); transaction.commit(); – Vyacheslav Tsivina Jun 12 '15 at 06:39
  • @Konstantin I save comments in such way because other way it will be Exception "detached entity passed to persist". – Vyacheslav Tsivina Jun 12 '15 at 06:52
  • Detached entity could imply you are not using transactions correctly – Konstantin Jun 12 '15 at 07:01
  • @Konstantin so how should I realize add method? – Vyacheslav Tsivina Jun 12 '15 at 07:28
  • Either use merge if you really have detached entities OR make your transaction span the complete "operation". I can see that you currently start the transaction inside of add method. You then need to look a bit broader. Where did the incoming parameter "News entity" com from? If you can make transaction span that part as well you are good... – Konstantin Jun 12 '15 at 07:44
  • @Konstantin I cant. I write Java EE application and entity comes from Controller and I have to use services – Vyacheslav Tsivina Jun 12 '15 at 07:58
  • Ok then you need to use merge – Konstantin Jun 12 '15 at 08:01
  • Start by merging news. This will make it part of current transaction – Konstantin Jun 12 '15 at 08:02
  • It looks really strange what you do inside of add in both cases – Konstantin Jun 12 '15 at 08:04
  • If i merge so I don't have insesrted id of news. If I persist in simple way I have exception – Vyacheslav Tsivina Jun 12 '15 at 08:15
  • Try marking the relationship as LAZY rather than EAGER. If that works read this: http://stackoverflow.com/questions/1995080/hibernate-criteria-returns-children-multiple-times-with-fetchtype-eager – Alan Hay Jun 12 '15 at 08:34
  • @AlanHay yeah, it seems my problem. Thank you. If I use LAZY than i can't use comments in the upper layer(Service -> Controller -> jsp), can I? Because of it I use EAGER. Sorry if I mistake but I'm new to this. – Vyacheslav Tsivina Jun 12 '15 at 08:43

2 Answers2

1

The issue is due to the fact that the relationship between News and Comment is marked as EAGER. For more information on why this is the case see here:

Hibernate Criteria returns children multiple times with FetchType.EAGER

You can mark the relationship as LAZY and this will prevent the problem. It is normally best practice to have relationships defined in this way anyway and to enable eager fetching of relationships as required for a given use via, for example, query hints or the new 'Entity Graphs' feature of JPA 2.1.

https://blogs.oracle.com/theaquarium/entry/jpa_2_1_entity_graphs

For now you can simply add the following line in your DAO:

news.getComments().size().

which will force the lazy collection to be loaded. It will then obviously be available in your web tier.

Community
  • 1
  • 1
Alan Hay
  • 22,665
  • 4
  • 56
  • 110
0

Move transaction start/commit higher and make sure your news object points at its comments and vice versa

Transaction transaction = beginTransaction();  //start a transaction 
News news = new News();      
news.setTitle("qwerty");
news.setShortText("qwerty");
news.setFullText("qwerty");

Comment comment = new Comment(null,"qwerty",new Date(),news); //note news not news.getId()

news.addComment(comment);

//You now have a correct "graph" news points at comment and comment points at news

//persist
jpaNewsDAO.add(news);

commitTransaction(transaction); //commit

//Hibernate will now have persisted both Comment and News for you

Add only persists nothing else needed handle exceptions how ever you seem fit

@Override
public Long add(News entity) throws DAOException {

    manager.persist(entity);
}
Konstantin
  • 3,626
  • 2
  • 33
  • 45