|
I have setup a ManyToMany relationship for User/Groups and have cascading set to MERGE and PERSIST. I have a listener registered to add audit data to my tables on persist and update via the lifecycle events to help keep the code clean.
When adding a reference to the many to many set, the cascading appears to take effect, as expected, as I see an insert on the intermediate reference table between users and groups (debug logs with show_sql). However, the lifecycle events are not triggering, as I do not have the audit data present in the insert statement, resulting in a null constraint violation. I have stepped through the code execution with a debugger and do not catch my breakpoint when the intermediate reference table insert statement occurs and the exception is thrown.
Auditable
public interface Auditable {
void setTouchedBy(String touchedBy);
void setTouchedAt(Date touchedAt);
void setCreatedBy(String createdBy);
void setCreatedAt(String createdAt);
}
AuditListener
public AuditListener {
@PrePersist
public void onPrePersist(Auditable entity) {
Date date = new Date();
String user = getUser();
entity.setTouchedBy(user);
entity.setTouchedAt(date);
entity.setCreatedBy(user);
entity.setCreatedAt(date);
}
@PreUpdate
public void onPreUpdate(Auditable entity) {
Date date = new Date();
String user = getUser();
entity.setTouchedBy(user);
entity.setTouchedAt(date);
}
private String getUser() {
Authentication auth = SecurityContextHolder.getContext().getAutnetication();
if(auth != null) {
Object principal = auth.getPrincipal();
if(principal isinstance String) {
return principal.toString();
} else {
return ((UserDetails) principal).getUsername();
}
}
return "unknown";
}
}
User
@Entity
@Table(name="user")
@EntityListeners(AuditListener.class)
public class User implements Serializable,Auditable {
private Integer userId;
private String username;
private String password;
private Date touchedBy;
private String touchedAt;
private String createdBy;
private Date createdAt;
private Set<UserGroup> userGroups = new HashSet<UserGroups>();
private Set<Group> groups = new HashSet<Group>();
/* snipped constructors for brevity */
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "UserID",unique = true,nullable = false)
public Integer getId() {
return this.id;
}
@Column(name = "Password", nullable = false)
public String getPassword() {
return this.password;
}
@Column(name = "TouchedBy", nullable = false, length = 50)
public String getTouchedBy() {
return this.touchedBy;
}
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "TouchedAt", nullable = false, length = 19)
public Date getTouchedAt() {
return this.touchedAt;
}
@Column(name = "CreatedBy", nullable = false, length = 50)
public String getCreatedBy() {
return this.createdBy;
}
@Temporal(TemporalType.TIMESTAMP)
@Column(name = "CreatedAt", nullable = false, length = 19)
public Date getCreatedAt() {
return this.createdAt;
}
@OneToMany(fetch = FetchType.EAGER, mappedBy = "user")
public Set<UserGroup> getUserGroup() {
return this.userGroups;
}
@ManyToMany(cascade = { CascadeType.MERGE,CascadeType.PERSIST })
@JoinTable(name = "UserGroup",
joinColumns = { @JoinColumn(name = "UserId") },
inverseJoinColumns = { @JoinColumn(name = "GroupId") })
public Set<Group> getGroups() {
return groups;
}
/* Setters snipped for brevity */
}
Group
@Entity
@Table(name="group")
@EntityListener(AuditListener.class)
public class Group implements Serialiable,Auditable {
private Ingeger groupId;
private String name;
private Boolean enabled;
private Date touchedBy;
private String touchedAt;
private String createdBy;
private Date createdAt;
/* snipped permission relationship for brevity */
private Set<UserGroup> userGroups = new HashSet<UserGroups>();
private Set<User> users = new HashSet<User>();
/* snipped constructors for brevity */
/* snipped the boring getters for brevity */
@OneToMany(fetch = FetchType.EAGER, mappedBy = "group")
public Set<UserGroup> getUserGroup() {
return this.userGroups;
}
@ManyToMany(mappedBy = "groups", cascade = cascade = { CascadeType.MERGE,CascadeType.PERSIST })
public Set<Users> getUsers() {
return users;
}
/* snipped setters for brevity */
}
UserGroup
@Entity
@Table(name="usergroup")
@EntityListener(AuditListener.class)
public class UserGroup implements Serialiable,Auditable {
private UserGroupdId id;
private User user;
private Group group;
private Date touchedBy;
private String touchedAt;
private String createdBy;
private Date createdAt;
/* snipped constructors for brevity */
@EmbeddedId
@AttributeOverrides({ @AttributeOverride(name = "userId", column = @Column(name = "UserID", nullable = false)),
@AttributeOverride(name = "groupId", column = @Column(name = "GroupID", nullable = false)) })
public UserGroup getId() {
return this.id;
}
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "UserID", nullable = false, insertable = false, updatable = false)
public User getUser() {
return this.user;
}
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "GroupID", nullable = false, insertable = false, updatable = false)
public Group getGroup() {
return this.group;
}
/* snipped the boring getters for brevity */
/* snipped setters for brevity */
}
UserGroupId
@Embeddable
public class UserGroupdId implements Serializable {
private Integer UserId;
private Integer GroupId;
/* snipped constructorss for brevity */
@Column(name="UserID", nullable=false)
public Integer getUserId() {
return this.userId;
}
@Column(name="GroupID", nullable=false)
public Integer getGroupId() {
return this.groupId;
}
/* snipped setters for brevity */
/* snipped equals/hash code for brevity */
}
Intended usage
User u = userRepository.findOne(1);
Group g = groupRepository.findOne(1);
u.getGroups().add(g);
userRepository.save(u);
I am seeing these sql statments:
update
people
set
CreatedAt=?,
CreatedBy=?,
Password=?,
TouchedAt=?,
TouchedBy=?,
Username=?
where
UserID=?
insert
into
UserGroup
(UserId, GroupId)
values
(?, ?)
And this results on the null constraint violation
I'd expect this:
update
people
set
CreatedAt=?,
CreatedBy=?,
Password=?,
TouchedAt=?,
TouchedBy=?,
Username=?
where
UserID=?
insert
into
UserGroup
(CreatedBy, CreatedAt, TouchedBy, TouchedAt, UserId, GroupId)
values
(?, ?, ?, ?, ?, ?)
Again, it looks like the cascade is working, since the insert on UserGroup gets sent, however, the AuditListener's @PrePersist method is not getting called
|