Как обеспечить целостность данных при использовании таблицы на подкласс?
Я использую таблицу по стратегии подкласса в Grails, установив tablePerHierarchy
свойство статики mapping
поле в моем суперклассе ложно. Таким образом, Grails создает одну таблицу для моего суперкласса и одну дополнительную таблицу для каждого из моих подклассов.
Однако, хотя записи суперкласса и подкласса имеют один и тот же идентификатор (первичный ключ), нет ограничений внешнего ключа для их согласованности, т. Е. Запись суперкласса можно удалить, оставив запись подкласса в недопустимом состоянии. Я хочу знать, есть ли параметр / свойство, чтобы заставить GORM решить эту проблему каким-либо образом, например, через ограничения. Или я могу добавить внешние ключи вручную?
Например, в качестве суперкласса указан следующий класс домена:
class Product {
String productCode
static mapping = {
tablePerHierarchy false
}
}
И следующий класс домена в качестве подкласса:
class Book extends Product {
String isbn
}
Это приводит к созданию двух таблиц, Product
стол и Book
Таблица. При создании книги - например, через страницы в скаффолдинге - в каждую таблицу вставляется запись, единственной ссылкой которой является тот факт, что значение идентификатора одинаково для каждой. В частности, данные могут выглядеть так:
PRODUCT
Id Version ProductCode
1 1 BLAH-02X1
BOOK
Id ISBN
1 123-4-56-7891011-1
Поскольку для этих таблиц не определено формальное отношение на уровне базы данных, можно удалить одну из записей и оставить другую, что приведет к неверным данным. Очевидно, я могу использовать SQL, чтобы вручную создать ограничение внешнего ключа для двух полей ID, но я надеялся, что Grails справится с этим. Это возможно?
Использование Grails 2.2.1
2 ответа
Решено!
Следующее решение исправило эту проблему для меня. Добавьте класс ниже, чтобы 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 (не помню, какой именно).
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
}
Держать logSql
правда в DataSource.groovy
чтобы увидеть выполнение соответствующих sqls.
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