转载

SpringData JPA - 1: 基本使用

这里使用 IDEA 来进行工程的创建,使用了 Gradle 对整个项目进行管理,具体的过程如下:

1.1 创建新项目

点击 Create New Project -> Spring Initializr , 之后选择默认的 Initalizr Service URL ,然后填写项目的信息,如下所示:

SpringData JPA - 1: 基本使用
SpringData JPA - 1: 基本使用

这里采用了 Gradle 来进行项目的管理,然后就是对 SpringBoot 进行选择,如下:

SpringData JPA - 1: 基本使用
依赖组件 含义
Spring Boot DevTools 热部署插件,可以在项目运行时自动替换 class,生产环境禁用
Lombok 简化 Java 代码
Spring Web Starter Spring Web 开发的起步依赖
Spring Data JPA 持久层 ORM 框架
MySQL Driver MySQL 驱动

做完这一步之后就是选择项目的存储位置。全部完成之后项目的结构如下所示:

../blog/
├── blog.iml
├── build.gradle
├── .gradle
└── src
    ├── main
    │   ├── java
    │   └── resources
    └── test
        └── java
复制代码

1.2 对项目进行一些小小的改进

这里主要是对 Gradle 的配置做出小小的改动,在 repositories 节点下增加新的仓库配置,具体如下:

plugins {
    id 'org.springframework.boot' version '2.1.6.RELEASE'
    id 'java'
}

apply plugin: 'io.spring.dependency-management'

group = 'hk.mars'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '11'

configurations {
    developmentOnly
    runtimeClasspath {
        extendsFrom developmentOnly
    }
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    maven { url "https://maven.aliyun.com/repository/spring-plugin" }
    maven { url "https://maven.aliyun.com/repository/spring" }
    maven { url "https://repo.spring.io/libs-release" }
    maven { url "https://repo.spring.io/milestone" }
    mavenLocal()
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    developmentOnly 'org.springframework.boot:spring-boot-devtools'
    runtimeOnly 'mysql:mysql-connector-java'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
复制代码

这里需要注意的地方是 Gradle 在初始化的时候速度略慢,甚至可能会失败,主要是 Gradle 在初始化时会下载一个 .zip 文件,受到国内网络大环境的影响,该网站响应较慢,不过对此网络上有很多解决的办法。

同时国内访问 Maven 中央仓库也受到网络大环境的影响比较慢,这里设置了阿里云提供的 Maven 仓库,可以提高访问速度(位置靠上的仓库优先访问)。

2. 数据库的配置

SpringData JPA 可以在项目启动的时候会自动的数据库中的表,因此不必提前初始化数据库,只需在配置文件中配置数据源即可,当然这种自动生成表的方法是不允许在生产环境中使用的,具体的配置如下:

application.properties

spring.datasource.url=jdbc:mysql://x.x.x.x:3306/test?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
spring.datasource.username=root
spring.datasource.password=xxxxxx
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

spring.jpa.properties.hibernate.hbm2ddl.auto=create
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5InnoDBDialect
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
复制代码

这里需要进一步说明的是 spring.jpa.properties.hibernate.hbm2ddl.auto 的含义:

前文提到过 SpringBoot JPA 在项目启动的时候会自动的在数据库中生成相应的表,该项配置就是设置自动生成的策略,一个有四个选项,分别如下:

生成策略 含义
create 每次加载 hibernate 时都会重新生成表
create-drop 每次加载 hibernate 时都会重新生成表,在 sessionFactory 关闭时删除表结构
update 第一次加载 hibernate 时都会重新生成表,之后每次加载 hibernate 会更新表结构
validate 验证模型与数据库表结构是否匹配
none 如果不配置该项,默认不对数据库做任何改动

3.code

3.1BaseEntity.java: 模型超类

在设计数据库的时候,不同的表可能具有相同的字段,用来存储一些通用的信息,例如每一张表中都会有 id 字段作为主键,同时存在 create_timeupdate_time 用来存储表中每一行数据的通用信息,这时候就需要定义一个超类来表示这些信息。 这一功能可以使用 @MappedSuperclass 注解实现,具体实现如下的 BaseEntity.java

package hk.mars.user;

import lombok.Data;
import org.hibernate.annotations.GenericGenerator;

import javax.persistence.Column;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.MappedSuperclass;
import java.sql.Timestamp;

/**
 * @author qingke.hk@gmmail.com
 */
@MappedSuperclass
@Data
public abstract class BaseEntity {
    @Id
    @GeneratedValue(generator = "id-uuid")
    @GenericGenerator(name = "id-uuid", strategy = "uuid")
    private String id;

    @Column(name = "create_time")
    private Timestamp createTime;

    @Column(name = "update_time")
    private Timestamp updateTime;
}
复制代码

@Data 注解是 Lombok 提供的注解,可以简化 Java 代码。

@Id 注解一般是用在属性上,表示该属性对应的数据库表字段为主键类型,同时可以使用 @GeneratedValue@GenericGenerator 指定主键生成策略,在上文使用了内置的 uuid 的生成策略,但 hibernate 还提供了替他的策略,同时还支持自定义的主键生成策略。

@MappedSuperclass 注解是 SpringData JPA 提供的注解,主要是用于用于类上,表示该类是所有的 Entity 的父类,当然你要在你的 Entity 中显示的继承,具体的用法可以继续向下看。

  • todo: 写一篇 hibernate 的主键生成策略相关的博客
  • todo: 写一篇 Lombok 的博客

3.2UserEntity.java: 用户模型

首先定义用户角色的枚举:

package hk.mars.user;

import lombok.Getter;

/**
 * @author qingke.hk@gmmail.com
 */
public enum UserRole {
    /** 管理员角色 */
    ADMIN("admin"),

    /** 用户角色 */
    USER("user");
    @Getter
    private String role;

    UserRole(String role) {
        this.role = role;
    }
}
复制代码

@Getter 是有 Lombok 提供的,可以在编译的时候自动生成 Getter 方法。

接下来我们将在继承 BaseEntity 的基础之上定义用户模型,如下 UserEntity.java 所示:

package hk.mars.commons;

import hk.mars.commons.BaseEntity;
import lombok.Data;
import lombok.EqualsAndHashCode;
import org.springframework.format.annotation.DateTimeFormat;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.Table;
import java.util.Date;

/**
 * @author qingke.hk@gmmail.com
 */
@Data
@Entity
@Table(name = "user")
@EqualsAndHashCode(callSuper = false)
@SuppressWarnings("WeakerAccess")
public class UserEntity extends BaseEntity {

    private String name;

    @Column(name = "first_name")
    private String firstName;

    @Column(name = "last_name")
    private String lastName;

    @DateTimeFormat(pattern = "yyyy-mm-dd")
    private Date birthday;

    private String email;

    private String password;

    @Enumerated(EnumType.STRING)
    private UserRole role;
}
复制代码

@Data@EqualsAndHashCode 注解是有 Lombok 提供,用来简化 Java 代码。

@Entity 注解是最主要的一个注解,表示这个类表示数据库中的一个表。

@Table 注解主要是声明表的配置,例如上文可以设置表名,当然还可以设置 数据库的名称、设置索引等等。

@Column 注解主要是对数据库表字段与 Java 类属性之间的关系进行配置,上文仅仅是设置了字段名称,同时还可以设施是否唯一、长度限制、是否可控等等。

@Enumerated 表示属性是一个注解类型,但是在存储的时候字符串形式进行存储。

@DateTimeFormat 注解是有 Spring 提供的,在这里因为 birthday 是时间类型,而调用 RESTful 接口的时候是以 JSON 形式,如果不走任何处理是无法将 JSON 字符串转化为时间类型的,因此我们在这里使用了 @DataTimeFormat 注解。

  • todo: 后面写一些关于 SpringData JPA 的更多的用法。

3.3UserRepository.java: 数据库操作

SpringBoot JPA 可以将接口中的方法名解析为相应的 SQL 语句,但同时针对一些常见的 CRUD 操作提供了一个 CrudRepository 的接口,通过继承可以实现一些常见的 CRUD 操作,当然如果 CrudRepository 提供的操作不能满足业务的需求可以编写新的接口,如下,定义了一个新的分页查询的接口:

UserRepository.java

package hk.mars.user;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.repository.CrudRepository;

/**
 * @author qingke.hk@gmmail.com
 */
public interface UserRepository extends CrudRepository<UserEntity, String> {
    /**
     * 查询所有的用户信息 - 分页
     *
     * @param pageConf 分页参数
     * @return 用户信息
     */
    Page<UserEntity> findAll(Pageable pageConf);
}
复制代码

3.4 此时项目的结构

当你已经完成上述的操作后,此时的项目的结构如下所示:

../blog/
├── blog.iml
├── build.gradle
└── src
    ├── main
    │   ├── java
    │   │   └── hk.mars
    │   │       ├── Application.java
    │   │       ├── commons
    │   │       │   ├── BaseEntity.java
    │   │       │   └── response
    │   │       │       ├── ResultResponse.java
    │   │       │       └── ResultResponseBuilder.java
    │   │       ├── config
    │   │       └── user
    │   │           ├── UserController.java
    │   │           ├── UserEntity.java
    │   │           ├── UserRepository.java
    │   │           ├── UserRole.java
    │   │           └── UserService.java
    │   └── resources
    │       └── application.properties
    └── test
        └── java
            └── hk
                └── mars
复制代码

当然此时的项目代码多了一些别的东西,不过都与 SpringData JPA 无关,在下载源码之后大家可以自行查看即可。

4. 测试

4.1 Controller 与 Service

接下来就可以编写 Service 和 Controller 来对之前的代码进行测试:

UserService.java:

package hk.mars.user;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;

import java.util.List;

/**
 * @author qingke.hk@gmmail.com
 */
@SuppressWarnings("WeakerAccess")
@Service
public class UserService {

    private final UserRepository userRepository;

    public UserService(UserRepository userRepository) {
        this.userRepository = userRepository;
    }

    public List<UserEntity> getUserList(Pageable pageConf) {
        Page<UserEntity> userInPage = userRepository.findAll(pageConf);
        return userInPage.getContent();
    }

    public void saveUser(UserEntity userEntity) {
        String password = userEntity.getPassword();
        userEntity.setPassword(DigestUtils.md5DigestAsHex(password.getBytes()));

        this.userRepository.save(userEntity);
    }
}
复制代码

UserController.java:

package hk.mars.user;

import hk.mars.commons.response.ResultResponse;
import hk.mars.commons.response.ResultResponseBuilder;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.*;

import java.util.List;

/**
 * @author qingke.hk@gmmail.com
 */
@RestController
public class UserController {
    private static final int DEFAULT_PAGE_NUMS = 10;

    private final UserService userService;

    public UserController(UserService userService) {
        this.userService = userService;
    }

    @GetMapping("/user")
    public ResultResponse<List<UserEntity>> getUsers(
            @RequestParam(value = "page-num", defaultValue = "0") Integer pageNum,
            @RequestParam(value = "page-size", defaultValue = "10") Integer pageSize) {

        Pageable pageRequest = PageRequest.of(pageNum, pageSize == null ? DEFAULT_PAGE_NUMS : pageSize);
        List<UserEntity> userList = this.userService.getUserList(pageRequest);
        return ResultResponseBuilder.buildSuccess(userList);
    }

    @PostMapping("/user")
    public ResultResponse saveUser(UserEntity user) {
        if (user == null) {
            return ResultResponseBuilder.buildError("use information is null");
        }

        this.userService.saveUser(user);
        return ResultResponseBuilder.buildSuccess();
    }
}
复制代码

4.2 启动项目

然后就是启动项目,开始测试:

SpringData JPA - 1: 基本使用

同时在启动的过程中通过观察日志输出可以看到 SpringData JPA 会在项目启动的过程中删除掉数据库的旧表,然后重新建表:

SpringData JPA - 1: 基本使用

4.3 进行测试

对于 RESTful 风格的接口可以通过 IDEA 自带的功能进行测试,可以新建一个 rest-api.http 文件,通过该文件就可以直接在 IDEA 中进行测试,文件内容如下:

POST http://localhost:8080/user?name=Tom&password=123456&birthday=2019-07-11&email=test@test.test
Accept: */*
Cache-Control: no-cache

###
GET http://localhost:8080/user?page-num=0&page-size=10
Accept: */*
Cache-Control: no-cache
复制代码

通过点击左侧边栏上的三角标志就可以启动一次测试,同时 IDEA 会将测试结果保存为一个 JSON 文件,这样就可以查看测试的历史,查看的方法是在下图的 2019-07-14T125418.200.json 使用快捷键 Command + B 就可以直接转跳到文件中去。

SpringData JPA - 1: 基本使用

点击测试之后会显示如下,可以看到测试的返回结果是成功了,然后我们去查看数据库中的信息,可以看到数据库中已经插入了一条数据:

SpringData JPA - 1: 基本使用
SpringData JPA - 1: 基本使用

然后运行第二个测试,看分页查询是否成功:

SpringData JPA - 1: 基本使用
原文  https://juejin.im/post/5d29ecd4e51d454fd8057c20
正文到此结束
Loading...