我是小先,一個專注大數據、分布式技術的非斜槓青年,愛Coding,愛閱讀、愛攝影,更愛生活!
大數據小先博客主頁:https://me.csdn.net/u010974701
原始碼倉庫:https://github.com/zhshuixian/learn-spring-boot-2
上一小節主要介紹了 Spring Boot 2.x 實戰--整合 Log4j2 與 Slf4j 實現日誌列印和輸出到文件。
在應用開發中,難免要和資料庫打交道,在 Java 生態中,常用的開源持久層框架有 MyBatis 、Hibernate 等,這裡要說明一點的是,《Spring Boot 2.X》實戰的示例項目將主要使用 MyBatis 或者 MyBatis-Plus。
MyBatis 是一款優秀的持久層框架,它支持定製化 SQL、存儲過程以及高級映射。MyBatis 避免了幾乎所有的 JDBC 代碼和手動設置參數以及獲取結果集。
這一小節將以用戶信息表的為例子實戰 Spring Data JPA 連接 SQL 資料庫並讀寫數據,主要分為如下幾個部分:
JPA 的依賴引入
JPA 連結 MySQL
JPA 實體 @Entity、@Id
JPA 寫入、更新、刪除、查詢數據
JPA 多條記錄寫入、查詢、分頁查詢
JPA 自定義 SQL 查詢
這裡使用 MySQL,如果你想使用如:PostgreSQL 等其他的資料庫,只需要更改相對應的依賴和指定 Driver 驅動包即可。
這裡需要你提前安裝好 MySQL 或其他 SQL 資料庫。
參考文章在 Linux 下安裝 MySQL 8 : https://blog.csdn.net/u010974701/article/details/85625228
安裝完成後運行如下命令:
1create database spring;
JPA(Java Persistence API),中文名稱為 Java 持久化 API,從 JDK 5 開始引入,是 ORM 的標準 Java 規範。JPA 主要是為了簡化 Java 持久層應用的開發,整合像 Hibernate、TopLink、JDO 等 ORM 框架,並不提供具體實現。
JPA 的一些優點特性:
標準化 :標準的 JPA 規範提供的接口 / 類,幾乎不用修改代碼就可以遷移到其他 JPA 框架。
簡單易用:使用註解的方式定義 Java 類和關係資料庫之間的映射,無需 XML 配置。
遷移方便:更改資料庫、更換 JPA 框架幾乎不用修改代碼。
高級特性:媲美 JDBC 的查詢能力;可以使用面向對象的思維來操作資料庫;支持大數據集、事務、並發等容器級事務;
JPA 主要的技術:
ORM:使用註解或者 XML 描述對象和數據表的映射關係
API:規範的 JPA 接口、類。
JPQL:面向對象的查詢語言,避免程序和具體 SQL 緊密耦合。
Spring Data JPA 是 Spring Data 的子集,默認使用 Hibernate 作為底層 ORM。官網文檔 https://spring.io/projects/spring-data-jpa 是這麼介紹 :
Spring Data JPA, part of the larger Spring Data family, makes it easy to easily implement JPA based repositories. This module deals with enhanced support for JPA based data access layers. It makes it easier to build Spring-powered applications that use data access technologies.
Spring 對 JPA 的支持非常強大,使得 JPA 的配置更加靈活;將 EntityManager 的創建與銷毀、事務管理等代碼抽取出來統一管理;實現了部分 EJB 的功能,如容器注入支持。Spring Data JPA 則更進一步,簡化業務代碼,我們只需要聲明持久層的接口即可,剩下的則交給框架幫你完成,其使用規範的方法名,根據規範的方法名確定你需要實現什麼樣的數據操作邏輯。
擴展閱讀 :根據方法名自動生成 SQL 規則可以參考微笑哥的博客:http://ityouknow.com/springboot/2016/08/20/spring-boot-jpa.html
使用 Spring Data JPA,你只需要編寫 Repository 接口,依照規範命名你的方法即可。2、Spring Data JPA 的配置Spring Data JPA 如何引入依賴和連結 SQL 數據。2.1、依賴引入在 IDEA 新建一個項目 02-sql-spring-data-jpa,勾選如下的依賴,相關依賴下載慢的話可以更換國內的鏡像源:
New ProjectGradle 依賴配置
1dependencies {
2 implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
3 implementation 'org.springframework.boot:spring-boot-starter-web'
4 compileOnly 'org.projectlombok:lombok'
5 runtimeOnly 'mysql:mysql-connector-java'
6 annotationProcessor 'org.projectlombok:lombok'
7 testImplementation('org.springframework.boot:spring-boot-starter-test') {
8 exclude group: 'org.junit.vintage', module: 'junit-vintage-engine'
9 }
10}
1<dependencies>
2 <dependency>
3 <groupId>org.springframework.boot</groupId>
4 <artifactId>spring-boot-starter-data-jpa</artifactId>
5 </dependency>
6 <dependency>
7 <groupId>org.springframework.boot</groupId>
8 <artifactId>spring-boot-starter-web</artifactId>
9 </dependency>
10 <dependency>
11 <groupId>mysql</groupId>
12 <artifactId>mysql-connector-java</artifactId>
13 <scope>runtime</scope>
14 </dependency>
15 <dependency>
16 <groupId>org.projectlombok</groupId>
17 <artifactId>lombok</artifactId>
18 <optional>true</optional>
19 </dependency>
20 <dependency>
21 <groupId>org.springframework.boot</groupId>
22 <artifactId>spring-boot-starter-test</artifactId>
23 <scope>test</scope>
24 <exclusions>
25 <exclusion>
26 <groupId>org.junit.vintage</groupId>
27 <artifactId>junit-vintage-engine</artifactId>
28 </exclusion>
29 </exclusions>
30 </dependency>
31 </dependencies>
編輯 /src/main/resources/application.properties 文件,寫入如下內容:
1# 資料庫 URL、用戶名、密碼、JDBC Driver更換資料庫只需更改這些信息即可
2# MySQL 8 需要指定 serverTimezone 才能連接成功
3spring.datasource.url=jdbc:mysql://localhost:3306/spring?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC
4spring.datasource.password=xiaoxian
5spring.datasource.username=root
6spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
7# Hibernate 的一些配置
8spring.jpa.database-platform=org.hibernate.dialect.MySQL5InnoDBDialect
9# 是否在 Log 顯示 SQL 執行語句
10spring.jpa.show-sql=true
11# hibernate.ddl-auto 配置對資料庫表的操作
12# create和create-drop:每次都刪掉 Entity 對應的數據表並重新創建
13# update : 根據 Entity 更新數據表結構,不會刪除數據表
14# none: 默認值,不做任何操作,實際中推薦使用這個
15spring.jpa.hibernate.ddl-auto=none
特別說明的是,spring.jpa.hibernate.ddl-auto 使用 create 模式可以方便的自動根據 @Entity 註解的類自動生成對應的數據表,但實際開發中不建議使用,不然重新運行項目就是一次刪(pao)庫(lu)。
1# create 模式的一些日誌
2Hibernate: drop table if exists sys_user
3Hibernate: create table sys_user (user_id bigint not null, user_address varchar(128), user_age integer, username varchar(16), primary key (user_id)) engine=InnoDB
項目按功能分為如下三層:
API 接口層:提供 RESTful API 接口,是系統對外的交互的接口。
接口服務層:應用的主要邏輯部分,不推薦在 API 接口層寫應用邏輯。
數據持久層:編寫相應的 Repository 接口,實現與 MySQL 資料庫的交互。
這裡使用一個用戶信息表作為示例,結構如下所示:
欄位名欄位類型備註user_idbigint主鍵,自增usernamevarchar(18)用戶名,非空唯一nicknamevarchar(36)用戶暱稱,非空user_agetinyint用戶年齡user_sexvarchar(2)用戶性別SQL:如果是 create、update 模式,在代碼運行的時候自動生成 1-- MySQL 資料庫,其他資料庫可能需要自行修改
2create table sys_user
3(
4 user_id bigint auto_increment,
5 username varchar(18) not null,
6 nickname varchar(36) not null,
7 user_age tinyint null,
8 user_sex varchar(2) null,
9 constraint sys_user_pk
10 primary key (user_id)
11);
1@Entity
2@Getter
3@Setter
4@Table(name = "sys_user",schema = "spring")
5public class SysUser {
6 @Id
7 @GeneratedValue(strategy=GenerationType.IDENTITY)
8 private Long userId;
9
10 @Column(length = 18,unique = true,nullable = false,name = "username",updatable = true)
11 @NotEmpty(message = "用戶名不能為空")
12 @Pattern(regexp = "^[a-zA-Z0-9]{3,16}$", message = "用戶名需3到16位的英文,數字")
13 private String username;
14
15 @Column(length = 18,nullable = false)
16 @NotEmpty(message = "用戶暱稱不能為空")
17 private String nickname;
18
19 @Range(min=0, max=100,message = "年齡需要在 0 到 100 之間")
20 private Integer userAge;
21
22 // userSex 會自動映射到 user_sex 的欄位名
23 @Column(length = 2)
24 private String userSex;
25}
代碼解析:
@Entity:表明這是一個實體類,在 JPA 中用於註解 ORM 映射類。
@Table(name = "sys_user", schema = "spring"):註明 ORM 映射類對應的表名,默認是類名。如:SysUser 映射為 sys_user,Java 駝峰法命名映射到 SQL 時,用下劃線 _ 隔開,欄位名也是同樣的規則。schema 指定資料庫,默認就是資料庫連接配置中指定的。
@Id:主鍵註解。
@GeneratedValue(strategy=GenerationType.IDENTITY):指定主鍵生成策略。@Column(length = 18,unique = true,nullable = false,name = " ", updatable = true):指定欄位的長度、是否唯一、是否可以 null、欄位名,是否可以更新這個欄位。其中默認非唯一約束、可以為 null 值,欄位名默認根據名稱規則映射,updateable 默認 true。
@NotEmpty(message = " "):不能為空,message 表示 null 或者字符長度為 0 時候的提示信息。
@Pattern:正則表達式,例如你可以用來驗證用戶名、密碼是否符合規範。
@Range:指定最大值和最小值,例如指定分數最大是 100。
3.2、編寫 JpaRepository 接口新建 repository 包,新建 SysUserRepository 接口,你並不需要編寫其他代碼,就已經能夠基本對資料庫進行 CURD 了:1import org.springframework.data.jpa.repository.JpaRepository;
2import org.springframework.stereotype.Repository;
3import org.xian.boot.entity.SysUser;
4
5@Repository
6public interface SysUserRepository extends JpaRepository<SysUser, Long> {
7// JpaRepository<SysUser, Long> ,第一個參數指定 Entity 實體類,第二個指定主鍵類型
8}
MyResponse:通用消息返回類,增加、刪除、修改操作是否成功和信息返回的類:
1@Getter
2@Setter
3@NoArgsConstructor
4@AllArgsConstructor
5@ToString
6public class MyResponse implements Serializable {
7 private static final long serialVersionUID = -2L;
8 private String status;
9 private String message;
10}
1package org.xian.boot.service;
2import org.springframework.stereotype.Service;
3import org.xian.boot.MyResponse;
4import org.xian.boot.entity.SysUser;
5import org.xian.boot.repository.SysUserRepository;
6import javax.annotation.Resource;
7
8@Service
9public class SysUserService {
10 @Resource
11 private SysUserRepository sysUserRepository;
12
13 /**
14 * 保存一條記錄
15 * @param sysUser 用戶信息
16 * @return 保存結果
17 */
18 public MyResponse save(SysUser sysUser) {
19 try {
20 sysUserRepository.save(sysUser);
21 return new MyResponse("success", "新增成功");
22 } catch (Exception e) {
23 return new MyResponse("error", e.getMessage());
24 }
25 }
26}
代碼解析:
@Service:定義一個 Bean ,此註解的類會自動註冊到 Spring 容器。
@Resource:相對於 @Autowired 註解,Bean 的自動裝配。
在上文 SysUserRepository 接口中,save() 這個方法繼承自 CrudRepository,部分源碼如下: 1package org.springframework.data.repository;
2@NoRepositoryBean
3public interface CrudRepository<T, ID> extends Repository<T, ID> {
4 /**
5 * Saves a given entity. Use the returned instance for further operations as the save operation might have changed the
6 * entity instance completely.
7 *
8 * @param entity must not be {@literal null}.
9 * @return the saved entity; will never be {@literal null}.
10 * @throws IllegalArgumentException in case the given {@literal entity} is {@literal null}.
11 */
12 <S extends T> S save(S entity);
13}
1@RestController
2@RequestMapping(value = "/api/user")
3public class SysUserController {
4 @Resource
5 private SysUserService sysUserService;
6
7 @PostMapping(value = "/save")
8 public MyResponse save(@RequestBody SysUser sysUser) {
9 return sysUserService.save(sysUser);
10 }
11}
1{
2 "username":"xiaoxian",
3 "nickname":"小先哥哥",
4 "userAge":17,
5 "userSex":"男"
6}
功能,根據用戶名 username 查詢用戶信息,SysUserRepository 類中新增:
1 /**
2 * 根據用戶名查詢用戶信息
3 *
4 * @param username 用戶名
5 * @return 用戶信息
6 */
7 SysUser findByUsername(String username);
1 public SysUser find(String username) {
2 return sysUserRepository.findByUsername(username);
3 }
1 @PostMapping(value = "/find")
2 public SysUser find(@RequestBody String username) {
3 return sysUserService.find(username);
4 }
目標,實現一個根據用戶名更改用戶信息的接口,SysUserService 新增:
1 public MyResponse update(SysUser sysUser) {
2 // 實際開發中,需要根據具體業務來寫相應的業務邏輯,這裡只是做一個示例
3 try {
4 // 需要先根據用戶名 username 查詢出主鍵,然後再使用 save 方法更新
5 SysUser oldSysUser = sysUserRepository.findByUsername(sysUser.getUsername());
6 sysUser.setUserId(oldSysUser.getUserId());
7 sysUserRepository.save(sysUser);
8 return new MyResponse("success", "更新成功");
9 } catch (Exception e) {
10 return new MyResponse("error", e.getMessage());
11 }
12 }
1 @PostMapping(value = "/update")
2 public MyResponse update(@RequestBody SysUser sysUser){
3 return sysUserService.update(sysUser);
4 }
目標,實現一個 API 接口,實現對用戶信息的刪除,SysUserService 新增:
1 public MyResponse delete (String username){
2 try {
3 SysUser oldSysUser = sysUserRepository.findByUsername(username);
4 sysUserRepository.delete(oldSysUser);
5 return new MyResponse("success", "刪除成功");
6 } catch (Exception e) {
7 return new MyResponse("error", e.getMessage());
8 }
9 }
1 @PostMapping(value = "/delete")
2 public MyResponse delete(@RequestBody String username){
3 return sysUserService.delete(username);
4 }
多條記錄的寫入跟單條記錄的寫入差不多。SysUserService 新增:
1 public MyResponse saveAll(List<SysUser> sysUserList) {
2 try {
3 sysUserRepository.saveAll(sysUserList);
4 return new MyResponse("success", "新增成功");
5 } catch (Exception e) {
6 return new MyResponse("error", e.getMessage());
7 }
8 }
1 @PostMapping(value = "/saveAll")
2 public MyResponse saveAll(@RequestBody List<SysUser> sysUserList) {
3 return sysUserService.saveAll(sysUserList);
4 }
1 public List<SysUser> list(){
2 return sysUserRepository.findAll();
3 }
SysUserController 新增:
1 @GetMapping(value = "list")
2 public List<SysUser> list(){
3 return sysUserService.list();
4 }
重新運行,使用 Postname GET 方式訪問 http://localhost:8080/api/user/list ,可以看到返回資料庫表中所有的數據。
3.6、分頁瀏覽在 3.5 小節中,使用此方式查詢的是全部數據,對於數據量大的表來說非常不方便,這裡將實現一個分頁瀏覽的 API 接口:
SysUserService 新增:
1 public Page<SysUser> page(Integer page, Integer size) {
2 // 根據 userId 排序,Sort.Direction.ASC/DESC 升序/降序
3 Pageable pageable = PageRequest.of(page, size, Sort.Direction.ASC, "userId");
4 return sysUserRepository.findAll(pageable);
5 }
SysUserController 新增:
1 @PostMapping(value = "page")
2 public Page<SysUser> page(@RequestParam(defaultValue = "0") Integer page, @RequestParam(defaultValue = "3") Integer size) {
3 // page 從 0 開始編號
4 // 默認瀏覽第一頁,每頁大小為 3
5 return sysUserService.page(page, size);
6 }
重新運行,使用 Postname 方式訪問 http://localhost:8080/api/user/page?page=1&size=2 ,可以看到返回如下所示的數據:
1{ // content 結果集
2 "content": [
3 {
4 "userId": 12,
5 "username": "zhang",
6 "nickname": "張小先",
7 "userAge": 23,
8 "userSex": "男"
9 },
10 {
11 "userId": 16,
12 "username": "daxian",
13 "nickname": "大先哥哥",
14 "userAge": 19,
15 "userSex": "男"
16 }
17 ],
18 "pageable": {
19 // 排序信息
20 "sort": {
21 "sorted": true,
22 "unsorted": false,
23 "empty": false
24 },
25 "offset": 2,
26 "pageSize": 2, // 每頁數據集大小
27 "pageNumber": 1, // 頁數
28 "paged": true,
29 "unpaged": false
30 },
31 "totalElements": 5, // 總數據量
32 "last": false, // 是否最後一頁
33 "totalPages": 3, // 總頁數
34 "size": 2, // 每頁數據集大小
35 "number": 1, // 當前頁數
36 "sort": {
37 "sorted": true,
38 "unsorted": false,
39 "empty": false
40 },
41 "numberOfElements": 2, // content 內容的數量
42 "first": false, // 是否第一頁
43 "empty": false // content 內容是否為空
44}
1 /**
2 * 根據用戶暱稱查詢用戶信息 等價於 findByNicknameLike
3 *
4 * @param nickname 用戶暱稱
5 * @param pageable 分頁
6 * @return 用戶信息
7 */
8 @Query("SELECT sysUser from SysUser sysUser where sysUser.nickname like %:nickname%")
9 Page<SysUser> searchByNickname(@Param("nickname") String nickname, Pageable pageable);
10
11 /**
12 * 根據用戶暱稱查詢用戶信息 和 searchByNickname 等價
13 *
14 * @param nickname 用戶暱稱
15 * @param pageable 分頁
16 * @return 用戶信息
17 */
18 Page<SysUser> findByNicknameLike(@Param("nickname") String nickname, Pageable pageable);
SysUserService 新增:
1 public Page<SysUser> searchByNickname(String nickname, Integer page, Integer size) {
2 // 根據 userId 排序
3 Pageable pageable = PageRequest.of(page, size, Sort.Direction.ASC, "userId");
4 return sysUserRepository.searchByNickname(nickname,pageable);
5 }
SysUserController 新增:
1 @PostMapping(value = "search")
2 public Page<SysUser> search(@RequestParam String nickname, @RequestParam(defaultValue = "0") Integer page, @RequestParam(defaultValue = "3") Integer size) {
3 return sysUserService.searchByNickname(nickname, page, size);
4 }
重新運行,使用 Postname 方式訪問 http://localhost:8080/api/user/search?nickname=瑞&page=0&size=5 ,可以看到返回如下所示的數據:
4、本章小結本章主要介紹 Spring Data JPA 和如何使用,實戰演示了 JPA 如何進行資料庫的增加、刪除、修改、更新操作,對於像 JPA 的多表查詢,多數據源支持由於篇幅的原因不再一一展開,感興趣的讀者可以通過參考文檔、擴展閱讀和搜尋引擎進一步深入了解。
在 JPA 中,多表查詢有兩種方式:
一種是 JPA 的級聯查詢,@OneToMany,@ManyToOne等註解在 @Entity 實體類 中指定多表關聯規則;
另一種就是新建一個類,用於接收返回的結果集,然後通過 @Query 自定義查詢 SQL;
擴展閱讀:http://ityouknow.com/springboot/2016/08/20/spring-boot-jpa.html
參考連結:https://www.ibm.com/developerworks/cn/opensource/os-cn-spring-jpa/index.html
https://docs.spring.io/spring-data/jpa/docs/2.2.5.RELEASE/reference/html/
下一章,將實戰 Spring Boot 如何集成 MyBatis 或者 MyBatis-Plus,後續的實戰演示項目也會以這兩個框架之一為主 。個人比較傾向於 MyBatis-Plus ,不過實際開發中 MyBatis 使用比較廣泛。如果你有什麼好的建議,歡迎點擊下方留言告訴小先。
原創不易,感謝支持!