Я изо всех сил пытаюсь найти способ вставить БОЛЬШИЕ изображения (> 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)
Код (объекты домена, классы репозитория, конфигурация)
Вот стек технологий, которые я использую (от БД до уровня бизнес-логики). Я использую JDK6.
- Oracle Database 10g Enterprise Edition, выпуск 10.2.0.4.0 - Версия
- ojdbc6.jar (для выпуска 11.2.0.3)
- Hibernate 4.0.1 Final
- Spring 3.1.GA RELEASE
У меня есть два класса предметной области, сопоставленные по принципу «один ко многим». A DocumentVersion
имеет множество 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. Изображения записываются в базу данных.
Отлаживая мой код, я смог обнаружить, что драйверы jdbc PostgreSQL записывают в базу данных с использованием буферизованного потока вывода ... в то время как драйвер 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.
Тестовая настройка:
- Тест проводился с использованием теста JUnit, описанного в предыдущем разделе.
- Размер кучи JVM был установлен на 512 МБ с использованием параметра -Xmx512MB
- Для базы данных Oracle я использовал ojdbc6.jar драйвер
- Для базы данных Postgres я использовал 9.0-801.jdbc3 драйвер (через Maven)
Первый тест с размером файла около 150 МБ
В этом первом тесте Oracle и Postgres прошли тест (это БОЛЬШИЕ новости).
Размер файла составляет 1/3 доступного размера кучи JVM.
Вот картина потребления памяти JVM:
Тестирование Oracle, размер кучи 512 МБ, файл 150 МБ
Тестирование PostgreSQL, размер кучи 512 МБ, файл 150 МБ
Второй тест, с файлом около 485 МБ
Во втором тесте только Postgres прошел тест, а Oracle не прошел .
Размер файла очень близок к размеру доступного пространства кучи JVM.
Вот картина потребления памяти JVM:
Тестирование Oracle, размер кучи 512 МБ, файл 485 МБ
Тестирование PostgreSQL, размер кучи 512 МБ, файл 485 МБ
Анализ тестов:
Похоже, что драйвер PostgreSQL обрабатывает память без превышения определенного порога, в то время как драйвер Oracle ведет себя совсем по-другому.
Я не могу честно объяснить, почему драйвер Oracle jdbc приводит меня к ошибке (то же самое java.lang.OutOfMemoryError: Java heap space
) при использовании файла размером около доступного пространства кучи.
Есть ли кто-нибудь, кто может дать мне больше идей?
Большое спасибо за помощь:)