Статьи

Подсказка гибернации: сортировка и порядок

Давайте представим еще один совет по производительности в спящем режиме . Вы помните модель предыдущего поста в спящем режиме ? У нас был звездный корабль и офицер, связанный с ассоциацией один ко многим.

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
29
30
31
32
33
34
@Entity
public class Starship {
 
 @Id @GeneratedValue(strategy=GenerationType.SEQUENCE)
 private Long id;
 public Long getId() {return id;}
 protected void setId(Long id) {this.id = id;}
 
 @OneToMany(mappedBy="starship", cascade={CascadeType.ALL})
 private List<Officer> officers = new ArrayList<Officer>();
 public List<Officer> getOfficers() {return Collections.unmodifiableList(officers);}
 protected void setOfficers(List<Officer> officers) {this.officers = officers;}
 public void addOfficer(Officer officer) {
  officer.setStarship(this);
  this.officers.add(officer);
 }
 
        //more code
}
 
@Entity
public class Officer {
 
 @Id @GeneratedValue(strategy=GenerationType.SEQUENCE)
 private Long id;
 public Long getId() {return id;}
 protected void setId(Long id) {this.id = id;}
 
 @ManyToOne private Starship starship;
 public Starship getStarship() {return starship;}
 protected void setStarship(Starship starship) {this.starship = starship;}
 
        //more code
}

Теперь у нас есть следующее требование:
Мы назначим всех офицеров на звездолет в алфавитном порядке.
Для решения этого требования мы можем:

  1. реализация HQL- запроса с предложением order by .
  2. используя сортировку
  3. используя порядок заказа .

Первое решение хорошо с точки зрения производительности, но подразумевает большую работу в качестве разработчиков, потому что мы должны написать запрос, чтобы найти всех офицеров данного звездолета, упорядоченных по имени, а затем создать метод поиска в слое DAO (в случае, если вы используете шаблон DAO ) ,
Давайте рассмотрим второе решение, мы могли бы использовать класс SortedSet в качестве ассоциации и сделать Officer реализуемым Comparable , чтобы у Officer было естественный порядок. Это решение подразумевает меньше работы, чем первое, но требует использования аннотации @Sort hibernate для определения ассоциации. Итак, давайте изменим предыдущую модель, чтобы она соответствовала нашему новому требованию. Обратите внимание, что в спецификации JPA нет эквивалентной аннотации.
Сначала мы собираемся реализовать

01
02
03
04
05
06
07
08
09
10
11
@Entity
public class Officer implements Comparable<Officer>{
 
 
        //getters, setters, equals, ... code
 
 public int compareTo(Officer officer) {
  return this.name.compareTo(officer.getName());
 }
 
}

Сопоставимый интерфейс в классе Officer .

Мы заказываем офицера по имени, просто сравнивая имя поля. Следующим шагом является аннотирование ассоциации с @Sort .

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
@Entity
public class Starship {
 
        //more code
 
 @OneToMany(mappedBy="starship", cascade={CascadeType.ALL})
 @Sort(type=SortType.NATURAL)
 private SortedSet>Officer< officers = new TreeSet>Officer<();
 public SortedSet>Officer< getOfficers() {return Collections.unmodifiableSortedSet(officers);}
 protected void setOfficers(SortedSet>Officer< officers) {this.officers = officers;}
 public void addOfficer(Officer officer) {
  officer.setStarship(this);
  this.officers.add(officer);
 }
}

Обратите внимание, что теперь ассоциация офицеров реализована с использованием SortedSet вместо List . Кроме того, мы добавляем аннотацию @Sort к отношениям, заявляя, что офицеры должны быть в естественном порядке. Перед тем, как закончить этот пост, мы будем настаивать больше в теме @Sort , но пока этого достаточно.

И, наконец, метод, позволяющий упорядочить всех офицеров данного звездолета по имени и распечатать их в лог-файле.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
EntityManager entityManager = this.entityManagerFactory.createEntityManager();
EntityTransaction transaction = entityManager.getTransaction();
 
transaction.begin();
log.info("Before Find Starship By Id");
 
Starship newStarship = entityManager.find(Starship.class, starshipId);
SortedSet<Officer> officers = newStarship.getOfficers();
  
for (Officer officer : officers) {
 log.info("Officer name {} with rank {}", officer.getName(), officer.getRank());
}
   
log.info("After Find Starship By Id and Before Commit");
 
transaction.commit();
entityManager.close();

Все офицеры отсортированы по именам, но давайте рассмотрим, какие запросы отправляются в СУБД .

1
2
3
4
5
Hibernate: select starship0_.id as id1_0_, starship0_.affiliationEnum as affiliat2_1_0_, starship0_.launched as launched1_0_, starship0_.height as height1_0_, starship0_.length as length1_0_, starship0_.power as power1_0_, starship0_.width as width1_0_, starship0_.registry as registry1_0_, starship0_.starshipClassEnum as starship9_1_0_
from Starship starship0_ where starship0_.id=?
 
Hibernate: select officers0_.starship_id as starship7_1_1_, officers0_.id as id1_, officers0_.id as id0_0_, officers0_.affiliationEnum as affiliat2_0_0_, officers0_.homePlanet as homePlanet0_0_, officers0_.name as name0_0_, officers0_.rank as rank0_0_, officers0_.speciesEnum as speciesE6_0_0_, officers0_.starship_id as starship7_0_0_
from Officer officers0_ where officers0_.starship_id=?

Первый запрос является результатом вызова метода find на экземпляре EntityManager для поиска звездолета.

Поскольку отношения один ко многим по умолчанию являются ленивыми, когда мы вызываем метод getOfficers и мы впервые обращаемся к SortedSet , выполняется второй запрос для получения всех сотрудников. Обратите внимание, что в запросе нет порядка по пунктам, но, внимательно изучив выходные данные, сотрудники извлекаются в алфавитном порядке.

1
2
3
4
5
6
7
<Officer name Beverly Crusher with rank COMMANDER>
<Officer name Data with rank LIEUTENANT_COMMANDER>
<Officer name Deanna Troi with rank COMMANDER>
<Officer name Geordi La Forge with rank LIEUTENANT>
<Officer name Jean-Luc Picard with rank CAPTAIN>
<Officer name William Riker with rank COMMANDER>
<Officer name Worf with rank LIEUTENANT>

Так кто же сортирует офицерские сущности? Объяснение на аннотации @Sort . В hibernate отсортированная коллекция сортируется в памяти, так как Java отвечает за сортировку данных с помощью метода CompareTo .
Очевидно, что этот метод не лучший способ производительности для сортировки коллекции элементов. Вероятно, нам понадобится гибридное решение между использованием предложения SQL и использованием аннотации вместо написания запроса.

И это приводит нас к объяснению третьей возможности, используя подход к упорядочению. Аннотация @OrderBy , доступная как аннотация hibernate и аннотация JPA , позволяет нам указать, как упорядочить коллекцию, добавив предложение « order by » в сгенерированный SQL .

Имейте в виду, что использование javax.persistence.OrderBy позволяет нам определять порядок коллекции с помощью свойств объекта, в то время как org.hibernate.annotations.OrderBy упорядочивает коллекцию, добавляя непосредственно фрагмент SQL (не HQL ) для упорядочения по предложению.
Теперь к классу Officer не нужно прикасаться, нам не нужно реализовывать метод CompareTo или java.util.Comparator . Нам нужно только аннотировать поле офицеров аннотацией @OrderBy . Поскольку в этом случае мы упорядочиваем по простому атрибуту, аннотация JPA используется для обеспечения полной совместимости с другими механизмами ORM, « готовыми к JPA ». По умолчанию предполагается восходящий порядок.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
@Entity
public class Starship {
 
        //code
 
 @OneToMany(mappedBy="starship", cascade={CascadeType.ALL})
 @OrderBy("name")
 private List<Officer> officers = new ArrayList<Officer>();
 public List<Officer> getOfficers() {return Collections.unmodifiableList(officers);}
 protected void setOfficers(List<Officer> officers) {this.officers = officers;}
 public void addOfficer(Officer officer) {
  officer.setStarship(this);
  this.officers.add(officer);
 }
}

И если мы перезапустим метод get all employee, будут отправлены следующие запросы:

1
2
3
4
5
Hibernate: select starship0_.id as id1_0_, starship0_.affiliationEnum as affiliat2_1_0_, starship0_.launched as launched1_0_, starship0_.height as height1_0_, starship0_.length as length1_0_, starship0_.power as power1_0_, starship0_.width as width1_0_, starship0_.registry as registry1_0_, starship0_.starshipClassEnum as starship9_1_0_
from Starship starship0_ where starship0_.id=?
 
Hibernate: select officers0_.starship_id as starship7_1_1_, officers0_.id as id1_, officers0_.id as id0_0_, officers0_.affiliationEnum as affiliat2_0_0_, officers0_.homePlanet as homePlanet0_0_, officers0_.name as name0_0_, officers0_.rank as rank0_0_, officers0_.speciesEnum as speciesE6_0_0_, officers0_.starship_id as starship7_0_0_
from Officer officers0_ where officers0_.starship_id=? order by officers0_.name asc

Оба запроса все еще выполняются, но имейте в виду, что теперь запрос select также содержит предложение order by .

С этим решением вы экономите время процесса, позволяя быстрой сортировке данных СУБД , а не упорядочиваете данные в Java после их получения.
Кроме того, аннотация OrderBy не заставляет вас использовать коллекцию SortedSet или SortedMap . Вы можете использовать любую коллекцию, такую ​​как HashMap , HashSet или даже Bag , потому что hibernate будет внутренне использовать LinkedHashMap , LinkedHashSet или ArrayList соответственно.

В этом примере мы увидели важность правильного выбора стратегии заказа. По возможности, вы должны стараться использовать возможности СУБД , поэтому первым вариантом будет использование аннотации OrderBy ( hibernate или JPA ) вместо сортировки . Но иногда оговорки OrderBy будет недостаточно. В этом случае я рекомендую вам использовать аннотацию Sort с пользовательским типом (используя класс java.util.Comparator ) вместо того, чтобы использовать естественный порядок, чтобы не касаться классов моделей.

1
@Sort(type=SortType.COMPARATOR, comparator=TimeComparator.class)

Я хотел бы, чтобы этот пост помог вам понять разницу между «сортировать» и «порядок» в спящем режиме .

Продолжай учиться.

Ссылка: Hibernate Совет: сортировка и заказ от нашего партнера JCG Алекса Сото в блоге One Jar To Rule All .