这里使用 IDEA 来进行工程的创建,使用了 Gradle 对整个项目进行管理,具体的过程如下:
点击 Create New Project -> Spring Initializr , 之后选择默认的 Initalizr Service URL ,然后填写项目的信息,如下所示:
这里采用了 Gradle 来进行项目的管理,然后就是对 SpringBoot 进行选择,如下:
| 依赖组件 | 含义 |
|---|---|
| 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
复制代码
这里主要是对 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 仓库,可以提高访问速度(位置靠上的仓库优先访问)。
SpringData JPA 可以在项目启动的时候会自动的数据库中的表,因此不必提前初始化数据库,只需在配置文件中配置数据源即可,当然这种自动生成表的方法是不允许在生产环境中使用的,具体的配置如下:
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 | 如果不配置该项,默认不对数据库做任何改动 |
在设计数据库的时候,不同的表可能具有相同的字段,用来存储一些通用的信息,例如每一张表中都会有 id 字段作为主键,同时存在 create_time 、 update_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 中显示的继承,具体的用法可以继续向下看。
首先定义用户角色的枚举:
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 注解。
SpringBoot JPA 可以将接口中的方法名解析为相应的 SQL 语句,但同时针对一些常见的 CRUD 操作提供了一个 CrudRepository 的接口,通过继承可以实现一些常见的 CRUD 操作,当然如果 CrudRepository 提供的操作不能满足业务的需求可以编写新的接口,如下,定义了一个新的分页查询的接口:
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);
}
复制代码
当你已经完成上述的操作后,此时的项目的结构如下所示:
../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 无关,在下载源码之后大家可以自行查看即可。
接下来就可以编写 Service 和 Controller 来对之前的代码进行测试:
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);
}
}
复制代码
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();
}
}
复制代码
然后就是启动项目,开始测试:
同时在启动的过程中通过观察日志输出可以看到 SpringData JPA 会在项目启动的过程中删除掉数据库的旧表,然后重新建表:
对于 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 就可以直接转跳到文件中去。
点击测试之后会显示如下,可以看到测试的返回结果是成功了,然后我们去查看数据库中的信息,可以看到数据库中已经插入了一条数据:
然后运行第二个测试,看分页查询是否成功: