6

我通过将我的超类中的静态字段的属性设置为 false 来使用Grails中的每个子类的表策略。这样,Grails 为我的超类创建一个表,并为我的每个子类创建一个附加表。tablePerHierarchymapping

然而,虽然超类和子类记录共享相同的 ID(主键),但没有外键约束来保持它们的一致性,即可以删除超类记录,使子类记录处于无效状态。我想知道是否有一个设置/属性可以让 GORM 以某种方式解决这个问题,例如通过约束。还是我手动添加外键的唯一选择?


例如,给定以下域类作为超类:

class Product {
    String productCode

    static mapping = {
        tablePerHierarchy false
    }
}

并将以下域类作为子类:

class Book extends Product {
    String isbn
}

这导致创建两个表,Product表和Book表。当创建一本书时——例如,通过脚手架的页面——一条记录被插入到每个表中,它们唯一的链接是每个表的 ID 值相同的事实。具体来说,数据可能如下所示:

PRODUCT
Id      Version     ProductCode
1       1           BLAH-02X1

BOOK
Id      ISBN
1       123-4-56-7891011-1

由于这些表没有在数据库级别定义正式关系,因此可能会删除其中一条记录而留下另一条记录,从而导致数据无效。显然,我可以使用 SQL 在两个 ID 字段上手动创建外键约束,但我希望让 Grails 处理它。这可能吗?


使用 Grails 2.2.1

4

2 回答 2

4

解决了!

以下解决方案为我解决了这个问题。添加下面的类src/java(这个类不能用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;
    }
}

然后DataSource.groovy将其设置为配置类

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"
}

更新

我已针对此问题向 Grails提交了拉取请求。该修复程序包含在 Grails 2.3.8 或 2.3.9 中(不记得是哪个)。

于 2014-02-07T19:30:23.353 回答
1

Hibernate 在每个子类的表的情况下确保数据完整性。在每个子类的表的情况下,子类维护与超类的主键关联。查看每个子类的 Hibernate Table。为了验证这里的事实是你的测试用例:


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
}

保持logSqltrueDataSource.groovy以查看相应的 sql 被执行。


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]

使用 Grails 2.2.2

于 2013-05-26T06:56:05.100 回答