基礎數據類型,如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)
}
}
生成的表爲: