Использование JPA в SpringBoot

задняя часть база данных Spring SQL

предисловие

Когда я впервые использовал Spring JPA, я почувствовал, что эта штука просто артефакт, почти не нужно было писать код о доступе к базе данных, и получилась базовая функция CURD. Давайте используем пример для описания основных операций, используемых в следующем JPA.

Создайте новый проект и добавьте зависимости

Создайте новый пустой проект SpringBoot в Intellij IDEA. Обратитесь к конкретным шагамПервое знакомство со SpringBoot. Согласно требованиям этого примера нам нужно добавить следующие три зависимости

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>6.0.6</version>
</dependency>

Подготовьте среду базы данных

Для этого проекта мы создадим новую базу данных springboot_jpa и авторизуем пользователя springboot.

create database springboot_jpa;

grant all privileges on springboot_jpa.* to 'springboot'@'%' identified by 'springboot';

flush privileges;

Конфигурация проекта

#通用数据源配置
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://10.110.2.56:3306/springboot_jpa?charset=utf8mb4&useSSL=false
spring.datasource.username=springboot
spring.datasource.password=springboot
# Hikari 数据源专用配置
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.minimum-idle=5
# JPA 相关配置
spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql=true
spring.jpa.hibernate.ddl-auto=create

Конфигурация источника данных перед этой статьей Ли и предыдущей статьей«Использование шаблона JDBC в SpringBoot»так же, как в. Следующие конфигурации должны быть объяснены

  1. spring.jpa.show-sql=true Настройте вывод информации о выполненном операторе SQL в журнал.
  2. Конфигурация spring.jpa.hibernate.ddl-auto=create указывает, что таблица, соответствующая классу сущностей, должна быть удалена и создана при запуске программы. Этот параметр опасен, поскольку он удалит соответствующую таблицу и перестроит ее. Поэтому не используйте его в среде сборки. Его можно использовать только один раз при инициализации структуры базы данных в тестовой среде.
  3. spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect . В SrpingBoot версии 2.0, когда Hibernate создавал таблицу данных, механизмом хранения базы данных по умолчанию был MyISAM (раньше это был InnoDB, что было странно). Этот параметр используется для переключения механизма хранения по умолчанию на InnoDB при создании таблицы.

Создайте первый класс сущности данных

Класс сущности базы данных является объектом POJO Bean. Здесь мы сначала создаем объект базы данных UserDO. Исходный код объекта базы данных выглядит следующим образом

package com.yanggaochao.springboot.learn.springbootjpalearn.security.domain.dao;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 * 用户实体类
 *
 * @author 杨高超
 * @since 2018-03-12
 */
@Entity
@Table(name = "AUTH_USER")
public class UserDO {
    @Id
    private Long id;
    @Column(length = 32)
    private String name;
    @Column(length = 32)
    private String account;
    @Column(length = 64)
    private String pwd;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAccount() {
        return account;
    }

    public void setAccount(String account) {
        this.account = account;
    }

    public String getPwd() {
        return pwd;
    }

    public void setPwd(String pwd) {
        this.pwd = pwd;
    }
}

в:

  1. @Entity — это обязательная аннотация, которая объявляет, что этот класс соответствует таблице базы данных.
  2. @Table(name = "AUTH_USER") — необязательная аннотация. Объявите информацию о таблице, соответствующую объекту базы данных. Включая имя таблицы, информацию об индексе и т. д. Здесь объявляется, что имя таблицы, соответствующее этому классу сущностей, — AUTH_USER. Если не указано, имя таблицы и имя сущности совпадают.
  3. Аннотация @Id объявляет атрибут, соответствующий уникальному идентификатору объекта.
  4. @Column(length = 32) Определение полей таблицы, используемых для объявления свойств объекта. Каждый атрибут сущности по умолчанию соответствует полю таблицы. Имя поля совпадает с именем свойства по умолчанию (не обязательно равно). Тип поля автоматически выводится из типа свойства объекта. Здесь главное объявить длину символьного поля. Если не указано, система будет использовать 255 в качестве длины поля.

Все вышеперечисленные конфигурации верны, тогда, запустив этот проект в это время, мы увидим в логе следующее содержимое:

Hibernate: drop table if exists auth_user
Hibernate: create table auth_user (id bigint not null, account varchar(32), name varchar(32), pwd varchar(64), primary key (id)) engine=InnoDB

Система автоматически создала для нас таблицу данных. Просмотр таблиц и структур таблиц в базе данных

springboot_jpa 表及表结构

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

Реализовать службу уровня сохраняемости

В мире Spring Data JPA реализация службы уровня сохраняемости — очень простое дело. Взяв в качестве примера вышеприведенный объект объекта UserDO, мы хотим реализовать службу уровня сохраняемости с функциями добавления, удаления, изменения и запроса, тогда мне нужно только объявить интерфейс, который наследует Подойдет интерфейс org.springframework.data.repository.Repository или его подинтерфейс. Для полной функциональности мы наследуем интерфейс org.springframework.data.jpa.repository.JpaRepository. где T — класс сущностей базы данных, а ID — первичный ключ класса сущностей базы данных. Затем просто добавьте аннотацию @Repository к этому интерфейсу, и все готово.

package com.yanggaochao.springboot.learn.springbootjpalearn.security.dao;


import com.yanggaochao.springboot.learn.springbootjpalearn.security.domain.dao.UserDO;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

/**
 * 用户服务数据接口类
 *
 * @author 杨高超
 * @since 2018-03-12
 */

@Repository
package com.yanggaochao.springboot.learn.springbootjpalearn.security.dao;


import com.yanggaochao.springboot.learn.springbootjpalearn.security.domain.dao.UserDO;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

/**
 * 用户服务数据接口类
 *
 * @author 杨高超
 * @since 2018-03-12
 */

@Repository
public interface UserDao extends JpaRepository<UserDO, Long> {
}

Не нужно писать ни строчки кода. Тогда для класса сущности UserDO у нас уже есть следующие функции

UserDao 保存实体功能

UserDao 保存实体删除功能

UserDao 查询实体删除功能

Например, мы используем следующий код для сохранения некоторых пользовательских сущностей в базе данных.

package com.yanggaochao.springboot.learn.springbootjpalearn;

import com.yanggaochao.springboot.learn.springbootjpalearn.security.dao.UserDao;
import com.yanggaochao.springboot.learn.springbootjpalearn.security.domain.dao.UserDO;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

import java.util.Optional;

@RunWith(SpringRunner.class)
@SpringBootTest
public class UserDOTest {

    @Autowired
    private UserDao userDao;

    @Before
    public void before() {
        UserDO userDO = new UserDO();
        userDO.setId(1L);
        userDO.setName("风清扬");
        userDO.setAccount("fengqy");
        userDO.setPwd("123456");
        userDao.save(userDO);
        userDO = new UserDO();
        userDO.setId(3L);
        userDO.setName("东方不败");
        userDO.setAccount("bubai");
        userDO.setPwd("123456");
        userDao.save(userDO);
        userDO.setId(5L);
        userDO.setName("向问天");
        userDO.setAccount("wentian");
        userDO.setPwd("123456");
        userDao.save(userDO);
    }
    @Test
    public void testAdd() {
        UserDO userDO = new UserDO();
        userDO.setId(2L);
        userDO.setName("任我行");
        userDO.setAccount("renwox");
        userDO.setPwd("123456");
        userDao.save(userDO);
        userDO = new UserDO();
        userDO.setId(4L);
        userDO.setName("令狐冲");
        userDO.setAccount("linghuc");
        userDO.setPwd("123456");
        userDao.save(userDO);
    }

    @After
    public void after() {
        userDao.deleteById(1L);
        userDao.deleteById(3L);
        userDao.deleteById(5L);
    }

}

Это должно использовать Junit для выполнения тестовых случаев. Аннотация @Before выполняет подготовленный код перед тестовым примером. Здесь сначала вставьте три информации о пользователе. Выполните этот тест, и когда он будет выполнен, посмотрите на базу данных, и вы увидите, что в базе данных есть 5 записей:

数据库记录

Мы также можем проверить правильность функции поиска объекта путем идентификации, функции запроса всех данных, функция запроса может даже выполнять сортировку и листание тестовых случаев.

    @Test
    public void testLocate() {
        Optional<UserDO> userDOOptional = userDao.findById(1L);
        if (userDOOptional.isPresent()) {
            UserDO userDO = userDOOptional.get();
            System.out.println("name = " + userDO.getName());
            System.out.println("account = " + userDO.getAccount());
        }
    }

    @Test
    public void testFindAll() {
        List<UserDO> userDOList = userDao.findAll(new Sort(Sort.Direction.DESC,"account"));
        for (UserDO userDO : userDOList) {
            System.out.println("name = " + userDO.getName());
            System.out.println("account = " + userDO.getAccount());
        }
    }

Можно видеть, что все, что мы сделали, это добавили информацию о конфигурации базы данных в проект SpingBoot, объявили объект сущности базы данных UserDO, а затем объявили интерфейс уровня сохраняемости, который наследуется от org.springframework.data.jpa.repository.JpaRepository интерфейс. Затем система автоматически имеет множество функций добавления, удаления, изменения и запроса. Функция запроса даже имеет возможность сортировки и разбиения на страницы.

В этом сила JPA. В дополнение к этим интерфейсам у пользователей будут другие потребности, и JPA также может удовлетворить ваши потребности.

Развернуть запрос

На приведенном выше снимке экрана «Функция удаления объекта запроса UserDao» мы видим, что функция запроса неудовлетворительна, и многие функции запроса, которые нам нужны, еще недоступны. Но не волнуйтесь. JPA имеет очень удобный и элегантный способ решения

Запрос по свойству

Если мы хотим сделать запрос на основе свойства объекта, мы можем сделать объявление интерфейса в интерфейсе UserDao. Например, если мы хотим выполнить запрос на основе атрибута учетной записи объекта (который может использоваться в функции входа в систему). Мы можем добавить объявление интерфейса в com.yanggaochao.springboot.learn.springbootjpalearn.security.dao.UserDao.

  UserDO findByAccount(String account);

Затем добавьте тестовый пример

@Test
    public void testFindByAccount() {
        UserDO userDO = userDao.findByAccount("wentian");
        if (userDO != null) {
            System.out.println("name = " + userDO.getName());
            System.out.println("account = " + userDO.getAccount());
        }
    }

После запуска он будет распечатан в журнале

name = 向问天
account = wentian

Этот метод очень мощный, он может поддерживать не только один атрибут, но и несколько комбинаций атрибутов. Например, если мы хотим найти интерфейс, учетная запись и пароль которого одновременно удовлетворяют условиям запроса. Затем объявляем в интерфейсе UserDao

UserDO findByAccountAndPwd(String account, String pwd);

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

List<UserDO> findAllByIdGreaterThan(Long id);

Эту структуру оператора можно проиллюстрировать следующей таблицей.

JPA 关键字说明

пользовательский запрос

Если вышеуказанная ситуация не соответствует потребностям. Затем мы можем решить эту проблему, импортировав аннотацию org.springframework.data.jpa.repository.Query. Например, если мы хотим запросить список всех пользователей, имена которых равны двум именам, мы можем объявить следующий интерфейс

@Query("SELECT O FROM UserDO O WHERE O.name = :name1  OR O.name = :name2 ")
List<UserDO> findTwoName(@Param("name1") String name1, @Param("name2") String name2);

Вот запрос, определенный в синтаксисе PQL. Два имени параметра имеют : в заявлении для принятия решения о следующем платеже

Если вы привыкли писать операторы SQL для завершения запроса, вы также можете реализовать его следующим образом.

@Query(nativeQuery = true, value = "SELECT * FROM AUTH_USER WHERE name = :name1  OR name = :name2 ")
List<UserDO> findSQL(@Param("name1") String name1, @Param("name2") String name2);

Здесь, добавив атрибут nativeQuery = true в аннотацию @Query, вы можете использовать собственные операторы SQL для написания запросов.

первичный ключ союза

Из определения интерфейса org.springframework.data.jpa.repository.JpaRepository первичный ключ объекта данных является отдельным объектом, тогда, если первичный ключ таблицы базы данных представляет собой комбинацию двух или более полей как это решить.

Давайте расширим предыдущий сценарий. Предположим, у нас есть объект Role с двумя атрибутами, идентификатор и имя, соответствующие таблице данных auth_role, и объект отношения пользователя роли, RoleUser, указывающий отношение между ролями и пользователями, и два атрибута roleId, userId, соответствующие auth_role_user. стол . Затем нам нужно объявить объект RoleDO следующим образом.

package com.yanggaochao.springboot.learn.springbootjpalearn.security.domain.dao;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;

/**
 * 角色实体类
 *
 * @author 杨高超
 * @since 2018-03-12
 */
@Entity
@Table(name = "AUTH_ROLE")
public class RoleDO {
    @Id
    private Long id;
    @Column(length = 32)
    private String name;
    @Column(length = 64)
    private String note;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getNote() {
        return note;
    }

    public void setNote(String note) {
        this.note = note;
    }
}

В случае, когда в качестве общего первичного ключа используется несколько атрибутов, обычно требуется создать отдельный класс первичного ключа, атрибуты которого совпадают с полями первичного ключа объекта базы данных.Для реализации интерфейса java.io.Serializable , объявление класса выглядит следующим образом

package com.yanggaochao.springboot.learn.springbootjpalearn.security.domain.dao;

import java.io.Serializable;

/**
 * 联合主键对象
 *
 * @author 杨高超
 * @since 2018-03-12
 */
public class RoleUserId implements Serializable {
    private Long roleId;
    private Long userId;
}

Точно так же мы объявляем объект RoleUserDO следующим образом.

package com.yanggaochao.springboot.learn.springbootjpalearn.security.domain.dao;

import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.Table;
import java.io.Serializable;

/**
 * 角色用户关系实体类
 *
 * @author 杨高超
 * @since 2018-03-12
 */
@Entity
@IdClass(RoleUserId.class)
@Table(name = "AUTH_ROLE_USER")
public class RoleUserDO  {
    @Id
    private Long roleId;
    @Id
    private Long userId;

    public Long getRoleId() {
        return roleId;
    }

    public void setRoleId(Long roleId) {
        this.roleId = roleId;
    }

    public Long getUserId() {
        return userId;
    }

    public void setUserId(Long userId) {
        this.userId = userId;
    }
}

Здесь, поскольку атрибуты класса объекта данных и класса первичного ключа объекта данных одинаковы, мы можем удалить класс первичного ключа объекта данных, а затем объявить класс первичного ключа класса объекта данных как самого себя. Конечно, вам придется реализовать интерфейс java.io.Serializable самостоятельно.

Таким образом, если мы хотим запросить список всех пользователей в роли, мы можем объявить следующий интерфейс

@Query("SELECT U FROM UserDO U ,RoleUserDO RU WHERE U.id = RU.userId AND RU.roleId = :roleId")
List<UserDO> findUsersByRole(@Param("roleId") Long roleId);

Разумеется, в этом случае мы увидим, что система автоматически создает таблицы AUTH_ROLE и AUTH_ROLE_USER. Структура их таблицы выглядит так

auth_role 和 auth_role_user 表结构

Обратите внимание, что в таблице auth_role_user имя атрибута userId преобразуется в user_id, а roleId — в role_id.

Если мы хотим реализовать вышеуказанные функции в виде операторов SQL, то мы изменим объявление интерфейса на следующую форму.

@Query("SELECT U.* FROM AUTH_USER U ,AUTH_ROLE_USER RU WHERE U.id = RU.user_id AND RU.role_id = :roleId")
List<UserDO> findUsersByRole(@Param("roleId") Long roleId);

постскриптум

Этот пример в основном описывает некоторые детали использования JPA. Мы видим, что. Очень удобно и быстро использовать JPA для выполнения функций добавления, удаления, изменения и запроса реляционных баз данных. Весь код загружен в репозиторий github.springboot-jpa-learnна

Первоначально опубликовано вкороткая книга