问题
I have a Note
domain class, and when a new note is saved I need to create for it a NoteEvent
, recording for posterity that the note has been created. Note
has a collection of NoteEvents
, and each NoteEvent
keeps track of which Note
it belongs to.
The Note
class:
class Note {
String noteText
Date dateCreated
static hasMany = [events : NoteEvent]
}
The NoteEvent
class:
class NoteEvent {
Date dateCreated
String type
static belongsTo = [note : Note]
}
To handle the saving of new NoteEvents
when a note was created, I was using afterInsert
, because I’m saving note instances all over the place (it would be repetitive and time-consuming to have specific event-creating code after each saving of a new note), and beforeInsert
obviously is not dealing with a persisted instance of Note
yet — there will be nothing for the NoteEvent
to have as its note
.
So now my Note
class is:
class Note {
String noteText
Date dateCreated
static hasMany = [events : NoteEvent]
def afterInsert = {
def event = new NoteEvent(type: "CREATED")
addToEvents(event)
save()
}
}
But I also need to create a NoteEvent
when one of these notes is updated, and this is where confusion and dismay and a significant lack of coffee come in. To attach a new “updated” NoteEvent
to a note when it was updated, I brilliantly decided to use afterUpdate
, again so as to avoid having the event creation code sprinkled all over the app whenever I needed to update a Note
instance.
So now, for Note
, I have:
class Note {
String noteText
Date dateCreated
static hasMany = [events : NoteEvent]
def afterInsert = {
def event = new NoteEvent(type: "CREATED")
addToEvents(event)
save()
}
def afterUpdate = {
def event = new NoteEvent(type: "UPDATED")
addToEvents(event)
save()
}
}
To add a new event to a note’s collection, I’m using the dynamic addTo()
methods, which then require a save()
of the instance. But in the case of an “after” event, this is a second call to save()
. Thus when I first save a new instance and the afterInsert
is called, the just-saved instance is immediately saved again, which causes the afterUpdate
event to be fired, and now I have two note events: the “created” one from when I just saved the note, and an “updated” one from when the “created” one caused the note to be saved again.
It’s not clear to me how using “before” events instead could help in this situation. How else can I do this?
回答1:
You can actually use beforeInsert
and beforeUpdate
methods. This is because the addTo*
method does not require Note
to be a persisted instance.
The NoteEvent
will save when the Note
saves because the NoteEvent
is added before the Note
is saved in the beforeUpdate
method. Check out the addTo* docs for a longer explanation.
I was able to get both of the following Note
classes to work how I believe you want them to. I did run into one issue where when updating Note
two NoteEvent
objects would be added. I was able to fix this by making sure that the update method of the controller was using noteInstance.save()
instead of noteInstance.save(flush:true)
.
class Note {
String noteText
Date dateCreated
static hasMany = [events : NoteEvent]
def beforeInsert = {
def event = new NoteEvent(type: "CREATED")
addToEvents(event)
}
def beforeUpdate = {
def event = new NoteEvent(type: "UPDATED")
addToEvents(event)
}
}
If you want a more condensed version the addTo*
method knows what type of object is being added you can just use the Map
constructor of NoteEvent
class Note {
String noteText
Date dateCreated
static hasMany = [events : NoteEvent]
def beforeInsert = {
addToEvents(type: "CREATED")
}
def beforeUpdate = {
addToEvents(type: "UPDATED")
}
}
回答2:
There's probably a way to do this, possible using beforeInsert
and beforeUpdate
since those wouldn't require saving the Note
instance. The typical way to do secondary updates/inserts like this is to use withNewSession
but in this case I'm not sure that it makes sense because that's more for creating an independent object, and you'd need to re-load the Note
in the new session. Not that bad, but not performant.
One way to do this would be to remove the collection and save NoteEvent
instances directly:
class Note {
String noteText
Date dateCreated
Set<NoteEvent> getEvents() {
NoteEvent.findAllByNote(this)
}
def afterInsert() {
new NoteEvent(type: "CREATED", note: this).save()
}
def afterUpdate() {
new NoteEvent(type: "UPDATED", note: this).save()
}
}
class NoteEvent {
Date dateCreated
String type
Note note
}
You lose cascading, so you'd want to delete a Note
instance in a transactional service method so you can delete its associated NoteEvent
s. But that's really the solution to the whole problem. Just delete the afterInsert
and afterUpdate
callbacks and do all the work (creates, updates, and deletes) in transactional service methods. Whenever you do multiple database updates you should do them transactionally so they all succeed or all fail. This also meets your anti-clutter requirement since all of the work is encapsulated in the service.
回答3:
"because I’m saving note instances all over the place"
Could I ask where you are saving them? I would avoid saving your domain instances in your controllers. If you are saving them all over the place it might be worth looking at your overall design.
Personally, I would favour creating some sort of NoteService where I would centralise CRUD operations if possible. An example service would be:
class NoteService
{
Note create (String noteText)
{
Note note = new Note(noteText: noteText)
.addToEvents(new NoteEvent(type: NoteEvent.CREATED))
.save()
}
Note update (int id, String noteText)
{
Note note = Note.findById(id)
note.setNoteText(noteText)
note.addToEvents(new NoteEvent(type: NoteEvent.UPDATED))
.save()
}
....
}
The reason I prefer my above approach is that it scales better if you find yourself then wanting to do more in response to these events and avoids duplication of code.
Another approach could be to do the logging in filters. However, this might be tricky/messy if you are saving your note instances in many places.
Otherwise I'd look into using the beforeInsert/beforeUpdate functions as mentioned above.
来源:https://stackoverflow.com/questions/14248783/weird-afterinsert-afterupdate-loop-in-grails