`
xiefeifeihu
  • 浏览: 99262 次
  • 性别: 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
分享到:
评论

相关推荐

    Hibernate使用——自定义数据类型

    然而,当数据库中存在特殊类型或者我们的业务逻辑需要特定的数据结构时,就需要自定义数据类型。 创建自定义数据类型主要包括以下几个步骤: 1. **定义映射类**:创建一个Java类,它将作为自定义数据类型的实现。...

    JAVA数据类型与Hibernate的类型映射

    而在Java持久化框架Hibernate中,这些数据类型需要与数据库中的字段类型进行映射,以便正确地存储和检索数据。这篇博客主要探讨了Java数据类型如何与Hibernate的类型映射进行对应。 首先,Java的基本数据类型在...

    ibatis自定义数据类型在不支持中文的数据库存储汉字

    总结来说,`iBatis`的自定义数据类型机制允许我们在不支持中文的数据库中存储汉字,通过编写自定义的TypeHandler,我们可以灵活地将中文字符串转换为数据库可以接受的格式,然后在读取时恢复原状。这种方式在无法...

    (完整word)AB-PLC编程软件RSLOGIX5000入门7-UDT用户自定义数据类型.doc

    在本文中,我们将详细介绍如何使用用户自定义数据类型(UDT)和数据范围划定来规划标签数据库,并了解使用 UDT 的优势、如何优化 UDT 规划、使用数据范围划定来帮助简化并加快开发工作。 用户自定义数据类型(UDT)...

    自定义表单设计思路.docx

    2. **实体属性定义**:为每个字段设置数据类型、长度等属性。 3. **关联关系**:定义不同表单之间的关联规则,支持主从表之间的数据引用。 #### 五、可视化的表单定制工具 1. **图形化界面**:提供易于使用的拖拽...

    ABPLC编程软件RSLOGIX5000入门7——UDT用户自定义数据类型[整理].pdf

    在AB PLC编程软件RSLOGIX5000中,用户自定义数据类型(UDT)是一种强大的工具,它允许工程师以更加逻辑和有序的方式组织控制系统的数据。UDT,也称为结构,允许将多种不同类型的数据(如整数、实数、定时器、布尔值等...

    Java数据类型,Hibernate数据类型,标准sql数据类型之间的对应表

    此外,Hibernate还支持自定义数据类型的映射,允许开发者根据需求扩展和定制。 在实际开发中,理解这些映射关系至关重要,因为它直接影响到数据的存储和查询效率,以及数据的一致性和完整性。例如,如果错误地将...

    KETTLE数据库转换类型例子

    同时,Kettle支持自定义转换逻辑,可以应对各种复杂的数据清洗和转换需求,比如数据类型转换、缺失值处理、数据验证等。这种灵活性和强大的功能使得Kettle成为数据工程师在数据集成项目中的首选工具之一。

    阿里的EasyExcel+Mysql方式实现数据库数据导出生成exce

    1. 数据转换:确保数据库表结构与Excel文件的列名和数据类型匹配,以便正确地映射和转换。 2. 异常处理:导入和导出过程中可能出现各种异常,如文件格式错误、数据库连接问题等,应合理处理这些异常,提供友好的错误...

    简要分析Java的Hibernate框架中的自定义类型

    在Java的Hibernate框架中,自定义类型是一种非常实用的功能,它允许开发者根据特定业务需求扩展Hibernate内置的数据类型。当我们发现Hibernate默认的数据类型无法满足我们存储数据的特殊要求时,例如需要处理复杂...

    Android高级应用源码-AndroidInject增加sqlite3数据库映射注解(ORM).zip

    这个压缩包"Android高级应用源码-AndroidInject增加sqlite3数据库映射注解(ORM).zip"提供了一个示例,展示了如何在Android项目中使用SQLite3数据库,并通过ORM(对象关系映射)技术来简化数据库操作。ORM允许开发者...

    hibernate映射Oracle中LONG类型

    在 Hibernate 框架中,使用自定义类型映射 Oracle 中的 LONG 类型字段是一种常见的解决方案。通过实现 UserType 接口,我们可以定制 LONG 类型字段的读写操作。在本例中,我们定义了一个名为 CustomLong 的类,该类...

    自定义表单设计思路

    难点为数据绑定,也就是页面元素与数据表字段的映射,另外动态数据存储结构问题、表间数据校验和计算、建立主从表的问题是难点。 五、表单加载、表单解析、表单数据处理和表单存储功能 除了可视化表单定制工具外,...

    安卓Andriod源码——Inject增加sqlite3数据库映射注解(ORM).zip

    本资料包"安卓Andriod源码——Inject增加sqlite3数据库映射注解(ORM)"聚焦于如何通过注入(Inject)和对象关系映射(ORM)技术来简化SQLite3数据库的使用。 首先,我们需要理解ORM(Object-Relational Mapping)的...

    EF Core 是 .NET 的新式对象数据库映射器

    例如,使用`DbContext`类作为数据库的上下文,定义实体类来映射数据库表,然后通过`DbSet<T>`属性进行数据操作。 在项目中,`efcore-master`可能是一个包含EF Core源码的仓库,开发者可以深入研究其内部实现,学习...

    将对象映射到关系数据库.rar

    学习将对象映射到关系数据库的关键在于理解数据库与对象之间的关系,掌握如何定义映射,以及如何利用ORM框架进行数据操作。这份资料可能涵盖了这些基础知识,以及一些高级话题,如事务管理、性能优化等。通过深入...

    MyEclipse 2014 使用hibernate3 生成数据库实体类 和 xml映射文件

    在IT行业中,数据库管理和持久化框架是至关重要的组成部分,尤其是对于企业级应用开发。...在实际项目中,开发者可以根据需要自定义实体类的属性和方法,以及XML映射文件的配置,以满足复杂的业务需求。

    仅供参考 JAVA动态表单设计,自定义表单,自定义数据

    在Java中,可以利用ORM(对象关系映射)框架如Hibernate,将自定义数据与数据库模型相结合,实现数据的持久化存储。 4. **在线设计**: 在线设计表单通常涉及Web界面,用户可以直接在浏览器中拖拽、编辑表单元素。...

    AndroidInject增加sqlite3数据库映射注解(ORM)-IT计算机-毕业设计.zip

    本项目“AndroidInject增加sqlite3数据库映射注解(ORM)”是针对Android应用的一个源码示例,旨在教授如何通过对象关系映射(ORM)技术简化与SQLite3数据库的交互,提升开发效率,特别是在毕业设计或论文撰写过程中...

    简易数据库关系映射框架EasyDBO.zip

    2. **自动数据类型转换**:框架内部处理了Java类型和数据库类型之间的转换,确保数据在对象与数据库间无缝流动。 3. **CRUD操作**:EasyDBO提供了简单易用的API,支持对数据库的创建(Create)、读取(Read)、更新...

Global site tag (gtag.js) - Google Analytics