I'm on Grails 2.3.5 and I'm saving data with gorm inside a task block.
When saving the domain model the task hangs.
Here an excerpt:
task {
def j = new Job()
j.name = "test"
j.save()
println "saved !" // never get here
}
Any idea on how to save domain models inside a task block ?
Thanks
Luca
Try the variation below. It's worth noting that there are nuances to be aware of concerning async threads and Hibernate sessions. See this section in the ref docs for more.
Job.async.task {
def j = new Job()
j.name = "test
j.save()
...
}
I'm posting here the solution:
task {
Job.withNewSession {
def j = new Job()
j.name = "test"
j.save()
...
}
}
Related
I've a Grails 2.5.6 project with a rest controller in which I create n async tasks each of them invoking a method in a service like this:
// MyController
...
def statuses = ['where', 'ownership', 'store']
def tasks = statuses.collect { st ->
task {
return myService.invokeMethod(st, [aDomain, data])
}
}
def props = waitAll(tasks)
...
// MyService
...
store(aDomain, data) {
...
def store = Store.get(data.store)
...
}
If I execute the application, the Store is correctly found in the database.
I've also created an integration test. To avoid conflicts with existing data in the database I create a new Store during the test:
// Integration test (simplified)
....
def store = new Store(....)
store.save(flush: true)
...
def json = [store: store.id] as JSON
...
controller.request.content = params.toString()
controller.request.method = "POST"
controller.update()
...
If I execute the tests, the new store created is not found in the service and the test fails.
I've verified the situation in some points of the application and found that:
- if I search the store in the controller (before or after the tasks are executed), it is found
- if I list all the stores in the service method the new store doesn't exist.
I suppose that this behaviour is due to how hibernate session is handled during tests but I don't know how to solve it.
Any suggestion is welcome.
Thanks
Try using Synchronouse promise factory for tests.
void setup() {
Promises.promiseFactory = new SynchronousPromiseFactory()
}
What is the best way to process a large list of Domain Objects?
For example, I have 'Users' and 'Book' domains, and there is a permission READ on Book object.
When I'm adding a new Book, I'd like to set READ permission to all users for this Book.
At first was a code:
def users = Users.findAll{ ... }
users.each { addPermission(book, it, READ) }
I'm using Spring Security Core and ACL plugin.
But now, I think it is not best way to load 10000 Users Objects to memory.
I gonna use the SCROLL method with maxResults(???) from Criteria.
So my question is What the best way? How to determinate the best number of MaxResults?
For something like this, I would do a bulk update. ExecuteUpdate allows you to do such a thing and its much more performant. Look at this example and customize it to your need.
def updatedRecords = User.executeUpdate("update User set permission = 'READ' where somecriteriaOrNot ")
A more of Grails way to do this would be to use the batch processing. Try the example given below:
EDIT : Improved answer. Now, using pagination based batch processing.
def noOfObjectsTobeProcessedAtAtime=1000//Step or pagination size...
List offsetMaxMapList = (0..User.count()-1).step(noOfObjectsTobeProcessedAtAtime).collect{[max:noOfObjectsTobeProcessedAtAtime,offset:it]}
offsetMaxMapList.each{offsetMaxMap->
addPermissionToUserInBatch(params)
}
def addPermissionToUserInBatch(params){
def batch = []
def session
def users = Users.createCriteria().list(params){}
users.eachWithIndex { user, index ->
batch << user
if (batch.size() >= batchSize) {
User.withTransaction {
batch.each {User userObject ->
addPermission(book, userObject, READ)
}
}
batch.clear()
} else if (batch.size() < batchSize && (users.size() - index - 1) == 0) {
User.withTransaction {
batch.each {User userObject ->
addPermission(book, userObject, READ)
}
}
batch.clear()
}
session = sessionFactory.getCurrentSession()
session.clear()
}
}
Hope that helps!!!
Thank you all. I'd like to summarize. I hope ti will be a TEMPLATE to me.
def dc = new DetachedCriteria(Users).build{
//some conditions of criteria
}
def count = dc.count()
// Optional:
// dc = dc.build{
// projections { property('username') }
// }
def batchSize = 50 // Hibernate Doc recommends 10..50
0.step(count, batchSize){ offset->
dc.list(offset:offset, max:batchSize).each{
// doSmthWithTransaction(it)
}
//clear the first-level cache
//def hiberSession = sessionFactory.getCurrentSession()
//hiberSession.clear()
// or
Users.withSession { session -> session.clear() }
}
P.S. I don't use Transaction here since I use it on the doSmthWithTransaction method
I am in an need to use audit trail in my grails application i have tried all methods but audit log is empty is there any way to rectify it.I need to actually record operations such as insert,delete and update.
Below is what I followed:-
package audit
class Person {
static auditable = true
String firstName
static constraints = {
firstName(nullable:true,size:0..60)
}
def onSave = {
println "new person inserted"
}
def onUpdate = {
println "person was updated"
}
def onDelete = {
println "person was deleted"
}
def onChange = { oldMap,newMap ->
println "Person was changed ..."
oldMap.each{ key, oldVal ->
if(oldVal != newMap[key]) {
println " * $key changed from $oldVal to " + newMap[key]
}
}
}
}
Other listservs that I check have suggested that the current audit-logging plugin is buggy, so you may just be experiencing a bug in the plugin. Also, I believe it has been forked and is actively being rewritten (http://jira.grails.org/browse/GPAUDITLOGGING), so you may not want to spend too much time with it right now.
With that said, I scaffolded a simple application with the domain you provided and the plugin did write out the println statements, but it only recorded the updates correctly in the database to the AUDIT_LOG table. The 2 inserts I attempted recorded null for both the NEW_VALUE and PROPERTY_NAME.
I've got a simple bi-directional one-to-many mapping, as follows, with a default sort order specified on the owning side. However, the sort order doesn't seem to be getting applied? I'm using Grails v2.0.1 (I've now replicated this example with v1.3.7).
package playground
class User {
String name
static hasMany = [ posts : Post ]
static mapping = {
posts sort:'position'
}
}
and
package playground
class Post {
int position = 1
String message
static belongsTo = [ user : User ]
}
this is the integration test code I'm using to exercise it ...
def User user = new User(name:'bob')
user.addToPosts(new Post(position:2, message:'two'))
user.addToPosts(new Post(position:3, message:'three'))
user.addToPosts(new Post(position:1, message:'one'))
assertTrue user.validate()
assertFalse user.hasErrors()
assertNotNull user.save()
for (post in user.posts) {
log.debug "Post message> ${post.message}"
}
Please put me out of my misery, it's presumably something obvious but I can't see it! Thanks.
use this code:
package playground
class User {
String name
static hasMany = [ posts : Post ]
static mapping = {
posts sort:'position' order:'desc'//order:'asc'
}
}
Turns out this is a bit of odd edge-case behaviour, that's really a result of the way the test is written. Basically, everything is happening in the scope of a single Hibernate session/txn (see conversations above). So the test is fetching back the object graph it just created (with the out of order Set), rather than fetching data from the database.
If you force a separate transaction then you get the behaviour you are looking for and the 'order by' works as expected. E.g.
User.withNewSession{ session ->
def User foundUser = User.get(user.id);
for (post in foundUser.posts) {
println "Post message> ${post.message}"
}
}
Code courtesy of ndtreviv.
If you use a list instead of a Set (the default) the framework will maintain the order for you.
List posts = new ArrayList()
static hasMany = [ posts : Post ]
Owens answer got me out of the confusion i was in. i was trying to the the ordering defined on a relationship between users (1) and posts (many), but when i wrote the initial tests they were failing as using User.get (u.id) was in the same session - and so was just reading out of the cache and they came back in the order i'd written tem not newest first as i'd expected.
I then rewrote the test across two sessions and low and behold in the second session this time returned the posts in desc order.
So you just have to be careful. if you are in the same original session that creates the posts then you have to use User.get (u.id).posts.sort().
All these little gotchas with not understanding properly how the session cache and underlying DB work in the scope of the same session/transaction. makes your brain ache sometimes.
whilst we are noting things - this errored in integration test in 3.2.5, but i spotted a thread by Jeff and the fix that had gone. So i upgraded to grails 3.2.6 last night and this test now works
void "test query"() {
given:"a user and where query for users, and posts "
User u
when: "create a post for user "
User.withNewSession { session ->
u = new User(username: 'will')
u.save(flush: true, failOnError: true)
Post p1 = new Post(comment: [food: "bought coffee and cake "])
Post p2 = new Post(comment: [dinner: "bought wine and dinner"])
Post p3 = new Post(comment: [view: "spectacular view of lake and sunset"])
u.addToPosts(p1)
u.addToPosts(p2)
u.addToPosts(p3)
u.save(flush: true)
if (u.hasErrors())
println "error saving posts on user u : ${u.errors}"
def postList = User.get(u.id).posts
postList.each { println "query via user.list using same session > $it.dateCreated : $it.comment" }
Post.findAll().each { println "query via Post using same session > $it.dateCreated : $it.comment" }
}
//because still in same session it just returns the order from the 1st level cache - so force a
//new session and let the DB do the sort
def lookupPosts
User.withNewSession{ session ->
User uNew = User.get(1)
assert uNew
lookupPosts = uNew.posts
lookupPosts.each {println "query via user in new session > $it.dateCreated : $it.comment" }
}
then: " check post was added"
!u.hasErrors ()
lookupPosts.size() == 3
lookupPosts[1].comment.dinner == "bought wine and dinner"
}
I am having a problem with transaction in Grails. I want to save a list of object to DB by a checking condition at each object. All these process I want to put to one transaction, it means if the k-th object does not satisfied the checking condition, all previous objects (from the first object to the (k-1)th one) will be rolled back from DB. Here is my example:
static transactional = true
public void saveManyPeople() {
// ...
List<People> peoples = new ArraysList();
for(i = 0, i < n, i++) {
People newPeople = createPeopleFromRawData(); // return a people object in memory
if(<checking-condition>) {
newPeople.save(flush : false)
} else {
throw new MyCustomizedException() // MyCustomizedException has extended from RuntimException
}
}
// ...
}
As you may see, I set transactional variable to true and I've tried to use flush : true and flush : false, but it didn't work as I want. I've read this article Rolling back a transaction in a Grails Service
And the author recommended that the service method should throw a RuntimeException then the process will be rollbacked. But if I want to throw another exception, so what I have to do?
Could you please give me some suggestions on this problem?
Thank you so much!
You can throw any exception that extends from RuntimeException to rollback the transaction. Or you can use Programmatic Transactions, using withTransation, to have more control over the transaction.
Could you verify that saveManyPeople() is within a Service and not a Controller?
The static transactional = true isn't respected in a Controller. I am suspecting that this is the issue.
If you need to have transactional support with the controller, you could always use DomainClass.withTransaction. Reference Documentation
Example:
Account.withTransaction { status ->
def source = Account.get(params.from)
def dest = Account.get(params.to)
def amount = params.amount.toInteger()
if(source.active) {
source.balance -= amount
if(dest.active) {
dest.amount += amount
}
else {
status.setRollbackOnly()
}
}
}