Ok, so I'd like to implement a simple forum example. So, I have threads, messages and users, of course and these are the pojos (I omitted the usually getters and simplicity)
**Message**Code:
@Entity
@Table(name = "message")
public class Message implements java.io.Serializable, RecognizedServerEntities
{
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id", unique = true, nullable = false)
private Integer id;
@ManyToOne(fetch = FetchType.LAZY)
@Cascade({ CascadeType.SAVE_UPDATE })
@JoinColumn(name = "thread", nullable = false)
private Thread thread;
@ManyToOne(fetch = FetchType.LAZY)
@Cascade({ CascadeType.SAVE_UPDATE })
@JoinColumn(name = "author", nullable = true)
private User user;
@Column(name = "title", nullable = false, length = 31)
private String title;
@Column(name = "body", nullable = false, columnDefinition = "Text")
private String body;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "last_modified_date", nullable = false, length = 19)
private Date lastModifiedDate;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_date", nullable = false, updatable = false, length = 19)
private Date createdDate;
}
**User**Code:
@Entity
@Table(name = "user", uniqueConstraints =
{ @UniqueConstraint(columnNames = "email"),
@UniqueConstraint(columnNames = "nick") })
public class User implements java.io.Serializable, RecognizedServerEntities
{
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id", unique = true, nullable = false)
private Integer id;
@Column(name = "email", unique = true, nullable = false, length = 31)
private String email;
@Column(name = "password", nullable = false, length = 31)
private String password;
@Column(name = "nick", unique = true, nullable = false, length = 31)
@NaturalId(mutable = false)
private String nick;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "registered_date", nullable = false, updatable = false, length = 19)
private Date registeredDate;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "user", orphanRemoval = false)
private Set<Thread> threads = new HashSet<Thread>(0);
@OneToMany(fetch = FetchType.LAZY, mappedBy = "user", orphanRemoval = false)
private /**transient /**/ Set<Message> messages = new HashSet<Message>(0);
}
**Thread**Code:
@Entity
@Table(name = "thread")
public class Thread implements java.io.Serializable, RecognizedServerEntities
{
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "id", unique = true, nullable = false)
private Integer id;
@ManyToOne(fetch = FetchType.LAZY)
@Cascade({CascadeType.SAVE_UPDATE})
@JoinColumn(name = "parent_thread", nullable = true)
private Thread parentThread;
@ManyToOne(fetch = FetchType.LAZY)
@Cascade({CascadeType.SAVE_UPDATE})
@JoinColumn(name = "author", nullable = true)
private User user;
@Column(name = "title", nullable = false, length = 63)
private String title;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "last_modified_date", nullable = false, length = 19)
private Date lastModifiedDate;
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "created_date", nullable = false, updatable = false, length = 19)
private Date createdDate;
@OneToMany(fetch = FetchType.LAZY, mappedBy = "thread"/**/, orphanRemoval = true/**/)
@Cascade({ CascadeType.REMOVE })
private /**transient /**/ Set<Message> messages = new HashSet<Message>(0);
@OneToMany(fetch = FetchType.LAZY, mappedBy = "parentThread", orphanRemoval = true)
@Cascade({CascadeType.REMOVE })
private /**transient /**/ Set<Thread> subThreads = new HashSet<Thread>(0);
}
I have many doubts on the annotations of course, but these are the relevant choice.
- When I delete an user, I don't want to delete all his threads and messages, so it make sense to don't use orphan-removal or cascade delete on the @OneToMany associations (ie the messages and threads collections).
- Also, because the id is automatically generated from the database, I don't think it make sense at all to use the annotation CascadeType.UPDATE (or SAVE_UPDATE) on the collections of all the entity.
- When we delete a thread, we want that all its subthreads and all its messages to be deleted. So, I use the CascadeType.REMOVE and orphan-removal annotations (don't know if it make sense to use both).
- On all the @ManyToOne associations, I use the CascadeType.ALL. The idea is that if we delete a message or a subthread, all the parents will be updated. Make sense?
- All the collections are not transient.
Feel free to propose suggestion on this of course. To complicate a little bit more the whole story, I also have another class, StorageManager<T>, that is used to encapsulate the common code between entities (I can post the code if you think it is useful). Briefly, it implements the "one session per transaction" pattern. So each methodX() of StorageManager<T> class:
- invoke `sessionFactory.openSession()` and `session.beginTransaction()`
- invoke `session.methodX()`
- invoke `transaction.commit()`
- invoke `session.clear()` and `session.close`
Btw, given the whole story, this is the question: suppose I have a thread "mThread" started from the user "mUser" with many messages from different users, how can I safely delete "mUser"? Then, how can I safely remove all other users?
I tried different things, but I'm not sure of anything and in most cases I only have exceptions.
So, in the following code, this is what I've done to safely delete "mUser"
Code:
for (Thread t : mUser.getThreads())
{
t.setUser(null);
storageManagerThread.update(t);
}
for (Message m : mUser.getMessages())
{
m.setUser(null);
storageManagerMessage.update(t);
}
storageManagerUser.delete(mUser);
Until this point, all the table in the database have the right values. However, I don't know if it is the right way to proceed, because (I think) it leaves dirty collections.
Indeed, when at later point I try to execute some other options (e.g. `update(mThread)` or delete a message from `mThread`) a `NullPointerException` was thrown.