网站设计的专业流程,免费申请qq邮箱,哪里有网站建设电话,网站备案可以强制撤销吗hibernate连接泄露我们最近发布了一篇文章#xff0c;介绍如何在SQL / JDBC和jOOQ中正确绑定Oracle DATE类型 。 这篇文章在Reddit上颇受关注#xff0c; Vlad Mihalcea对此发表了有趣的评论#xff0c;他经常在他的博客上撰写有关Hibernate#xff0c;JPA#xff0c;事务… hibernate连接泄露 我们最近发布了一篇文章介绍如何在SQL / JDBC和jOOQ中正确绑定Oracle DATE类型 。 这篇文章在Reddit上颇受关注 Vlad Mihalcea对此发表了有趣的评论他经常在他的博客上撰写有关HibernateJPA事务管理和连接池的博客 。 Vlad指出使用Hibernate也可以解决此问题我们很快将对此进行研究。 Oracle DATE有什么问题 上一篇文章中提出的问题涉及以下事实查询在Oracle DATE列上使用过滤器 // execute_at is of type DATE and theres 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 {IdColumn(name rental_id)public Long rentalId;Column(name rental_date)public Timestamp rentalDate;
} 现在让我们在这里运行此查询例如我使用的是Hibernate API而不是JPA ListRental 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 {IdColumn(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 AttributeConverterTimestamp, 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 {IdColumn(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 ValueBinderX getBinder(JavaTypeDescriptorX javaTypeDescriptor) {if ( Serializable.class.isAssignableFrom( javaTypeDescriptor.getJavaTypeClass() ) ) {return VarbinaryTypeDescriptor.INSTANCE.getBinder( javaTypeDescriptor );}return new BasicBinderX( javaTypeDescriptor, this ) {Overrideprotected void doBind(PreparedStatement st, X value, int index, WrapperOptions options)throws SQLException {st.setObject( index, value, jdbcTypeCode );}};} 当然我们可能可以通过某种方式调整此SqlTypeDescriptorRegistry来创建自己的“绑定程序”但随后我们将返回特定于Hibernate的API。 这个特定的实现可能是Hibernate端的“错误”已在此处注册以作记录 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 SQLojdbc和Hibernate的特定于供应商的API-而不是假装该SQL JDBC和JPA标准是底线。 但这可能没关系。 对于大多数项目最终的实现锁定是完全可以接受的。 翻译自: https://www.javacodegeeks.com/2015/01/leaky-abstractions-or-how-to-bind-oracle-date-correctly-with-hibernate.htmlhibernate连接泄露