Reputation: 2456
I have a stateless ejb bean with an entitymanager (em) and this function.
public void what(Long commentId) {
Comment c = em.find(Comment.class, commentId);
em.refresh(c);
CommentUpdate cu = new CommentUpdate(c, "new Text");
em.persist(cu);
c.getUpdates().add(0, cu);
int i = c.getUpdates().size();
em.flush();
int j = c.getUpdates().size();
if (i != j)
System.err.println("What?");
}
CommentUpdate is a pretty simple entity classes.
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class CommentUpdate extends AbstractEntity {
@ManyToOne
@Getter
@Setter
private Comment comment;
@Getter
@Setter
private String text;
public CommentUpdate(Comment comment, String text) {
this.comment = comment;
this.text = text;
}
}
Comment is slightly more complex but having the relation defined like this:
@OneToMany(mappedBy = "comment")
@OrderBy("createdAt DESC")
@Getter
private List<CommentUpdate> updates = new ArrayList<>();
The thing is now, that sometimes "What?" is printed to the log. The newly created CommentUpdate is vanished after the flush. but is back on the next refresh. It is a method in a larger vaadin project and I was not yet able to reproduce this in a small/simple project. Is there a situation where this is logical behaviour or might this be a bug in eclipselink (2.6) used by glassfish (4.1.1)?
Upvotes: 1
Views: 95
Reputation:
The most possible cause that could get you out with such results is that Caching is enabled and you set your datasource for dirty reads which allows reading uncommited updates. When using caching, the EntityManager will cache queries to enhance performance. In such case when you read a relation it will read it from the cached set and wont inquire the table. The reason that the update entity appears in the second call to the method is because the CommentUpdate is already commited to the DB and so the cache is updated as well.
You can disable or enable caching using theses properties if you are using Eclipselink as your provider
<property name="eclipselink.query-results-cache" value="false"/>
<property name="eclipselink.cache.shared.default" value="false"/>
<property name="eclipselink.cache.size.default" value="0"/>
<property name="eclipselink.cache.type.default" value="None"/>
Upvotes: 0
Reputation: 9965
The first suspicious thing is that you have a bidirectional relationship between Comment
and CommentUpdate
@OneToMany(mappedBy = "comment")
private List<CommentUpdate> updates = new ArrayList<>();
but you are setting only one side of it:
c.getUpdates().add(0, cu);
For bidirectional relationships you must set both references yourself, ie.
c.getUpdates().add(0, cu);
cu.setComment(c);
Not doing that can lead to inconsistent state, like the one you are just experiencing.
JPA specs (chapter 2.9 Entity Relationships) say that:
The owning side of a relationship determines the updates to the relationship in the database, as described in section 3.2.4
The many side of one-to-many / many-to-one bidirectional relationships must be the owning side
And in section 3.2.4 Synchronization to the Database:
Bidirectional relationships between managed entities will be persisted based on references held by the owning side of the relationship. It is the developer’s responsibility to keep the in-memory references held on the owning side and those held on the inverse side consistent with each other when they change.
It is particularly important to ensure that changes to the inverse side of a relationship result in appropriate updates on the owning side, so as to ensure the changes are not lost when they are synchronized to the database.
Usually if you closely follow the JPA specs the EM really takes care of sorting everything out. But if you make one little mistake things can get weird. So the bottom line is: set the comment
in the commentUpdate
object, which is the owning side in this relationship.
NB.: after calling
Comment c = em.find(Comment.class, commentId);
there should be no need to refresh c
since it just has been fetched and is managed.
Upvotes: 1