Как сохранить БОЛЬШИЕ BLOB-объекты (> 100 МБ) в Oracle с помощью Hibernate

Я изо всех сил пытаюсь найти способ вставлять БОЛЬШИЕ изображения (> 100 МБ, в основном в формате TIFF) в мою базу данных Oracle, используя столбцы BLOB.

Я тщательно искал в Интернете и даже в StackOverflow, но не смог найти ответа на эту проблему.
Прежде всего, проблема ... затем короткий раздел по соответствующему коду (классы / конфигурация java), наконец, третий раздел , где я показываю тест junit, который я написал для проверки устойчивости изображения (я получить сообщение об ошибке во время выполнения теста junit)

Изменить: я добавил раздел в конце вопроса, где я описываю некоторые тесты и анализ с помощью JConsole

Проблема

Я получаю 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)

Код (объекты домена, классы репозитория, конфигурация)

Вот стек технологий i Использую (от БД до уровня бизнес-логики). Я использую JDK6.

  • Oracle Database 10g Enterprise Edition Release 10.2.0.4.0 - Prod
  • ojdbc6.jar (для выпуска 11.2.0.3)
  • Hibernate 4.0.1 Final
  • Spring 3.1.GA RELEASE

I ' Два класса предметной области, отображаемые по принципу «один ко многим». DocumentVersion имеет много DocumentData , каждый из которых может представлять различное двоичное содержимое для одной и той же DocumentVersion .

Соответствующая выдержка из DocumentVersion class:

@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 class:

@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 ):

@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 пытается выделить сразу все байт [] ], представляющий изображение.

Из стека ошибок:

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 был установлен на 512 МБ с использованием параметра -Xmx512MB
  3. Для базы данных Oracle я использовал ojdbc6.jar драйвер
  4. Для базы данных Postgres я использовал драйвер 9.0-801.jdbc3 (через Maven)

Первый тест с файлом размером примерно 150 МБ

В этом первом test, и Oracle, и Postgres прошли тест (это БОЛЬШАЯ новость). Размер файла составляет 1/3 доступного размера кучи JVM. Вот изображение потребления памяти JVM:

Тестирование Oracle, размер кучи 512 МБ, файл 150 МБ Testing Oracle, 512MB Heap Size, 150MB file

Тестирование PostgreSQL, размер кучи 512 МБ,Файл 150 МБ Testing PostgreSQL, 512MB Heap Size, 150MB file

Второй тест с файлом размером примерно 485 МБ

В этом втором тесте только Postgres прошел тест, а Oracle провалил его. Размер файла очень близок к размеру доступного пространства кучи JVM. Вот изображение потребления памяти JVM:

Тестирование Oracle, размер кучи 512 МБ, файл 485 МБ Testing Oracle, 512MB Heap Size, 485MB file

Тестирование PostgreSQL, размер кучи 512 МБ, файл 485 МБ Testing PostgreSQL, 512MB Heap Size, 485MB file

Анализ тестов:

Похоже, что драйвер PostgreSQL обрабатывает память, не превышая определенного порога, в то время как драйвер Oracle ведет себя совсем иначе.

Я не могу честно объяснить, почему драйвер Oracle jdbc приводит меня к ошибке (тот же самый java.lang.OutOfMemoryError: пространство кучи Java ) при использовании файла размером около доступного пространства кучи.

Есть ли кто-нибудь, кто может дать мне больше информации? Большое спасибо за вашу помощь :)

33
задан paoloyx 14 February 2012 в 21:50
поделиться