How to ensure data integrity when using Table Per Subclass? - grails

I am using the table per subclass strategy in Grails by setting the tablePerHierarchy property of the static mapping field in my superclass to false. This way, Grails creates one table for my superclass and one additional table for each of my subclasses.
However, while the superclass and subclass records share the same ID (primary key), there are no foreign key constraints to keep them consistent, i.e. it is possible to delete the superclass record, leaving the subclass record in an invalid state. I want to know if there is a setting/property to make GORM address this in some way, e.g. through constraints. Or is my only option to add foreign keys manually?
For example, given the following domain class as superclass:
class Product {
String productCode
static mapping = {
tablePerHierarchy false
}
}
And the following domain class as subclass:
class Book extends Product {
String isbn
}
This results in the creation of two tables, the Product table and the Book table. When creating a Book – through scaffolded pages, for instance – a record is inserted into each table, their only link being the fact that the ID value is the same for each. Specifically, the data might look like this:
PRODUCT
Id Version ProductCode
1 1 BLAH-02X1
BOOK
Id ISBN
1 123-4-56-7891011-1
Because there is no formal relationship defined at the database level for these tables, it is possible to delete one of the records and leave the other, which results in invalid data. Obviously I can use SQL to manually create a foreign key constraint on the two ID fields, but I was hoping to let Grails handle that. Is this possible?
Using Grails 2.2.1

Solved!
The following solution fixed this issue for me. Add the class below to src/java (this class cannot be written in Groovy)
package org.example;
import org.codehaus.groovy.grails.orm.hibernate.cfg.GrailsAnnotationConfiguration;
import org.hibernate.MappingException;
import org.hibernate.mapping.JoinedSubclass;
import org.hibernate.mapping.PersistentClass;
import org.hibernate.mapping.RootClass;
import java.util.Iterator;
public class TablePerSubclassConfiguration extends GrailsAnnotationConfiguration {
private static final long serialVersionUID = 1;
private boolean alreadyProcessed = false;
#Override
protected void secondPassCompile() throws MappingException {
super.secondPassCompile();
if (alreadyProcessed) {
return;
}
for (PersistentClass persistentClass : classes.values()) {
if (persistentClass instanceof RootClass) {
RootClass rootClass = (RootClass) persistentClass;
if (rootClass.hasSubclasses()) {
Iterator subclasses = rootClass.getSubclassIterator();
while (subclasses.hasNext()) {
Object subclass = subclasses.next();
// This test ensures that foreign keys will only be created for subclasses that are
// mapped using "table per subclass"
if (subclass instanceof JoinedSubclass) {
JoinedSubclass joinedSubclass = (JoinedSubclass) subclass;
joinedSubclass.createForeignKey();
}
}
}
}
}
alreadyProcessed = true;
}
}
Then in DataSource.groovy set this as the configuration class
dataSource {
configClass = 'org.example.TablePerSubclassConfiguration'
pooled = true
driverClassName = "org.h2.Driver"
username = "sa"
password = ""
dbCreate = "update"
url = "jdbc:h2:mem:testDb;MVCC=TRUE;LOCK_TIMEOUT=10000"
}
Update
I've submitted a pull request to Grails for this issue. The fix was included in Grails 2.3.8 or 2.3.9 (can't remember which).

Hibernate ensures the data integrity in case of table per subclass. In case of table per subclass, subclass maintains a primary key association with superclass. Have a look at Hibernate Table per subclass. To validate the fact here is your test case:
class Product {
String productCode
static mapping = {
tablePerHierarchy false
}
}
class Book extends Product{
String isbn
}
//Test Case
def testTablePerSubclass{
def product = new Product(productCode: 'XYZ456')
product.save(flush: true, failOnError: true)
def book = new Book(isbn: '123456123', productCode: 'ABC123')
book.save(flush: true, failOnError: true)
assert Book.list().size() == 1 //One Book
assert Book.list()*.id == [2] //Book id
assert Product.list().size() == 2 //One Product, one Book (2 Products)
assert Product.list()*.id == [1, 2] //Product id, Book Id
//Grab the product (book) to delete
def productToDelete = Product.get(book.id)
productToDelete.delete(flush: true)
assert Book.list().isEmpty() //Book deleted from Book table as well
assert Product.list().size() == 1 //One Product remaining in Product table
assert Product.list()*.id == [1] //Remaining Product Id
}
Keep logSql true in DataSource.groovy to see corresponding sqls getting executed.
Log Sql Output:-
Hibernate: insert into product (id, version, product_code) values (null, ?, ?)
Hibernate: insert into product (id, version, product_code) values (null, ?, ?)
Hibernate: insert into book (isbn, id) values (?, ?)
Hibernate: select this_.id as id0_0_, this_1_.version as version0_0_, this_1_.product_code as product3_0_0_, this_.isbn as isbn1_0_ from book this_ inner join product this_1_ on this_.id=this_1_.id
[com.example.Book : 2]
Hibernate: select this_.id as id0_0_, this_.version as version0_0_, this_.product_code as product3_0_0_, this_1_.isbn as isbn1_0_, case when this_1_.id is not null then 1 when this_.id is not null then 0 end as clazz_0_ from product this_ left outer join book this_1_ on this_.id=this_1_.id
[com.example.Product : 1, com.example.Book : 2]
Hibernate: delete from book where id=?
Hibernate: delete from product where id=? and version=?
Hibernate: select this_.id as id0_0_, this_1_.version as version0_0_, this_1_.product_code as product3_0_0_, this_.isbn as isbn1_0_ from book this_ inner join product this_1_ on this_.id=this_1_.id
[]
Hibernate: select this_.id as id0_0_, this_.version as version0_0_, this_.product_code as product3_0_0_, this_1_.isbn as isbn1_0_, case when this_1_.id is not null then 1 when this_.id is not null then 0 end as clazz_0_ from product this_ left outer join book this_1_ on this_.id=this_1_.id
[com.example.Product : 1]
Using Grails 2.2.2

Related

Grails 2.4.4 executeQuery() to join tables from a table of a database

this is the code for my domains.
class Btr {
Date dateBreak
int timeBreak
String typeBreak
User usuario
static constraints = {
}
static mapping = {
}
}
class User {
String name
String user
String password
String confirmPassword
String state
String extent
String movileNumber
String email
String address
Rol rol
static constraints = {
}
static mapping = {
}
}
This is the code for my controller.
def df = new SimpleDateFormat("yyyy-MM-dd HH:mm")
def startDate = params.startDate
def stopDate = params.stopDate
resultado = Btr .executeQuery("select dateBreak, timeBreak, typeBreak,
user, usuario.rol from Btr inner join User on user = usuario.rol where
dateBreak between :startDate" and :stopDate", [startDate:
df.parse(startDate), stopDate: df.parse(stopDate)])
render (view: "data", model: [result: resultado])
This is my view.
<g:each in="${result}" var="results" status="i">
<tr><td>{results.dateBreak}</td><td>{results.timeBreak}</td><td>
{results.typeBreak} </td><td>${results.usuario.rol}</td></tr>
</g:each>
Then i get this error when i submit the form.
in the GSP, when i am printing data,
Exception evaluating property 'dateBreak' for java.util.Arrays$ArrayList, Reason: groovy.lang.MissingPropertyException: No such property: dateBreak for class: java.sql.Timestamp
could someone please tell me how to join tables in grails with executeQuery and also would be nice to learn to do it with, withCriteria
resultado = Btr .executeQuery("select dateBreak, timeBreak, typeBreak,
user, usuario.rol from Btr inner join User on user = usuario.rol where
dateBreak between :startDate" and :stopDate", [startDate:
df.parse(startDate), stopDate: df.parse(stopDate)])
Should be
resultado = Btr .executeQuery("""select new map (btr.dateBreak as dateBreak, btr.timeBreak as timeBreak, btr.typeBreak as typeBreak,
u as user, user.usuario.rol as rol) from Btr btr join btr.user u where
btr.dateBreak between :startDate and :stopDate""", [startDate:
df.parse(startDate), stopDate: df.parse(stopDate)])
what you have is raw sql and not HQL which is a slight variation and uses actual domain objects to join
Use left join for hasMany where it may be null join for typical one to one relationship
Also use left join if one to one relationship can be null
Beyond that you could have put your actual query as a raw sql query like so
def sql=new Sql(dataSource)
return sql.rows(query,whereParams)

Grails - Use logical OR in CreateCriteria

Starting with the following Domains:
class Person {
String login
String name
}
class MyDomain {
Person creator
static hasMany = [userlist:Person]
}
I wrote a critera to retreive all MyDomainIntances where I'm as creator OR where I'm in the userlist:
def login = "myself"
def result = MyDomain.createCriteria().list () {
projections { distinct ( "id" )
property("name")
property("id")
}
or {
userlist{eq("login", login)}
creator {eq("login",login)}
}
order("name","desc")
}
The problem is with this code I get only instances where I'm in the userlist.
Though creator {eq("login",login)} works well: if I use it itself only, I get a list where I'm as creator
generated query:
Hibernate:
select distinct this_.id as y0_,this_.name as y1_,this_.id as y2_
from mydomain this_ inner join person creator_al2_ on this_.creator_id=creator_al2_.id
inner join mydomain_person userlist5_ on this_.id=userlist5_.mydomain_userlist_id
inner join person userlist1_ on userlist5_.person_id=userlist1_.id
where ((userlist1_.login=?) or (creator_al2_.login=?))
order by this_.name desc
I think the issue is in the OR, as you state that you only get results where 'myself' is in the user list. This blog may help, which also cites this helpful article on createAlias.
def result = MyDomain.createCriteria().list () {
createAlias('creator', 'c')
projections { distinct ( "id" )
property("name")
property("id")
}
or {
userlist{
eq("login", login)
}
eq("c.login", login)
}
order("name","desc")
}

Mapping breaks my domain class

When I add the following mapping to my domain class, I get an error. Can someone help me see what I'm doing wrong here?
Domain Class
package sample
class Todo {
String name
String note
Date dateCreated
Date lastUpdated
Date dueDate
Date lastModifiedDate
Date completedDate
String priority
String status = "Started"
User owner
Category category
static belongsTo = [User, Category]
static constraints = {
name (blank:false)
priority()
status()
note (maxSize:1000, nullable:true)
completedDate(nullable:true)
dueDate(nullable:true)
}
String toString() {
name
}
}
Mapping I Want to Add
static mapping = {
table 'todo-tbl'
columns {
name column: 'name-str'
note column: 'note-str'
}
cache true
}
Error I Get When Adding Mapping
Error 500: Internal Server Error URI /sample/todo/list Class
org.h2.jdbc.JdbcSQLException Message Table "TODO" not found; SQL
statement: select this_.id as id72_0_, this_.version as version72_0_,
this_.category_id as category3_72_0_, this_.completed_date as
completed4_72_0_, this_.date_created as date5_72_0_, this_.due_date as
due6_72_0_, this_.last_modified_date as last7_72_0_,
this_.last_updated as last8_72_0_, this_.name-str as name9_72_0_,
this_.note-str as note10_72_0_, this_.owner_id as owner11_72_0_,
this_.priority as priority72_0_, this_.status as status72_0_ from
todo-tbl this_ limit ? [42102-164]
I solved this. Apparently using the hyphen in the table name was a big no-no. This stopped the table from being created in the DB.
Changed
table "todo-tbl"
to
table "todoTbl

Is it possible in grails to disable persistence of a domain class dynamically with inheritance

My question is related to/or identical to
Is it possible in grails to disable persistence of a domain class?
I am using grails 2.2.1 where I tried to turn off a domain class by putting
static mapWith = "none"
gorm creates the database table, and calling .save() will actually put an entry to the database. So the mapWith flag did nothing for me. I also cannot find any documentation regarding the mapWith flag. Is there a replacement in Grails 2?
Update on sept 16 with code
package test
class Person {
String name
static constraints = {
}
static mapping = {
version false
tablePerHierarchy false
table 'psn'
}
}
-------------------
package test
class Employer extends Person{
static mapWith = 'none'
String title
static constraints = {
}
static mapping = {
version false
tablePerHierarchy false
table 'emplyr'
}
}
---------
package test
class Employee extends Person{
String description
static constraints = {
}
static mapping = {
version false
tablePerHierarchy false
table 'emplyee'
}
}
I would assume Employer should not be treated as a domain object. However, when I perform, a Person.list(), this sql was shown:
Hibernate: select this_.id as id0_0_, this_.name as name0_0_, this_1_.description as descript2_1_0_, this_2_.title as title2_0_, case when this_1_.id is not null then 1 when this_2_.id is not null then 2 when this_.id is not null then 0 end as clazz_0_ from psn this_ left outer join emplyee this_1_ on this_.id=this_1_.id left outer join emplyr this_2_ on this_.id=this_2_.id limit ?
Hibernate: select count(*) as y0_ from psn this_ left outer join emplyee this_1_ on this_.id=this_1_.id left outer join emplyr this_2_ on this_.id=this_2_.id
Why would it join emplyr????? My purpose is to eliminate this table join when this is mark as "none". Am I doing something wrong?

How to set version property on domain object creation?

I'm trying to model a parent/child relationship.
I have the following domain class:
package test
class Person {
Person mother
Person father
String name
static hasOne = [father: Person, mother: Person]
static constraints = {
name()
father(nullable:true)
mother(nullable:true)
}
def Set<Person> children(){
return Person.findAllByMother(this)
}
}
I have performed generate-all
However, if I try to create a new Person, I get the following error:
Message: Parameter "#2" is not set; SQL statement:
insert into person (id, version, father_id, mother_id, name) values (null, ?, ?, ?, ?) [90012-164]
Line | Method
->> 329 | getJdbcSQLException in org.h2.message.DbException
Where should the version parameter be generated? I thought that this should be generated auto-magically during the save call.
UPDATE: The issue seems to be related to the father/mother relationship, since removing it and re-generating views means that elements are persisted ok.
The issue seems to be related to the fact that I had tried to create a di-directional relationship in the class. This in fact is not required. There is a uni directional relationship Person -> father and Person -> mother. The inverse is calculated under children (which I shall extend to include father as well.)
My final class is:
package test
class Person {
int id
Person mother
Person father
String name
static constraints = {
name()
father(nullable:true)
mother(nullable:true)
}
def Set<Person> children(){
return Person.findAllByMother(this)
}
}
I'm still new to Grails.

Resources