hibernate连接泄露_泄漏抽象,或如何正确地与Hibernate绑定Oracle DATE
hibernate連接泄露
我們最近發(fā)布了一篇文章,介紹如何在SQL / JDBC和jOOQ中正確綁定Oracle DATE類型 。 這篇文章在Reddit上頗受關(guān)注, Vlad Mihalcea對此發(fā)表了有趣的評論,他經(jīng)常在他的博客上撰寫有關(guān)Hibernate,JPA,事務(wù)管理和連接池的博客 。 Vlad指出,使用Hibernate也可以解決此問題,我們很快將對此進(jìn)行研究。
Oracle DATE有什么問題?
上一篇文章中提出的問題涉及以下事實(shí):查詢在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);…那么,即使我們應(yīng)該進(jìn)行常規(guī)的INDEX RANGE SCAN,執(zhí)行計劃對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))這是因?yàn)橥ㄟ^此INTERNAL_FUNCTION()將數(shù)據(jù)庫列從Oracle DATE擴(kuò)展到Oracle TIMESTAMP ,而不是將java.sql.Timestamp值截斷為Oracle DATE 。
有關(guān)問題本身的更多詳細(xì)信息,請參見上一篇文章。
使用Hibernate防止此INTERNAL_FUNCTION()
您可以使用org.hibernate.usertype.UserType通過Hibernate的專有API進(jìn)行修復(fù)。
假設(shè)我們具有以下實(shí)體:
@Entity public class Rental {@Id@Column(name = "rental_id")public Long rentalId;@Column(name = "rental_date")public Timestamp rentalDate; }現(xiàn)在,讓我們在這里運(yùn)行此查詢(例如,我使用的是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();我們現(xiàn)在得到的執(zhí)行計劃再次效率低下:
------------------------------------- | 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批注添加到所有相關(guān)列中…
@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 }這將起作用,因?yàn)槭褂霉?yīng)商特定的oracle.sql.DATE類型將對您的執(zhí)行計劃產(chǎn)生與在SQL語句中顯式強(qiáng)制轉(zhuǎn)換綁定變量相同的效果,如上一篇文章 CAST(? AS DATE) 。 現(xiàn)在,執(zhí)行計劃是所需的計劃:
------------------------------------------------------ | 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)如果要重現(xiàn)此問題,只需通過JPA / Hibernate用java.sql.Timestamp綁定值查詢?nèi)魏蜲racle DATE列, 并按照此處所示獲取執(zhí)行計劃 。
不要忘記刷新共享池和緩沖區(qū)高速緩存以在兩次執(zhí)行之間強(qiáng)制執(zhí)行新計劃的計算,因?yàn)槊看紊蒘QL都是相同的。
我可以使用JPA 2.1嗎?
乍一看,看起來JPA 2.1中的新轉(zhuǎn)換器功能( 就像jOOQ的轉(zhuǎn)換器功能一樣 )應(yīng)該可以解決問題。 我們應(yīng)該能夠?qū)?#xff1a;
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();} }然后可以將此轉(zhuǎn)換器與我們的實(shí)體一起使用:
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; }但是不幸的是,這不是開箱即用的,因?yàn)镠ibernate 4.3.7會認(rèn)為您將要綁定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 );}};}當(dāng)然,我們可能可以通過某種方式調(diào)整此SqlTypeDescriptorRegistry來創(chuàng)建自己的“綁定程序”,但隨后我們將返回特定于Hibernate的API。 這個特定的實(shí)現(xiàn)可能是Hibernate端的“錯誤”,已在此處注冊以作記錄:
https://hibernate.atlassian.net/browse/HHH-9553
結(jié)論
即使在JCP視為“標(biāo)準(zhǔn)”的情況下,抽象在各個級別上都是泄漏的。 標(biāo)準(zhǔn)通常是事后證明行業(yè)實(shí)際標(biāo)準(zhǔn)的一種手段(當(dāng)然要涉及一些政治因素)。 我們不要忘記,Hibernate并不是從一個標(biāo)準(zhǔn)開始的,而是在14年前徹底改變了標(biāo)準(zhǔn)的J2EE人士對持久性的思考方式。
在這種情況下,我們有:
- Oracle SQL的實(shí)際實(shí)現(xiàn)
- SQL標(biāo)準(zhǔn),它指定的DATE與Oracle完全不同
- ojdbc,它擴(kuò)展了JDBC以允許訪問Oracle功能
- JDBC,在時間類型方面遵循SQL標(biāo)準(zhǔn)
- Hibernate,它提供專有的API,以便在綁定變量時訪問Oracle SQL和ojdbc功能
- JPA,它在時間類型方面再次遵循SQL標(biāo)準(zhǔn)和JDBC
- 您的實(shí)體模型
如您所見,實(shí)際的實(shí)現(xiàn)(Oracle SQL)通過Hibernate的UserType或JPA的Converter泄漏到您自己的實(shí)體模型中。 從那時起,它將有望與您的應(yīng)用程序隔離開(直到不會),使您無需理會這個討厭的Oracle SQL詳細(xì)信息。
無論如何,如果您想解決實(shí)際的客戶問題(即即將出現(xiàn)的重大性能問題),那么您將需要使用Oracle SQL,ojdbc和Hibernate的特定于供應(yīng)商的API-而不是假裝該SQL ,JDBC和JPA標(biāo)準(zhǔn)是底線。
但這可能沒關(guān)系。 對于大多數(shù)項(xiàng)目,最終的實(shí)現(xiàn)鎖定是完全可以接受的。
翻譯自: https://www.javacodegeeks.com/2015/01/leaky-abstractions-or-how-to-bind-oracle-date-correctly-with-hibernate.html
hibernate連接泄露
總結(jié)
以上是生活随笔為你收集整理的hibernate连接泄露_泄漏抽象,或如何正确地与Hibernate绑定Oracle DATE的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: rest服务swagger_在Java
- 下一篇: jboss fuse 教程_IDC关于使