Как мне использовать кэш второго уровня Hibernate с JPA?
Я реализую механизм персистентности на основе значения атрибута сущности. Весь доступ к БД осуществляется через Hibernate. У меня есть таблица, которая содержит пути для узлов, это очень просто, просто идентификатор и путь (строка). Пути будут маленькими, около нескольких тысяч.
Основная таблица содержит миллионы строк, и вместо того, чтобы повторять пути, я нормализовал пути к их собственной таблице. Вот то, что я хочу при вставке в основную таблицу
1) Проверьте, существует ли путь в таблице путей (запрос через менеджер сущностей, используя значение пути в качестве параметра)
2) если он не существует, вставьте и получите идентификатор (сохраните через менеджер сущностей)
3) поместите id в качестве значения внешнего ключа в строку основной таблицы и вставьте его в основную таблицу.
Это будет происходить тысячи раз для набора доменных объектов, которые соответствуют множеству строк в основной таблице и некоторых других таблицах. Таким образом, описанные выше шаги повторяются с использованием одной транзакции, подобной этой:
EntityTransaction t = entityManager.getTransaction();
t.begin();
//perform steps given above, check, and then persist etc..
t.commit();
Когда я выполняю шаг 2, это приводит к значительному снижению производительности всей операции. Это требует кеширования, потому что через некоторое время эта таблица будет содержать не более 10-20 тыс. Записей с очень редкими новыми вставками. Я пытался сделать это с Hibernate, и потерял почти 2 дня.
Я использую Hibernate 4.1, с аннотациями JPA и ECache. Я попытался включить кэширование запросов, даже используя один и тот же объект запроса во всех вставках, как показано ниже:
Query call = entityManager.createQuery("select pt from NodePath pt " +
"where pt.path = :pathStr)");
call.setHint("org.hibernate.cacheable", true);
call.setParameter("pathStr", pPath);
List<NodePath> paths = call.getResultList();
if(paths.size() > 1)
throw new Exception("path table should have unique paths");
else if (paths.size() == 1){
NodePath path = paths.get(0);
return path.getId();
}
else {//paths null or has zero size
NodePath newPath = new NodePath();
newPath.setPath(pPath);
entityManager.persist(newPath);
return newPath.getId();
}
Сущность NodePath аннотируется следующим образом:
@Entity
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
@Table(name = "node_path", schema = "public")
public class NodePath implements java.io.Serializable {
Насколько я вижу из статистики, кеш запросов используется, но об использовании кеша второго уровня не сообщается:
queries executed to database=1
query cache puts=1
query cache hits=689
query cache misses=1
....
second level cache puts=0
second level cache hits=0
second level cache misses=0
entities loaded=1
....
Простая рукописная хеш-таблица в виде кэша работает, как и ожидалось, значительно сокращая общее время. Я предполагаю, что не могу вызвать кеширование Hibernate из-за характера моих операций.
Как мне использовать кэш второго уровня hibernate с этой настройкой? Для записи, это мой постоянный XML:
http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd "version =" 2.0 ">
<provider>org.hibernate.ejb.HibernatePersistence</provider>
<class>...</class>
<exclude-unlisted-classes>true</exclude-unlisted-classes>
<shared-cache-mode>ENABLE_SELECTIVE</shared-cache-mode>
<properties>
<property name="hibernate.connection.driver_class" value="org.postgresql.Driver" />
<property name="hibernate.connection.password" value="zyx" />
<property name="hibernate.connection.url" value="jdbc:postgresql://192.168.0.194:5432/testdbforml" />
<property name="hibernate.connection.username" value="postgres"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.PostgreSQLDialect"/>
<property name="hibernate.search.autoregister_listeners" value="false"/>
<property name="hibernate.jdbc.batch_size" value="200"/>
<property name="hibernate.connection.autocommit" value="false"/>
<property name="hibernate.generate_statistics" value="true"/>
<property name="hibernate.cache.use_structured_entries" value="true"/>
<property name="hibernate.cache.use_second_level_cache" value="true"/>
<property name="hibernate.cache.use_query_cache" value="true"/>
<property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.ehcache.SingletonEhCacheRegionFactory"/>
</properties>
1 ответ
Хорошо, я нашел это. Моя проблема заключалась в том, что кэшированный запрос хранил в кэше только идентификаторы результатов запроса и (вероятно) возвращался к базе данных, чтобы получить действительные значения, а не получать их из кэша второго уровня.
Проблема, конечно, в том, что запрос не помещал эти значения в кэш второго уровня, поскольку они не были выбраны по первичному идентификатору. Поэтому решение состоит в том, чтобы использовать метод, который будет помещать значения в кэш второго уровня, а в hibernate 4.1 мне удалось сделать это с помощью естественного идентификатора. Вот функция, которая либо вставляет, либо возвращает значение из кэша, на случай, если это поможет кому-либо еще:
private UUID persistPath(String pPath) throws Exception{
org.hibernate.Session session = (Session) entityManager.getDelegate();
NodePath np = (NodePath) session.byNaturalId(NodePath.class).using("path", pPath).load();
if(np != null)
return np.getId();
else {//no such path entry, so let's create one
NodePath newPath = new NodePath();
newPath.setPath(pPath);
entityManager.persist(newPath);
return newPath.getId();
}
}