Hibernate识别数据库特有字段实例详解
前言:
Hibernate已经为绝大多数常用的数据库数据类型提供了内置支持,但对于某些数据库的专属字段支持就不够好了。 这些特殊数据类型往往提供了比常规数据类型更好的数据表达能力,更符合我们的业务场景。比如PostgreSQL的Interval类型,可以非常方便的保存一个时间段的数据。 本文以添加Interval类型支持为例,说明为Hibernate添加特有数据类型支持的方法。
Hibernate提供了丰富的数据类型支持,但对于部分数据库专有的数据类型,提供的支持就很有限了。比如PostgreSQL的Interval类型,对于保存一个"时间段"数据就非常方便。
在开发中,我们期望将Interval类型映射为Java 8 的Duration类型。但是Hibernate默认对Duration类型的映射是直接映射到数据库的BigInt类型,直接保存纳秒值。显然对于不直接支持Interval类型的数据库来说,是比较合适的,但是我们仍然期望直接映射到数据库的Interval类型。
为此,我们需要调整Hibernate对于两种数据类型(Java世界的Duration和Db世界的Interval)的映射关系。
幸运的是,Hibernate提供了非常方便的方法可以实现数据类型的映射。
为此,我们需要一个实现org.hibernate.usertype.UserType接口的类,来实现两个世界的数据转换/映射工作。
Hibernate的自定义类型(UserType)
UserType是Hibernate提供的一个自定义数据类型的接口。所有自定义数据均需实现此接口,或者从org.hibernate.usertype中定义的接口中选择一个合适的接口。
鉴于我们的场景比较简单,直接实现UserType即可满足需求。此接口提供了如下一组方法需要自己实现:
assemble(Serializable cached, Object owner)
从序列化中重新构建(Java)对象。
deepCopy(Object value)
返回深度副本。
disassemble(Object value)
转换对象的序列化数据。
equals(Object x, Object y)
返回两个映射的数据是否相等。
hashCode(Object x)
获取对象的散列。
isMutable()
返回对象是否是可变类型。
nullSafeGet(ResultSet rs, String[] names, Object owner)
从数据库类型的数据,返回对应的Java对象。核心实现方法
nullSafeSet(PreparedStatement st, Object value, int index)
从Java对象,返回对应的数据库类型的数据。核心实现方法
replace(Object original, Object target, Object owner)
合并期间,将实体中的目标值(target)替换为原始值(original)。
returnedClass()
nullSafeGet返回的类。
sqlTypes()
返回对应的数据库类型。
实例
package framework.postgresql; import org.hibernate.HibernateException; import org.hibernate.engine.spi.SharedSessionContractImplementor; import org.hibernate.usertype.UserType; import org.postgresql.util.PGInterval; import java.io.Serializable; import java.sql.PreparedStatement; import java.sql.ResultSet; import java.sql.SQLException; import java.sql.Types; import java.time.Duration; /** * PostgreSql Inteval字段与java.time.Duration映射 * 目前只支持到最多1个月(30天)的间隔 * <p> * 使用方法: * 在实体类上增加 * \@TypeDef(name="interval", typeClass = IntervalType.class) * 在字段定义上增加: * \@Type(type = "interval") * <p> * http://stackoverflow.com/questions/1945615/how-to-map-the-type-interval-in-hibernate/6139581#6139581 * * @version 1.0 * @since 1.0 */ public class IntervalType implements UserType { public Object assemble(Serializable cached, Object owner) throws HibernateException { return cached; } public Object deepCopy(Object value) throws HibernateException { return value; } public Serializable disassemble(Object value) throws HibernateException { return (Serializable) value; } public boolean equals(Object arg0, Object arg1) throws HibernateException { return arg0 != null && arg1 != null && arg0.equals(arg1) || arg0 == null && arg1 == null; } public int hashCode(Object object) throws HibernateException { return object.hashCode(); } @Override public Object nullSafeGet(ResultSet resultSet, String[] names, SharedSessionContractImplementor sessionImplementor, Object o) throws HibernateException, SQLException { String interval = resultSet.getString(names[0]); if (resultSet.wasNull() || interval == null) { return null; } PGInterval pgInterval = new PGInterval(interval); return getDuration(pgInterval); } @Override public void nullSafeSet(PreparedStatement st, Object value, int index, SharedSessionContractImplementor sessionImplementor) throws HibernateException, SQLException { if (value == null) { st.setNull(index, Types.OTHER); } else { //this http://postgresql.1045698.n5.nabble.com/Inserting-Information-in-PostgreSQL-interval-td2175203.html#a2175205 Duration duration = (Duration) value; st.setObject(index, getInterval(duration), Types.OTHER); } } public static Duration getDuration(PGInterval pgInterval) { return Duration.ofSeconds(pgInterval.getDays() * 24 * 3600 + pgInterval.getHours() * 3600 + pgInterval.getMinutes() * 60 + (int) pgInterval.getSeconds()); } private static PGInterval getInterval(Duration value) { long seconds = value.getSeconds(); int days = (int) (seconds / (24 * 3600)); seconds -= days * 24 * 3600; int hours = (int) (seconds / 3600); seconds -= hours * 3600; int minutes = (int) (seconds / 60); seconds -= minutes * 60; seconds = Math.abs(seconds); return new PGInterval(0, 0, days, hours, minutes, seconds); } public boolean isMutable() { return false; } public Object replace(Object original, Object target, Object owner) throws HibernateException { return original; } public Class returnedClass() { return Duration.class; } public int[] sqlTypes() { return new int[]{Types.OTHER}; } }
使用自定义类型
至此,我们已经定义好了自己的数据类型。但Hibernate还不知道怎么使用它。为此,我们需要通过在Entity上使用使用TypeDef注解,并在属性上使用Type注解。
比如:
... @Entity @TypeDef(name = "interval", typeClass = IntervalType.class) public class PaperStatis implements Serializable { ... @Column(name = "avg_duration") @Type(type = "interval") public Duration getAvgDuration() { return this.avgDuration; } ... }
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!
免责声明:本站资源来自互联网收集,仅供用于学习和交流,请遵循相关法律法规,本站一切资源不代表本站立场,如有侵权、后门、不妥请联系本站删除!
《魔兽世界》大逃杀!60人新游玩模式《强袭风暴》3月21日上线
暴雪近日发布了《魔兽世界》10.2.6 更新内容,新游玩模式《强袭风暴》即将于3月21 日在亚服上线,届时玩家将前往阿拉希高地展开一场 60 人大逃杀对战。
艾泽拉斯的冒险者已经征服了艾泽拉斯的大地及遥远的彼岸。他们在对抗世界上最致命的敌人时展现出过人的手腕,并且成功阻止终结宇宙等级的威胁。当他们在为即将于《魔兽世界》资料片《地心之战》中来袭的萨拉塔斯势力做战斗准备时,他们还需要在熟悉的阿拉希高地面对一个全新的敌人──那就是彼此。在《巨龙崛起》10.2.6 更新的《强袭风暴》中,玩家将会进入一个全新的海盗主题大逃杀式限时活动,其中包含极高的风险和史诗级的奖励。
《强袭风暴》不是普通的战场,作为一个独立于主游戏之外的活动,玩家可以用大逃杀的风格来体验《魔兽世界》,不分职业、不分装备(除了你在赛局中捡到的),光是技巧和战略的强弱之分就能决定出谁才是能坚持到最后的赢家。本次活动将会开放单人和双人模式,玩家在加入海盗主题的预赛大厅区域前,可以从强袭风暴角色画面新增好友。游玩游戏将可以累计名望轨迹,《巨龙崛起》和《魔兽世界:巫妖王之怒 经典版》的玩家都可以获得奖励。
更新日志
- 【雨果唱片】中国管弦乐《鹿回头》WAV
- APM亚流新世代《一起冒险》[FLAC/分轨][106.77MB]
- 崔健《飞狗》律冻文化[WAV+CUE][1.1G]
- 罗志祥《舞状元 (Explicit)》[320K/MP3][66.77MB]
- 尤雅.1997-幽雅精粹2CD【南方】【WAV+CUE】
- 张惠妹.2007-STAR(引进版)【EMI百代】【WAV+CUE】
- 群星.2008-LOVE情歌集VOL.8【正东】【WAV+CUE】
- 罗志祥《舞状元 (Explicit)》[FLAC/分轨][360.76MB]
- Tank《我不伟大,至少我能改变我。》[320K/MP3][160.41MB]
- Tank《我不伟大,至少我能改变我。》[FLAC/分轨][236.89MB]
- CD圣经推荐-夏韶声《谙2》SACD-ISO
- 钟镇涛-《百分百钟镇涛》首批限量版SACD-ISO
- 群星《继续微笑致敬许冠杰》[低速原抓WAV+CUE]
- 潘秀琼.2003-国语难忘金曲珍藏集【皇星全音】【WAV+CUE】
- 林东松.1997-2039玫瑰事件【宝丽金】【WAV+CUE】