Spring Boot 使用 JPA
1. 前言
使用 JDBC ,或者 JdbcTemplate 操作數(shù)據(jù)庫,需要編寫大量的 SQL 語句。SQL 語句基本都是些模板代碼,實(shí)際上是可以通過分析模板代碼的規(guī)則自動(dòng)生成的。
JPA 就是簡化 Java 持久層數(shù)據(jù)操作的技術(shù)標(biāo)準(zhǔn),是一種方案和規(guī)范。最開始是 Sun 公司提出的, Sun 公司就是開發(fā)出 Java 的公司,一度非常厲害,結(jié)果被 Oracle 收購了。Sun 公司雖然提出了 JPA 標(biāo)準(zhǔn),但是并沒有具體實(shí)現(xiàn)。JPA 的實(shí)現(xiàn)里面比較出名的就是 Hibernate 了,所以本篇我們也是以 Hibernate 實(shí)現(xiàn)為基礎(chǔ)進(jìn)行 Spring Boot + JPA 的實(shí)例講解。
本篇演示一個(gè) Spring Boot 商品管理項(xiàng)目實(shí)例,其中數(shù)據(jù)持久層操作采用 JPA ,以體會(huì) JPA 的優(yōu)雅與高效。
2. JPA 基本原理
在開始實(shí)例之前,還是有必要聊聊 JPA 是如何實(shí)現(xiàn)的,便于大家理解。
首先是 ORM 映射,通過注解或 XML 描述對(duì)象和表直接的映射關(guān)系。例如 GoodsDo 商品類對(duì)應(yīng)數(shù)據(jù)庫中的 goods 商品表,商品類里面的屬性和商品表里面的列一一對(duì)應(yīng),商品類的一個(gè)對(duì)象就對(duì)應(yīng)商品表中的一行數(shù)據(jù)。
然后就是對(duì)數(shù)據(jù)庫進(jìn)行 CRUD (增刪改查)操作了,由于已經(jīng)配置了對(duì)象和表的映射關(guān)系,所以可以自動(dòng)生成對(duì)應(yīng)的 SQL 語句,然后執(zhí)行語句即可。
3. 開發(fā)流程
光說不練那是假把式,我們來使用 Spring Boot + JPA 開發(fā)一個(gè)完整實(shí)例。
3.1 使用 Spring Initializr 創(chuàng)建項(xiàng)目
Spring Boot 版本選擇 2.2.5 ,Group 為 com.imooc
, Artifact 為 spring-boot-jpa
,生成項(xiàng)目后導(dǎo)入 Eclipse 開發(fā)環(huán)境。
3.2 引入項(xiàng)目依賴
我們引入 Web 項(xiàng)目依賴、熱部署依賴。由于本項(xiàng)目需要使用 JPA 訪問數(shù)據(jù)庫,所以引入 spring-boot-starter-jdbc
、 mysql-connector-java
和 spring-boot-starter-data-jpa
依賴。 pom.xml 文件中依賴項(xiàng)如下:
實(shí)例:
<!-- 熱部署 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
</dependency>
<!-- web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- jpa -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- myql驅(qū)動(dòng) -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
3.3 修改配置文件
在 application.properties
中添加以下配置:
實(shí)例:
# 配置數(shù)據(jù)庫驅(qū)動(dòng)
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
# 配置數(shù)據(jù)庫url
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/shop?useUnicode=true&characterEncoding=utf-8&serverTimezone=UTC
# 配置數(shù)據(jù)庫用戶名
spring.datasource.username=root
# 配置數(shù)據(jù)庫密碼
spring.datasource.password=Easy@0122
# 啟動(dòng)時(shí)更新表結(jié)構(gòu),保留數(shù)據(jù)
spring.jpa.hibernate.ddl-auto=update
此處需要注意的是 spring.jpa.hibernate.ddl-auto=update
。可以理解項(xiàng)目啟動(dòng)時(shí),根據(jù)實(shí)體類結(jié)構(gòu)更新數(shù)據(jù)庫表結(jié)構(gòu),且保留數(shù)據(jù)庫中的數(shù)據(jù)。
3.4 開發(fā)商品類
開發(fā)商品類 GoodsDo ,并通過注解實(shí)現(xiàn)類結(jié)構(gòu)與數(shù)據(jù)表結(jié)構(gòu)的映射。
實(shí)例:
/**
* 商品類
*/
@Entity // 表示這是一個(gè)數(shù)據(jù)對(duì)象類
@Table(name = "goods") // 對(duì)應(yīng)數(shù)據(jù)庫中的goods表
public class GoodsDo {
/**
* 商品id
*/
@Id // 該字段對(duì)應(yīng)數(shù)據(jù)庫中的列為主鍵
@GeneratedValue(strategy = GenerationType.IDENTITY) // 主鍵自增長
@Column(name = "id") // 對(duì)應(yīng)goods表中的id列
private Long id;
/**
* 商品名稱
*/
@Column(name = "name") // 對(duì)應(yīng)goods表中的name列
private String name;
/**
* 商品價(jià)格
*/
@Column(name = "price") // 對(duì)應(yīng)goods表中的price列
private String price;
/**
* 商品圖片
*/
@Column(name = "pic") // 對(duì)應(yīng)goods表中的pic列
private String pic;
// 省略get set方法
}
3.5 開發(fā)數(shù)據(jù)操作接口
開發(fā)商品數(shù)據(jù)接口,代碼如下:
實(shí)例:
/**
* 商品數(shù)據(jù)操作接口
*/
@Repository
public interface IGoodsDao extends CrudRepository<GoodsDo, Long> {
}
解釋下,@Repository
將接口標(biāo)注為數(shù)據(jù)訪問層組件,該接口通過繼承 CrudRepository
實(shí)現(xiàn) CRUD 操作。泛型參數(shù)分別為實(shí)體類及主鍵的數(shù)據(jù)類型。
注意此時(shí)已經(jīng)可以通過 IGoodsDao 對(duì)數(shù)據(jù)庫 goods 表進(jìn)行增刪改查操作了。
3.6 開發(fā)服務(wù)層
開發(fā) Goods Service ,注入 IGoodsDao 類型組件實(shí)現(xiàn)服務(wù)方法。
實(shí)例:
/**
* 商品服務(wù)類
*/
@Service
public class GoodsService {
@Autowired
private IGoodsDao goodsDao;
/**
* 新增商品
*/
public void add(GoodsDo goods) {
goodsDao.save(goods);
}
/**
* 刪除商品
*/
public void remove(Long id) {
goodsDao.deleteById(id);
}
/**
* 編輯商品信息
*/
public void edit(GoodsDo goods) {
goodsDao.save(goods);
}
/**
* 按id獲取商品信息
*/
public Optional<GoodsDo> getById(Long id) {
return goodsDao.findById(id);
}
/**
* 獲取商品信息列表
*/
public Iterable<GoodsDo> getList() {
return goodsDao.findAll();
}
}
此處需要解釋下 Optional
類,它是一個(gè)包裝類。它的內(nèi)容是空或者包含的對(duì)象,所以可以避免空指針問題。此處稍作了解即可。
3.7 開發(fā)控制器
我們還是遵循 RESTful 風(fēng)格,開發(fā)控制器類。
實(shí)例:
/**
* 商品控制器類
*/
@RestController
public class GoodsController {
@Autowired
private GoodsService goodsService;
/**
* 按id獲取商品信息
*/
@GetMapping("/goods/{id}")
public Optional<GoodsDo> getOne(@PathVariable("id") long id) {
return goodsService.getById(id);
}
/**
* 獲取商品列表
*/
@GetMapping("/goods")
public Iterable<GoodsDo> getList() {
return goodsService.getList();
}
/**
* 新增商品
*/
@PostMapping("/goods")
public void add(@RequestBody GoodsDo goods) {
goodsService.add(goods);
}
/**
* 編輯商品
*/
@PutMapping("/goods/{id}")
public void update(@PathVariable("id") long id, @RequestBody GoodsDo goods) {
// 修改指定id的博客信息
goods.setId(id);
goodsService.edit(goods);
}
/**
* 移除商品
*/
@DeleteMapping("/goods/{id}")
public void delete(@PathVariable("id") long id) {
goodsService.remove(id);
}
}
4. 測試
我們主要是測試 JPA 模塊正確可用,所以直接在測試類發(fā)起對(duì) IGoodsDao 方法的測試即可。
4.1 新增測試
首先我們建立數(shù)據(jù)庫 shop ,數(shù)據(jù)庫中不必有表 goods ,如果有 goods 表的話可以將它刪除。因?yàn)槲覀冊(cè)O(shè)置了 spring.jpa.hibernate.ddl-auto=update
, JPA 會(huì)在項(xiàng)目啟動(dòng)時(shí)自動(dòng)建立表結(jié)構(gòu)。
實(shí)例:
@RunWith(SpringRunner.class)
@SpringBootTest
public class JpaAddTest {
@Autowired
private IGoodsDao goodsDao;
/**
* 新增測試
*/
@Test
public void testAdd() {
GoodsDo goods = new GoodsDo();
goods.setName("梨張");
goods.setPic("梨圖片");
goods.setPrice("2.0");
GoodsDo result = goodsDao.save(goods);
System.out.println("新增商品id:" + result.getId());
assertNotNull(result);
}
}
運(yùn)行測試類,控制臺(tái)輸出新增商品id:1
,說明插入一條數(shù)據(jù)成功,且插入數(shù)據(jù) id 為 1 。
同時(shí)查看數(shù)據(jù)庫,發(fā)現(xiàn)已經(jīng)自動(dòng)構(gòu)建表結(jié)構(gòu):
4.2 修改測試
當(dāng)調(diào)用 save 方法,如果給參數(shù)中 id 屬性賦值,則會(huì)進(jìn)行數(shù)據(jù)更新操作。
實(shí)例:
@RunWith(SpringRunner.class)
@SpringBootTest
public class JpaEditTest {
@Autowired
private IGoodsDao goodsDao;
/**
* 修改測試
*/
@Test
public void testEdit() {
GoodsDo goods = new GoodsDo();
goods.setId(1L);
goods.setName("梨張");
goods.setPic("梨圖片");
goods.setPrice("100.0");
GoodsDo result = goodsDao.save(goods);
assertNotNull(result);
}
}
此時(shí)查看數(shù)據(jù)庫中數(shù)據(jù),發(fā)現(xiàn)金額已修改成功。
4.3 查詢測試
我們進(jìn)行按 id 查詢、查詢所有操作,并打印查詢結(jié)果。
實(shí)例:
@RunWith(SpringRunner.class)
@SpringBootTest
public class JpaQueryTest {
@Autowired
private IGoodsDao goodsDao;
/**
* 按id查詢
*/
@Test
public void testQueryById() {
Optional<GoodsDo> goodsOptional = goodsDao.findById(1L);
GoodsDo goods = goodsOptional.get();
System.out.println(goods.getId() + "-" + goods.getName() + "-" + goods.getPic() + "-" + goods.getPrice());
}
/**
* 查詢?nèi)? */
@Test
public void testQueryAll() {
Iterable<GoodsDo> goodsIt = goodsDao.findAll();
for (GoodsDo goods : goodsIt) {
System.out.println(goods.getId() + "-" + goods.getName() + "-" + goods.getPic() + "-" + goods.getPrice());
}
}
}
4.4 刪除測試
指定刪除 id 為 1 的商品。
實(shí)例:
@RunWith(SpringRunner.class)
@SpringBootTest
public class JpaRemoveTest {
@Autowired
private IGoodsDao goodsDao;
/**
* 刪除測試
*/
@Test
public void testRemove() {
goodsDao.deleteById(1L);
}
}
運(yùn)行后,數(shù)據(jù)庫中商品信息被刪除,大功告成!
5. 小結(jié)
使用 JPA 后,最大的好處就是不用寫 SQL 了,完全面向?qū)ο缶幊?,簡潔又省心,何樂而不為?/p>