三分鐘,迎接一個更加高效和簡便的開發體驗。
在快節奏的軟件開發領域,每一個簡化工作流程的機會都不容錯過。想要一個無需繁瑣配置、能夠迅速啓動的數據持久化方案嗎?這篇文章將是你的首選攻略。在這裏,我們將向你展示如何將 Spring Boot 的便捷性、JPA 的強大查詢能力和 SQLite 的輕量級特性結合在一起,實現快速而又優雅的數據管理。
爲什麼選擇 SQLite
SQLite 是一個用 C 語言編寫的開源、輕量級、快速、獨立且高可靠性的 SQL 數據庫引擎,它提供了功能齊全的數據庫解決方案。對於大多數的應用,SQLite 都可以滿足。使用 SQLite 可以零配置啓動,對於小型應用或者快速原型設計是一個非常大的優勢。
使用 SQLite 具有下面幾個優點:
- 輕量級:SQLite很小巧,不需要獨立服務器,便於集成到應用中。
- 零配置:啓用 SQLite 無需複雜配置,只需指定一個文件路徑存放 DB 文件,簡化了數據庫的設置流程。
- 便於移植:數據庫是單一文件,方便備份和在不同環境間遷移。
- 跨平臺:SQLite 支持各種操作系統,容易實現應用的跨平臺運行。
- 性能良好:對於小型應用,SQLite 提供足夠的讀寫性能。
- 遵循ACID:SQLite事務符合ACID原則,數據操作可靠。
- 社區支持:雖然簡單,但擁有強大的社區和廣泛的文檔資源。
之前寫過一篇 SQLite 入門教程 (https://www.wdbyte.com/db/sqlite/),感情的同學可以參考。
爲什麼 選擇 JPA
Spring Data JPA 是Spring Data項目的一部分,旨在簡化基於JPA(Java Persistence API)的數據訪問層(Repository層)的實現。JPA是一種 ORM(對象關係映射)規範,它允許開發者以面向對象的方式來操作數據庫,
通常應用程序實現數據訪問層可能非常麻煩,必須編寫太多的樣板代碼才能實現簡單的查詢,更不用說分頁等其他操作,而 Spring Data JPA 可以讓開發者非常容易地實現對數據庫的各種操作,顯著減少實際需要的工作量。
詳細介紹 JPA 並不是本文目的,關於 JPA 的更多內容可以訪問:
創建 Spring Boot 項目
用於後續演示,首先創建一個簡單的 Spring Boot 項目。你可以自由創建,或者使用 Spring 官網提供的快速創建工具:https://start.spring.io/
注意,文章示例項目使用 Java 21 進行演示。
爲了方便開發,創建一個基礎的 Spring Boot 項目後,添加以下依賴。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- 從 Hibernate 6 開始,支持 SQLite 方言。-->
<!-- https://mvnrepository.com/artifact/org.hibernate.orm/hibernate-community-dialects -->
<dependency>
<groupId>org.hibernate.orm</groupId>
<artifactId>hibernate-community-dialects</artifactId>
<version>6.4.3.Final</version>
</dependency>
<!-- sqlite jdbc 驅動 -->
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>3.45.1.0</version>
</dependency>
<!-- Lombok 簡化 get set tostring log .. -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- apache java 通用工具庫 -->
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
<version>3.13.0</version>
</dependency>
<!-- 編碼通用工具庫 -->
<dependency>
<groupId>commons-codec</groupId>
<artifactId>commons-codec</artifactId>
<version>1.16.0</version>
</dependency>
配置 SQLite & JPA
在 Spring Boot 中,對 SQLite 的配置非常簡單,只需要指定一個位置存放 SQLite 數據庫文件。SQLite 無服務端,因此可以直接啓動。
spring.datasource.url=jdbc:sqlite:springboot-sqlite-jpa.db
spring.datasource.driver-class-name=org.sqlite.JDBC
# JPA Properties
spring.jpa.database-platform=org.hibernate.community.dialect.SQLiteDialect
# create 每次都重新創建表,update,表若存在則不重建
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
配置實體映射
在使用 JPA 開發時,就是使用 jakarta.persistence
包中的註解配置 Java 實體類和表的映射關係,比如使用 @Table
指定表名,使用 @Column
配置字段信息。
import java.time.LocalDateTime;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
@Entity
@Getter
@Setter
@ToString
@Table(name = "website_user")
public class WebsiteUser {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@Column(name = "username", nullable = false, unique = true, length = 64)
private String username;
@Column(name = "password", nullable = false, length = 255)
private String password;
@Column(name = "salt", nullable = false, length = 16)
private String salt;
@Column(name = "status", nullable = false, length = 16, columnDefinition = "VARCHAR(16) DEFAULT 'active'")
private String status;
@Column(name = "created_at", nullable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
private LocalDateTime createdAt;
@Column(name = "updated_at", nullable = false, columnDefinition = "TIMESTAMP DEFAULT CURRENT_TIMESTAMP")
private LocalDateTime updatedAt;
}
編寫 JPA 查詢方法
Spring Data JPA 提供了多種便捷的方法來實現對數據庫的查詢操作,使得能夠以非常簡潔的方式編寫對數據庫的訪問和查詢邏輯。比如 Spring Data JPA 允許通過在接口中定義遵循一定命名方法的方式來創建數據庫查詢。如findByName
將生成一個根據 name
查詢指定實體的 SQL。
代碼示例:
@Repository
public interface WebsiteUserRepository extends CrudRepository<WebsiteUser, Long> {
/**
* 根據 username 查詢數據
* @param name
* @return
*/
WebsiteUser findByUsername(String name);
}
代碼示例中,繼承的 CrudRepository
接口中包含了常見的 CURD 操作方法。自定義的 findByUsername
方法可以根據 WebsiteUser
中的 Username 進行查詢。
編寫 Controller
編寫三個 API 用來演示 Spring Boot 結合 SQLite 以及 JPA 是否成功。
初始化方法 init()
:
-
映射到
"/sqlite/init"
的 GET請求。 -
創建了10個
WebsiteUser
實體,爲每個用戶生成隨機的用戶名和鹽值,並用MD5加密其密碼("123456" + 鹽)。 -
用戶信息包括用戶名、加鹽後的密碼、創建和更新的時間戳,以及用戶狀態。
-
用戶信息被保存到數據庫中,並記錄日誌。
查找用戶方法 findByUsername(String username)
:
- 映射到
"/sqlite/find"
的GET請求。 - 通過用戶名查詢用戶。如果找到,返回用戶的字符串表示;否則返回
null
。
登錄方法 findByUsername(String username, String password)
:
- 映射到
"/sqlite/login"
的GET請求。 - 驗證傳入的用戶名和密碼。首先通過用戶名查詢用戶,然後將傳入的密碼與鹽值結合,並與數據庫中存儲的加鹽密碼進行MD5加密比對。
- 如果密碼匹配,則認證成功,返回 "login succeeded";否則,返回 "login failed"。
代碼示例:
import java.time.LocalDateTime;
import com.wdbyte.springsqlite.model.WebsiteUser;
import com.wdbyte.springsqlite.repository.WebsiteUserRepository;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @author https://www.wdbyte.com
*/
@Slf4j
@RestController
public class SqliteController {
@Autowired
private WebsiteUserRepository userRepository;
@GetMapping("/sqlite/init")
public String init() {
for (int i = 0; i < 10; i++) {
WebsiteUser websiteUser = new WebsiteUser();
// 隨機4個字母
websiteUser.setUsername(RandomStringUtils.randomAlphabetic(4));
// 隨機16個字符用於密碼加鹽加密
websiteUser.setSalt(RandomStringUtils.randomAlphanumeric(16));
String password = "123456";
// 密碼存儲 = md5(密碼+鹽)
password = password + websiteUser.getSalt();
websiteUser.setPassword(DigestUtils.md5Hex(password));
websiteUser.setCreatedAt(LocalDateTime.now());
websiteUser.setUpdatedAt(LocalDateTime.now());
websiteUser.setStatus("active");
WebsiteUser saved = userRepository.save(websiteUser);
log.info("init user {}", saved.getUsername());
}
return "init success";
}
@GetMapping("/sqlite/find")
public String findByUsername(String username) {
WebsiteUser websiteUser = userRepository.findByUsername(username);
if (websiteUser == null) {
return null;
}
return websiteUser.toString();
}
@GetMapping("/sqlite/login")
public String findByUsername(String username, String password) {
WebsiteUser websiteUser = userRepository.findByUsername(username);
if (websiteUser == null) {
return "login failed";
}
password = password + websiteUser.getSalt();
if (StringUtils.equals(DigestUtils.md5Hex(password), websiteUser.getPassword())) {
return "login succeeded";
} else {
return "login failed";
}
}
}
至此,項目編寫完成,完整目錄結構如下:
├── pom.xml
└── src
├── main
├── java
│ └── com
│ └── wdbyte
│ └── springsqlite
│ ├── SpringBootSqliteApp.java
│ ├── controller
│ │ └── SqliteController.java
│ ├── model
│ │ └── WebsiteUser.java
│ └── repository
│ └── WebsiteUserRepository.java
└── resources
├── application.properties
├── static
└── templates
啓動測試
Spring Boot 啓動時由於庫表不存在,自動創建庫表:
Hibernate: create table website_user (id integer, created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP not null, password varchar(255) not null, salt varchar(16) not null, status VARCHAR(16) DEFAULT 'active' not null, updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP not null, username varchar(64) not null unique, primary key (id))
Hibernate: alter table website_user drop constraint UK_61p1pfkd4ht22uhlib72oj301
2024-02-27T20:00:21.279+08:00 INFO 70956 --- [main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
2024-02-27T20:00:21.578+08:00 WARN 70956 --- [main] JpaBaseConfiguration$JpaWebConfiguration : spring.jpa.open-in-view is enabled by default. Therefore, database queries may be performed during view rendering. Explicitly configure spring.jpa.open-in-view to disable this warning
2024-02-27T20:00:21.931+08:00 INFO 70956 --- [main] o.s.b.w.embedded.tomcat.TomcatWebServer : Tomcat started on port 8080 (http) with context path ''
2024-02-27T20:00:21.938+08:00 INFO 70956 --- [main] c.w.springsqlite.SpringBootSqliteApp : Started SpringBootSqliteApp in 3.944 seconds (process running for 5.061)
請求初始化接口
$ curl http://127.0.0.1:8080/sqlite/init
init success
可以看到輸出日誌成功寫入了 10條數據,且輸出了 username
值。
2024-02-27T20:01:04.120+08:00 ...SqliteController : init user HUyz
Hibernate: insert into website_user (created_at,password,salt,status,updated_at,username) values (?,?,?,?,?,?)
Hibernate: select last_insert_rowid()
2024-02-27T20:01:04.123+08:00 ...SqliteController : init user ifQU
Hibernate: insert into website_user (created_at,password,salt,status,updated_at,username) values (?,?,?,?,?,?)
Hibernate: select last_insert_rowid()
2024-02-27T20:01:04.126+08:00 ...SqliteController : init user GBPK
Hibernate: insert into website_user (created_at,password,salt,status,updated_at,username) values (?,?,?,?,?,?)
Hibernate: select last_insert_rowid()
2024-02-27T20:01:04.129+08:00 ...SqliteController : init user rytE
Hibernate: insert into website_user (created_at,password,salt,status,updated_at,username) values (?,?,?,?,?,?)
Hibernate: select last_insert_rowid()
2024-02-27T20:01:04.132+08:00 ...SqliteController : init user iATH
Hibernate: insert into website_user (created_at,password,salt,status,updated_at,username) values (?,?,?,?,?,?)
Hibernate: select last_insert_rowid()
2024-02-27T20:01:04.134+08:00 ...SqliteController : init user ZQRW
Hibernate: insert into website_user (created_at,password,salt,status,updated_at,username) values (?,?,?,?,?,?)
Hibernate: select last_insert_rowid()
2024-02-27T20:01:04.137+08:00 ...SqliteController : init user cIPM
Hibernate: insert into website_user (created_at,password,salt,status,updated_at,username) values (?,?,?,?,?,?)
Hibernate: select last_insert_rowid()
2024-02-27T20:01:04.140+08:00 ...SqliteController : init user MemS
Hibernate: insert into website_user (created_at,password,salt,status,updated_at,username) values (?,?,?,?,?,?)
Hibernate: select last_insert_rowid()
2024-02-27T20:01:04.143+08:00 ...SqliteController : init user GEeX
Hibernate: insert into website_user (created_at,password,salt,status,updated_at,username) values (?,?,?,?,?,?)
Hibernate: select last_insert_rowid()
2024-02-27T20:01:04.146+08:00 ...SqliteController : init user ZQrT
請求查詢用戶接口
$ curl http://127.0.0.1:8080/sqlite/find\?username\=ZQrT
WebsiteUser(id=10, username=ZQrT, password=538ea3b5fbacd1f9354a1f367b36135a, salt=RxaivBHlyJCxtOEv, status=active, createdAt=2024-02-27T20:01:04.144, updatedAt=2024-02-27T20:01:04.144)
查詢成功,回顯了查詢到的用戶信息。
請求登錄接口
在初始化數據時,密碼統一配置爲 123456,下面的測試可以看到使用正確的密碼可以通過校驗。
$ curl http://127.0.0.1:8080/sqlite/login\?username\=ZQrT\&password\=123456
login succeeded
$ curl http://127.0.0.1:8080/sqlite/login\?username\=ZQrT\&password\=12345
login failed
SQLite 3 數據審查
使用 Sqlite3 命令行工具查看 SQLite 數據庫內容。
$ ./sqlite3 springboot-sqlite-jpa.db
SQLite version 3.42.0 2023-05-16 12:36:15
Enter ".help" for usage hints.
sqlite> .tables
website_user
sqlite> .mode table
sqlite> select * from website_user;
+----+---------------+----------------------------------+------------------+--------+---------------+----------+
| id | created_at | password | salt | status | updated_at | username |
+----+---------------+----------------------------------+------------------+--------+---------------+----------+
| 1 | 1709035264074 | 4b2b68c0df77669540fc0a487d753400 | 1njFP8ykWmlu01Z8 | active | 1709035264074 | HUyz |
| 2 | 1709035264120 | 7e6444d57f753cfa6c1592a17e68e66e | 9X3El5jQaMhrROSf | active | 1709035264120 | ifQU |
| 3 | 1709035264124 | 1d24c4ddb351eb56f665adb13708f981 | Jn9IrT6MYqVqzpu8 | active | 1709035264124 | GBPK |
| 4 | 1709035264126 | 960747cc48aeed71e8ff714deae42e87 | wq8pb1G9pIalGHwP | active | 1709035264126 | rytE |
| 5 | 1709035264129 | cf1037b95a997a1b1b9d9aa598b9f96b | An0hwV2n9cN4wpOy | active | 1709035264129 | iATH |
| 6 | 1709035264132 | b68d42108e5046bd25b74cda947e0ffc | EozfDAkpn5Yx4yin | active | 1709035264132 | ZQRW |
| 7 | 1709035264134 | 78d4841af9a12603204f077b9bf30dcc | 2FRNQ2zWksJHOyX9 | active | 1709035264135 | cIPM |
| 8 | 1709035264137 | 60b8051ca3379c569a3fb41ed5ff05aa | KpT3IGwWmhlWIUq7 | active | 1709035264137 | MemS |
| 9 | 1709035264140 | 0ca0a2dce442315c11f5488c0127f905 | RhGOYnNEMYbnWoat | active | 1709035264140 | GEeX |
| 10 | 1709035264144 | 538ea3b5fbacd1f9354a1f367b36135a | RxaivBHlyJCxtOEv | active | 1709035264144 | ZQrT |
+----+---------------+----------------------------------+------------------+--------+---------------+----------+
sqlite>
一如既往,文章中代碼存放在 Github.com/niumoo/javaNotes.
參考
本文 Github.com/niumoo/JavaNotes倉庫已經收錄。
本文原發於網站:Spring Boot 整合 SQLite 和 JPA
本文原發於公衆號:三分鐘數據持久化:Spring Boot, JPA 與 SQLite 的完美融合