I'm a newbie here, and hopefully I can explain this thoroughly.
Working through Grails in Action book, Second Edition using the Groovy Grails Tool Suite - GGTS (aka Spring Tool Suite - STS).
GGTS release 3.6.4. Grails version 2.4.4
I'm still on Chapter 1. By this time, I've added several 'quotes' to my database. When I do a "println Quote.count()" through the Grails Console I see I have 4 quotes.
I try to run my random GSP and receive the following error:
Line | Method
->> 7 | doCall in C:/Users/donahujc/Documents/workspace-ggts-3.6.4.RELEASE/qotd/grails-app/views/quote/random.gsp
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
Caused by NullPointerException: Cannot get property 'content' on null object**
->> 7 | doCall in C__Users_donahujc_Documents_workspace_ggts_3_6_4_RELEASE_qotd_grails_app_views_quote_random_gsp$_run_closure2_closure4
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 10 | run in C__Users_donahujc_Documents_workspace_ggts_3_6_4_RELEASE_qotd_grails_app_views_quote_random_gsp
| 198 | doFilter in PageFragmentCachingFilter.java
| 63 | doFilter in AbstractFilter.java
| 1142 | runWorker in java.util.concurrent.ThreadPoolExecutor
| 617 | run in java.util.concurrent.ThreadPoolExecutor$Worker
^ 745 | run . . . in java.lang.Thread
What this is telling me is that my call is pointing to nothing. So, I go into the DBConsole back end, and sure enough, my Quote table (which contains Content and Author) isn't there.
How's that possible, when my DataSource.groovy file was changed to the following:
// environment specific settings
environments {
development {
dataSource {
dbCreate = "update" // one of 'create', 'create-drop', 'update', 'validate', ''
url = "jdbc:h2:devDb;MVCC=TRUE;LOCK_TIMEOUT=10000;DB_CLOSE_ON_EXIT=FALSE"
I changed "create-drop" to "update" and removed reference to memory (mem:).
I know the data is there, because I can use the Grails Console to query it.
The complicated part is that I can't go through the book all at once. So I've had to close and re-launch GGTS multiple times over several days. I thought that re-running the app would re-initialize the table, but that doesn't seem to be the case.
How can I get this table initialized? I tried adding a new quote. (Somehow my index went from quote #4 to quote #33.) But still no table for my GSP to pull from.
I'm just at a loss at how to get this table (and the data that's in there somewhere) initialized. This is something I'm going to have consistent problems with, because I'll be constantly closing/re-opening GGTS.
Help
EDIT: (Adding more of my code)
Quote Controller:
def random(){
def allQuotes = Quote.list()
def randomQuote
if (allQuotes.size() > 0){
def randomIdx = new Random().nextInt(allQuotes.size())
}else {
randomQuote = new Quote(author:"Anonymous",
content:"Real Programmers double peace out on quiche")
}
[quote:randomQuote]
}
Random.gsp
<html>
<head>
<title>Random Quote</title>
</head>
<body>
<div id="quote">
<q>${quote.content}</q>
<p>${quote.author}</p>
</div>
</body>
</html>
Quote.groovy
class Quote {
String author
String content
Date created = new Date()
static constraints = {
}
}
Everything has worked fine until now. I know my data is in the DB because I can query it from the Grails Console. But DB console doesn't even show my Quote table :(
You're going to kick yourself if that is in fact the code that you're using :). Note that uninitialised variables in Groovy are given a default value of null. So the initial value for the randomQuote variable in the random action is null.
The value of the randomQuote variable is then assigned to the quote variable in the view's model. Looking at the view, I can tell that the NullPointerException is being thrown by the ${quote.content} expression. So if quote is null in the model, that must mean that randomQuote is null in the action.
So what happens when there are quotes in the database? The action takes this branch:
if (allQuotes.size() > 0) {
def randomIdx = new Random().nextInt(allQuotes.size())
}
As you can see, there is no assignment to the randomQuote variable, so it remains null. The code from listing 1.3 of the book has this:
if (allQuotes.size() > 0) {
 def randomIdx = new Random().nextInt(allQuotes.size())
randomQuote = allQuotes[randomIdx]
}
Or at least I hope it does. It's showing up in my PDF version.
This was a little long-winded, but I'm hoping that you can follow the reasoning and use that to diagnose other issues that you encounter. I recognise that it's not always easy for newcomers to interpret the various errors they come across.
Related
I'm executing the account creation process as a background task.
The function works well without the "delay". It fails many times when I'm using delayed_jobs. For example, if I send 9 accounts for creation, only 6 will be created.
Here is the handler stored in the delayed_jobs table:
--- !ruby/object:Delayed::PerformableMethod
object: !ruby/class 'Tool::AccountCreation'
method_name: :create_account
args:
- ''
- test pmf
- test67#pmf.com
- test pmf
- '2907'
- ''
- 'true'
- '64'
- ''
- 40
Here is the error in the delayed_jobs table:
wrong number of arguments (given 10, expected 6..7)
Here is the controller code that is using delayed_jobs:
Tool::AccountCreation.delay.create_account(first_name, last_name, email, password, params[:student_group_id], partner_user_id, params[:send_code_by_email], params[:catalogue_offer_id], remote_login_secret_token, accounts_in_creation.id)
Here is the model for my "create_account function:
def create_account(first_name, last_name, email, password, student_group_id, partner_user_id, send_code_by_email, catalogue_offer_id, remote_login_secret_token, accounts_in_creation_id, options = {})
[...]
end
Any clues are welcome !
I'm trying to get FitNesse (slim tests running via fitSharp) to process tables stored in a variable. Both approach A & B below render the same on the page, but only approach B will run.
Approach A
!define test (
| Table:myTest | someValue |
)
${test}
Approach B
| Table:myTest | someValue |
This example is rather superficial, but in my tests I'm looking to vary some parameters and reexecute the same test (without a lot of copy and paste).
Adding additional details requested;
Approach A renders this to page when saving;
<br><span class="meta">variable defined: test=
| Table:myTest | someValue |
</span>
<br><br><table>
<tbody><tr class="slimRowTitle">
<td>Table:myTest</td>
<td>someValue</td>
</tr>
</tbody></table>
<br>
...but when running the test the page doesn't seem to process the table and shows just the variable definition
<br><span class="meta">variable defined: test=
| Table:myTest | someValue |
</span>
<br><br><br></div>
Try creating a separate page with the test table.
In your real test page you can include this page multiple times, after assigning values to the variables.
Imagine you have the following controller in a Grails 2.5.5 application:
def index() {
bookService.method()
Book bako = Book.findById(4)
System.out.println(bako.title);
}
And inside the bookService (with Grails default transaction management) you have the following method:
class BookService
def method() {
Book bako = Book.findById(4)
System.out.println(bako.title);
// MANUAL UPDATE OF DB HAPPENS HERE
Book bako = Book.findById(4)
System.out.println(bako.title);
}
}
And that your db does have a Book with id 4 called "The Lord of The Rings".
If you then set breakpoints on all System.out.println() and, after the first findById has been executed, you manually edit the title of that object to "Harry Potter and the Goblet of Fire", I expected:
Both findById(4) in bookService.method() to read the same value, after all, they were performed in isolation in the same transaction, and if that transaction read it in that state, the second one should read it too.
The findById(4) performed in the controller to already see the new state of the object, after the manual update. After all, the first transaction has already commited, and this second find, even if done outside a service, should create a new transaction.
However, the output will always be the object in the same state it was at the beginning.
Update: The output is:
The Lord Of The Rings
The Lord Of The Rings
The Lord Of The Rings
If, in any case, you modifiy the controller, in order to:
def index() {
bookService.method()
Book.withNewTransaction {
Book bako = Book.findById(4)
System.out.println(bako.title);
}
}
The result is still the same.
Update: The output is:
The Lord Of The Rings
The Lord Of The Rings
The Lord Of The Rings
Only if you modify it to:
def index() {
bookService.method()
Book.withNewSession {
Book bako = Book.findById(4)
System.out.println(bako.title);
}
}
does the correct behavior ensue.
Update: The output is:
The Lord Of The Rings
The Lord Of The Rings
Harry Potter and the Goblet of Fire
Can someone explain me why:
Just being outside the transaction that read an object in a certain state is not enough to read fresh data;
Even forcing a new transaction is not enough to read the object with its most up to date state;
Why is the new session what allows us to do so.
Firstly Book bako = Book.findById(4) findById should be used in rare cases refer to Book.get(1L) Book.load(1L) Book.read(1L)
You are firing up a query to look for id when you could have just run .get
Actual issue
After much talk, no matter how much a service is transactional. If you decide to update the DB using mysql manually. You will break hibernate cache. You can try disabling - first / second level cache. First being if you declared caching in your domain class mapping.
This is really unwise and will have application impact. The reality is a transaction service should be doing the updates for you. If you need to manually update the database. Stop the app update / start the app. It is really that simple
There is a reason why I have been trying to push you down the route of attempting this scenario using an example project.
Why ?
Because it helps answer any speculation. I have taken a copy of your sample project and added some actual record updates to the demo.
https://github.com/vahidhedayati/grails-transactions
I have also made a pull request on your version so you can merge it and test it locally.
Basically flush:true not required. .get(id) not required.
As you can see from the results below In service after .save() on method 1 the results was updated. In controller it returned the correct result using method() after service returned it.
-- transactions.Book : 1 1 added
| Server running. Browse to http://localhost:8080/transactions
2016-09-05 18:12:48,520 [http-bio-8080-exec-4] DEBUG hibernate.SQL - select book0_.id as id1_0_0_, book0_.version as version2_0_0_, book0_.title as title3_0_0_ from book book0_ where book0_.id=?
--method1: before update: ------------------------------> TITLE_SET_BY_BOOTSTRAP
--method1 before get: ---------------------------------> New title from method1
method1 after get: ----------------------------------> New title from method1
2016-09-05 18:12:48,618 [http-bio-8080-exec-4] DEBUG hibernate.SQL - update book set version=?, title=? where id=? and version=?
After service1 call 1 New title from method1
--method2 update: ------------------------------> New title from method1
2016-09-05 18:12:48,687 [http-bio-8080-exec-4] DEBUG hibernate.SQL - update book set version=?, title=? where id=? and version=?
--method2 before get: --------------------------> New title from method2
method2 after get: ----------------------------> New title from method2
After service call 2 New title from method2
--method3 before update: ---------------------------> New title from method2
2016-09-05 18:12:48,795 [http-bio-8080-exec-4] DEBUG hibernate.SQL - update book set version=?, title=? where id=? and version=?
--method3 updated before get: -------------------------> New title from method3
--method3 after get: -----------------------------------> New title from method3
After service call 3 New title from method3
After reviewing the user issue ages back and having understood that they were manually updating DB record then expecting screen to show the same result.
In short if you have no cache enabled in the application then yes it should all work. If you have some form of Hibernate cache or ehcache enabled then it is likely you will be looking at some cache object. I had suggested restarting application to ensure you have latest. But if you simply:
wrap a
DomainClass.withNewTransaction {
//get the latest copy
DomainClass clazz = DomainClass.get(recordId)
println "-- ${clazz.value}"
}
This should ensure you are getting the very latest from the DB, it isn't going to be speed efficient but if you are expecting manual db updates you could always ensure the latest is above..
Ok, the example from plugin birt helped me tons, but one question is bothering me, how can I pass params to the birt to select from id?
question | description
----------------------
1 | blabla
2 | xoxoxo
3 | tititi
4 | buhbuh
Example: I have this table above... From my grails application, I choose what question I want, so if I choose 1,3,4... The Birt report shows me selected only.
Basically, I have to change my dataset too, because my query is static and needed to be dynamic:.
...(query) and a1.question_id = 1 and a2.question_id = 2 and and a3.question_id = 3 (query)...
But in grails how I will pass the params to dataset?
the Birt report receives the parameters with params, you can use this part of code
def options = birtReportService.getRenderOption(request, 'html')
def result=birtReportService.runAndRender(reportName, params, options)
render result
. Remember don't forget place your rptdesing in a folder on (web-app)
In my app, the user makes a search. That search gives back a list of people that can be hired for a duty. But, user can make more than one search for the same duty (say, i need two persons for this city, and three for that city). Every search makes a "Prepresupuesto", and every Prepresupuesto makes a "Cocontrato". All "Cocontrato" makes a "Contrato", and all "Prepresupuestos" makes a single "Presupuesto". And all together conforms the entire duty, called "Campaign" in the app.
When i am trying to save that "Cocontrato", I receive the exception. This is the code:
ArrayList<Prepresupuesto> prepresus=Prepresupuesto.findAllByCamp(campid)
Presupuesto pres=new Presupuesto()
for(int i=0;i<prepresus.size();i++){
pres.prepresu.add(prepresus[i])
pres.camp=Campaign.get(prepres.camp.id)
pres.estado="Pending"
total=total+prepresus[i].total
crearCocontrato(prepresus[i])
}
Method crearCocontrato():
public crearCocontrato(Prepresupuesto prep){
Set <Azafata>azas=prep.azafatas
Cocontrato cocon=new Cocontrato()
cocon.contrato=con
cocon.precio=prep.precio
cocon.azafatas=azas
cocon.total=prep.total
cocon.horas=prep.horas
cocon.horario=prep.horario
cocon.localidad=prep.localidad
cocon.fechaInicio=prep.fechaInicio
cocon.fechaFin=prep.fechaFin
cocon.presu=prep
cocon.camp=prep.camp
if(!(cocon.save(flush:true))){
render (view:"fallos", model:[azafataInstance:cocon])
}
}
The Exception is launched in the if.
Any help? I am a bit lost, I am a newcomer to Grails, and I think i don't understand completely the exception.
Thank you!
EDIT: The Exception:
Found shared references to a collection: com.publidirecta.Cocontrato.azafatas. Stacktrace follows:
Message: Found shared references to a collection: com.publidirecta.Cocontrato.azafatas
Line | Method
->> 2448 | crearCocontrato in com.publidirecta.EntidadController$$ENmku0D2
- - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
| 2394 | boton in ''
| 886 | runTask . . . . in java.util.concurrent.ThreadPoolExecutor$Worker
| 908 | run in ''
^ 680 | run . . . . . . in java.lang.Thread
You're probably trying to save more than one entity (cocon) when both refer to the same collection. It's hard to detect that in this code you posted and it could be somewhere else, but I would say it's when you do this:
Set <Azafata>azas=prep.azafatas
cocon.presu=prep
Then when you save cocon, it points to the same collection that prep does. Take a look at this link, that says the same thing.
A way out of this would be to add element by element to the list you want to copy to instead of sharing the reference by doing a.list = b.list.
This can also help you.
Yes, Tiago, after diving a bit in the code (it is inherited code...) i found that the problem was what you are saying. I solved it by doing this:
ArrayList <Azafata> azas=new ArrayList<Azafata>()
for(int i=0;i<prep.azafatas.size(); i++){
azas.add(prep.azafatas.toArray()[i])
}
And everything was ok :) Thank you for your answer.