基础数据类型,如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) } }
数据库结构:
测试保存:
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.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 } } }
生成的数据库表结构:
测试保存:
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.Entity
class MyComp { String name String code } @grails.persistence.Entity
class MyEntity { String keyName MyComp comp
static constraints = { comp(nullable:
true) } }
这样,它会在数据库中映射成两个表,用ID关联起来。
因为关联表很简单,能不能组合成一张表呢?可以,用embedded:
class MyComp { String name String code } @grails.persistence.Entity
class MyEntity { String keyName MyComp comp
static embedded = ['comp']
static constraints = { comp(nullable:
true) } }
生成的表为: