Spring Boot (三): ORM 框架 JPA 與連線池 Hikari
前面兩篇文章我們介紹瞭如何快速建立一個 Spring Boot 工程《Spring Boot(一):快速開始》和在 Spring Boot 中如何使用模版引擎 Thymeleaf 渲染一個Web頁面《Spring Boot (二):模版引擎 Thymeleaf 渲染 Web 頁面》,本篇文章我們繼續介紹在 Spring Boot 中如何使用資料庫。
1. 概述
資料庫方面我們選用 Mysql , Spring Boot 提供了直接使用 JDBC 的方式連線資料庫,畢竟使用 JDBC 並不是很方便,需要我們自己寫更多的程式碼才能使用,一般而言在 Spring Boot 中我們常用的 ORM 框架有 JPA 和 Mybaties ,本篇文章我們要介紹的就是 JPA 的使用姿勢。
說道使用 ORM 框架,就不得不順便聊一下連線池,市面上很多成熟的資料庫連線池,如 C3P0 、 Tomcat 連線池、 BoneCP 等等很多產品,但是我們為什麼要介紹 Hikari ?這個要從 BoneCP 說起。
因為,傳說中 BoneCP 在快速這個特點上做到了極致,官方資料是C3P0等的25倍左右。不相信?其實我也不怎麼信。可是,有圖有真相啊,傳說圖片來源於官網,然而筆者在官網並沒有找到,大家看一下:
看起來是不是完全吊打,但是當 HikariCP 橫空出世以後,這個局面就被完全改寫了, BoneCP 被 HikariCP 完全吊打,看了一下 BoneCP Github 上面的版本更新,發現在2013年10月23日以後就再也沒有更新過了,包括在倉庫介紹上面都寫著建議大家使用 HikariCP ,看來作者已經完全心灰意冷了。
Hikari 這個詞來源於日文,是“光”的意思,估計作者的意思是這個連線池將會和光一樣快,不知道作者是不是日本人。
HikariCP 的口號是快速,簡單,可靠。不知道是否真的如它自己宣傳的一樣,官方又提供了一張圖,大家感受一下,這張圖來源於:https://github.com/brettwooldridge/HikariCP 。
更多有關 HikariCP 的資訊,大家可以訪問官方的 Github 倉庫瞭解:https://github.com/brettwooldridge/HikariCP ,筆者這裡不多做介紹,畢竟我們更關注的如何使用。
2. JPA 介紹
JPA (Java Persistence API) 是 Sun 官方提出的 Java 持久化規範。它為 Java 開發人員提供了一種物件/關聯對映工具來管理 Java 應用中的關係資料。它的出現主要是為了簡化現有的持久化開發工作和整合 ORM 技術,結束現在 Hibernate,TopLink,JDO 等 ORM 框架各自為營的局面。
值得注意的是,JPA 是在充分吸收了現有 Hibernate,TopLink,JDO 等 ORM 框架的基礎上發展而來的,具有易於使用,伸縮性強等優點。從目前的開發社群的反應上看, JPA 受到了極大的支援和讚揚,其中就包括了 Spring 與 EJB3. 0的開發團隊。
注意: JPA 是一套規範,不是一套產品,那麼像 Hibernate,TopLink,JDO 他們是一套產品,如果說這些產品實現了這個 JPA 規範,那麼我們就可以叫他們為 JPA 的實現產品。
Spring Boot JPA 是 Spring 基於 ORM 框架、 JPA 規範的基礎上封裝的一套 JPA 應用框架,可使開發者用極簡的程式碼即可實現對資料的訪問和操作。它提供了包括增刪改查等在內的常用功能,且易於擴充套件!學習並使用 Spring Data JPA 可以極大提高開發效率!
Spring Boot JPA 讓我們解脫了 DAO 層的操作,基本上所有 CRUD 都可以依賴於它來實現。
Spring Boot JPA 幫我們定義了很多自定義的簡單查詢,並且可以根據方法名來自動生成 SQL ,主要的語法是 findXXBy
,readAXXBy
,queryXXBy
,countXXBy
,getXXBy
後面跟屬性名稱:
public interface UserRepository extends JpaRepository<UserModel,Long> {
UserModel getByIdIs(Long id);
UserModel findByNickName(String nickName);
int countByAge(int age);
List<UserModel> findByNickNameLike(String nickName);
}複製程式碼
具體的關鍵字,使用方法和生產成SQL如下表所示:
Keyword |
Sample |
JPQL snippet |
---|---|---|
And |
findByLastnameAndFirstname |
… where x.lastname = ?1 and x.firstname = ?2 |
Or |
findByLastnameOrFirstname |
… where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals |
findByFirstname,findByFirstnameIs,findByFirstnameEquals | … where x.firstname = 1? |
Between |
findByStartDateBetween |
… where x.startDate between 1? and ?2 |
LessThan |
findByAgeLessThan |
… where x.age |
LessThanEqual |
findByAgeLessThanEqual |
… where x.age => |
GreaterThan |
findByAgeGreaterThan |
… where x.age > ?1 |
GreaterThanEqual |
findByAgeGreaterThanEqual |
… where x.age >= ?1 |
After |
findByStartDateAfter |
… where x.startDate > ?1 |
Before |
findByStartDateBefore |
… where x.startDate |
IsNull |
findByAgeIsNull |
… where x.age is null |
IsNotNull,NotNull | findByAge(Is)NotNull |
… where x.age not null |
Like |
findByFirstnameLike |
… where x.firstname like ?1 |
NotLike |
findByFirstnameNotLike |
… where x.firstname not like ?1 |
StartingWith |
findByFirstnameStartingWith |
… where x.firstname like ?1 (parameter bound with appended %) |
EndingWith |
findByFirstnameEndingWith |
… where x.firstname like ?1 (parameter bound with prepended %) |
Containing |
findByFirstnameContaining |
… where x.firstname like ?1 (parameter bound wrapped in %) |
OrderBy |
findByAgeOrderByLastnameDesc |
… where x.age = ?1 order by x.lastname desc |
Not |
findByLastnameNot |
… where x.lastname ?1 |
In |
findByAgeIn(Collection |
… where x.age in ?1 |
NotIn |
findByAgeNotIn(Collection |
… where x.age not in ?1 |
True |
findByActiveTrue() |
… where x.active = true |
False |
findByActiveFalse() |
… where x.active = false |
IgnoreCase |
findByFirstnameIgnoreCase |
… where UPPER(x.firstame) = UPPER(?1) |
3. 工程實戰
這裡我們建立工程 spring-boot-jpa-hikari 。
3.1 工程依賴 pom.xml
程式碼清單:spring-boot-jpa-hikari/pom.xml***
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.8.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.springboot</groupId>
<artifactId>spring-boot-jpa-hikari</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>spring-boot-jpa-hikari</name>
<description>spring-boot-jpa-hikari</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>複製程式碼
- mysql-connector-java:mysql連線驅動
- spring-boot-starter-data-jpa:jpa相關的依賴包,這個包裡包含了很多內容,包括我們使用的連線池 HikariCP ,從 Spring Boot 2.x 開始, Spring Boot 預設的連線池更換成為 HikariCP ,在當前的 Spring Boot 2.1.8 RELEASE 版本中,所使用的 HikariCP 版本為 3.2.0 ,如圖:
3.2 配置檔案 application.yml
程式碼清單:spring-boot-jpa-hikari/src/main/resources/application.yml***
server:
port: 8080
spring:
application:
name: spring-boot-jpa-hikari
jpa:
database: mysql
show-sql: true
generate-ddl: true
database-platform: org.hibernate.dialect.MySQL5InnoDBDialect
hibernate:
ddl-auto: update
datasource:
url: jdbc:mysql://192.168.0.128:3306/test?serverTimezone=Asia/Shanghai&useUnicode=true&characterEncoding=UTF-8&useSSL=false
username: root
password: 123456
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.zaxxer.hikari.HikariDataSource
hikari:
auto-commit: true
minimum-idle: 2
idle-timeout: 60000
connection-timeout: 30000
max-lifetime: 1800000
pool-name: DatebookHikariCP
maximum-pool-size: 5複製程式碼
注意:
- 有關 JPA 的配置有一點需要的,
spring.jpa.hibernate.ddl-auto
,這個屬性需謹慎配置,它的幾個值的含義對資料庫來講都是高危操作,筆者這裡方便起見配置了update
,各位讀者請根據具體使用場景配置。- create :每次載入hibernate時都會刪除上一次的生成的表,然後根據你的model類再重新來生成新表,哪怕兩次沒有任何改變也要這樣執行,這就是導致資料庫表資料丟失的一個重要原因。
- create-drop :每次載入hibernate時根據model類生成表,但是sessionFactory一關閉,表就自動刪除。
- update :最常用的屬性,第一次載入hibernate時根據model類會自動建立起表的結構(前提是先建立好資料庫),以後載入hibernate時根據 model類自動更新表結構,即使表結構改變了但表中的行仍然存在不會刪除以前的行。要注意的是當部署到伺服器後,表結構是不會被馬上建立起來的,是要等 應用第一次執行起來後才會。
- validate :每次載入hibernate時,驗證建立資料庫表結構,只會和資料庫中的表進行比較,不會建立新表,但是會插入新值。
- none :不做任何操作
- 有關 HikariCP 更多的配置可以參考原始碼類
com.zaxxer.hikari.HikariConfig
,筆者這裡僅簡單配置了自動提交、超時時間、最大最小連線數等配置。
3.3 對映實體類 UserModel.java
程式碼清單:spring-boot-jpa-hikari/src/main/java/com/springboot/springbootjpahikari/model/UserModel.java***
@Entity
@Data
@Table(name = "user")
public class UserModel {
@Id
@GeneratedValue(generator = "paymentableGenerator")
@GenericGenerator(name = "paymentableGenerator",strategy = "uuid")
@Column(name ="ID",nullable=false,length=36)
private String id;
@Column(nullable = true,unique = true)
private String nickName;
@Column(nullable = false)
private int age;
}複製程式碼
- unique : 唯一約束
- 主鍵生成策略為uuid
3.4 資源類 UserRepository.java
程式碼清單:spring-boot-jpa-hikari/src/main/java/com/springboot/springbootjpahikari/repository/UserRepository.java***
public interface UserRepository extends JpaRepository<UserModel,Long> {
UserModel getByIdIs(Long id);
UserModel findByNickName(String nickName);
int countByAge(int age);
List<UserModel> findByNickNameLike(String nickName);
}複製程式碼
3.5 介面測試類 UserController.java
程式碼清單:spring-boot-jpa-hikari/src/main/java/com/springboot/springbootjpahikari/controller/UserController.java***
@RestController
public class UserController {
@Autowired
UserRepository userRepository;
/**
* 查詢使用者列表
* @return
*/
@GetMapping("/user")
public List<UserModel> user() {
return userRepository.findAll(Sort.by("id").descending());
}
/**
* 新增或更新使用者資訊
* @param user
* @return
*/
@PostMapping("/user")
public UserModel user(UserModel user) {
return userRepository.save(user);
}
/**
* 根據id刪除使用者
* @param id
* @return
*/
@DeleteMapping("/user")
public String deleteUserById(Long id) {
userRepository.deleteById(id);
return "success";
}
}複製程式碼
4. 測試
測試我們藉助工具 PostMan ,啟動工程,首先我們新增一個使用者資訊,如圖:
如果我們引數中加入 id ,並且 id 的值和資料庫中的 id 維持一致,這是會更新當前 id 的資料,如圖:
我們執行查詢操作,如圖:
執行刪除操作,如圖:
至此,測試完成。
5. 示例程式碼
6. 參考
https://github.com/brettwooldridge/HikariCP
https://docs.spring.io/spring-data/jpa/docs/current/reference/html/
http://www.ityouknow.com/springboot/2016/08/20/spring-boot-jpa.html