`
xiefeifeihu
  • 浏览: 97318 次
  • 性别: Icon_minigender_1
  • 来自: 广州
社区版块
存档分类
最新评论

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

阅读更多

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

import grails.persistence.Entity

@Entity
class 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.Entity
class 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.Entity
class MyEntity {
    MyString name
    static constraints = {
        name(nullable: true)
    }
    static mapping = {
        name(length: 10)
    }
}

数据库结构:

2NWINP{S356}`6I]H2X2]]B

测试保存:

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

数据库记录为:

[GJD~VB%`60S5KA79RTWLY4

测试查询:

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.Entity
class MyEntity {
    MyChineseName name
    static constraints = {
        name(nullable: true)
    }
    static mapping = {
        name type: MyChineseName, {
            column name: "chineseFamilyName", length: 10
            column name: "chineseGivenName", length: 10
        }
    }
}

生成的数据库表结构:

`RI8WCT(_F2YBPLI8WOAU0N

测试保存:

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

数据库记录为:

NI[1ACND@UM4}5I3}0FWY$H

 

这种方式的麻烦之处在于映射时需要指定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
    }
}

生成的数据库记录为:

D1_CP9CU28G(0]}20WATW_Y

 

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

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

@grails.persistence.Entity
class MyComp {
    String name
    String code
}
@grails.persistence.Entity
class MyEntity {
    String keyName
    MyComp comp

    static constraints = {
        comp(nullable: true)
    }
}

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

T`X[@8@}P@P%$B%[J}F4_1L

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

class MyComp {
    String name
    String code
}
@grails.persistence.Entity
class MyEntity {
    String keyName
    MyComp comp

    static embedded = ['comp']
    static constraints = {
        comp(nullable: true)
    }
}

生成的表为:

4RQ`N4)J7[A%`~)2UWC(_~3

1
2
分享到:
评论

相关推荐

    DBeaver连接国产数据库的正确打开方式

    DBeaver 怎么自定义连接多个国产数据库,实现万能数据库连接工具。稍微懂点jdbc 连接的人都能够使用。 DBeaver 下载地址: https://dbeaver.io/download/

    基于数据库的代码自动生成工具,生成JavaBean、生成数据库文档、生成前后端代码等(TableGo v7.0.0版)

    9、JSON参数配置新增文件操作功能、字段数据类型映射转换等功能 10、优化JSON参数配置,可以同时使用表命名方式和驼峰命名方式填写参数,前端可以直接复制Swagger上的实体名和属 性名配置上去生成代码,无需关注...

    VB/C#.Net实体代码生成工具(EntitysCodeGenerate)【ECG】4.7 201705更新

    从数据库中生成表对应的VB/C#实体代码,并提取表及字段的注释等信息,亦可对生成的实体数据类型进行各种自定义的设置和方便的数据库操作等。如数据库列和VB/C#代码类型的映射、实体命名空间、代码个性化注释、是否...

    ORM框架-VB/C#.Net实体代码生成工具(EntitysCodeGenerate)【ECG 4.2】 2010岁末最新版

    从数据库中生成表对应的VB/C#实体代码,并提取表及字段的注释等信息,亦可对生成的实体数据类型进行各种自定义的设置和方便的数据库操作等。如数据库列和VB/C#代码类型的映射、实体命名空间、代码个性化注释、是否...

    C#实现根据实体类自动创建数据库表

    属性指的类中封装的数据字段;而特性是对类、字段、方法和属性等元素标注的声明性信息 如下代码(Id、Name为User的属性,[DbKey]为Id的特性) /// /// 用户信息 /// public class User { [DbKey] public string...

    VB/C#.Net实体代码生成工具(EntitysCodeGenerate)_4.3

    从数据库中生成表对应的VB/C#实体代码,并提取表及字段的注释等信息,亦可对生成的实体数据类型进行各种自定义的设置和方便的数据库操作等。如数据库列和VB/C#代码类型的映射、实体命名空间、代码个性化注释、是否...

    vb/c#.net实体代码生成工具(entityscodegenerate

    从数据库中生成表对应的vb/c#实体代码,并提取表及字段的注释等信息,亦可对生成的实体数据类型进行各种自定义的设置和方便的数据库操作等。如数据库列和vb/c#代码类型的映射、实体命名空间、代码个性化注释、是否...

    houseelectricsorm:android上sqlite的对象关系映射

    可自定义类型到数据库字段映射 深度加载 原始查询 延迟加载 这是计划的内容: 包含和非包含关系的模型区别 - 启用级联删除 更多延迟加载测试 数据迁移/多并发模式版本支持 ##Support - 征求意见 电子邮件支持:[ ] ...

    ORM框架-VB/C#.Net实体代码生成工具(EntitysCodeGenerate)[ECG 4.3] 201105

    从数据库中生成表对应的VB/C#实体代码,并提取表及字段的注释等信息,亦可对生成的实体数据类型进行各种自定义的设置和方便的数据库操作等。如数据库列和VB/C#代码类型的映射、实体命名空间、代码个性化注释、是否...

    58同城数据库中间件-58同城数据库中间件

    在DB存储需求中,尽管业务不同,技术难点还是类似的,开源世界有很多DB中间件,解决方案也以通用方案为主,满足业务需要为前提,支持各种类型的需求。 Oceanus致力于打造一个功能简单、可依赖、易于上手、易于扩展...

    nbearlite

    <br>推荐在使用NBearLite的同时结合使用NBearMapping进行IDataReader/DataRow数据类型和自定义.NET类 (如:实体类或业务类)之间的填充转换...和自定义.NET类之间的数据映射转换(如异构系统间的数据类型整合)...

    VB/C#.Net实体代码生成工具(EntitysCodeGenerate)_4.3_及免安装文件

    从数据库中生成表对应的VB/C#实体代码,并提取表及字段的注释等信息,亦可对生成的实体数据类型进行各种自定义的设置和方便的数据库操作等。如数据库列和VB/C#代码类型的映射、实体命名空间、代码个性化注释、是否...

    黄淮学院2011数据库考试提目

    用有向图结构表示实体类型及实体间联系的数据模型称为__________模型,数据之间的联系通常通过__________实现。 14 .__________是目前最常用也是最重要的一种数据模型。采用该模型作为数据的组织方式的数据库系统...

    VB/C#.Net实体代码生成工具(EntitysCodeGenerate)【ECG】4.7

    从数据库中生成表对应的VB/C#实体代码,并提取表及字段的注释等信息,亦可对生成的实体数据类型进行各种自定义的设置和方便的数据库操作等。如数据库列和VB/C#代码类型的映射、实体命名空间、代码个性化注释、是否...

    VB/C#.Net实体代码生成工具(EntitysCodeGenerate)【ECG】4.4

    从数据库中生成表对应的VB/C#实体代码,并提取表及字段的注释等信息,亦可对生成的实体数据类型进行各种自定义的设置和方便的数据库操作等。如数据库列和VB/C#代码类型的映射、实体命名空间、代码个性化注释、是否...

    mybatis面试题(经典问答)

    简而言之,ORM就是用元数据来描述对象与数据库的映射关系,将程序中的对象与关系数据库进行映射。 ORM 提供了另一种实现持久层的方式。它使用映射元数据来描述对象关系的映射,使ORM中间件可以充当任何应用程序的...

    VB/C#.Net实体代码生成工具(EntitysCodeGenerate)【ECG】4.5

    从数据库中生成表对应的VB/C#实体代码,并提取表及字段的注释等信息,亦可对生成的实体数据类型进行各种自定义的设置和方便的数据库操作等。如数据库列和VB/C#代码类型的映射、实体命名空间、代码个性化注释、是否...

    VB/C#.Net实体代码生成工具(EntitysCodeGenerate)【ECG】4.6

    从数据库中生成表对应的VB/C#实体代码,并提取表及字段的注释等信息,亦可对生成的实体数据类型进行各种自定义的设置和方便的数据库操作等。如数据库列和VB/C#代码类型的映射、实体命名空间、代码个性化注释、是否...

    VB/C#.Net实体代码生成工具(EntitysCodeGenerate)【ECG】4.8

    从数据库中生成表对应的VB/C#实体代码,并提取表及字段的注释等信息,亦可对生成的实体数据类型进行各种自定义的设置和方便的数据库操作等。如数据库列和VB/C#代码类型的映射、实体命名空间、代码个性化注释、是否...

Global site tag (gtag.js) - Google Analytics