ptg 182 Chapter 8 Database Access Table 8.2 RESTful Verbs REST Verb Overridden Method Sample URL GET (Collection) onList() /resources/bookmark GET (Singleton) onRetrieve() /resources/bookmark/123 POST (Singleton) onCreate() /resources/bookmark PUT (Singleton) onUpdate() /resources/bookmark/123 PUT (Collection) onPutCollection() /resources/bookmark DELETE (Singleton) onDelete() /resources/bookmark/123 By using the ZRM delegate, you can potentially save yourself hundreds of lines of boiler- plate code per resource. That’s a huge savings in terms of development time and maintenance costs. ZRM is the best junior developer your team has ever had, without all that clowning around. Adding Data to a Zero Resource Model A database-backed resource without data is like a circus without dancing poodles—not much to look at. Let’s fix that situation by adding data records to our bookmark table. This can be done in a couple different ways. If you have a large block of data to load, it’s best to format it into a special fixed format used by WebSphere sMash and bulk load it into the table. Alternately, you can use the predefined sample page to manually add records one by one. Obviously this is not an ideal solution if there is a lot of data to load, so spend the effort to swizzle your data and per- form the bulk load. Most databases also provide a native means to bulk load data into tables as well, but that’s not within the spirit of sMashing things, so let’s create a sample loader file instead. To perform a bulk data load, you need to create a JSON data file in the /app/model/fixtures directory of the project. In AppBuilder, with the Book.DB.ZRM project open, select New File > Other File. In the dialog box, type the full filename of /app/models/fixtures/load.json and click Create.You may use any filename you want, but load.json keeps things obvious. The structure of the load file allows for defining data for multiple tables at once, as set by the "type" value. An empty editor will be displayed, where you can paste in your JSON-formatted data. The contents are a JSON array of objects, with a type member, which is a string that maps to the ZRM model name, and a fields object, which contains a direct mapping of our data fields. Only fields defined as required must be present in the JSON. Because several of our fields will be automatically generated by our database, we don’t need to reference them at all in the load file. A sample of bookmark data is shown in Listing 8.10. Download from www.wowebook.com ptg Zero Resource Model 183 Listing 8.10 Sample Bookmark Data [ { "type": "bookmark", "fields": { "url": "http://www.projectzero.org", "description": "Project Zero", "tags" : "zero,smash,groovy,php,java,dojo" } }, { "type": "bookmark", "fields": { "url": "http://dojotoolkit.org", "description" : "Dojo Toolkit", "tags": "dojo" } }, ] To load this JSON data file into the table, go back to the Console tab of the AppBuilder and run the command zero model loaddata load.json, as shown in Figure 8.7. Match the file- name to the one you used as a loader file. Assuming that everything goes OK, your data will be loaded and ready to go. If there are problems, you should check your file for syntax errors using the AppBuilder editor or by using a site such as jsonlint.com to validate your file structure. There is a special file called /app/models/fixtures/initial_data.json that can be defined that will automatically load anytime there is a model sync command run. This can save you the extra loaddata step and can be useful during development of new ZRM models. The structure of this file is the same as any other load file. The model sync command will simply look for this specially named file and load it for you automatically. Loading Data Using a ZRM Test Page If you recall from earlier when running the ZRM Wizard, on step 3 there was an option to create a test page for the model. You can use this to view and manually enter or modify records in the database. The sample page requires the Dojo toolkit, which is not currently added to the project during the wizard process. So, before we can start the application and load the test page, you need to go to the project’s Dependencies tab and add the zero:zero.dojo dependency. You need the zero version of Dojo to pull in some widgets provided by Zero and not available under the stan- dard Dojo build. If this module does not show up in your list of available modules, click Manage Download from www.wowebook.com ptg 184 Chapter 8 Database Access Figure 8.7 Results of the zero model loaddata command Repository, enter the search filters as shown in Figure 8.8, and add the latest version located. Now you can start the application and load the test page in your browser. Go to http://localhost:8080/test_bookmark.html. Assuming that things are all in order, you should see a page similar to that in Figure 8.9. Not too shabby! Go ahead and explore both the UI and the source code to the test page located under the /public folder of the application. This test page enables you to add, edit, and delete records. To edit a field, just double-click, and the field will become editable. Don’t forget to click the Save button when you’ve made some edits. If you are using Firefox and have the Firebug debugger installed you can see the REST calls being made with each action. A word of caution is in order here. DO NOT (guess that makes two words) leave this test page around on a production application! That is, unless you don’t mind having your company’s inventory being manipulated by some vengeful clown. I know this is obvious, but still, it has to be said. Iterative Zero Resource Model Design During development, you will frequently find yourself creating and modifying models as new requirements arise. Zero helps in this effort. For newly created models, you can simply run the zero model sync command, and these models will be processed to create the new tables. Existing models that have been modified get just a bit trickier. For these, we need to export the current data (assuming that you want to keep it, of course), reset the model, and then reload the exported data. If needed, you can edit the exported data to include new data for the modified fields, or as long as they are not required fields, simply reload the original data into the new model. Download from www.wowebook.com ptg Zero Resource Model 185 Figure 8.8 Adding the zero:zero.dojo dependency Figure 8.9 Test ZRM page The zero model dumpdata <filename> command exports all ZRM data to the speci- fied file. The filename is defined relative to the project’s home directory. So, assuming you want Download from www.wowebook.com ptg 186 Chapter 8 Database Access to export the data to the fixtures directory, use the command zero model dumpdata app/models/ fixtures/export.json. If you remember what was said a moment ago, you could export the data to the app/models/fixtures/initial_data.json file so that the same data will be automatically loaded when you re-sync the models. This command is also a good way to make quick data backups and works well in daily administration scripts. The next step is to use the zero model reset command to drop any existing tables and re-create them based on the new model definitions. This is a nonrecoverable step, so be sure you have exported any data you want to retain. Otherwise, you will be sticking your head in a hungry lion’s mouth. The model and tables will now be in sync, and you can reload the exported data using the zero model loaddata <filename> command. Be sure that any field name changes in the model are reflected in the data to be reloaded, or load errors will occur. WebSphere sMash’s Zero Resource Model enables ridiculously easy and iterative develop- ment of database-driven applications. It takes the best practices of model-driven table design and convention-driven RESTful access of data and even provides a simple, yet useful, test page to verify and modify data. It doesn’t get much easier than that. So, with all that time you’ve saved, sit back and watch the rest of the circus that is your traditional development environments. Database Access with pureQuery The Zero Resource Model is a wonderful way to quickly define and access relational data. How- ever, it does have limitations in how you can manipulate the data, and how to query data using complex joins. If your application outgrows the capabilities provided by the ZRM, you need to step up to using the pureQuery layer within WebSphere sMash. Don’t worry—this is still a simple way to quickly and easily work with your data. WebSphere sMash provide an implementation of the pureQuery database access layer defined by the IBM Information Management team. The pureQuery’s API provides for easy access to JDBC resources without all the normal structure and management of Connections, Statements, and ResultSets that are typical of normal database access logic. To access data using pureQuery, you start by defining your database manager in the appli- cation configuration file. This configuration is used to create a database manager handle that is used to perform queries and obtain other information about your database connection. It’s then a matter of using the manager to access or modify data using normal SQL statements. For queries, when you get your results back, you process them as you want and move on. There is no need to track and close down your ResultSet, Statement, and Connection handles, as pureQuery will take care of all that messiness for you. Of course, if you DO need to worry about these things, classic JDBC access is always available as well. Working with pureQuery In its simplest form, pureQuery consists of obtaining a handle to the database manager as defined in the configuration, passing in the SQL and parameters to the appropriate query or modifier Download from www.wowebook.com ptg Database Access with pureQuery 187 function, and then processing the results. You don’t need to worry about tracking or closing handles. To set up the following scenario, we used the ZRM steps described earlier in this chapter to create a bookmark database using a MySQL instance. We used ZRM just as a quick-and-dirty way to define and populate the bookmark database. Later in this chapter, we cover how you can use the Zero command line to process table creation and modification statements, inserts, and other administration-level commands. Listing 8.11 shows the configuration for the MySQL- based bookmark connection. Listing 8.11 MySQL Bookmark Connection Configuration /config/db/bookmark = { "class" : "com.mysql.jdbc.jdbc2.optional.MysqlDataSource", "serverName" : "localhost", "portNumber" : 3306, "databaseName" : "bookmark", "user" : "root", "password" : "<xor>Lz4sLChvLTs=" } Because we have ZRM manage the data access through a bookmark resource using the zrm.delegate() statement, we created a new resource file called /app/resources/link.groovy to use for the examples in this section. Listing 8.12 displays a rather typical flow of performing a query of a database within WebSphere sMash. The first task is to obtain a reference to the Database driver. This is done using the Manager.create() statement. The manager object provides all the required information on the connection to perform queries or update statements to the associated database defined in the configuration. The manager is then used to execute the query, which returns our data records. After we have our data, this sample sets the response type to JSON and returns the results. The response sent back to the browser in this case is a JSON structure of all our links in the book- mark table. Notice that we didn’t use the word ResultSet, because the returned data is automati- cally coerced into an easy-to-process object or list structure based on the type of query that is used. Listing 8.12 Typical sMash Query Flow def onList() { def mgr = zero.data.Manager.create('bookmark') def result = mgr.queryList('SELECT * FROM bookmark') logger.INFO{"Query Links Results size: " + result.size() } request.headers.out."Content-Type" = "application/json-comment- filtered" request.view="JSON" Download from www.wowebook.com ptg 188 Chapter 8 Database Access request.json.output = result render() } If you are merging legacy application code, or for some reason have not defined your data- base in the configuration file, you may also instantiate a Manager instance by passing in a javax.jdbc.Datasource or javax.jdbc.Connection object. Under normal circumstances, you should follow convention and place database connection information inside your config file. Simple Query Methods For queries where we either know we will get only a single item back, such as when specifying something by ID, or you want only the first of potentially multiple results, use the queryFirst function, and the results will be a simple object. Other times, when you know you’ll be getting multiple records, or are unsure if you’ll get one or more records, the queryList command is more appropriate, as it will return a list of items as the result. In the preceding example, we used the mgr.queryList() method passing in a simple select query. The logging statement shows that for our example we retrieved 18 links from our database, as shown in Listing 8.13. Make a REST GET call using your browser to localhost:8080/resources/links, and you should receive a JSON array of links. Listing 8.13 Query Log Output 2010-02-23 20:20:57 app.resources.link.groovy::onList Thread-12 INFO [ Query Links Results size: 18 ] There are other times when you may want to query for a link specifically by ID or some other specific query where you know we will obtain a single result. This can be done using the mgr.queryFirst() method, as shown in Listing 8.14. The results of this type query returns a single object represented in JSON. Listing 8.14 Single Link Request def onRetrieve() { def id = request.params.linkId[] def mgr = zero.data.Manager.create('bookmark') def result = mgr.queryFirst("select * from bookmark where id = ${id}") logger.INFO{"Link URL Result for ID: " + id + " = " + result.url} request.headers.out."Content-Type" = "application/json-comment- filtered" request.view="JSON" Download from www.wowebook.com ptg Database Access with pureQuery 189 request.json.output = result render() } When we make a singular REST call to our link resource (http://localhost:8080/ resources/link/3), we get back the record identified by ID = 3. In the database, this is storing our favorite Dojo documentation site, as shown in the console output in Listing 8.15. Of course, the whole record is also returned to your browser for examination as well. Listing 8.15 Log Output Of Single Link Request 2010-02-23 20:29:30 app.resources.link.groovy::onRetrieve Thread-11 INFO [ Link URL Result for ID: 3 = http://docs.dojocampus.org ] Yo u a r e n ot lim i ted to ret r i evin g co m pl ex m ap s o r li s ts of map s in pu r eQ u ery. I f y o u r q ue r y requests a single field, you may request the result type as the second argument of the call, and the resulting data will be coerced into that type. Logically, a list of the scoped type is returned if there are multiple results matching your query. To showcase this, let’s assume that I only wanted to retrieve a list of the URLs as type String in the bookmark table tagged with "Dojo". The SQL query would look like that shown in Listing 8.16: ”select url from bookmark where tags like '%Dojo%'". The results returned when called with the mgr.queryList call would be a list of strings. Listing 8.16 Custom Query for Dojo URLs as Strings def result = mgr.queryList("SELECT url FROM bookmark where tags like '%Dojo%'", String) The results when returned as JSON to the browser should resemble Listing 8.17. Listing 8.17 Results of Dojo URLs Query ["http://www.projectzero.org","http://dojotoolkit.org","http://docs.d ojocam-pus.org","http://api.dojotoolkit.org/","http://dojodocs. uxebu.com/"," http://download.dojotoolkit.org/release-1.4.0/cheat.html", "http://www.sitepen.com/"] Retrieving JavaBeans or GroovyBeans is done the exact same way. Just supply the wanted Bean class as the second argument to the queryXXX() method. The result will then be the requested bean type or a list of beans as appropriate. Assume we have a GroovyBean called Link, as shown in Listing 8.18. We then make a DB query, as shown in Listing 8.19. The result Download from www.wowebook.com ptg 190 Chapter 8 Database Access list contains Link bean instances. The bean’s toString() method is then iterated to display our results, as shown in Listing 8.20. Listing 8.18 Link Class: /app/scripts/Link.groovy class Link { // properties Integer id String url String description String toString() { return this.id + ":" + this.url + " = " + this.description } } Listing 8.19 Selecting Data into a List of Beans def links = mgr.queryList("SELECT * FROM bookmark where tags like '%dojo%'", Link) links.each{ link -> logger.INFO{ "Link: " + link } } Listing 8.20 Results of Iterating the Bean List 2010-02-23 21:32:50 app.resources.link.groovy::onList Thread-11 INFO [ Link: 1:http://www.projectzero.org = Project Zero ] 2010-02-23 21:32:50 app.resources.link.groovy::onList Thread-11 INFO [ Link: 2:http://dojotoolkit.org = Dojo Toolkit ] 2010-02-23 21:32:50 app.resources.link.groovy::onList Thread-11 INFO [ Link: 3:http://docs.dojocampus.org = Dojo Campus Docs ] The Manager class provides a convenience method called eachRow that allows you to mix a queryList with an each call on the results. Listing 8.19 could be rewritten as shown in Listing 8.21. Listing 8.21 Selecting Data into a List of Beans mgr.eachRow("SELECT * FROM bookmark where tags like '%dojo%'", Link) { link -> logger.INFO{ "Link: " + link } } Download from www.wowebook.com ptg Database Access with pureQuery 191 Data Manipulation Statements Yo u are n o t l im i ted t o m a ki n g rea d - on l y qu e r ie s i n to y o ur da t a. Yo u ma y a l so p e r fo r m the o t h er c l as - sic CRUD operations: essentially Create, Read, Update, and Delete. Because we have covered the Read action, let’s focus on using statements that alter data. There is nothing different here, other than instead of obtaining results back in the form of an object, or list of objects, we get a simple integer that indicates how many rows were affected by the modification statement.You can test this value to verify the operation performed as expected. Getting a zero back would typically indicate that something is amiss, unless the filtering of the statement excluded all records. Also, if the SQL were poorly formed, the statement would throw a com.ibm.pdq.runtime.exception.DataRuntimeException that you can trap and recover from appropriately. Let’s briefly cover each of the modifier statements. If you need more information, consult the documentation for your database of choice. Let’s walk through a few data manipulation statements. The only unique thing to notice is when dealing with auto-generated keys during an insert statement. You need to inform the meta manager of the keys that will be auto-generated. This is done by passing in an array of keys that are auto-generated by the database. In the case of our first sample in Listing 8.22, we have a single 'id' field that is auto-generated. Passing this in as the second argument ensures that this field is managed properly. Other parameter values are passed after the auto-gen fields array. The other statements for updates and deletes operate as you would expect. Listing 8.22 Sample Data Manipulation Calls // Create a manager reference def manager = zero.data.groovy.Manager.create('bookmark') // Perform an Insert, using mapped parameters def map = [ 'url' : 'http://www.w3schools.com/sql/default.asp', 'description' : 'W3Schools SQL tutorial', 'tags' : 'sql' ] def sql = """INSERT INTO bookmark (url, description, tags) VALUES (${map.url}, ${map.description}, ${map.tags} )""" // Notice how we need to tell the manager our auto-gen fields below def result = manager.insert( sql, ['id'] ) logger.INFO{"Rows inserted: "+ result} // An Update (Reset all visited counts to zero.) sql = "UPDATE bookmark SET visited=0"; Download from www.wowebook.com . zero.data.groovy.Manager.create('bookmark') // Perform an Insert, using mapped parameters def map = [ 'url' : 'http://www.w3schools.com/sql/default.asp', 'description' : 'W3Schools. Thread-11 INFO [ Link: 1:http://www.projectzero.org = Project Zero ] 201 0-0 2-2 3 21: 32:50 app.resources.link.groovy::onList Thread-11 INFO [ Link: 2:http://dojotoolkit.org = Dojo Toolkit ] 201 0-0 2-2 3. manipulate the data, and how to query data using complex joins. If your application outgrows the capabilities provided by the ZRM, you need to step up to using the pureQuery layer within WebSphere