Одна статья для получения Spring Data JPA

Spring Boot
Одна статья для получения Spring Data JPA

Spring Data JPA является продуктом дальнейшей инкапсуляции на основе спецификации JPA.Как и предыдущий JDBC и slf4j, он определяет только серию интерфейсов. Конкретно в процессе использования общий доступ представляет собой реализацию Hibernate, тогда конкретный Spring Data JPA можно рассматривать как объектно-ориентированный ORM. Хотя серверной реализацией является Hibernate, фактическая настройка и использование намного проще, чем Hibernate, и вы можете быстро приступить к работе. Если бизнес не слишком сложен, я лично думаю, что он проще и удобнее, чем Mybatis.

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

основное использование

  1. Создайте проект и выберите соответствующие зависимости. Как правило, не используйте драйвер 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>
  1. Настройте глобальный файл 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_sqlsql, сгенерированный hibernate, можно открыть для легкой отладки.
  • loggingСледующие параметры используются для отображения параметров sql.
  1. Создание классов сущностей и добавление аннотаций 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;
}
  1. Создайте соответствующий репозиторий

Реализуйте интерфейс 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();
}
  1. @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);

удалить и изменить

  • Удалить
  1. просто используйте значение по умолчаниюdeleteById().
  2. Создайте соответствующий метод удаления с помощью декларативного запроса.deleteByXxx.
  3. Удалить с помощью аннотаций SQL\HQL
  • Добавить и изменить

Вызовите метод сохранения.Если он изменен, вам нужно сначала найти соответствующий объект, а затем изменить соответствующие атрибуты.

дела

Spring Boot по умолчанию интегрирует транзакции, поэтому нет необходимости вручную включать использование.@EnableTransactionManagementаннотация, вы можете использовать@TransactionalАннотации для управления транзакциями. Когда вам нужно его использовать, вы можете проверить определенные параметры.

@TransactionalДля использования аннотаций см.:Тщательное понимание использования @transactional в Spring.

Давайте поговорим о нескольких итогах использования:

  1. Наследование методов слоя сохраняемостиJpaRepository, соответствующий классу реализацииSimpleJpaRepositoryсодержит@Transactional(readOnly = true)аннотация, такВсе методы CRUD на уровне сохраняемости по умолчанию добавляют транзакции.
  2. Декларативная транзакция чаще используется в методе на уровне службы. Как правило, для завершения бизнес-логики вызывается несколько репозиториев. Во время процесса может работать несколько таблиц данных. Если возникает исключение, обычно требуется каскадный откат. Общие операции, добавленные непосредственно в методе сервисного слоя@TransactionalТо есть уровень изоляции данных используется по умолчанию, и все методы Repository добавляются к транзакции на уровне Service по умолчанию.
  3. @TransactionalДва основных параметра в аннотации:propagationа такжеisolation. Первый используется для управления поведением распространения транзакций, указывая, добавляются ли небольшие транзакции к большим транзакциям или все транзакции выполняются отдельно и т. д.; последний используется для управления уровнем изоляции транзакций, который по умолчанию соответствует MySQL. и является неповторяемым чтением. Мы также можем вручную изменить уровень изоляции отдельной транзакции через это поле. Конкретные сценарии применения можно найти в другом моем блогеРазговор об изоляции транзакций и ее применении в разработке.
  4. вызовы методов на том же сервисном уровне,если добавлено@TransactionalЗапустит кеш первого уровня гибернации, один и тот же запрос будет кэшироваться на уровне сеанса, если он выполняется несколько раз. В противном случае один и тот же запрос несколько раз будет выполняться независимо как транзакция и не может быть закэширован.
  5. Если вы используете реляционные аннотации, вы обычно сталкиваетесь с ними во время отложенной загрузки.LazyInitializationExceptionЭту проблему можно решить, добавив@Transactional, разместите сеанс в Spring Transaction для разрешения.
  6. Использование транзакций только для чтения. Транзакции только для чтения можно настроить глобально на сервисном уровне.@Transactional(readOnly =true), для транзакций с чтением и записью его можно переопределить в соответствующем методе. В транзакции только для чтения операция записи не может быть выполнена, поэтому hibernate будет пропускать грязную проверку до того, как транзакция будет зафиксирована, а Spring и JDBC будут иметь различные оптимизации, чтобы сделать запрос более эффективным.

JPA Audit

Аудит, поставляемый с JPA, может быть введен в виде АОП, добавляя такую ​​информацию, как время создания и обновления во время операции сохранения. Конкретное использование:

  1. Чтобы объявить класс сущности, вам нужно добавить к классу аннотацию @EntityListeners(AuditingEntityListener.class).
  2. Добавьте аннотацию @EnableJpaAuditing в класс запуска приложения.
  3. Аннотируйте необходимые поля с помощью @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-компоненты нужно создавать условно в соответствии с конфигурационным файлом.

  1. Добавить информацию о нескольких источниках данных в файл конфигурации
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
  1. Внедрение компонента источника данных
@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();
    }
}
  1. Внедрить 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());
    }
}
  1. Просто добавьте соответствующий репозиторий в соответствующий пакет, настроенный ранее. Если база данных источника данных одна и та же, можно реализовать основной репозиторий, а остальные можно наследовать.
@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, а имеют четыре состояния.

  1. Transient: класс сущности, который только что был создан с помощью оператора new, не был сохранен и не находится в кэше сеанса.
  2. Постоянное состояние (постоянное): классы сущностей, которые были сохранены и находятся в кэше сеанса.
  3. Удаленное состояние (удалено): классы сущностей, которых нет в кэше сеанса и которые сеанс запланировал удалить из базы данных.
  4. Отсоединенное состояние (отсоединено): класс сущности, который был сохранен, но больше не находится в кэше сеанса.

image002-35

Особое внимание следует уделить объектам с постоянным состоянием.Такие объекты, как правило, запрашиваются из базы данных и хранятся в кэше сеанса.Благодаря наличию механизмов очистки кеша и грязной проверки при изменении свойств объектов нет необходимости вручную выполнить метод сохранения, когда транзакция будет поднята, изменения будут автоматически зафиксированы в базе данных.

Очистка кеша и грязная проверка

Когда транзакция будет зафиксирована, будет выполнена операция очистки кеша, и все постоянные объекты в сеансе будут проверены на наличие загрязнений. Кратко опишу процесс:

  1. Различные результаты запроса в транзакции кэшируются в соответствующем сеансе, а моментальный снимок сохраняется.
  2. Прежде чем транзакция будет зафиксирована, она будет вызванаsession.flush()Выполните очистку кеша и грязную проверку. Сравните объекты во всех сеансах с соответствующими снимками.Если есть изменения, объект грязный.
  3. Выполняйте такие операции, как обновление и удаление, чтобы синхронизировать измененные данные в сеансе с базой данных.

Включение транзакций только для чтения может заблокировать грязную проверку и повысить эффективность запросов.

Troubleshooting

  1. Проблема с 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»

  1. Spring boot JPA: решение неизвестного объекта

После инициализации объекта двумя фигурными скобками при вызове метода сохранения JPA будет выдано исключение Unknown entity, что вызвано неспособностью JPA правильно идентифицировать анонимные внутренние классы.

Решение. Вручную создайте новый объект, а затем вызовите метод set, чтобы присвоить значение.

Spring boot JPA: решение неизвестного объекта

  1. LazyInitializationException при использовании аннотации отношения

org.hibernate.LazyInitializationException: could not initialize proxy - no Session

Если вы используете реляционные аннотации Hibernate, вы можете столкнуться с этой проблемой из-за ленивой загрузки значений в объекте get после закрытия сеанса.

Решение:

  1. Добавление аннотаций к классам сущностей@Proxy(lazy = false)
  2. Добавьте методы уровня сервисов@Transactional, оставив управление сессиями весенним транзакциям

Суммировать

В этой статье в основном рассказывается об основах использования Spring Data JPA и немного личного опыта.

До сих пор ORM развивался от Hibernate до JPA, а теперь и Spring Data JPA. Видно, что это процесс непрерывного упрощения.В прошлом большой раздел xml исчез, и можно было сохранить только базовую строку sql. Хотя Spring Data JPA прост в настройке и использовании, поскольку его основа по-прежнему реализуется Hibernate, некоторые вещи все же необходимо понять. Что касается текущего использования, у меня есть следующие ощущения:

  1. Чтобы эффективно использовать Spring Data JPA, связанные с Hibernate механизмы по-прежнему должны иметь определенное понимание, например, вышеупомянутый цикл объявления объекта и механизм обновления сеанса. Если вы этого не понимаете, могут возникнуть необъяснимые проблемы.
  2. Если вы новичок, лично я не рекомендую использовать аннотации отношений. Сама технология постепенно упрощается, если она не очень сложная, например, ERP-система, то нет необходимости использовать нативные вещи JPA и Hibernate, вполне возможно несколько раз вручную запрашивать замену реляционных аннотаций. Причина этого в том, что существуют некоторые скрытые опасности из-за отсутствия глубокого понимания использования реляционных аннотаций JPA и типов различных каскадных операций.

использованная литература

приложение

Ключевые слова поддерживаемых методов

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(*)