黑基网 首页 学院 电脑技术 查看内容

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

2012-8-31 14:02| 投稿: computer

摘要: 基础数据类型,如String、Integer、Date、Boolean等它们可以很方便的映射到数据库: import grails.persistence.Entity @Entityclass M...
基础数据类型,如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 = nullelse 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 truereturn 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 nullreturn 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) } } 生成的表为:
小编推荐:欲学习电脑技术、系统维护、网络管理、编程开发和安全攻防等高端IT技术,请 点击这里 注册黑基账号,公开课频道价值万元IT培训教程免费学,让您少走弯路、事半功倍,好工作升职加薪!



免责声明:本文由投稿者转载自互联网,版权归原作者所有,文中所述不代表本站观点,若有侵权或转载等不当之处请联系我们处理,让我们一起为维护良好的互联网秩序而努力!联系方式见网站首页右下角。


鲜花

握手

雷人

路过

鸡蛋

相关阅读

最新评论


新出炉

返回顶部