Обратите внимание на публичный номер: Большой парень вне банка
Ежедневно публикуйте отличные зарубежные статьи с техническими переводами, которые вдохновляют на то, чтобы помочь отечественным разработчикам стать лучше!
JPA
иHibernate
позволить вамJPQL
иCriteria
используется в запросеDTO
иEntity
как карта. Когда я обсуждаю на своем онлайн-тренинге или семинареHibernate
Когда дело доходит до производительности, меня часто спрашивают, важно ли выбирать подходящее отображение для использования?
Ответ: да! Выбор правильного сопоставления для вашего варианта использования может оказать огромное влияние на производительность. Я выбираю только те данные, которые вам нужны. Очевидно, что выбор ненужной информации не даст вам преимущества в производительности.
1. Основное различие между DTO и Entity
Entity
иDTO
Часто упускаемая из виду разница между -Entity
Управляется контекстом постоянства. когда вы хотите обновитьEntity
, просто позвониsetter
способ установки нового значения.Hibernate
Требуемые операторы SQL будут обработаны, и изменения будут записаны в базу данных.
В мире нет бесплатных обедов.Hibernate
Грязные проверки должны выполняться для всех управляемых объектов, чтобы определить, нужно ли сохранять изменения в базе данных. Это отнимает много времени и совершенно не нужно, когда вы хотите отправить клиенту только небольшой объем информации.
Также нужно помнить,Hibernate
и любой другойJPA
Реализации хранят все управляемые объекты в кэше L1. Кажется, это хорошо. Это предотвращает выполнение повторяющихся запросов, что требуется для оптимизации записи Hibernate. Однако для управления кешем L1 требуется время, и проблемы могут возникнуть даже в том случае, если запрашиваются сотни или тысячи сущностей.
использоватьEntity
будет нести накладные расходы, в то время как вы можете использоватьDTO
избежать этих накладных расходов. Но значит ли это, что не следует использоватьEntity
? Очевидно нет.
2. Напишите проекцию операции
Entity Projections применяются ко всем операциям записи.Hibernate
И другиеJPA
Реализация управляет состоянием объекта и создает необходимые операторы SQL для сохранения изменений в базе данных. Это делает реализацию большинства операций создания, обновления и удаления очень простой и эффективной.
EntityManager em = emf.createEntityManager();
em.getTransaction().begin();
Author a = em.find(Author.class, 1L);
a.setFirstName("Thorben");
em.getTransaction().commit();
em.close();
3. Прочитайте прогноз операции
Но операции только для чтения обрабатываются по-другому. Если вы хотите прочитать данные из базы данных, тоHibernate
Он не управляет состоянием и не выполняет грязные проверки.
Итак, теоретически для чтения данныхDTO
Проекция — лучший выбор. Но действительно ли все иначе? Чтобы ответить на этот вопрос, я провел небольшой тест производительности.
3.1. Настройка теста
Я использую следующую модель предметной области для тестирования. это состоит изAuthor
иBook
Композиция сущностей с использованием ассоциаций «многие к одному». Итак, каждая книга написана одним автором.
@Entity
public class Author {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", updatable = false, nullable = false)
private Long id;
@Version
private int version;
private String firstName;
private String lastName;
@OneToMany(mappedBy = "author")
private List bookList = new ArrayList();
...
}
для обеспеченияHibernate
не получая никаких дополнительных данных, я установил@ManyToOne
изFetchType
заLAZH
. ты можешь читать Introduction to JPA FetchTypesстать другимFetchType
и больше информации о его эффектах.
@Entity
public class Book {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", updatable = false, nullable = false)
private Long id;
@Version
private int version;
private String title;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "fk_author")
private Author author;
...
}
Я создал тестовую базу данных с 10 авторами, каждый из которых написал по 10 книг, так что всего база данных содержит 100 книг. В каждом тесте я буду использовать другую проекцию для запроса 100 книг и измерять время, необходимое для выполнения запроса и транзакции. Чтобы уменьшить влияние любых побочных эффектов, я делаю это 1000 раз и измеряю среднее время. Хорошо, давайте начнем.
3.2 Сущности запроса
В большинстве приложений Entity Projection является наиболее популярным. имеютEntity
,JPA
Их можно легко использовать в качестве проекций.
Запустите этот небольшой тестовый пример и измерьте получение 100Book
время, необходимое субъекту.
long timeTx = 0;
long timeQuery = 0;
long iterations = 1000;
// Perform 1000 iterations
for (int i = 0; i < iterations; i++) {
EntityManager em = emf.createEntityManager();
long startTx = System.currentTimeMillis();
em.getTransaction().begin();
// Execute Query
long startQuery = System.currentTimeMillis();
List<Book> books = em.createQuery("SELECT b FROM Book b").getResultList();
long endQuery = System.currentTimeMillis();
timeQuery += endQuery - startQuery;
em.getTransaction().commit();
long endTx = System.currentTimeMillis();
em.close();
timeTx += endTx - startTx;
}
System.out.println("Transaction: total " + timeTx + " per iteration " + timeTx / (double)iterations);
System.out.println("Query: total " + timeQuery + " per iteration " + timeQuery / (double)iterations);
В среднем запрос выполняется, результаты извлекаются и сопоставляются со 100Book
Сущность занимает 2 мс. 2,89 мс, если включены транзакции. Неплохо для маленького и не очень нового ноутбука.
Transaction: total 2890 per iteration 2.89
Query: total 2000 per iteration 2.0
3.3 Влияние FetchType по умолчанию на ассоциацию To-One
Когда я показывал вам сущность Книга, я указал, чтоFetchTypeУстановить какLAZY
чтобы избежать других запросов. по умолчанию,To-one
СвязанныйFetchtType
даEAGER
, который говоритHibernate
Ассоциация инициализируется немедленно.
Для этого требуется дополнительный запрос, который может сильно повлиять на производительность, если ваш запрос выбирает несколько сущностей. давай менятьсяBook
объект для использования по умолчаниюFetchType
и проведите тот же тест.
@Entity
public class Book {
@ManyToOne
@JoinColumn(name = "fk_author")
private Author author;
...
}
Это небольшое изменение увеличило время выполнения теста более чем в три раза. Теперь для выполнения запроса и сопоставления результатов требовалось 7,797 мс вместо 2 мс. Время на транзакцию увеличилось до 8,681 мс вместо 2,89 мс.
Transaction: total 8681 per iteration 8.681
Query: total 7797 per iteration 7.797
Поэтому лучше всего убедиться, чтоTo-one
Настройки ассоциацииFetchType
заLAZY
.
3.4. Выберите объект @Immutable
Жоао Шарнет посоветовал мне в комментариях добавить в тест Immutable Entity. Интересный вопрос: вернуться к использованию@Immutable
Аннотированные сущности, будет ли производительность запросов лучше?
Hibernate
Нет необходимости выполнять какие-либо грязные проверки этих сущностей, поскольку они неизменяемы. Это может привести к повышению производительности. Итак, давайте попробуем.
Я добавил следующее в свой тестImmutableBook
организация.
@Entity
@Table(name = "book")
@Immutable
public class ImmutableBook {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "id", updatable = false, nullable = false)
private Long id;
@Version
private int version;
private String title;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "fk_author")
private Author author;
...
}
этоBook
Копия объекта с двумя дополнительными аннотациями.@Immutable
Аннотация рассказываетHibernate
, эта сущность неизменна. и@Table(name =“book”)
сопоставлять объекты сbook
поверхность. Таким образом, мы можем запустить тот же тест с теми же данными, что и раньше.
long timeTx = 0;
long timeQuery = 0;
long iterations = 1000;
// Perform 1000 iterations
for (int i = 0; i < iterations; i++) {
EntityManager em = emf.createEntityManager();
long startTx = System.currentTimeMillis();
em.getTransaction().begin();
// Execute Query
long startQuery = System.currentTimeMillis();
List<Book> books = em.createQuery("SELECT b FROM ImmutableBook b")
.getResultList();
long endQuery = System.currentTimeMillis();
timeQuery += endQuery - startQuery;
em.getTransaction().commit();
long endTx = System.currentTimeMillis();
em.close();
timeTx += endTx - startTx;
}
System.out.println("Transaction: total " + timeTx + " per iteration " + timeTx / (double)iterations);
System.out.println("Query: total " + timeQuery + " per iteration " + timeQuery / (double)iterations);
Интересно, что для запроса не имеет значения, является ли сущность неизменной или нет. Измеренное среднее время выполнения транзакций и запросов почти такое же, как и в предыдущем тесте.
Transaction: total 2879 per iteration 2.879
Query: total 2047 per iteration 2.047
3.5 Запрос объекта с помощью QueryHints.HINT_READONLY
Эндрю Буржуа предложил включить в тесты запросы только для чтения. Итак, смотрите здесь.
В этом тесте используется то, что я показал вам в начале статьи.Book
организация. Но это требует модификации тестовых случаев.
JPA
иHibernate
Поддерживает набор подсказок запроса (соответствий), которые позволяют предоставить дополнительную информацию о запросе и о том, как он выполняется. подсказка запросаQueryHints.HINT_READONLY
РассказыватьHibernate
Объекты запросов в режиме только для чтения. следовательно,Hibernate
Нет необходимости выполнять для них какие-либо грязные проверки, и можно применять и другие оптимизации.
вы можете пройти вQuery
вызов на интерфейсsetHint
Метод установлен для установки этой подсказки.
long timeTx = 0;
long timeQuery = 0;
long iterations = 1000;
// Perform 1000 iterations
for (int i = 0; i < iterations; i++) {
EntityManager em = emf.createEntityManager();
long startTx = System.currentTimeMillis();
em.getTransaction().begin();
// Execute Query
long startQuery = System.currentTimeMillis();
Query query = em.createQuery("SELECT b FROM Book b");
query.setHint(QueryHints.HINT_READONLY, true);
query.getResultList();
long endQuery = System.currentTimeMillis();
timeQuery += endQuery - startQuery;
em.getTransaction().commit();
long endTx = System.currentTimeMillis();
em.close();
timeTx += endTx - startTx;
}
System.out.println("Transaction: total " + timeTx + " per iteration " + timeTx / (double)iterations);
System.out.println("Query: total " + timeQuery + " per iteration " + timeQuery / (double)iterations);
Вы можете сделать запрос доступным только для чтения, чтобы получить значительный прирост производительности.Hibernate
Выполняется меньше работы, поэтому она должна быть быстрее.
Но, как вы можете видеть ниже, время выполнения почти такое же, как и в предыдущем тесте. По крайней мере, в этом тестовом сценарииQueryHints.HINT_READONLY
Установить какtrue
Не улучшит производительность.
Transaction: total 2842 per iteration 2.842
Query: total 2006 per iteration 2.006
3.6 Запрос DTO
Загрузка 100 объектов книги занимает около 2 мс. Давайте посмотрим наJPQL
Лучше ли использовать выражения конструктора в запросах, чтобы получить те же данные.
Конечно, вы также можетеCriteria
Используйте выражения конструктора в запросах.
long timeTx = 0;
long timeQuery = 0;
long iterations = 1000;
// Perform 1000 iterations
for (int i = 0; i < iterations; i++) {
EntityManager em = emf.createEntityManager();
long startTx = System.currentTimeMillis();
em.getTransaction().begin();
// Execute the query
long startQuery = System.currentTimeMillis();
List<BookValue> books = em.createQuery("SELECT new org.thoughts.on.java.model.BookValue(b.id, b.title) FROM Book b").getResultList();
long endQuery = System.currentTimeMillis();
timeQuery += endQuery - startQuery;
em.getTransaction().commit();
long endTx = System.currentTimeMillis();
em.close();
timeTx += endTx - startTx;
}
System.out.println("Transaction: total " + timeTx + " per iteration " + timeTx / (double)iterations);
System.out.println("Query: total " + timeQuery + " per iteration " + timeQuery / (double)iterations);
Как и ожидалось,DTO
Проекционное отношение实体(Entity)
Проекция работает лучше.
Transaction: total 1678 per iteration 1.678
Query: total 1143 per iteration 1.143
В среднем для выполнения запроса требуется 1,143 мс, а для выполнения транзакции — 1,678 мс. Производительность запросов улучшена на 43%, а производительность транзакций улучшена примерно на 42%.
Неплохо для небольшого изменения, реализация которого заняла минуту.
В большинстве проектов,DTO
Прирост производительности для проекции будет еще выше. Это позволяет вам выбирать данные, необходимые для варианта использования, а не только все свойства сопоставления объектов. Выбор меньшего количества данных почти всегда приводит к повышению производительности.
4. Резюме
Выбор правильной проекции для вашего варианта использования проще и важнее, чем вы думаете.
Если вы хотите реализовать операции записи, вы должны использовать Entity в качестве проекции.Hibernate
Его состояние будет управляемым, вам просто нужно обновить его свойства в бизнес-логике. потомHibernate
с остальным справится.
Вы видели результаты моего небольшого теста производительности. Мой ноутбук может быть не лучшей средой для проведения этих тестов, он определенно медленнее, чем производственный. Но прирост производительности настолько велик, что очевидно, какую проекцию следует использовать.
использоватьDTO
Прогнозируемые запросы выполняются примерно на 40 % быстрее, чем запросы, выбирающие сущности. Так что лучше потратить дополнительные усилия на создание для ваших операций только для чтенияDTO
и использовать его как проекцию.
Кроме того, убедитесь, что все ассоциации используютFetchType.LAZY
. Как видно из тестов, даже нетерпеливый выборto-one
, что также может утроить время выполнения запроса. Поэтому лучше использоватьFetchType.LAZY
и инициализируйте то, что нужно вашему варианту использованиясвязь.
Оригинальная ссылка:мысли-на-java.org/entities- но он…
Автор: Торбен Янссен.
Переводчик: Юнооа