第三章 Spring Data JPA

本章是《 Spring Boot 快速入門 》系列教程的第三章,若要查看本系列的所有章節,請點擊 這裏 php

目錄

  • 簡介
  • 源碼下載
  • 軟件版本
  • JPA簡介
  • 在項目中配置JPA
  • 編寫實體類
  • 編寫 Repository 接口
  • 使用原生SQL查詢
  • 總結說明

簡介

在上一章《 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

軟件版本

相關軟件使用的版本:程序員

  • Java: 1.8
  • Maven: 3.3.9
  • MYSQL: 5.5

程序在以上版本均調試過,能夠正常運行,其它版本僅做參考。web

JPA簡介

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步,就能搞定一切:數據庫

  1. 在pom.xml中配置spring-boot-starter-data-jpa,及在 application配置文件中配置數據庫鏈接。
  2. 編寫 Entity 類,依照 JPA 規範,定義實體。
  3. 編寫 Repository 接口,依靠 Spring Data 規範,定義數據訪問接口(注意,只要接口,不須要任何實現)

另外,若是有複雜的SQL查詢,Spring Data JPA 也提供了編寫原生 SQL 實現的方式。  apache

在項目中配置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相關的 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"的表:

t_user.png

表示JPA在啓動時根據實體類,自動在數據庫中建立了對應的表了。

注意: 實體類 User 必定要放在 HelloJPAApp 類所在包中或子包中,否則 HelloJPAApp 啓動時 Spring Boot 可能掃描不到。

編寫 Repository 接口

有了表以後,咱們要寫對錶進行增刪改查的代碼,用JPA幹這事簡直是簡單到姥姥家了,只須要繼續一個接口就搞定了,請看代碼:

package com.terran4j.springboot.jpa;

import org.springframework.data.jpa.repository.JpaRepository;

public interface UserRepository extends JpaRepository<User, Long> {

}

這樣就寫完了基本的增刪改查的代碼? 的確是這樣,由於 JpaRepository 接口已經定義了不少方法,JpaRepository 的父類也定義了不少方法,如:

JPA.png

而 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

sayHello運行效果

能夠看到, userRepository.findOne(id) 的確把數據給查出來了。

使用原生SQL查詢

然而,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

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查詢結果

咱們看到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及微服務 的微信公衆號,感興趣的同窗請掃描下面的二維碼關注下吧,關注後就能夠收到咱們按期分享的技術乾貨哦! SpringBoot及微服務-公衆號二維碼

相關文章
相關標籤/搜索