本章是《 Spring Boot 快速入門 》系列教程的第三章井辆,若要查看本系列的全部章節(jié),請點擊 這里 溶握。
目錄
- 簡介
- 源碼下載
- 軟件版本
- JPA簡介
- 在項目中配置JPA
- 編寫實體類
- 編寫 Repository 接口
- 使用原生SQL查詢
- 總結(jié)說明
簡介
在上一章《 Spring Boot MVC 》中杯缺,我們了解了使用 Spring Boot MVC 來開發(fā) Http Restful API的相關(guān)技術(shù),但只處理HTTP請求是不夠的睡榆,現(xiàn)在的應(yīng)用程序大多使用了關(guān)系型數(shù)據(jù)庫夺谁,因此本章我們會帶著大家繼續(xù) Spring Boot 體驗之旅,這次我們將采用 JPA 技術(shù)來訪問數(shù)據(jù)庫肉微,給 Hello Spring Boot 程序添加帶數(shù)據(jù)庫訪問演示代碼匾鸥。
源碼下載
本章的示例代碼放在“碼云”上,大家可以免費下載或瀏覽:
https://git.oschina.net/terran4j/springboot/tree/master/springboot-jpa
軟件版本
相關(guān)軟件使用的版本:
- Java: 1.8
- Maven: 3.3.9
- MYSQL: 5.5
程序在以上版本均調(diào)試過碉纳,可以正常運行勿负,其它版本僅作參考。
JPA簡介
JPA是Java Persistence API的簡稱劳曹,中文名Java持久層API奴愉,是JDK 5.0注解或XML描述對象-關(guān)系表的映射關(guān)系,并將運行期的實體“對象持久化”到數(shù)據(jù)庫中铁孵。
JPA技術(shù)可以極大的降低了對數(shù)據(jù)庫編程的復(fù)雜性锭硼,一些簡單的增刪改查的操作,代碼只需要操作對象就可以了蜕劝,JPA自動的幫你映射成數(shù)據(jù)庫的SQL操作檀头。
不過 JPA 只是標準標準轰异,而 Spring Boot 提供了它的技術(shù)實現(xiàn): Spring Data JPA。不過 Spring Data JPA 也不是重復(fù)造輪子暑始,它是基于一個非常著名的ORM框架——Hibernate——之上封裝實現(xiàn)的搭独。
Spring Data JPA 極大簡化了數(shù)據(jù)庫訪問層代碼,只要3步廊镜,就能搞定一切:
- 在pom.xml中配置
spring-boot-starter-data-jpa
牙肝,及在 application配置文件中配置數(shù)據(jù)庫連接。 - 編寫 Entity 類嗤朴,依照 JPA 規(guī)范配椭,定義實體。
- 編寫 Repository 接口雹姊,依靠 Spring Data 規(guī)范颂郎,定義數(shù)據(jù)訪問接口(注意,只要接口容为,不需要任何實現(xiàn))
另外乓序,如果有復(fù)雜的SQL查詢,Spring Data JPA 也提供了編寫原生 SQL 實現(xiàn)的方式坎背。
在項目中配置JPA
首先替劈,我們要在 pom.xml 文件中添加 spring-boot-starter-data-jpa
的依賴,代碼如下:
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>terran4j</groupId>
<artifactId>springboot-jpa</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springboot-jpa</name>
<url>https://git.oschina.net/terran4j/springboot/tree/master/springboot-jpa</url>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.2.RELEASE</version>
</parent>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<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>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
</dependencies>
</project>
注意新增的兩個依賴得滤,一個是 spring-boot-starter-data-jpa
陨献,它集成了JPA相關(guān)的 jar 包;另一個是 mysql-connector-java
, 因為本示例中我們要連MYSQL的數(shù)據(jù)庫懂更,所以 mysql jdbc 驅(qū)動(java) 是必不可少的眨业。
然后,我們要在application.properties
配置文件中配置數(shù)據(jù)庫連接及JPA配置沮协,如下所示:
spring.datasource.driverClassName = com.mysql.jdbc.Driver
spring.datasource.url = jdbc:mysql://127.0.0.1:3306/test
spring.datasource.username = root
spring.datasource.password =
spring.jpa.hibernate.ddl-auto = update
spring.jpa.show-sql = true
以spring.datasource
開頭的是數(shù)據(jù)庫連接的配置龄捡,請注意一定要保持對應(yīng)的數(shù)據(jù)庫是存在的,并且用戶名密碼都沒錯慷暂,不然待會程序運行時無法啟動聘殖。
以spring.jpa
開發(fā)的是 JPA 的配置,spring.jpa.hibernate.ddl-auto
表示每次程序啟動時對數(shù)據(jù)庫表的處理策略行瑞,有以下可選值:
create:
每次程序啟動時奸腺,都會刪除上一次的生成的表,然后根據(jù)你的實體類再重新來生成新表血久,哪怕兩次沒有任何改變也要這樣執(zhí)行突照。
這種策略適合于執(zhí)行自動化測試的環(huán)境下使用,其它環(huán)境請慎用氧吐。create-drop :
每次程序啟動時讹蘑,根據(jù)實體類生成表末盔,但是程序正常退出時,表就被刪除了衔肢。update:
最常用的屬性,第一次程序啟動時豁翎,根據(jù)實體類會自動建立起表的結(jié)構(gòu)(前提是先建立好數(shù)據(jù)庫)角骤,以后程序啟動時會根據(jù)實體類自動更新表結(jié)構(gòu),即使表結(jié)構(gòu)改變了心剥,但表中的記錄仍然存在邦尊,不會刪除以前的記錄。要注意的是當(dāng)部署到服務(wù)器后优烧,表結(jié)構(gòu)是不會被馬上建立起來的蝉揍,是要等 第一次訪問JPA時后才會建立。validate :
每次程序啟動時,驗證創(chuàng)建數(shù)據(jù)庫表結(jié)構(gòu),只會和數(shù)據(jù)庫中的表進行比較他炊,不會創(chuàng)建新表捞烟,但是會插入新值。
因此苹粟,建議大多數(shù)場景下用 update 就可以了,線上環(huán)境(或需要慎重的環(huán)境)中用 validate 會更保險一些,沒有特殊情況下不建議用 create 及 create-drop 模式滑燃。
配置完成后,我們運行下 main 程序(代碼如下):
@SpringBootApplication
public class HelloJPAApp {
public static void main(String[] args) {
SpringApplication.run(HelloJPAApp.class, args);
}
}
結(jié)果控制臺輸入里多了一些東西:
......
2017-08-04 15:51:27.017 INFO 20248 --- [ main] org.hibernate.Version : HHH000412: Hibernate Core {5.0.12.Final}
2017-08-04 15:51:27.018 INFO 20248 --- [ main] org.hibernate.cfg.Environment : HHH000206: hibernate.properties not found
2017-08-04 15:51:27.020 INFO 20248 --- [ main] org.hibernate.cfg.Environment : HHH000021: Bytecode provider name : javassist
2017-08-04 15:51:27.086 INFO 20248 --- [ main] o.hibernate.annotations.common.Version : HCANN000001: Hibernate Commons Annotations {5.0.1.Final}
2017-08-04 15:51:27.666 INFO 20248 --- [ main] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.MySQL5Dialect
2017-08-04 15:51:28.230 INFO 20248 --- [ main] org.hibernate.tool.hbm2ddl.SchemaUpdate : HHH000228: Running hbm2ddl schema update
2017-08-04 15:51:28.424 INFO 20248 --- [ main] j.LocalContainerEntityManagerFactoryBean : Initialized JPA EntityManagerFactory for persistence unit 'default'
......
如果控制臺輸出中沒有報錯颓鲜,并且有這之類的輸出表窘,表示配置成功了。
編寫實體類
要操作數(shù)據(jù)庫數(shù)據(jù)甜滨,首先得建表乐严。然而 JPA 使用起來非常簡單,簡單得你只需要在Java的實體類上加上一些注解衣摩,就可以自動映射成數(shù)據(jù)庫表麦备。
下面是一個實體類的代碼:
package com.terran4j.springboot.jpa;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Index;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
@Entity(name = "t_user") // 定義數(shù)據(jù)庫表名稱。
@Table(indexes = { // 定義數(shù)據(jù)庫索引昭娩。
// 唯一索引凛篙。
@Index(name = "ux_user_login_name", columnList = "loginName", unique = true), //
// 非唯一索引。
@Index(name = "idx_user_age", columnList = "age"), //
})
public class User {
/**
* id, 自增主鍵栏渺。
*/
@Id
@GeneratedValue
@Column(length = 20)
private Long id;
/**
* 用戶的登錄名呛梆。
*/
@Column(length = 100, nullable = false)
private String loginName;
/**
* 用戶的年齡。
*/
@Column(length = 3)
private Integer age;
/**
* 用戶的狀態(tài)磕诊。
*/
@Column(length = 16, nullable = false)
@Enumerated(EnumType.STRING)
private UserStatus status = UserStatus.enable;
/**
* 用戶的注冊時間填物。
*/
@Temporal(TemporalType.TIMESTAMP)
@Column(nullable = false)
private Date registTime;
public final Long getId() {
return id;
}
public final void setId(Long id) {
this.id = id;
}
public final String getLoginName() {
return loginName;
}
public final void setLoginName(String loginName) {
this.loginName = loginName;
}
public final Integer getAge() {
return age;
}
public final void setAge(Integer age) {
this.age = age;
}
public final UserStatus getStatus() {
return status;
}
public final void setStatus(UserStatus status) {
this.status = status;
}
public final Date getRegistTime() {
return registTime;
}
public final void setRegistTime(Date registTime) {
this.registTime = registTime;
}
}
首先纹腌,我們看 User 類上兩個注解 @Entity 和 @Table :
@Entity(name = "t_user") 注解 加在 User 上,表示它是一個實體類滞磺, 表名是 t_user 升薯。
@Table(indexes = { // 定義數(shù)據(jù)庫索引。
// 唯一索引击困。
@Index(name = "ux_user_login_name", columnList = "loginName", unique = true), //
// 非唯一索引涎劈。
@Index(name = "idx_user_age", columnList = "age"), //
})
@Table 里面定義了這個表的索引,一個 @Index 注解定義了一個索引阅茶, name 屬性表示數(shù)據(jù)庫表中索引的名稱蛛枚, columnList 表示對應(yīng)的 java 屬性名稱, unique = true 表示此索引是唯一索引脸哀。
比如上面的 @Index(name = "ux_user_login_name", columnList = "loginName", unique = true)
表示對 loginName 屬性所對應(yīng)的字段(映射到數(shù)據(jù)庫表中應(yīng)該是 login_name 字段)建立唯一索引蹦浦,索引名為ux_user_login_name。
columnList 中可以放多個java屬性撞蜂,中間用逗號隔開盲镶,表示聯(lián)合索引,如:@Index(name = "idx_user_age_name", columnList = "age,loginName")
表示建立 age 與 login_name 字段的聯(lián)合索引蝌诡。
注意: java 屬性名都是駝峰命名法(如 loginName)徒河,而數(shù)據(jù)庫表字段都是下劃線命名法(如 login_name),JPA會自動根據(jù)java屬性名的駝峰命名法映射成數(shù)據(jù)庫表字段的下劃線命名法送漠,如 loginName 屬性映射到數(shù)據(jù)庫就是 login_name 字段顽照。
這個 User 實體類寫好后,我們再運行下之前的 main 程序闽寡,然后驚奇的發(fā)現(xiàn):數(shù)據(jù)庫里自動建了一個名為 "t_user"的表:
表示JPA在啟動時根據(jù)實體類代兵,自動在數(shù)據(jù)庫中創(chuàng)建了對應(yīng)的表了。
注意: 實體類 User 一定要放在 HelloJPAApp 類所在包中或子包中爷狈,不然
HelloJPAApp 啟動時 Spring Boot 可能掃描不到植影。
編寫 Repository 接口
有了表之后,我們要寫對表進行增刪改查的代碼涎永,用JPA干這事簡直是簡單到姥姥家了思币,只需要繼續(xù)一個接口就搞定了,請看代碼:
package com.terran4j.springboot.jpa;
import org.springframework.data.jpa.repository.JpaRepository;
public interface UserRepository extends JpaRepository<User, Long> {
}
這樣就寫完了基本的增刪改查的代碼羡微? 的確是這樣谷饿,因為 JpaRepository 接口已經(jīng)定義了很多方法,JpaRepository 的父類也定義了很多方法妈倔,如:
而 Spring Boot JPA又幫你實現(xiàn)了這些方法博投,你只需要在繼承 JpaRepository 時指定了實體類的類對象和 ID 屬性的類對象就可以了,如 public interface UserRepository extends JpaRepository<User, Long>
表示實體類是 User, User 中 ID 屬性是 Long 類型的盯蝴。
并且毅哗, Spring Boot JPA 掃描到 UserRepository 是 Repository 的子類后听怕,會以動態(tài)代理的方式對 UserRepository 進行實現(xiàn),并將實現(xiàn)的對象作為 Bean 注入到 Spring 容器中虑绵,而我們只需要像使用普通的 Spring Bean 一樣尿瞭,用 @Autowired 引入即可使用。
下面翅睛,我們編寫一個 Controller 類來調(diào)用 UserRepository 声搁,如下所示:
package com.terran4j.springboot.jpa;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController {
@Autowired
private HelloService helloService;
@Autowired
private UserRepository userRepository;
// URL示例: http://localhost:8080/hello/1
@RequestMapping(value = "/hello/{id}", method = RequestMethod.GET)
public String sayHello(@PathVariable("id") Long id) {
User user = userRepository.findOne(id);
if (user == null) {
return String.format("User NOT Found: %d", id);
}
String name = user.getLoginName();
return helloService.hello(name);
}
}
相關(guān)的 HelloService 的代碼為:
package com.terran4j.springboot.jpa;
import org.springframework.stereotype.Component;
@Component
public class HelloService {
public String hello(String name) {
return "Hello, " + name + "!";
}
}
代碼中, User user = userRepository.findOne(id);
表示根據(jù) id 從表中查出一條記錄宏所,并映射成 User 對象酥艳。
為了測試效果摊溶,我們先執(zhí)行以下SQL在數(shù)據(jù)庫中制造點數(shù)據(jù):
delete from `t_user`;
insert into `t_user` (`id`, `login_name`, `age`, `regist_time`, `status`) values
('1','Jim','12','2017-07-26 09:29:47','enable'),
('2','Mike','23','2017-07-25 09:30:54','disable');
然后啟動程序爬骤,在瀏覽器中用以下URL訪問:
http://localhost:8080/hello/1
可以看到, userRepository.findOne(id)
的確把數(shù)據(jù)給查出來了莫换。
使用原生SQL查詢
然而霞玄,JpaRepository 提供的僅是簡單的增刪查改方法,那遇到復(fù)雜的查詢怎么辦拉岁?
Spring Boot JAP 底層是 Hibernate 實現(xiàn)的坷剧, Hibernate 提供了 hql 的類SQL語法來編寫復(fù)雜查詢,不過我個人不建議用 HQL喊暖,因為畢竟 HQL 與SQL還是有較大不同的惫企,需要學(xué)習(xí)成本(但這個成本其實是沒必要投入的),另外就是一些場景下需要用數(shù)據(jù)庫的特定優(yōu)化機制時陵叽,HQL 實現(xiàn)不了狞尔。
所以,我的建議是使用原生 SQL 的方式實現(xiàn)巩掺,而 JPA 是提供了這個能力的偏序,下面我介紹一種用在 orm.xml 中寫原生 SQL的方法。
假如需求是這樣的胖替,我們要查詢某一年齡段的 User(如 10歲 ~ 20歲的)研儒,SQL大概要這樣寫:
SELECT * FROM t_user AS u
WHERE u.age >= '10' AND u.age <= '20'
ORDER BY u.regist_time DESC
Spring Boot JAP 約定是把 SQL 寫在類路徑的 META-INF/orm.xml 文件中(如果 META-INF 文件夾沒有就創(chuàng)建一個),文件路徑如下:
orm.xml 文件的內(nèi)容如下所示:
<?xml version="1.0" encoding="UTF-8" ?>
<entity-mappings xmlns="http://xmlns.jcp.org/xml/ns/persistence/orm"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence/orm http://xmlns.jcp.org/xml/ns/persistence/orm_2_0.xsd"
version="2.1">
<named-native-query name="User.findByAgeRange"
result-class="com.terran4j.springboot.jpa.User">
<query><![CDATA[
select * from t_user as u
where u.age >= :minAge and u.age <= :maxAge
order by u.regist_time desc
]]></query>
</named-native-query>
</entity-mappings>
<named-native-query>
表示是一個“原生SQL查詢”独令, name="User.findByAgeRange"
表示給這個查詢起了一個名字叫“User.findByAgeRange”端朵,后面代碼中會根據(jù)這個名字來引用這個查詢,result-class="com.terran4j.springboot.jpa.User"
表示SQL查詢返回的結(jié)果集燃箭,每條記錄轉(zhuǎn)化成 User 對象逸月。
<query>
里面是原生的SQL語句,其中 : 開始的是變量遍膜,如上面的SQL碗硬,有兩個變量 :minAge 和 :maxAge 瓤湘,這些變量的值,會從外面?zhèn)魅脒M來恩尾。
然后我們可以在 UserRepository 中添加一個findByAgeRange
方法來使用這個原生SQL查詢弛说,如下代碼所示:
package com.terran4j.springboot.jpa;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
public interface UserRepository extends JpaRepository<User, Long> {
/**
* 查詢某年齡段范圍之內(nèi)的用戶。
*
* @param minAge
* 最小年齡翰意。
* @param maxAge
* 最大年齡木人。
* @return
*/
@Query(nativeQuery = true, name = "User.findByAgeRange")
List<User> findByAgeRange(@Param("minAge") int minAge, @Param("maxAge") int maxAge);
}
這個findByAgeRange
方法上面有一個@Query(nativeQuery = true, name = "User.findByAgeRange")
注解,表示這個方法的實現(xiàn)使用名為User.findByAgeRange
的查詢冀偶,此查詢是用原生SQL寫的醒第;方法參數(shù)上有@Param
注解,表示將方法的參數(shù)值映射到查詢中的變量进鸠。
最后稠曼,我們寫一個 Controller 調(diào)用這個方法試,如下代碼所示:
package com.terran4j.springboot.jpa;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class HelloController2 {
@Autowired
private UserRepository userRepository;
// URL示例: http://localhost:8080/users
@RequestMapping(value = "/users", method = RequestMethod.GET)
@ResponseBody
public List<User> findByAgeRange(
@RequestParam(value = "minAge", defaultValue = "1") int minAge,
@RequestParam(value = "maxAge", defaultValue = "999") int maxAge) {
List<User> users = userRepository.findByAgeRange(minAge, maxAge);
return users;
}
}
然后訪問 URL: http://localhost:8080/users
客年,運行效果如下:
我們看到findByAgeRange
方法把數(shù)據(jù)給查出來了霞幅,同時控制臺有一行輸出:
Hibernate: select * from t_user as u where u.age >= ? and u.age <= ? order by u.regist_time desc
這也是 JPA 底層實際執(zhí)行的 SQL,也就是把我們寫的 SQL 中 :minAge 和 :maxAge 兩個變量換成“綁定變量”的方式量瓜。
總結(jié)說明
本文我們講解了用 Spring Boot JPA 來訪問數(shù)據(jù)庫司恳,是不是覺得用 Spring Boot 開發(fā)超級爽呢,本系列這三章就講到這了绍傲,主要是帶大家對 Spring Boot 快速上手扔傅,后面筆者會努力出更多關(guān)于 Spring Boot && Spring Cloud 的技術(shù)文章,敬請期待烫饼。
點擊 這里 可以查看本系列的全部章節(jié)猎塞。
(本系列的目標是幫助有 Java 開發(fā)經(jīng)驗的程序員們快速掌握使用 Spring Boot 開發(fā)的基本技巧,感受到 Spring Boot 的極簡開發(fā)風(fēng)格及超爽編程體驗枫弟。)
另外邢享,我們有一個名為 SpringBoot及微服務(wù) 的微信公眾號,感興趣的同學(xué)請掃描下面的二維碼關(guān)注下吧淡诗,關(guān)注后就可以收到我們定期分享的技術(shù)干貨哦骇塘!