抽象泄漏,或如何正确地将Oracle DATE与Hibernate绑定

我们最近发布了一篇文章,介绍如何在SQL / JDBC和jOOQ中正确绑定Oracle DATE类型 。 这篇文章在Reddit上颇受关注, Vlad Mihalcea对此发表了有趣的评论,他经常在其博客上撰写有关Hibernate,JPA,事务管理和连接池的博客 。 Vlad指出,使用Hibernate也可以解决此问题,我们很快将对此进行研究。

Oracle DATE有什么问题?

上一篇文章中提出的问题涉及以下事实:查询在Oracle DATE列上使用过滤器:

// execute_at is of type DATE and there's an index
PreparedStatement stmt = connection.prepareStatement("SELECT * " + "FROM rentals " +"WHERE rental_date > ? AND rental_date < ?");

…,我们使用java.sql.Timestamp作为绑定值:

stmt.setTimestamp(1, start);
stmt.setTimestamp(2, end);

…那么,即使我们应该进行常规的INDEX RANGE SCAN,执行计划对FULL TABLE SCAN还是INDEX FULL SCAN都会变得非常糟糕。

-------------------------------------
| Id  | Operation          | Name   |
-------------------------------------
|   0 | SELECT STATEMENT   |        |
|*  1 |  FILTER            |        |
|*  2 |   TABLE ACCESS FULL| RENTAL |
-------------------------------------Predicate Information (identified by operation id):
---------------------------------------------------1 - filter(:1<=:2)2 - filter((INTERNAL_FUNCTION("RENTAL_DATE")>=:1 AND INTERNAL_FUNCTION("RENTAL_DATE")<=:2))

这是因为通过此INTERNAL_FUNCTION()将数据库列从Oracle DATE扩展到Oracle TIMESTAMP ,而不是将java.sql.Timestamp值截断为Oracle DATE

有关问题本身的更多详细信息,请参见上一篇文章。

使用Hibernate防止此INTERNAL_FUNCTION()

可以使用org.hibernate.usertype.UserType通过Hibernate的专有API进行修复。

假设我们具有以下实体:

@Entity
public class Rental {@Id@Column(name = "rental_id")public Long rentalId;@Column(name = "rental_date")public Timestamp rentalDate;
}

现在,让我们在这里运行此查询(例如,我使用的是Hibernate API,而不是JPA):

List<Rental> rentals =
session.createQuery("from Rental r where r.rentalDate between :from and :to").setParameter("from", Timestamp.valueOf("2000-01-01 00:00:00.0")).setParameter("to", Timestamp.valueOf("2000-10-01 00:00:00.0")).list();

我们现在得到的执行计划再次效率低下:

-------------------------------------
| Id  | Operation          | Name   |
-------------------------------------
|   0 | SELECT STATEMENT   |        |
|*  1 |  FILTER            |        |
|*  2 |   TABLE ACCESS FULL| RENTAL |
-------------------------------------Predicate Information (identified by operation id):
---------------------------------------------------1 - filter(:1<=:2)2 - filter((INTERNAL_FUNCTION("RENTAL0_"."RENTAL_DATE")>=:1 AND INTERNAL_FUNCTION("RENTAL0_"."RENTAL_DATE")<=:2))

解决方案是将此@Type批注添加到所有相关列中…

@Entity
@TypeDefs(value = @TypeDef(name = "oracle_date", typeClass = OracleDate.class)
)
public class Rental {@Id@Column(name = "rental_id")public Long rentalId;@Column(name = "rental_date")@Type(type = "oracle_date")public Timestamp rentalDate;
}

并注册以下简化的UserType

import java.io.Serializable;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Timestamp;
import java.sql.Types;
import java.util.Objects;import oracle.sql.DATE;import org.hibernate.engine.spi.SessionImplementor;
import org.hibernate.usertype.UserType;public class OracleDate implements UserType {@Overridepublic int[] sqlTypes() {return new int[] { Types.TIMESTAMP };}@Overridepublic Class<?> returnedClass() {return Timestamp.class;}@Overridepublic Object nullSafeGet(ResultSet rs, String[] names, SessionImplementor session, Object owner)throws SQLException {return rs.getTimestamp(names[0]);}@Overridepublic void nullSafeSet(PreparedStatement st, Object value, int index, SessionImplementor session)throws SQLException {// The magic is here: oracle.sql.DATE!st.setObject(index, new DATE(value));}// The other method implementations are omitted
}

这将起作用,因为使用供应商特定的oracle.sql.DATE类型将对您的执行计划产生与在SQL语句中显式强制转换绑定变量相同的效果,如上一篇文章 CAST(? AS DATE) 。 现在,执行计划是所需的计划:

------------------------------------------------------
| Id  | Operation                    | Name          |
------------------------------------------------------
|   0 | SELECT STATEMENT             |               |
|*  1 |  FILTER                      |               |
|   2 |   TABLE ACCESS BY INDEX ROWID| RENTAL        |
|*  3 |    INDEX RANGE SCAN          | IDX_RENTAL_UQ |
------------------------------------------------------Predicate Information (identified by operation id):
---------------------------------------------------1 - filter(:1<=:2)3 - access("RENTAL0_"."RENTAL_DATE">=:1 AND "RENTAL0_"."RENTAL_DATE"<=:2)

如果您想重现此问题,只需通过JPA / Hibernate使用java.sql.Timestamp绑定值查询任何Oracle DATE列, 并按照此处所示获取执行计划 。

不要忘记刷新共享池和缓冲区高速缓存以在两次执行之间强制执行新计划的计算,因为每次生成的SQL都是相同的。

我可以使用JPA 2.1吗?

乍一看,看起来JPA 2.1中的新转换器功能( 就像jOOQ的转换器功能一样 )应该可以解决问题。 我们应该能够写:

import java.sql.Timestamp;import javax.persistence.AttributeConverter;
import javax.persistence.Converter;import oracle.sql.DATE;@Converter
public class OracleDateConverter 
implements AttributeConverter<Timestamp, DATE>{@Overridepublic DATE convertToDatabaseColumn(Timestamp attribute) {return attribute == null ? null : new DATE(attribute);}@Overridepublic Timestamp convertToEntityAttribute(DATE dbData) {return dbData == null ? null : dbData.timestampValue();}
}

然后可以将此转换器与我们的实体一起使用:

import java.sql.Timestamp;import javax.persistence.Column;
import javax.persistence.Convert;
import javax.persistence.Entity;
import javax.persistence.Id;@Entity
public class Rental {@Id@Column(name = "rental_id")public Long rentalId;@Column(name = "rental_date")@Convert(converter = OracleDateConverter.class)public Timestamp rentalDate;
}

但是不幸的是,这并不是开箱即用的,因为Hibernate 4.3.7会认为您将要绑定VARBINARY类型的变量:

// From org.hibernate.type.descriptor.sql.SqlTypeDescriptorRegistrypublic <X> ValueBinder<X> getBinder(JavaTypeDescriptor<X> javaTypeDescriptor) {if ( Serializable.class.isAssignableFrom( javaTypeDescriptor.getJavaTypeClass() ) ) {return VarbinaryTypeDescriptor.INSTANCE.getBinder( javaTypeDescriptor );}return new BasicBinder<X>( javaTypeDescriptor, this ) {@Overrideprotected void doBind(PreparedStatement st, X value, int index, WrapperOptions options)throws SQLException {st.setObject( index, value, jdbcTypeCode );}};}

当然,我们可能可以通过某种方式调整此SqlTypeDescriptorRegistry来创建自己的“绑定程序”,但随后我们将返回特定于Hibernate的API。 这个特定的实现可能是在Hibernate端的一个“ bug”,已在此处注册以作记录:

https://hibernate.atlassian.net/browse/HHH-9553

结论

即使抽象被JCP视为“标准”,抽象在各个级别上都是泄漏的。 标准通常是事后证明行业实际标准的一种手段(当然要涉及一些政治因素)。 我们不要忘记,Hibernate并不是从一个标准开始的,而是在14年前彻底改变了标准的J2EE人士对持久性的思考方式。

在这种情况下,我们有:

  • Oracle SQL的实际实现
  • SQL标准,它指定的DATE与Oracle完全不同
  • ojdbc,它扩展了JDBC以允许访问Oracle功能
  • JDBC,在时间类型方面遵循SQL标准
  • Hibernate,它提供专有的API,以便在绑定变量时访问Oracle SQL和ojdbc功能
  • JPA,它在时间类型方面再次遵循SQL标准和JDBC
  • 您的实体模型

如您所见,实际的实现(Oracle SQL)通过Hibernate的UserType或JPA的Converter泄漏到您自己的实体模型中。 从那时起,它将有望与您的应用程序隔离开来(直到不会),使您无需理会这个讨厌的Oracle SQL详细信息。

无论如何,如果您想解决实际的客户问题(即即将出现的重大性能问题),那么您将需要使用Oracle SQL,ojdbc和Hibernate的特定于供应商的API –而不是假装该SQL ,JDBC和JPA标准是最基本的要求。

但这可能没关系。 对于大多数项目,最终的实现锁定是完全可以接受的。

翻译自: https://www.javacodegeeks.com/2015/01/leaky-abstractions-or-how-to-bind-oracle-date-correctly-with-hibernate.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/361134.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Silverlight学习笔记(九)-----RenderTransform特效【五种基本变换】及【矩阵变换MatrixTransform】...

RenderTransform特效&#xff1a; 变形&#xff08;RenderTransform&#xff09;类是为了达到直接去改变某个Silverlight对象的形状&#xff08;比如缩放、旋转一个元素&#xff09;的目的而设计的&#xff0c;RenderTransform包含的变形属性成员就是专门用来改变Silverlight对…

CSS布局的三个关键属性:float、position、display

最近在出差&#xff0c;就我一个在这里。客户要做几个页面&#xff0c;涉及到了页面的布局问题&#xff0c;没办法自己得做了。然后就遇到了一些问题。页面不论怎么都不能按照设想的布局。 以前也没有做过网页布局方面的工作。上网上找类似的例子&#xff0c;看的是一头雾水。…

快速检查REST API是否有效的方法-从清单文件中获取详细信息

在某些情况下&#xff0c;您可能想快速验证部署&#xff0c;开发&#xff0c;测试或生产环境中的REST API是否完全可以访问。 一种常见的实现方法是构建通用资源&#xff0c;该资源可提供例如已部署API的版本。 您可以手动触发对此资源的请求&#xff0c;或者甚至更好的是&…

opengl 区域填充之种子填充线扫描法

青岚影视 www.qldyy.net 在事先画好的一个区域内&#xff0c;使用区域填充之种子填充线扫描法将其填充完毕。同时带有鼠标和键盘事件&#xff0c;并添加了菜单。对于存在的改变窗口大小程序会出现问题还没有很好的解决&#xff0c;只是仅仅将窗口大小固定住了&#xff0c;不能随…

React 篇 Search Bar and content Table

我们要构建一个模块&#xff0c;其中包含一个内容显示的表格&#xff0c;然后上面有一个提供Search的栏位&#xff0c;并对Search中输入栏进行监听&#xff0c;当有改变的时候&#xff0c;触发Search然后对内容表中的内容进行过滤。 Demo Link:http://czrmodel.mybluemix.net/…

PrimeFaces:在动态生成的对话框中打开外部页面

我已经在即将出版的PrimeFaces Cookbook 2版中写了一篇关于食谱的博客。 在这篇文章中&#xff0c;我想发表第二篇关于一个名为Dialog Framework的小型框架的文章。 我个人喜欢它&#xff0c;因为我记得我为使用Struts框架付出同样的努力而付出的代价。 当您想将外部页面加载到…

ctags: 提示错误ctags: unrecognized option '--format=2'

ctags&#xff1a; 提示错误ctags: unrecognized option --format2 原来的 taglist 插件用的好好的&#xff0c;自从安装了 emacs 之后&#xff0c;每次触发 taglist 都提示以下错误&#xff1a; Taglist: Failed to generate tags for ....(a file) ctags: unrecognized optio…

vue-router之 beforeRouteEnter

beforeRouteEnter在每次路由切换都执行 ,而项目优化后,切换路由mounted只在最开始执行一次beforeRouteEnter的具体用法可参考官方文档 https://cn.vuejs.org/v2/guide/migration-vue-router.html#activate-替换 需要注意的是&#xff1a;在这期间路由跳转携带的数据发生改变会影…

突破极限–如何将AeroGear Unified Push用于Java EE和Node.js

在2014年底的AeroGear队宣布红帽的JBoss统一推送服务器的可用性xPaaS 。 让我们仔细看看&#xff01; 总览 统一推送服务器允许开发人员将本地推送消息发送到Apple的推送通知服务&#xff08;APNS&#xff09;和Google的云消息传递&#xff08;GCM&#xff09;。 它具有一个内…

sqlserver 中事务与错误机制的处理

成功返回1并提交事务,错误返回0并回滚事务 BEGIN try begin tran --其中userName为varchar类型,数据库中该字段存在不为int类型的数据,必然导致错误 update WebOA_User_t SET UserNameUserName where UserName1 select 1 commit tran end try begin catch select 0 r…

js 获取json数组里面数组的长度

作为一个前端页面开发者第一次处理json数据&#xff0c;遇到了‘js 获取json数组里面数组的长度’&#xff1f;竟然不知道 json没有.length属性&#xff08;真是要嘲讽下自己&#xff09;&#xff0c;少壮不努力老大徒伤悲啊&#xff01;以前都是去寻求男朋友帮助&#xff0c;但…

针对WildFly和EAP运行Java Mission Control和Flight Recorder

Java Mission Control &#xff08;JMC&#xff09;使您可以监视和管理Java应用程序&#xff0c;而无需引入通常与这些类型的工具相关的性能开销。 它使用为正常的JVM动态优化而收集的数据&#xff0c;从而形成了一种非常轻量级的方法来观察和分析应用程序代码中的问题。 JMC由…

C#堆栈和堆的讲解

OS和CLR通常将用于容纳数据的内存划分为两个独立的区域&#xff0c;每个区域都采用截然不同的方式来管理&#xff1a;堆栈&#xff08;Stack&#xff09;和堆&#xff08;heap&#xff09;。&#xff08;1&#xff09; 调用一个方法时&#xff0c;它的参数以及它的局部变…

jQuery中的常用内容总结(一)

jQuery中的常用内容总结(一) 前言 不好意思(✿◠‿◠)&#xff0c;由于回家看病以及处理一些其它事情耽搁了&#xff0c;不然这篇博客本该上上周或者上周写的&#xff1b;同时闲谈几句&#xff1a;在这里建议各位开发的童鞋&#xff0c;如果有疾病尽快治疗&#xff0c;不要拖&a…

关于Firefox在Win8下界面显示错乱的解决方法

最近又把Windows8安装上&#xff0c;由于平常做前端开发用的最多的还是Firefox&#xff0c;因此在家的机器上也安装上Firefox&#xff0c;但使用中发现Firefox界面老是错误&#xff0c;特别是那种乱套的颜色&#xff0c;不得其解&#xff0c;查看设置发现默认启用了硬件加速&am…

react ant design路由配置

最初的时候&#xff0c;只使用了antd中的menu&#xff0c;header和footer都是自己写的组件&#xff0c;在写路由时&#xff0c;总是报如下错误&#xff1a; 相关的路由配置如下&#xff1a; 在网上查的说是组件未暴露出去或者是return 这一行必须有个括号或者标签&#xff0c;我…

线程魔术技巧:Java线程可以做的5件事

Java线程最鲜为人知的事实和用例是什么&#xff1f; 有些人喜欢爬山&#xff0c;有些人喜欢跳伞。 我&#xff0c;我喜欢Java。 我喜欢它的一件事是&#xff0c;您永不停止学习。 您每天使用的工具通常可以向您展示全新的方面&#xff0c;以及您还没有机会看到的方法和有趣的用…

强制删除tfs未迁入项的两个方法。

方法1&#xff1a; 打开Vs2008的命令提示&#xff1a; 查看用户的工作区&#xff1a; 输入&#xff1a; Tf workspaces /owner:所有者或* /server:服务器名称或IP ex: tf workspaces /owner:stcct(所有者) /server:203.156.1.100(Ip地址) 删除用户的未迁入项&#xff1a; 输…

VUE 全选

<div id"vue_det"> <p>全选:</p> <input type"checkbox" id"checkbox" v-model"checked" change"changAllChecked"/> <label for"checkbox"> {{checked}} <…

RabbitMQ –使用Spring集成Java DSL串行处理消息

如果您曾经需要使用RabbitMQ来串行处理消息&#xff0c;并且有一群监听器来处理消息&#xff0c;那么我所看到的最好方法是在监听器上使用“独占消费者”标志&#xff0c;每个监听器上有1个线程来处理消息。 专用使用者标志可确保只有1个使用者可以从特定队列中读取消息&#…