这里使用 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 就可以直接转跳到文件中去。
点击测试之后会显示如下,可以看到测试的返回结果是成功了,然后我们去查看数据库中的信息,可以看到数据库中已经插入了一条数据:
然后运行第二个测试,看分页查询是否成功: