首页 诗词 字典 板报 句子 名言 友答 励志 学校 网站地图
当前位置: 首页 > 教程频道 > 数据库 > 其他数据库 >

自定义数据类型的数据库照射方案

2012-09-03 
自定义数据类型的数据库映射方案基础数据类型,如String、Integer、Date、Boolean等它们可以很方便的映射到数

自定义数据类型的数据库映射方案

基础数据类型,如String、Integer、Date、Boolean等它们可以很方便的映射到数据库:

import grails.persistence.Entity@Entityclass MyEntity {    String code    String name    static constraints = {        code(unique: true, minSize: 4, maxSize: 4)        name(blank: false, maxSize: 255)    }}

这些基础数据类型是JAVA提供的语言级的,它没有语意。

比如要表达一个身份证号码:它有长度限制:15位或18位;还有规则限制;还能从身份证号码中提取出地址、性别、出生日期、年龄等信息。这些信息用一个String是无法表达,需要用类来描述:

class IDNumber{    String idNumber    Address address    InsDate birthday    Gender gender    IDNumber() {}    IDNumber(val) {               if (val.length() == 15) {            val = to18IdNumber(val)        }        if (val.length() != 18) {            throw new IllegalArgumentException("不是身份证格式")        }        this.idNumber = val        return    }    def getAddress() {        if (address) return address        else return address = parseAddress()    }    def getBirthday() {        if (birthday) return birthday        else return birthday = parseBirth()    }    def getGender() {        if (gender) return gender        else return gender = parseGender()    }    def parseBirth() {        ...    }}

这个类里面最核心的就是String idNumber身份证号码,其他属性都是暂存的临时数据,可以从身份证号码里解析出来。如果想把这个类映射到数据库中,现在只能映射成一个table,但映射成table又不合理,最好是能映射成一列:

@grails.persistence.Entityclass PersonInfo {    String name    IDNumber idNumber}

现在这样显然是不能达到这个目标的。

Hibernate提供了多种实现自定义类型的方法:

1、实现org.hibernate.usertype.UserType

2、实现org.hibernate.usertype.CompositeUserType

3、实现org.hibernate.usertype.UserCollectionType

4、实现org.hibernate.usertype.EnhanceUserType

通过实现这些接口,可以将自定义数据类型映射成数据库列。

UserType可以映射成单列,CompositeUserType可以映射成多列。

看个例子:

class MyString extends InsDataType implements UserType{    String value    @Override    void buildData(val) {        if (val instanceof MyString) {            value = val.value            return        }        if (val == null) value = null        else if (val instanceof String) value = val        else if (val instanceof Number) value = String.valueOf(val)        else value = val.toString()        return    }    static MyString from(val) {        if (val instanceof MyString) return val        MyString data = new MyString()        data.build(val)        return data    }    public String toString() {        return value    }    int[] sqlTypes() {        return [Types.VARCHAR]    }    Class returnedClass() {        return MyString    }    boolean equals(Object x, Object y) {        MyString mx, my        if (x instanceof String) mx = MyString.from(x)        if (x instanceof MyString) mx = x        if (y instanceof String) my = MyString.from(y)        if (y instanceof MyString) my = y        if (mx?.value == my?.value) return true        return false    }    int hashCode(Object x) {        return ((MyString) x)?.value?.hashCode()    }    Object nullSafeGet(ResultSet rs, String[] names, Object owner) {        if (rs.wasNull()) return null//        String stringFromDb = (String) Hibernate.STRING.nullSafeGet(rs, names[0]);        String stringFromDb = rs.getString(names[0]);        return MyString.from(stringFromDb)    }    void nullSafeSet(PreparedStatement st, Object value, int index) {        if (value == null)            st.setNull(index, Types.VARCHAR);        else {            MyString myString = (MyString) value;            st.setString(index, myString.value);//            Hibernate.STRING.nullSafeSet(st, myString.value, index);        }    }    Object deepCopy(Object value) {        if (!value || !((MyString) value).value) return null        return MyString.from(value)    }    boolean isMutable() {        return true    }    Serializable disassemble(Object value) {        return ((MyString) value).value    }    Object assemble(Serializable cached, Object owner) {        return MyString.from(cached)    }    Object replace(Object original, Object target, Object owner) {        return null    }}

这样就可以将MyString映射到数据库表中的一列了。

@grails.persistence.Entityclass MyEntity {    MyString name    static constraints = {        name(nullable: true)    }    static mapping = {        name(length: 10)    }}

数据库结构:

自定义数据类型的数据库照射方案

测试保存:

def testSave() {        MyEntity entity = new MyEntity(name: MyString.from("hehe"))        TestDomain.withTransaction {            if (entity.hasErrors() || !entity.save(flush: true)) {                println "save error:" + entity.errors            }        }    }

数据库记录为:

自定义数据类型的数据库照射方案

测试查询:

MyEntity entity = MyEntity.findByName(MyString.from("hehe"))

现在操作自定义的MyString就像操作基础数据类型一样了。

如果一个数据类型有多个字段要存储,比如姓名分姓氏和名称。一种方法是把多个字段合并成一个字段,仍然使用UserType。另一种方法是用CompositeUserType。

class MyChineseName implements CompositeUserType {    String familyName    String givenName    String[] getPropertyNames() {        return ["familyName", "givenName"] as String[]    }    Type[] getPropertyTypes() {        return [Hibernate.STRING, Hibernate.STRING] as Type[]    }    Object getPropertyValue(Object component, int property) {        MyChineseName name = (MyChineseName) component;        String result;        switch (property) {            case 0:                result = name.familyName;                break;            case 1:                result = name.givenName;                break;            default:                throw new IllegalArgumentException("unknow property: " + property);        }        return result;    }    void setPropertyValue(Object component, int property, Object value) {        MyChineseName name = (MyChineseName) component;        String nameValue = (String) value;        switch (property) {            case 0:                name.familyName = nameValue                break;            case 1:                name.givenName = nameValue                break;            default:                throw new IllegalArgumentException("unknow property: " + property);        }    }    Class returnedClass() {        return MyChineseName    }    boolean equals(Object x, Object y) {        if (x == y)            return true;        if (x == null || y == null)            return false;        return x.equals(y);    }    int hashCode(Object x) {        return x.hashCode()    }    Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner) {        if (rs.wasNull())            return null;        String firstname = rs.getString(names[0]);        String lastname = rs.getString(names[1]);        return new MyChineseName(familyName: firstname, givenName: lastname);    }    void nullSafeSet(PreparedStatement statement, Object value, int index, SessionImplementor session) {        if (value == null)            statement.setNull(index, Types.VARCHAR);        else {            MyChineseName name = (MyChineseName) value;//            statement.setString(index, name.familyName);//            statement.setString(index + 1, name.givenName);            Hibernate.STRING.nullSafeSet(statement, name.familyName, index + 0);            Hibernate.STRING.nullSafeSet(statement, name.givenName, index + 1);        }    }    Object deepCopy(Object value) {        if (value == null)            return null;        MyChineseName name = (MyChineseName) value;        return new MyChineseName(familyName: name.familyName, givenName: name.givenName);    }    boolean isMutable() {        return false    }    Serializable disassemble(Object value, SessionImplementor session) {        return (Serializable) deepCopy(value);    }    Object assemble(Serializable cached, SessionImplementor session, Object owner) {        return (Serializable) deepCopy(cached);    }    Object replace(Object original, Object target, SessionImplementor session, Object owner) {        return null    }}

这样,MyChineseName就能够映射成两列了。如果还像上面一样定义Entity类,Hibernate仍然无法映射,必须指定type和column:

@grails.persistence.Entityclass MyEntity {    MyChineseName name    static constraints = {        name(nullable: true)    }    static mapping = {        name type: MyChineseName, {            column name: "chineseFamilyName", length: 10            column name: "chineseGivenName", length: 10        }    }}

生成的数据库表结构:

自定义数据类型的数据库照射方案

测试保存:

def testSave() {        MyEntity entity = new MyEntity(name: new MyChineseName(familyName: "泛", givenName: "华"))        TestDomain.withTransaction {            if (entity.hasErrors() || !entity.save(flush: true)) {                println "save error:" + entity.errors            }        }        println ToStringBuilder.reflectionToString(entity)    }

数据库记录为:

自定义数据类型的数据库照射方案

这种方式的麻烦之处在于映射时需要指定type和column。如果用户不清楚它的实现方式,仍然当作普通的UserType,没有指定type和column,那么就会报错:

Caused by: org.hibernate.MappingException: property mapping has wrong number of columns: com.baoxian.domain.MyEntity.name type: com.baoxian.datatype.MyChineseName

仅仅根据这个错误描述就不太好定位了。


可以把多字段组合成一个字符串,从而映射成一个字段来解决:

class MyChineseName implements UserType {    String familyName    String givenName    String toOneString() {        return "fn:${familyName};gn:${givenName}"    }    MyChineseName parseString(String str) {        def regular = /(fn|gn):([^;]*)/        def result = str =~ regular        def map = [:]        result.each { map[it[1]] = it[2] }        return new MyChineseName(familyName: map["fn"], givenName: map["gn"])    }    int[] sqlTypes() {        return [Types.VARCHAR]    }    Class returnedClass() {        return MyChineseName    }    boolean equals(Object x, Object y) {        if (x == y)            return true;        if (x == null || y == null)            return false;        return x.equals(y);    }    int hashCode(Object x) {        return x.hashCode()    }    Object nullSafeGet(ResultSet rs, String[] names, Object owner) {        return parseString(rs.getString(names[0]))    }    void nullSafeSet(PreparedStatement st, Object value, int index) {        if (value == null)            st.setNull(index, Types.VARCHAR);        else {            MyChineseName name = (MyChineseName) value            st.setString(index, name.toOneString())        }    }    Object deepCopy(Object value) {        if (value == null)            return null;        MyChineseName name = (MyChineseName) value;        return new MyChineseName(familyName: name.familyName, givenName: name.givenName);    }    boolean isMutable() {        return false    }    Serializable disassemble(Object value) {        return (Serializable) deepCopy(value);    }    Object assemble(Serializable cached, Object owner) {        return (Serializable) deepCopy(cached);    }    Object replace(Object original, Object target, Object owner) {        return null    }}

生成的数据库记录为:

自定义数据类型的数据库照射方案

除了实现CompositeUserType能将一个对象映射成多列,还有一种方法能达到这种效果:embedded。它能将本应映射成两个table的组合成一个表。

假设有两个实体关联如下:

@grails.persistence.Entityclass MyComp {    String name    String code}@grails.persistence.Entityclass MyEntity {    String keyName    MyComp comp    static constraints = {        comp(nullable: true)    }}

这样,它会在数据库中映射成两个表,用ID关联起来。

自定义数据类型的数据库照射方案

因为关联表很简单,能不能组合成一张表呢?可以,用embedded:

class MyComp {    String name    String code}@grails.persistence.Entityclass MyEntity {    String keyName    MyComp comp    static embedded = ['comp']    static constraints = {        comp(nullable: true)    }}

生成的表为:

自定义数据类型的数据库照射方案

热点排行