本章是《 Spring Boot 快速入門 》系列教程的第三章,若要查看本系列的所有章節,請點擊 這裏 。php
在上一章《 Spring Boot MVC 》中,咱們瞭解了使用 Spring Boot MVC 來開發 Http Restful API的相關技術,但只處理HTTP請求是不夠的,如今的應用程序大多使用了關係型數據庫,所以本章咱們會帶着你們繼續 Spring Boot 體驗之旅,此次咱們將採用 JPA 技術來訪問數據庫,給 Hello Spring Boot 程序添加帶數據庫訪問演示代碼。java
本章的示例代碼放在「碼雲」上,你們能夠免費下載或瀏覽:mysql
https://git.oschina.net/terran4j/springboot/tree/master/springboot-jpagit
相關軟件使用的版本:程序員
程序在以上版本均調試過,能夠正常運行,其它版本僅做參考。web
JPA是Java Persistence API的簡稱,中文名Java持久層API,是JDK 5.0註解或XML描述對象-關係表的映射關係,並將運行期的實體「對象持久化」到數據庫中。 JPA技術能夠極大的下降了對數據庫編程的複雜性,一些簡單的增刪改查的操做,代碼只須要操做對象就能夠了,JPA自動的幫你映射成數據庫的SQL操做。spring
不過 JPA 只是標準標準,而 Spring Boot 提供了它的技術實現: Spring Data JPA。不過 Spring Data JPA 也不是重複造輪子,它是基於一個很是著名的ORM框架——Hibernate——之上封裝實現的。sql
Spring Data JPA 極大簡化了數據庫訪問層代碼,只要3步,就能搞定一切:數據庫
spring-boot-starter-data-jpa
,及在 application配置文件中配置數據庫鏈接。另外,若是有複雜的SQL查詢,Spring Data JPA 也提供了編寫原生 SQL 實現的方式。 apache
首先,咱們要在 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相關的 jar 包;另外一個是 mysql-connector-java
, 由於本示例中咱們要連MYSQL的數據庫,因此 mysql jdbc 驅動(java) 是必不可少的。
而後,咱們要在application.properties
配置文件中配置數據庫鏈接及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
開頭的是數據庫鏈接的配置,請注意必定要保持對應的數據庫是存在的,而且用戶名密碼都沒錯,否則待會程序運行時沒法啓動。 以spring.jpa
開發的是 JPA 的配置,spring.jpa.hibernate.ddl-auto
表示每次程序啓動時對數據庫表的處理策略,有如下可選值:
create: 每次程序啓動時,都會刪除上一次的生成的表,而後根據你的實體類再從新來生成新表,哪怕兩次沒有任何改變也要這樣執行。 這種策略適合於執行自動化測試的環境下使用,其它環境請慎用。
create-drop : 每次程序啓動時,根據實體類生成表,可是程序正常退出時,表就被刪除了。
update: 最經常使用的屬性,第一次程序啓動時,根據實體類會自動創建起表的結構(前提是先創建好數據庫),之後程序啓動時會根據實體類自動更新表結構,即便表結構改變了,但表中的記錄仍然存在,不會刪除之前的記錄。要注意的是當部署到服務器後,表結構是不會被立刻創建起來的,是要等 第一次訪問JPA時後纔會創建。
validate : 每次程序啓動時,驗證建立數據庫表結構,只會和數據庫中的表進行比較,不會建立新表,可是會插入新值。
所以,建議大多數場景下用 update 就能夠了,線上環境(或須要慎重的環境)中用 validate 會更保險一些,沒有特殊狀況下不建議用 create 及 create-drop 模式。
配置完成後,咱們運行下 main 程序(代碼以下):
@SpringBootApplication public class HelloJPAApp { public static void main(String[] args) { SpringApplication.run(HelloJPAApp.class, args); } }
結果控制檯輸入裏多了一些東西:
...... 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' ......
若是控制檯輸出中沒有報錯,而且有這之類的輸出,表示配置成功了。
要操做數據庫數據,首先得建表。然而 JPA 使用起來很是簡單,簡單得你只須要在Java的實體類上加上一些註解,就能夠自動映射成數據庫表。
下面是一個實體類的代碼:
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") // 定義數據庫表名稱。 @Table(indexes = { // 定義數據庫索引。 // 惟一索引。 @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; /** * 用戶的狀態。 */ @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 = { // 定義數據庫索引。 // 惟一索引。 @Index(name = "ux_user_login_name", columnList = "loginName", unique = true), // // 非惟一索引。 @Index(name = "idx_user_age", columnList = "age"), // })
@Table 裏面定義了這個表的索引,一個 @Index 註解定義了一個索引, name 屬性表示數據庫表中索引的名稱, columnList 表示對應的 java 屬性名稱, unique = true 表示此索引是惟一索引。 好比上面的 @Index(name = "ux_user_login_name", columnList = "loginName", unique = true)
表示對 loginName 屬性所對應的字段(映射到數據庫表中應該是 login_name 字段)創建惟一索引,索引名爲ux_user_login_name。 columnList 中能夠放多個java屬性,中間用逗號隔開,表示聯合索引,如:@Index(name = "idx_user_age_name", columnList = "age,loginName")
表示創建 age 與 login_name 字段的聯合索引。
注意: java 屬性名都是駝峯命名法(如 loginName),而數據庫表字段都是下劃線命名法(如 login_name),JPA會自動根據java屬性名的駝峯命名法映射成數據庫表字段的下劃線命名法,如 loginName 屬性映射到數據庫就是 login_name 字段。
這個 User 實體類寫好後,咱們再運行下以前的 main 程序,而後驚奇的發現:數據庫裏自動建了一個名爲 "t_user"的表:
表示JPA在啓動時根據實體類,自動在數據庫中建立了對應的表了。
注意: 實體類 User 必定要放在 HelloJPAApp 類所在包中或子包中,否則 HelloJPAApp 啓動時 Spring Boot 可能掃描不到。
有了表以後,咱們要寫對錶進行增刪改查的代碼,用JPA幹這事簡直是簡單到姥姥家了,只須要繼續一個接口就搞定了,請看代碼:
package com.terran4j.springboot.jpa; import org.springframework.data.jpa.repository.JpaRepository; public interface UserRepository extends JpaRepository<User, Long> { }
這樣就寫完了基本的增刪改查的代碼? 的確是這樣,由於 JpaRepository 接口已經定義了不少方法,JpaRepository 的父類也定義了不少方法,如:
而 Spring Boot JPA又幫你實現了這些方法,你只須要在繼承 JpaRepository 時指定了實體類的類對象和 ID 屬性的類對象就能夠了,如 public interface UserRepository extends JpaRepository<User, Long>
表示實體類是 User, User 中 ID 屬性是 Long 類型的。 而且, Spring Boot JPA 掃描到 UserRepository 是 Repository 的子類後,會以動態代理的方式對 UserRepository 進行實現,並將實現的對象做爲 Bean 注入到 Spring 容器中,而咱們只須要像使用普通的 Spring Bean 同樣,用 @Autowired 引入便可使用。
下面,咱們編寫一個 Controller 類來調用 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); } }
相關的 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);
表示根據 id 從表中查出一條記錄,並映射成 User 對象。
爲了測試效果,咱們先執行如下SQL在數據庫中製造點數據:
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)
的確把數據給查出來了。
然而,JpaRepository 提供的僅是簡單的增刪查改方法,那遇到複雜的查詢怎麼辦? Spring Boot JAP 底層是 Hibernate 實現的, Hibernate 提供了 hql 的類SQL語法來編寫複雜查詢,不過我我的不建議用 HQL,由於畢竟 HQL 與SQL仍是有較大不一樣的,須要學習成本(但這個成本實際上是不必投入的),另外就是一些場景下須要用數據庫的特定優化機制時,HQL 實現不了。 因此,個人建議是使用原生 SQL 的方式實現,而 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 文件夾沒有就建立一個),文件路徑以下:
orm.xml 文件的內容以下所示:
<?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」,後面代碼中會根據這個名字來引用這個查詢,result-class="com.terran4j.springboot.jpa.User"
表示SQL查詢返回的結果集,每條記錄轉化成 User 對象。 <query>
裏面是原生的SQL語句,其中 : 開始的是變量,如上面的SQL,有兩個變量 :minAge 和 :maxAge ,這些變量的值,會從外面傳入進來。
而後咱們能夠在 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> { /** * 查詢某年齡段範圍以內的用戶。 * * @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")
註解,表示這個方法的實現使用名爲User.findByAgeRange
的查詢,此查詢是用原生SQL寫的;方法參數上有@Param
註解,表示將方法的參數值映射到查詢中的變量。
最後,咱們寫一個 Controller 調用這個方法試,以下代碼所示:
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
方法把數據給查出來了,同時控制檯有一行輸出:
Hibernate: select * from t_user as u where u.age >= ? and u.age <= ? order by u.regist_time desc
這也是 JPA 底層實際執行的 SQL,也就是把咱們寫的 SQL 中 :minAge 和 :maxAge 兩個變量換成「綁定變量」的方式。
本文咱們講解了用 Spring Boot JPA 來訪問數據庫,是否是以爲用 Spring Boot 開發超級爽呢,本系列這三章就講到這了,主要是帶你們對 Spring Boot 快速上手,後面筆者會努力出更多關於 Spring Boot && Spring Cloud 的技術文章,敬請期待。
點擊 這裏 能夠查看本系列的所有章節。 (本系列的目標是幫助有 Java 開發經驗的程序員們快速掌握使用 Spring Boot 開發的基本技巧,感覺到 Spring Boot 的極簡開發風格及超爽編程體驗。)
另外,咱們有一個名爲 SpringBoot及微服務 的微信公衆號,感興趣的同窗請掃描下面的二維碼關注下吧,關注後就能夠收到咱們按期分享的技術乾貨哦!