簡體   English   中英

如何使用Hibernate在Oracle中保留大型BLOB(> 100MB)

[英]How to persist LARGE BLOBs (>100MB) in Oracle using Hibernate

我正在努力找到一種方法,使用BLOB列在我的Oracle數據庫中插入大圖像(> 100MB,主要是TIFF格式)。

我已經在網絡上進行了徹底搜索,甚至在StackOverflow中也沒有找到這個問題的答案。
首先,問題......然后是相關代碼(java類/配置)的一小部分 ,最后是第三部分 ,其中我展示了我寫的測試圖像持久性的junit測試(我在junit期間收到錯誤)測試執行)

編輯:我在問題的最后添加了一個部分,在那里我用JConsole描述了一些測試和分析

問題

我收到一個java.lang.OutOfMemoryError: Java heap space使用hibernate的java.lang.OutOfMemoryError: Java heap space錯誤,並試圖保留非常大的圖像/文檔:

java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:2786)
at java.io.ByteArrayOutputStream.toByteArray(ByteArrayOutputStream.java:133)
at org.hibernate.type.descriptor.java.DataHelper.extractBytes(DataHelper.java:190)
at org.hibernate.type.descriptor.java.BlobTypeDescriptor.unwrap(BlobTypeDescriptor.java:123)
at org.hibernate.type.descriptor.java.BlobTypeDescriptor.unwrap(BlobTypeDescriptor.java:47)
at org.hibernate.type.descriptor.sql.BlobTypeDescriptor$4$1.doBind(BlobTypeDescriptor.java:101)
at org.hibernate.type.descriptor.sql.BasicBinder.bind(BasicBinder.java:91)
at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:283)
at org.hibernate.type.AbstractStandardBasicType.nullSafeSet(AbstractStandardBasicType.java:278)
at org.hibernate.type.AbstractSingleColumnStandardBasicType.nullSafeSet(AbstractSingleColumnStandardBasicType.java:89)
at org.hibernate.persister.entity.AbstractEntityPersister.dehydrate(AbstractEntityPersister.java:2184)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2430)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2874)
at org.hibernate.action.EntityInsertAction.execute(EntityInsertAction.java:79)
at org.hibernate.engine.ActionQueue.execute(ActionQueue.java:273)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:265)
at org.hibernate.engine.ActionQueue.executeActions(ActionQueue.java:184)
at org.hibernate.event.def.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:321)
at org.hibernate.event.def.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:51)
at org.hibernate.impl.SessionImpl.flush(SessionImpl.java:1216)
at it.paoloyx.blobcrud.manager.DocumentManagerTest.testInsertDocumentVersion(DocumentManagerTest.java:929)

代碼(域對象,存儲庫類,配置)

這是我正在使用的一堆技術(從數據庫到業務邏輯層)。 我使用JDK6。

  • Oracle數據庫10g企業版10.2.0.4.0版 - 產品介紹
  • ojdbc6.jar(適用於11.2.0.3版本)
  • Hibernate 4.0.1 Final
  • 春季3.1.GA發布

我有兩個域類,以一對多的方式映射。 DocumentVersion有許多DocumentData ,每個DocumentData可以表示同一DocumentVersion不同二進制內容。

DocumentVersion類的相關摘錄:

@Entity
@Table(name = "DOCUMENT_VERSION")
public class DocumentVersion implements Serializable {

private static final long serialVersionUID = 1L;
private Long id;
private Set<DocumentData> otherDocumentContents = new HashSet<DocumentData>(0);


@Id
@GeneratedValue(strategy = GenerationType.TABLE)
@Column(name = "DOV_ID", nullable = false)
public Long getId() {
    return id;
}

@OneToMany
@Cascade({ CascadeType.SAVE_UPDATE })
@JoinColumn(name = "DOD_DOCUMENT_VERSION")
public Set<DocumentData> getOtherDocumentContents() {
    return otherDocumentContents;
}

DocumentData類的相關摘錄:

@Entity
@Table(name = "DOCUMENT_DATA")
public class DocumentData {

private Long id;

/**
 * The binary content (java.sql.Blob)
 */
private Blob binaryContent;

@Id
@GeneratedValue(strategy = GenerationType.TABLE)
@Column(name = "DOD_ID", nullable = false)
public Long getId() {
    return id;
}

@Lob
@Column(name = "DOD_CONTENT")
public Blob getBinaryContent() {
    return binaryContent;
}

這是我的Spring和Hibernate配置主要參數:

<bean id="sessionFactory"
    class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
    <property name="dataSource" ref="dataSource" />
    <property name="packagesToScan" value="it.paoloyx.blobcrud.model" />
    <property name="hibernateProperties">
        <props>
            <prop key="hibernate.dialect">org.hibernate.dialect.Oracle10gDialect</prop>
            <prop key="hibernate.hbm2ddl.auto">create</prop>
            <prop key="hibernate.jdbc.batch_size">0</prop>
            <prop key="hibernate.jdbc.use_streams_for_binary">true</prop>
        </props>
    </property>
</bean>
<bean class="org.springframework.orm.hibernate4.HibernateTransactionManager"
    id="transactionManager">
    <property name="sessionFactory" ref="sessionFactory" />
</bean>
<tx:annotation-driven transaction-manager="transactionManager" />

我的數據源定義:

<bean class="org.apache.commons.dbcp.BasicDataSource"
    destroy-method="close" id="dataSource">
    <property name="driverClassName" value="${database.driverClassName}" />
    <property name="url" value="${database.url}" />
    <property name="username" value="${database.username}" />
    <property name="password" value="${database.password}" />
    <property name="testOnBorrow" value="true" />
    <property name="testOnReturn" value="true" />
    <property name="testWhileIdle" value="true" />
    <property name="timeBetweenEvictionRunsMillis" value="1800000" />
    <property name="numTestsPerEvictionRun" value="3" />
    <property name="minEvictableIdleTimeMillis" value="1800000" />
    <property name="validationQuery" value="${database.validationQuery}" />
</bean>

屬性來自這里:

database.driverClassName=oracle.jdbc.OracleDriver
database.url=jdbc:oracle:thin:@localhost:1521:devdb
database.username=blobcrud
database.password=blobcrud
database.validationQuery=SELECT 1 from dual

我有一個服務類,它委托給一個存儲庫類:

@Transactional
public class DocumentManagerImpl implements DocumentManager {

DocumentVersionDao documentVersionDao;

public void setDocumentVersionDao(DocumentVersionDao documentVersionDao) {
    this.documentVersionDao = documentVersionDao;
}

現在來自存儲庫類的相關摘錄:

public class DocumentVersionDaoHibernate implements DocumentVersionDao {

@Autowired
private SessionFactory sessionFactory;

@Override
public DocumentVersion saveOrUpdate(DocumentVersion record) {
    this.sessionFactory.getCurrentSession().saveOrUpdate(record);
    return record;
}

導致錯誤的JUnit測試

如果我運行以下單元測試我得到上述錯誤( java.lang.OutOfMemoryError: Java heap space ):

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = { "classpath*:META-INF/spring/applicationContext*.xml" })
@Transactional
public class DocumentManagerTest {

@Autowired
protected DocumentVersionDao documentVersionDao;

@Autowired
protected SessionFactory sessionFactory;

@Test
public void testInsertDocumentVersion() throws SQLException {

    // Original mock document content
    DocumentData dod = new DocumentData();
    // image.tiff is approx. 120MB
    File veryBigFile = new File("/Users/paoloyx/Desktop/image.tiff");
    try {
        Session session = this.sessionFactory.getCurrentSession();
        InputStream inStream = FileUtils.openInputStream(veryBigFile);
        Blob blob = Hibernate.getLobCreator(session).createBlob(inStream, veryBigFile.length());
        dod.setBinaryContent(blob);
    } catch (IOException e) {
        e.printStackTrace();
        dod.setBinaryContent(null);
    }

    // Save a document version linked to previous document contents
    DocumentVersion dov = new DocumentVersion();
    dov.getOtherDocumentContents().add(dod);
    documentVersionDao.saveOrUpdate(dov);
    this.sessionFactory.getCurrentSession().flush();

    // Clear session, then try retrieval
    this.sessionFactory.getCurrentSession().clear();
    DocumentVersion dbDov = documentVersionDao.findByPK(insertedId);
    Assert.assertNotNull("Il document version ritornato per l'id " + insertedId + " è nullo", dbDov);
    Assert.assertNotNull("Il document version recuperato non ha associato contenuti aggiuntivi", dbDov.getOtherDocumentContents());
    Assert.assertEquals("Il numero di contenuti secondari non corrisponde con quello salvato", 1, dbDov.getOtherDocumentContents().size());
}

相同的代碼適用於PostreSQL 9安裝。 圖像正在數據庫中寫入。 調試我的代碼,我已經能夠發現PostgreSQL jdbc驅動程序使用緩沖輸出流寫入數據庫....而Oracle OJDBC驅動程序嘗試一次性分配表示圖像的所有byte[]

從錯誤堆棧:

java.lang.OutOfMemoryError: Java heap space
at java.util.Arrays.copyOf(Arrays.java:2786)
at java.io.ByteArrayOutputStream.toByteArray(ByteArrayOutputStream.java:133)

是否由於此行為導致錯誤? 誰能給我一些關於這個問題的見解?

感謝大家。

使用JConsole進行內存測試

感謝收到的關於我的問題的建議,我試圖做一些簡單的測試來顯示我的代碼的內存使用情況,使用兩個不同的jdbc驅動程序,一個用於PostgreSQL,一個用於Oracle。 測試設置:

  1. 測試是使用上一節中描述的JUnit測試進行的。
  2. JVM堆大小已設置為512MB,使用參數-Xmx512MB
  3. 對於Oracle數據庫,我使用了ojdbc6.jar驅動程序
  4. 對於Postgres數據庫,我使用了9.0-801.jdbc3驅動程序 (通過Maven)

首次測試,文件大約150MB

在第一次測試中,Oracle和Postgres都通過了測試(這是大新聞)。 該文件的大小為可用JVM堆大小的1/3。 這里是JVM內存消耗的圖片:

測試Oracle,512MB堆大小,150MB文件 測試Oracle,512MB堆大小,150MB文件

測試PostgreSQL,512MB堆大小,150MB文件 測試PostgreSQL,512MB堆大小,150MB文件

第二次測試,文件大約485MB

在第二次測試中,只有Postgres通過了測試而Oracle 失敗了。 該文件的大小非常接近可用JVM堆空間的大小。 這里是JVM內存消耗的圖片:

測試Oracle,512MB堆大小,485MB文件 測試Oracle,512MB堆大小,485MB文件

測試PostgreSQL,512MB堆大小,485MB文件 測試PostgreSQL,512MB堆大小,485MB文件

分析測試:

看起來PostgreSQL驅動程序在不超過某個閾值的情況下處理內存,而Oracle驅動程序的行為卻截然不同。

我無法誠實地解釋為什么Oracle jdbc驅動程序在使用可用堆空間附近的文件大小時會導致我出錯(同樣的java.lang.OutOfMemoryError: Java heap space )。

有沒有人可以給我更多的見解? 非常感謝你的幫助:)

在嘗試使用“blob”類型進行映射時,我遇到了與您相同的問題。 這是我在hibernate網站上發布的帖子的鏈接: https//forum.hibernate.org/viewtopic.php?p = 2452481#p2452481

Hibernate 3.6.9
Oracle Driver 11.2.0.2.0
Oracle Database 11.2.0.2.0

為了解決這個問題,我使用了具有Blob的自定義UserType的代碼,我的返回類型是java.sql.Blob。

以下是此UserType的關鍵方法實現:

public Object nullSafeGet(ResultSet rs, String[] names, Object owner) throws HibernateException, SQLException {

   Blob blob = rs.getBlob(names[0]);
   if (blob == null)
      return null;

   return blob;
}

public void nullSafeSet(PreparedStatement st, Object value, int index)
     throws HibernateException, SQLException {
   if (value == null) {
      st.setNull(index, sqlTypes()[0]);
   }
   else {
      InputStream in = null;
      OutputStream out = null;
      // oracle.sql.BLOB
      BLOB tempBlob = BLOB.createTemporary(st.getConnection(), true, BLOB.DURATION_SESSION);
      tempBlob.open(BLOB.MODE_READWRITE);
      out = tempBlob.getBinaryOutputStream();
      Blob valueAsBlob = (Blob) value;
      in = valueAsBlob.getBinaryStream();
      StreamUtil.toOutput(in, out);
      out.flush();
      StreamUtil.close(out);
      tempBlob.close();
      st.setBlob(index, tempBlob);
      StreamUtil.close(in);
   }
}

我個人使用Hibernate在Oracle BLOB列中存儲高達200MB的文件,因此我可以保證它可以工作。 所以...

您應該嘗試更新版本的Oracle JDBC驅動程序。 似乎這種使用字節數組而不是流的行為隨着時間的推移而改變了一點點。 並且驅動程序向后兼容。 我不確定,如果這會解決你的問題,但它對我有用。 另外你應該切換到org.hibernate.dialect.Oracle10gDialect - 它退出使用oracle.jdbc.driver包而不是oracle.jdbc - 它也可能有所幫助。

當我遇到與Oracle和Hibernate相同的問題時,我才發現了這個問題。 問題在於Hibernate blob處理。 它似乎根據使用的Dialect將blob復制到內存中。 我猜他們會這樣做,因為某些數據庫/驅動程序需要它。 但對於Oracle來說,似乎並不需要這種行為。

修復非常簡單,只需創建一個包含以下代碼的自定義OracleDialect:

public class Oracle10DialectWithoutInputStreamToInsertBlob extends Oracle10gDialect {
    public boolean useInputStreamToInsertBlob() {
        return false;
    }
}

接下來,您需要配置會話工廠以使用此Dialect。 我已經使用ojdbc6-11.2.0.1.0驅動程序對Oracle 11g進行了測試,並確認這解決了內存消耗問題。

如果你們中的一些人嘗試使用另一個Oracle數據庫和/或使用不同的Oracle驅動程序,我很樂意聽到它是否適合你。 如果它適用於多種配置,我會向Hibernate團隊發送拉取請求。

這不是最佳解決方案,但您可以允許Java使用-Xmx參數來使用更多內存

編輯:您應該嘗試更深入地分析問題,嘗試使用JConsole 它可以幫助您查看內存負載。

即使使用Postgres,您也​​可能會獲得堆大小限制,但不能跨越它,因為加載的驅動程序會占用更少的內存。

使用默認設置,您的heam大小限制大約是物理內存的一半。 試試你可以節省多少blob到postgres。

您是否嘗試在會話工廠中為oracle OracleLobHandler定義LobHandler及其版本?

這是一個例子:

<bean id="sessionFactory" class="org.springframework.orm.hibernate3.annotation.AnnotationSessionFactoryBean">
    <property name="dataSource" ref="oracleDocDataSource"/>
    <property name="annotatedClasses">
        <list>
        ...
        </list>
    </property>
    <property name="lobHandler">
        <bean class="org.springframework.jdbc.support.lob.OracleLobHandler">
            <property name="nativeJdbcExtractor">
                <bean class="org.springframework.jdbc.support.nativejdbc.WebSphereNativeJdbcExtractor"/>
            </property>
        </bean>
    </property>
</bean>

UPDATE

我剛剛意識到演講是關於休眠的4。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM