Details
-
Type:
Bug
-
Status:
Open
-
Priority:
Major
-
Resolution: Unresolved
-
Affects Version/s: 2.0.3
-
Fix Version/s: None
-
Component/s: Persistence
-
Labels:None
-
Testcase included:yes
Description
Per this thread on the user list:
http://grails.1312388.n4.nabble.com/More-fun-with-Map-properties-tp4617698p4622351.html
Given one or more domain objects with a map property, the hibernate select-clause mapping for can get corrupted. The error depends on the order in which the classes are initialized. Since the order is random, the error can be hard to reproduce. The test project which is attached, though, will show it every time. One of the three test cases will always fail. Which one it is, though, will vary.
-
Hide
- map-prop-bug.zip
- 11/May/12 2:34 PM
- 127 kB
- Eric Sword
-
- map-prop-bug/.classpath 0.7 kB
- map-prop-bug/.idea/.name 0.0 kB
- map-prop-bug/.idea/compiler.xml 0.6 kB
- map-prop-bug/.idea/.../profiles_settings.xml 0.1 kB
- map-prop-bug/.idea/encodings.xml 0.2 kB
- map-prop-bug/.idea/misc.xml 4 kB
- map-prop-bug/.idea/modules.xml 0.4 kB
- map-prop-bug/.idea/.../scope_settings.xml 0.1 kB
- map-prop-bug/.idea/vcs.xml 0.2 kB
- map-prop-bug/.idea/workspace.xml 34 kB
- map-prop-bug/.project 0.5 kB
- map-prop-bug/.../org.codehaus.groovy.eclipse.preferences.prefs 0.1 kB
- map-prop-bug/Map-prop-bug-grailsPlugins.iml 3 kB
- map-prop-bug/Map-prop-bug.iml 3 kB
- map-prop-bug/application.properties 0.1 kB
- map-prop-bug/.../ApplicationResources.groovy 0.1 kB
- map-prop-bug/grails-app/.../BootStrap.groovy 0.1 kB
- map-prop-bug/.../BuildConfig.groovy 2 kB
- map-prop-bug/grails-app/.../Config.groovy 5 kB
- map-prop-bug/.../DataSource.groovy 1 kB
- map-prop-bug/.../UrlMappings.groovy 0.2 kB
- map-prop-bug/grails-app/.../resources.groovy 0.0 kB
- map-prop-bug/grails-app/.../DomainOne.groovy 0.1 kB
- map-prop-bug/.../DomainThree.groovy 0.1 kB
- map-prop-bug/grails-app/.../DomainTwo.groovy 0.1 kB
- map-prop-bug/.../messages.properties 3 kB
- map-prop-bug/.../messages_cs_CZ.properties 3 kB
- map-prop-bug/.../messages_da.properties 3 kB
- map-prop-bug/.../messages_de.properties 4 kB
- map-prop-bug/.../messages_es.properties 3 kB
Activity
- All
- Comments
- Work Log
- History
- Activity
- Git Commits
We ran into the same problem. After debugging for quite a while, I discovered the root issue lies in the way that Hibernate creates aliases for key columns for a Map property, which can result in an alias which is identical to the Map's key/value column aliases. This is VERY bad and results in the parent table's row id appearing as the Map's key or value when a row is retrieved.
In general, Hibernate creates aliases by stripping the column name down so that it only contains letters, and then appending a unique integer and an '_'. For non key columns, the uniqueInteger used is that from the Column object, which is it's column number. However for key columns, it uses the uniqueInteger from the root (parent) table. The Table object's uniqueInteger is based on the order in which it was created when everything is initialized (secondPassCompile() in GrailsAnnotationConfiguration iterates through the HashSet of known GrailsDomainClass objects, so the order varies)
If all this comes together just right (or wrong!) this bug manifests itself - if the column names all begin with the same set of letters and the parent table is created as the 3rd or 4th table, the first (key) column will have an alias identical to the 2nd or 3rd column, and when toFragmentString() is called on the SelectFragment, the 2nd or 3rd column will be ignored.
This is exactly what happens with Grails, sometimes, as the default column naming convention happens to start all column names with the same substring of letters. If we have a domain object 'Object1' with a Map property 'options', Grails creates a table 'options' with the following columns:
Now, if the Object1 table is created as the 3rd table, Hibernate assigns its Table object a uniqueInteger property of 2. When the BasicCollectionPersister creates an alias for the keyColumn (line 299),it passes the dialect and root table 'Object1' to the following Column method:
{ return getAlias(dialect) + table.getUniqueInteger() + '_'; }public String getAlias(Dialect dialect, Table table)
The getAlias(dialect) returns the column name 'options' unchanged as it is all letters, and then "2_" is appended, with the resulting alias 'options2_'.
When the BasicCollectionPersister proceeds to create the next column (line 387), it calls the getAlias(dialect) method directly on the Column object. The column name i'options_idx' is stripped down to it's initial string of just letters ('options') and, as this is different from the column name, it appends this column's uniqueInteger (2) and the '' - resulting in 'options2'. As you can see, this is identical to the alias for the first column. To note, if the Object1 table is created as the 4th table, it will then have an alias identical to the 3rd column - 'options3_'
When the BasicCollectionPersister's selectFragment() is called, it creates a SelectFragment that has all 3 aliases (the alias for the 3rd column will be 'options3_'). When toFragmentString() is called on it, these aliases are added to a HashSet, 'columnsUnique', which causes the 2nd column to be ignored as it already exists in the Set (there is no 'else' clause to match line 150).
As Eric notes in his thread, a workaround is to override the name of the key column:
static mapping =
{ options joinTable: [key: "owner_id"] }Technically, this doesn't seem to be a Grails bug, but Grail's naming convention for Map properties exposing a Hibernate bug. I'll try to find out if this is fixed in a later Hibernate release (I debugged on Grails 2.0.3 which uses Hibernate Core 3.6.10).
Interested in comments from the Grails team...
cheers,
David