Using Oracle GUID () - Generated Identifier in Grails / Hibernate

I am trying to use Grails Scaffolding to quickly add a CRUD application around some old database tables. This is an Oracle database, and the primary key value is intended to be populated with the Oracle GUID() function.

Based on https://stackoverflow.com/a/4129608/1123, I tried to specify "guid" as the Hibernate generator for this column in my Grails domain class:

 ... static mapping = { table name: "OWNER" version false columns { id column: "OWNER_OID", generator: "guid" name column: "NAME" ... } } ... 

When I launch the Grails application, viewing and even editing posts is fine. However, when I try to create a new record, everyone will explode with the Oracle error message " ORA-02289: sequence does not exist ".

I turned on SQL logging for my data source and saw that Grails / Hibernate was trying to do the following during the save operation:

 select hibernate_sequence.nextval from dual 

This does not look like that at all and does not match the generated SQL from the previously mentioned StackOverflow question. Does anyone see something that I don’t see here, or else know how to get Grails / Hibernate to fill in the primary key column with Oracle GUID values?

+2
source share
2 answers

When ... after another day of dealing with it, I think I hugged it. This answer contains a little more reason than the original description of the question, but this is because I found even more problems after I passed the problems with the Hibernate generator.

Problem # 1: Getting Oracle GUID() Value

As Adam Hawks said in a reply, the Hibernate “hibernator” generator is not supported and only works for older versions of the Oracle dialect.

However, if you use the “assigned” Hibernate generator (which means that you want to manually set the primary keys and not automatically generate Hibernate), you can insert values ​​pulled from the Oracle SYS_GUID() call.

Despite the fact that Hibernate dialects for newer Oracle do not support "guid", they still understand the SQL necessary to generate these values. If you are inside the controller, you can receive this SQL query with the following:

 String guidSQL = grailsApplication.getMainContext().sessionFactory.getDialect().getSelectGUIDString() 

If you are inside a domain class, you can still do this ... but you need to first enter a link to grailsApplication. You might want to do this in the controller, though ... more on this below.

If you're interested, the actual string returned here (for Oracle) is as follows:

 select rawtohex(sys_guid()) from dual 

You can execute this SQL and get the value of the generated identifier as follows:

 String guid = grailsApplication.getMainContext().sessionFactory.currentSession.createSQLQuery(guidSQL).list().get(0) 

Problem # 2: Actually, using this value in a Grails domain object

To use this GUID value in your Grails domain class, you need to use the "assigned" Hibernate generator. As mentioned earlier, this states that you want to set your own identifier manually, rather than letting Grails / GORM / Hibernate generate them automatically. Compare this modified piece of code with the one in my original question above:

 ... static mapping = { table name: "OWNER" version false id column: "OWNER_OID", generator: "assigned" name column: "NAME" ... } ... 

In my domain class, I changed "guid" to "assigned". I also found that I needed to eliminate the " columns {} " grouping block and move all the information about my column to the (weird) level.

Now, depending on which controller creates these domain objects ... it will generate a GUID, as described above, and connect it to the " id " field. In a controller automatically created by Grails Scaffolding, the function will be " save() ":

 def save() { def ownerInstance = new Owner(params) String guidSQL = grailsApplication.getMainContext().sessionFactory.getDialect().getSelectGUIDString() ownerInstance.id = grailsApplication.getMainContext().sessionFactory.currentSession.createSQLQuery(guidSQL).list().get(0) if (!ownerInstance.save(flush: true, insert: true)) { render(view: "create", model: [ownerInstance: ownerInstance]) return } flash.message = message(code: 'default.created.message', args: [message(code: 'owner.label', default: 'Owner'), ownerInstance.id]) redirect(action: "show", id: ownerInstance.id) } 

You might be trying to apply this logic directly to a domain object in the " beforeInsert() " function. It will definitely be cleaner and more elegant, but there are some known errors with Grails that prevent the identifier from being set to " beforeInsert() ". Unfortunately, you will have to adhere to this logic at the controller level.

Problem # 3: Make Grails / GORM / Hibernate Properly Saved

The simple truth is that Grails is primarily intended for new applications, and its support for legacy databases is quite spotty (fairness, however, it is slightly less spotty than other "dynamic" frameworks I tried). Even if you use a “designated” generator, Grails is sometimes confused when it continues to hold a domain object.

One of these problems is that the " .save() " call sometimes tries to UPDATE when it needs to do an INSERT. Note that in the Controller snippet above, I added " insert: true " as the parameter for calling " .save() ". This suggests that Grails / GORM / Hibernate is clearly trying to perform an INSERT operation, not an UPDATE.

All stars and planets must be in order for this to work correctly. If your domain class " static mapping {} " does not set the Hibernate generator to " assigned " and also sets " version false ", then Grails / GORM / Hibernate will still get confused and try to return UPDATE, not INSERT.

If you use Grails Scaffolding auto-generated controllers, then it is safe to use " insert: true " in the controller's save() function, since this function is called only when the new object is first saved. When the user edits an existing object, the update() function is used instead "controller. However, if you are doing your own thing in your own code somewhere ... it will be important to check if the domain object is already in the database before you make a call to " .save() ", and only pass " insert: true "if this is really the first insert.

Problem # 4: Using Natural Keys with Grails / GORM / Hibernate

A final note, not related to Oracle GUID values, but related to these Grails issues in general. Let's say that in the old database (for example, the one I came across) some of your tables use a natural key as the main key. Let's say that you have an OWNER_TYPE table containing all the possible "types" of OWNER , and the NAME column is both an identifier read by a person and a primary key.

You will need to do a couple of other things to make this work with Grails Scaffolding. First, automatically generated views do not display an identifier field on the screen when users create new objects. You will need to insert some HTML in the corresponding View to add a field for the ID. If you specify the name " id " in the field, the automatically created function "Save ()" of the controller will receive this value as " params.id ".

Secondly, you must make sure that the save() auto-generator function correctly inserts the ID value. At the first generation, "save ()" starts by creating an instance of the domain object from the CGI parameters passed in the view:

 def ownerTypeInstance = new OwnerType.get( params ) 

However, this does not process the identifier field that you added to the view. You still have to install it manually. If in the view you gave the HTML field the name " id ", then it will be available in " save() " as " params.id ":

 ... ownerTypeInstance = new OwnerType() ownerTypeInstance.id = params.id // Proceed to the ".save()" step, making sure to pass "insert: true" ... 

A piece of cake, huh? Perhaps "Problem # 5" finds out why you put yourself through all this pain, and not just write your CRUD interface manually using Spring Web MVC (or even vanilla JSP) in the first place! :)

+1
source

Support for using SYS_GUID() depends on the dialect of Oracle you are using. Looking at the hibernate source on GitHub , it seems that the dialect was only configured to use the Oracle-oriented guid in Oracle9Dialect.java and Oracle8iDialect.java . Therefore, it will not work with 9i or 10g dialects.

You must send a patch for sleep mode, which will add the required features to enable the same functionality as other dialects.

0
source

Source: https://habr.com/ru/post/908161/


All Articles