Spring Data JPA является продуктом дальнейшей инкапсуляции на основе спецификации JPA.Как и предыдущий JDBC и slf4j, он определяет только серию интерфейсов. Конкретно в процессе использования общий доступ представляет собой реализацию Hibernate, тогда конкретный Spring Data JPA можно рассматривать как объектно-ориентированный ORM. Хотя серверной реализацией является Hibernate, фактическая настройка и использование намного проще, чем Hibernate, и вы можете быстро приступить к работе. Если бизнес не слишком сложен, я лично думаю, что он проще и удобнее, чем Mybatis.
В этой статье просто перечислены конкретные точки знаний, а подробное использование можно найти в блоге в ссылках. В этой статье будет конкретно рассмотрено общее использование JPA, транзакций и моментов, которые необходимо освоить для Hibernate.
основное использование
- Создайте проект и выберите соответствующие зависимости. Как правило, не используйте драйвер mysql напрямую, а выбирайте пул соединений.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.18</version>
</dependency>
- Настройте глобальный файл yml.
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://172.21.30.61:3306/gpucluster?serverTimezone=Hongkong&characterEncoding=utf-8&useSSL=false
username:
password:
jpa:
hibernate:
ddl-auto: update
open-in-view: false
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL57Dialect
show_sql: false
format_sql: true
logging:
level:
root: info # 是否需要开启 sql 参数日志
org.springframework.orm.jpa: DEBUG
org.springframework.transaction: DEBUG
org.hibernate.engine.QueryParameters: debug
org.hibernate.engine.query.HQLQueryPlan: debug
org.hibernate.type.descriptor.sql.BasicBinder: trace
-
hibernate.ddl-auto: update
Изменения в классе сущностей будут синхронизированы со структурой таблицы базы данных, используйте их с осторожностью. -
show_sql
sql, сгенерированный hibernate, можно открыть для легкой отладки. -
logging
Следующие параметры используются для отображения параметров sql.
- Создание классов сущностей и добавление аннотаций JPA
@Entity
@Table(name = "user")
@Data
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private Integer age;
private String address;
private LocalDateTime createTime;
private LocalDateTime updateTime;
}
- Создайте соответствующий репозиторий
Реализуйте интерфейс JpaRepository для создания шаблонного кода базовой операции CRUD. И вы можете создать простую операцию запроса на основе стратегий поиска запросов, поставляемых с Spring Data JPA, и ввести ее в IDEA.findBy
Дождитесь подсказки.
public interface IUserRepository extends JpaRepository<User,Long> {
List<User> findByName(String name);
List<User> findByAgeAndCreateTimeBetween(Integer age, LocalDateTime createTime, LocalDateTime createTime2);
}
Запрос
метод по умолчанию
Репозиторий наследуетJpaRepository
После этого будет ряд основных методов CRUD по умолчанию, таких как:
List<T> findAll();
Page<T> findAll(Pageable pageable);
T getOne(ID id);
T S save(T entity);
void deleteById(ID id);
Декларативный запрос
Репозиторий наследуетJpaRepository
После этого вы можете определить ряд методов в интерфейсе, которые обычно начинаются сfindBy
,countBy
,deleteBy
,existsBy
Дождитесь начала, если вы используете IDEA, будет предложено ответить после ввода следующих ключевых слов. Например:
public interface IUserRepository extends JpaRepository<User,Integer>{
User findByUsername(String username);
Integer countByDept(String dept);
}
Для некоторых однотабличных запросов с несколькими полями очень удобно использовать этот метод, и это полностью oop-мышление, не думая о том, как написать конкретный SQL. Но есть проблема.После того как полей станет больше,имя метода будет очень длинным,и вызывать его будет неудобно.В это время можно использовать характеристики jdk8 для его сокращения.Конечно можно и использовать это прямо в данном случае.@Query
Напишите HQL или SQL для решения.
User findFirstByEmailContainsIgnoreCaseAndField1NotNullAndField2NotNull(final String email);
default User getByEmail(final String email) {
return findFirstByEmailContainsIgnoreCaseAndField1NotNullAndField2NotNull(email);
}
Общие операции видныПриложение. Ключевые слова поддерживаемых методов
Использование аннотаций и SQL
@Transactional(readOnly = true)
public interface UserRepository extends JpaRepository<User, Long> {
@Query(nativeQuery = true, value = "select * from user where tel = ?1")
List<User> getUser(String tel);
@Modifying
@Transactional
@Query("delete from User u where u.active = false")
void deleteInactiveUsers();
}
-
@Query
Доступен для записи в HQL и SQL, если SQL, тоnativeQuery = true
.
Спецификация сложного запроса
// 复杂查询,创建 Specification
private Page<OrderInfoEntity> getOrderInfoListByConditions(String tel, int pageSize, int pageNo, String beginTime, String endTime) {
Specification<OrderInfoEntity> specification = new Specification<OrderInfoEntity>() {
@Override
public Predicate toPredicate(Root<OrderInfoEntity> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
List<Predicate> predicate = new ArrayList<>();
if (!Strings.isNullOrEmpty(beginTime)) {
predicate.add(cb.greaterThanOrEqualTo(root.get("createTime"), DateUtils.getDateFromTimestamp(beginTime)));
}
if (!Strings.isNullOrEmpty(endTime)) {
predicate.add(cb.lessThanOrEqualTo(root.get("createTime"), DateUtils.getDateFromTimestamp(endTime)));
}
if (!Strings.isNullOrEmpty(tel)) {
predicate.add(cb.equal(root.get("userTel"), tel));
}
return cb.and(predicate.toArray(new Predicate[predicate.size()]));
}
};
Sort sort = new Sort(Sort.Direction.DESC, "createTime");
Pageable pageable = new PageRequest(pageNo - 1, pageSize, sort);
return orderInfoRepository.findAllEntities(specification, pageable);
}
подзапрос
Specification<UserProject> specification = (root, criteriaQuery, criteriaBuilder) -> {
Subquery subQuery = criteriaQuery.subquery(String.class);
Root from = subQuery.from(User.class);
subQuery.select(from.get("userId")).where(criteriaBuilder.equal(from.get("username"), "mqy6289"));
return criteriaBuilder.and(root.get("userId").in(subQuery));
};
return userProjectRepository.findAll(specification);
удалить и изменить
- Удалить
- просто используйте значение по умолчанию
deleteById()
. - Создайте соответствующий метод удаления с помощью декларативного запроса.
deleteByXxx
. - Удалить с помощью аннотаций SQL\HQL
- Добавить и изменить
Вызовите метод сохранения.Если он изменен, вам нужно сначала найти соответствующий объект, а затем изменить соответствующие атрибуты.
дела
Spring Boot по умолчанию интегрирует транзакции, поэтому нет необходимости вручную включать использование.@EnableTransactionManagement
аннотация, вы можете использовать@Transactional
Аннотации для управления транзакциями. Когда вам нужно его использовать, вы можете проверить определенные параметры.
@Transactional
Для использования аннотаций см.:Тщательное понимание использования @transactional в Spring.
Давайте поговорим о нескольких итогах использования:
- Наследование методов слоя сохраняемости
JpaRepository
, соответствующий классу реализацииSimpleJpaRepository
содержит@Transactional(readOnly = true)
аннотация, такВсе методы CRUD на уровне сохраняемости по умолчанию добавляют транзакции. - Декларативная транзакция чаще используется в методе на уровне службы. Как правило, для завершения бизнес-логики вызывается несколько репозиториев. Во время процесса может работать несколько таблиц данных. Если возникает исключение, обычно требуется каскадный откат. Общие операции, добавленные непосредственно в методе сервисного слоя
@Transactional
То есть уровень изоляции данных используется по умолчанию, и все методы Repository добавляются к транзакции на уровне Service по умолчанию. -
@Transactional
Два основных параметра в аннотации:propagation
а такжеisolation
. Первый используется для управления поведением распространения транзакций, указывая, добавляются ли небольшие транзакции к большим транзакциям или все транзакции выполняются отдельно и т. д.; последний используется для управления уровнем изоляции транзакций, который по умолчанию соответствует MySQL. и является неповторяемым чтением. Мы также можем вручную изменить уровень изоляции отдельной транзакции через это поле. Конкретные сценарии применения можно найти в другом моем блогеРазговор об изоляции транзакций и ее применении в разработке. - вызовы методов на том же сервисном уровне,если добавлено
@Transactional
Запустит кеш первого уровня гибернации, один и тот же запрос будет кэшироваться на уровне сеанса, если он выполняется несколько раз. В противном случае один и тот же запрос несколько раз будет выполняться независимо как транзакция и не может быть закэширован. - Если вы используете реляционные аннотации, вы обычно сталкиваетесь с ними во время отложенной загрузки.
LazyInitializationException
Эту проблему можно решить, добавив@Transactional
, разместите сеанс в Spring Transaction для разрешения. - Использование транзакций только для чтения. Транзакции только для чтения можно настроить глобально на сервисном уровне.
@Transactional(readOnly =true)
, для транзакций с чтением и записью его можно переопределить в соответствующем методе. В транзакции только для чтения операция записи не может быть выполнена, поэтому hibernate будет пропускать грязную проверку до того, как транзакция будет зафиксирована, а Spring и JDBC будут иметь различные оптимизации, чтобы сделать запрос более эффективным.
JPA Audit
Аудит, поставляемый с JPA, может быть введен в виде АОП, добавляя такую информацию, как время создания и обновления во время операции сохранения. Конкретное использование:
- Чтобы объявить класс сущности, вам нужно добавить к классу аннотацию @EntityListeners(AuditingEntityListener.class).
- Добавьте аннотацию @EnableJpaAuditing в класс запуска приложения.
- Аннотируйте необходимые поля с помощью @CreatedDate, @CreatedBy, @LastModifiedDate, @LastModifiedBy и т. д.
Если вам нужно только обновить время создания и обновления, дополнительная настройка не требуется.
связь с базой данных
Если вам нужно выполнять каскадные запросы, вы можете использовать JPA @OneToMany, @ManyToMany и @OneToOne для украшения.Конечно, когда возникает ситуация «один ко многим», вы можете вручную запросить данные большего количества участников и заполнить его. в.
Из-за различий в дизайне баз данных аннотации также используются по-разному. Вот пример OneToMany.
Warehouse и Goods имеют отношение "один ко многим", и по дизайну таблица Goods содержит внешний ключ Repository, поэтому добавьте аннотации к Repository, которые не требуются для Goods.
@Entity
public class Repository{
@OneToMany(cascade = {CascadeType.ALL})
@JoinColumn(name = "repo_id")
private List<Goods> list;
}
public class Goods{
}
Для получения подробной информации см.:Объяснено @OneToMany, @ManyToOne и @ManyToMany (5)
Эти аннотации JPA относительно тесно связаны с Hibernate и обычно подходят для формы кода в первую очередь, то есть база данных создается после класса сущности. Здесь я не рекомендую, чтобы люди, которые не изучили Hibernate и непосредственно запустили Spring Data JPA, использовали эти аннотации, потому что после добавления аннотации отношения это удобно с точки зрения запроса, но включает некоторые каскадные операции, такие как удаление, Модификация и другие операции легко майнить. Требуется дополнительное знание механизма обновления кэша Hibernate.
Несколько источников данных
В случае с одним источником данных по умолчанию нам нужно только реализовать интерфейс JpaRepository для нашего репозитория, и автоматическая конфигурация Spring Boot автоматически внедрит для нас необходимые bean-компоненты, такие какLocalContainerEntityManagerFactoryBean
,EntityManager
,DataSource
.
Но в случае нескольких источников данных эти bean-компоненты нужно создавать условно в соответствии с конфигурационным файлом.
- Добавить информацию о нескольких источниках данных в файл конфигурации
spring:
datasource:
hangzhou: # datasource1
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://172.17.11.72:3306/gpucluster?serverTimezone=Hongkong&characterEncoding=utf-8&useSSL=false
username:
password:
shanghai: # datasource2
type: com.alibaba.druid.pool.DruidDataSource
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://172.21.30.61:3306/gpucluster?serverTimezone=Hongkong&characterEncoding=utf-8&useSSL=false
username:
password:
jpa:
open-in-view: false
properties:
hibernate:
dialect: org.hibernate.dialect.MySQL57Dialect
- Внедрение компонента источника данных
@Slf4j
@Configuration
public class DataSourceConfiguration {
@Bean(name = "HZDataSource")
@Primary
@Qualifier("HZDataSource")
@ConfigurationProperties(prefix = "spring.datasource.hangzhou")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().type(DruidDataSource.class).build();
}
@Bean(name = "SHDataSource")
@Qualifier("SHDataSource")
@ConfigurationProperties(prefix = "spring.datasource.shanghai")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().type(DruidDataSource.class).build();
}
}
- Внедрить bean-компоненты, связанные с JPA (один источник данных и один файл конфигурации)
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories(
entityManagerFactoryRef = "entityManagerFactoryHZ",
transactionManagerRef = "transactionManagerHZ",
basePackages = {"cn.com.arcsoft.app.repo.jpa.hz"},
repositoryBaseClass = IBaseRepositoryImpl.class)
public class RepositoryHZConfig {
private final DataSource HZDataSource;
private final JpaProperties jpaProperties;
private final HibernateProperties hibernateProperties;
public RepositoryHZConfig(@Qualifier("HZDataSource") DataSource HZDataSource, JpaProperties jpaProperties, HibernateProperties hibernateProperties) {
this.HZDataSource = HZDataSource;
this.jpaProperties = jpaProperties;
this.hibernateProperties = hibernateProperties;
}
@Primary
@Bean(name = "entityManagerFactoryHZ")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryHZ(EntityManagerFactoryBuilder builder) {
// springboot 2.x
Map<String, Object> properties = hibernateProperties.determineHibernateProperties(
jpaProperties.getProperties(), new HibernateSettings());
return builder.dataSource(HZDataSource)
.properties(properties)
.packages("cn.com.arcsoft.app.entity")
.persistenceUnit("HZPersistenceUnit")
.build();
}
@Primary
@Bean(name = "transactionManagerHZ")
public PlatformTransactionManager transactionManagerHZ(EntityManagerFactoryBuilder builder) {
return new JpaTransactionManager(entityManagerFactoryHZ(builder).getObject());
}
}
- Просто добавьте соответствующий репозиторий в соответствующий пакет, настроенный ранее. Если база данных источника данных одна и та же, можно реализовать основной репозиторий, а остальные можно наследовать.
@Primary
@Qualifier("volumeHZRepository")
public interface IVolumeRepository extends IBaseRepository<Volume, Integer> {
Volume findByUserIdAndIp(Integer userId, String ip);
}
@Qualifier("volumeSHRepository")
public interface IVolumeSHRepository extends IVolumeRepository {
}
JPA и спящий режим
При использовании Spring Data JPA, хотя нижний слой реализован Hibernate, у нас вообще нет ощущения в процессе его использования, потому что мы используем API, предоставляемый спецификацией JPA, для работы с базой данных. Но при столкновении с каким-то сложным бизнесом вам все же может понадобиться обратить внимание на Hibernate или некоторые реализации в нижней части JPA, такие как создание и использование EntityManager и EntityManagerFactory.
Теперь я расскажу о двух самых важных моментах.
жизненный цикл объекта
Любой, кто использовал Mybatis, знает, что это полуавтоматическая ORM, которая только сопоставляет результаты выполнения SQL с конкретными объектами.Хотя она также кэширует результаты запросов, как только данные найдены и инкапсулированы в классы сущностей, ничего общего с базой данных . Но Hibernate на бэкенде JPA отличается: как полностью автоматический ORM он имеет свой собственный сложный механизм для обработки взаимосвязей между объектами и базами данных, и они напрямую связаны.
Прежде всего, в Hibernate объекты больше не являются базовыми Java POJO, а имеют четыре состояния.
- Transient: класс сущности, который только что был создан с помощью оператора new, не был сохранен и не находится в кэше сеанса.
- Постоянное состояние (постоянное): классы сущностей, которые были сохранены и находятся в кэше сеанса.
- Удаленное состояние (удалено): классы сущностей, которых нет в кэше сеанса и которые сеанс запланировал удалить из базы данных.
- Отсоединенное состояние (отсоединено): класс сущности, который был сохранен, но больше не находится в кэше сеанса.
Особое внимание следует уделить объектам с постоянным состоянием.Такие объекты, как правило, запрашиваются из базы данных и хранятся в кэше сеанса.Благодаря наличию механизмов очистки кеша и грязной проверки при изменении свойств объектов нет необходимости вручную выполнить метод сохранения, когда транзакция будет поднята, изменения будут автоматически зафиксированы в базе данных.
Очистка кеша и грязная проверка
Когда транзакция будет зафиксирована, будет выполнена операция очистки кеша, и все постоянные объекты в сеансе будут проверены на наличие загрязнений. Кратко опишу процесс:
- Различные результаты запроса в транзакции кэшируются в соответствующем сеансе, а моментальный снимок сохраняется.
- Прежде чем транзакция будет зафиксирована, она будет вызвана
session.flush()
Выполните очистку кеша и грязную проверку. Сравните объекты во всех сеансах с соответствующими снимками.Если есть изменения, объект грязный. - Выполняйте такие операции, как обновление и удаление, чтобы синхронизировать измененные данные в сеансе с базой данных.
Включение транзакций только для чтения может заблокировать грязную проверку и повысить эффективность запросов.
Troubleshooting
- Проблема с Jpa с ломбоком вызывает StackOverflowError
При использовании @Data при использовании аннотации отношения Hibernate @ManyToMany при выполнении запроса возникает исключение StackOverflowError. Главным образом потому, что @Data помог нам реализовать метод hashCode(), и была проблема с циклическими зависимостями.
Решение: добавьте @EqualsAndHashCode.Exclude в реляционное поле.
@EqualsAndHashCode.Exclude
@ManyToMany(fetch = FetchType.LAZY,cascade = {CascadeType.PERSIST})
private Set<User> membersSet;
Проблема Lombok.hashCode с «java.lang.StackOverflowError: null»
- Spring boot JPA: решение неизвестного объекта
После инициализации объекта двумя фигурными скобками при вызове метода сохранения JPA будет выдано исключение Unknown entity, что вызвано неспособностью JPA правильно идентифицировать анонимные внутренние классы.
Решение. Вручную создайте новый объект, а затем вызовите метод set, чтобы присвоить значение.
Spring boot JPA: решение неизвестного объекта
- LazyInitializationException при использовании аннотации отношения
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
Если вы используете реляционные аннотации Hibernate, вы можете столкнуться с этой проблемой из-за ленивой загрузки значений в объекте get после закрытия сеанса.
Решение:
- Добавление аннотаций к классам сущностей
@Proxy(lazy = false)
- Добавьте методы уровня сервисов
@Transactional
, оставив управление сессиями весенним транзакциям
Суммировать
В этой статье в основном рассказывается об основах использования Spring Data JPA и немного личного опыта.
До сих пор ORM развивался от Hibernate до JPA, а теперь и Spring Data JPA. Видно, что это процесс непрерывного упрощения.В прошлом большой раздел xml исчез, и можно было сохранить только базовую строку sql. Хотя Spring Data JPA прост в настройке и использовании, поскольку его основа по-прежнему реализуется Hibernate, некоторые вещи все же необходимо понять. Что касается текущего использования, у меня есть следующие ощущения:
- Чтобы эффективно использовать Spring Data JPA, связанные с Hibernate механизмы по-прежнему должны иметь определенное понимание, например, вышеупомянутый цикл объявления объекта и механизм обновления сеанса. Если вы этого не понимаете, могут возникнуть необъяснимые проблемы.
- Если вы новичок, лично я не рекомендую использовать аннотации отношений. Сама технология постепенно упрощается, если она не очень сложная, например, ERP-система, то нет необходимости использовать нативные вещи JPA и Hibernate, вполне возможно несколько раз вручную запрашивать замену реляционных аннотаций. Причина этого в том, что существуют некоторые скрытые опасности из-за отсутствия глубокого понимания использования реляционных аннотаций JPA и типов различных каскадных операций.
использованная литература
- SpringBoot интегрирует Spring-data-jpa
- Spring Boot (5): использование Spring Boot Jpa
- Spring Data JPA Advanced (6): транзакции и блокировки
- Spring Data JPA - Reference Documentation
приложение
Ключевые слова поддерживаемых методов
Keyword | Sample | JPQL snippet |
---|---|---|
And | findByLastnameAndFirstname | … где x.lastname = ?1 и x.firstname = ?2 |
Or | findByLastnameOrFirstname | … где x.lastname = ?1 или x.firstname = ?2 |
Is,Equals | findByFirstname, findByFirstnameIs, findByFirstnameEquals | … где x.имя = ?1 |
Between | findByStartDateBetween | … где x.startDate между ?1 и ?2 |
LessThan | findByAgeLessThan | … где x.age |
LessThanEqual | findByAgeLessThanEqual | … где x.age |
GreaterThan | findByAgeGreaterThan | … где x.age > ?1 |
GreaterThanEqual | findByAgeGreaterThanEqual | … где x.age >= ?1 |
After | findByStartDateAfter | … где x.startDate > ?1 |
Before | findByStartDateBefore | … где x.startDate |
IsNull | findByAgeIsNull | … где x.age равно нулю |
IsNotNull,NotNull | findByAge(Is)NotNull | … где x.age не нуль |
Like | findByFirstnameLike | … где x.firstname как ?1 |
NotLike | findByFirstnameNotLike | … где x.firstname не похоже на ?1 |
StartingWith | findByFirstnameStartingWith | … где x.firstname как ?1 (дополнительная привязка параметра %) |
EndingWith | findByFirstnameEndingWith | … где x.firstname как ?1 (параметр % привязан к началу) |
Containing | findByFirstnameContaining | … где x.firstname как ?1 (привязка параметра оборачивает %) |
OrderBy | findByAgeOrderByLastnameDesc | …где x.age = ?1 порядок по x.lastname desc |
Not | findByLastnameNot | … где x.lastname ?1 |
In | findByAgeIn(Collection ages) | … где x.age в ?1 |
NotIn | findByAgeNotIn(Collection ages) | … где x.age не в ?1 |
True | findByActiveTrue() | … где х.актив = истина |
False | findByActiveFalse() | … где х.актив = ложь |
IgnoreCase | findByFirstnameIgnoreCase | … где ПРОПИСНЫЕ(x.firstname) = ПРОПИСНЫЕ(?1) |
Топ или первый | findTopByNameAndAge, найтиFirstByNameAndAge | где … предел 1 |
Topn или Firstn | найтиTop2ByNameAndAge, найтиFirst2ByNameAndAge | где … предел 2 |
Distinct | findDistinctPeopleByLastnameOrFirstname | выделить отдельно.... |
count | countByAge, количество | select count(*) |