Статьи

Учебник по уровням спящего режима Hibernate

Одной из распространенных проблем людей, начинающих использовать Hibernate, является производительность. Если у вас нет большого опыта работы с Hibernate, вы обнаружите, насколько быстро ваше приложение замедляется. Если вы включите трассировку sql, вы увидите, сколько запросов отправляется в базу данных, чего можно избежать, не зная знаний Hibernate . В этом посте я собираюсь объяснить, как использовать Hibernate Query Cache, чтобы избежать трафика между вашим приложением и базой данных.

Hibernate предлагает два уровня кэширования:

  • Кеш первого уровня — это кеш сессии. Объекты кэшируются в текущем сеансе, и они остаются активными только до закрытия сеанса.
  • Кэш второго уровня существует, пока фабрика сеансов жива. Имейте в виду, что в случае Hibernate кэш второго уровня не является деревом объектов; экземпляры объекта не кэшируются, вместо этого хранятся значения атрибутов.

После этого краткого введения (настолько краткого я знаю) о кэше Hibernate , давайте посмотрим, что такое Query Cache и как он связан с кэшем второго уровня.

Query Cache отвечает за кэширование комбинации запроса и значений, предоставляемых в качестве параметров в качестве ключа, и списка идентификаторов объектов, возвращаемых при выполнении запроса в качестве значений. Обратите внимание, что для использования Query Cache также требуется кэш второго уровня, потому что когда результат запроса получается из кеша (то есть списка идентификаторов), Hibernate будет загружать объекты, используя кешированные идентификаторы второго уровня.

Подводя итог, и в качестве концептуальной схемы, учитывая следующий запрос: « из страны, где население>: число », после первого выполнения, кеши Hibernate будут содержать следующие вымышленные значения (обратите внимание, что параметр числа установлен в 1000):

L2 Cache
[
id: 1, {name = ‘Spain’, население = 1000,….}
id: 2, {name = ‘Germany’, население = 2000,…}
….
] QueryCache
[{из страны, где население>: число, 1000}, {id: 2}]

Поэтому, прежде чем начать использовать Query Cache , нам нужно настроить кэш второго уровня.
Прежде всего вы должны решить, какой поставщик кэша вы собираетесь использовать. Для этого примера выбран Ehcache , но обратитесь к документации Hibernate для получения полного списка всех поддерживаемых провайдеров.

Чтобы настроить кэш второго уровня, установите следующие свойства Hibernate :

hibernate.cache.provider_class = org.hibernate.cache.EhCacheProvider
hibernate.cache.use_structured_entries = true
hibernate.cache.use_second_level_cache = true

И если вы используете подход аннотации, аннотируйте кэшируемые объекты с помощью:

@Cacheable
@Cache (использование = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)

Обратите внимание, что в этом случае стратегия параллельного использования кэша имеет вид NONSTRICT_READ_WRITE , но в зависимости от поставщика кэша могут использоваться другие стратегии, такие как TRANSACTIONAL, READ_ONLY , … посмотрите раздел кэширования документации Hibernate, чтобы выбрать тот, который лучше соответствует вашим требованиям.

И, наконец, добавьте зависимости Ehcache :

<зависимость>
<идентификатор_группы> net.sf.ehcache </ идентификатор_группы>
<артефакт> EHCache-ядро </ артефакт>
<версия> 2.5.0 </ версия>
</ зависимость>
<зависимость>
<идентификатор_группы> org.hibernate </ идентификатор_группы>
<артефакт> Hibernate-EHCache </ артефакт>
<версия> 3.6.0.Final </ версия>
</ зависимость>

Теперь настроен кеш второго уровня, но не кеш запросов ; во всяком случае, мы недалеко от нашей цели.

Установите для свойства hibernate.cache.use_query_cache значение true .

И для каждого кэшируемого запроса мы должны вызывать метод setCachable при создании запроса:

List <Страна> list = session.createQuery («из Страны, где население> 1000»). SetCacheable (true) .list ();

Чтобы сделать пример более практичным, я загрузил полный пример кэша запросов с помощью Spring Framework. Чтобы ясно увидеть, что кеш запросов работает, я использовал одну публичную базу данных, размещенную на ensembl.org . Проект Ensembl создает базы данных генома для позвоночных и других эукариотических видов и делает эту информацию свободно доступной в Интернете. В этом примере запрос к таблице днк кэшируется.

Прежде всего, конфигурация Hibernate :

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
@Configuration
public class HibernateConfiguration {
 
 @Value("#{dataSource}")
 private DataSource dataSource;
 
 @Bean
 public AnnotationSessionFactoryBean sessionFactoryBean() {
  Properties props = new Properties();
  props.put("hibernate.dialect", EnhancedMySQL5HibernateDialect.class.getName());
  props.put("hibernate.format_sql", "true");
  props.put("hibernate.show_sql", "true");
  props.put("hibernate.cache.provider_class", "org.hibernate.cache.EhCacheProvider");
  props.put("hibernate.cache.use_structured_entries", "true");
  props.put("hibernate.cache.use_query_cache", "true");
  props.put("hibernate.cache.use_second_level_cache", "true");
  props.put("hibernate.hbm2ddl.auto", "validate");
 
  AnnotationSessionFactoryBean bean = new AnnotationSessionFactoryBean();
  bean.setAnnotatedClasses(new Class[]{Dna.class}); 
  bean.setHibernateProperties(props);
  bean.setDataSource(this.dataSource);
  bean.setSchemaUpdate(true);
  return bean;
 }
 
}

Это простая конфигурация Hibernate , использующая свойства, описанные ранее для настройки кэша второго уровня.

Класс сущности — это сущность, представляющая последовательность ДНК .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
@Entity(name="dna")
@Cacheable
@Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
public class Dna {
 
 @Id
 private int seq_region_id;
 
 private String sequence;
 
 public int getSeq_region_id() {
  return seq_region_id;
 }
 
 public void setSeq_region_id(int seq_region_id) {
  this.seq_region_id = seq_region_id;
 }
 
 @Column
 public String getSequence() {
  return sequence;
 }
 
 public void setSequence(String sequence) {
  this.sequence = sequence;
 }
 
}

Чтобы попробовать кеш запросов , мы собираемся реализовать один тест, в котором один и тот же запрос выполняется несколько раз.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
@Autowired
private SessionFactory sessionFactory;
 
@Test
public void fiftyFirstDnaSequenceShouldBeReturnedAndCached() throws Exception {
 for (int i = 0; i < 5; i++) {
  Session session = sessionFactory.openSession();
  session.beginTransaction();
 
  Time elapsedTime = new Time("findDna"+i);
 
  List<Dna> list = session.createQuery(
    "from dna").setFirstResult(0).setMaxResults(50).setCacheable(true).list();
 
 
  session.getTransaction().commit();
  session.close();
  elapsedTime.miliseconds(System.out);
 
  for (Dna dna : list) {
   System.out.println(dna);
  }
 
 }
}

Мы можем видеть, что мы возвращаем первые пятьдесят последовательностей днк , и если вы выполните его, вы увидите, что печатается прошедшее время между созданием запроса и совершением транзакции. Как можно предположить, только первая итерация занимает около 5 секунд, чтобы получить все данные, а остальные только миллисекунды.

Строка foreach перед итерацией запроса выводит идентификатор объекта через консоль. Если вы посмотрите внимательно, ни один из этих идентификаторов не будет повторяться в течение всего выполнения. Этот факт показывает, что в кеше Hibernate сохраняются не объекты, а значения свойств, а сам объект создается каждый раз.

Последнее замечание: помните, что Hibernate по умолчанию не кэширует ассоциации.

Теперь, после написания запроса, подумайте, будет ли он содержать статические данные и будет ли он выполняться часто. Если это так, кеш запросов — ваш друг, чтобы приложения Hibernate работали быстрее.

Скачать код

Справка: руководство по уровням кэша Hibernate от нашего партнера по JCG