Spring Boot簡明教程之資料訪問(二):JPA(超詳細)
Spring Boot簡明教程之資料訪問(二):JPA(超詳細)
文章目錄
建立專案
建立的過程和我們的第一篇文章:SpringBoot簡明教程之快速建立第一個SpringBoot應用大致相同,差別只是我們在挑選所需要的元件時,除了Web元件外,我們需要新增如下三個元件:JPA、MySQL、JDBC
或者,我們按照第一次建立完成後,手動在pom.xml檔案中加入以下配置:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</ dependency>
<dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
我們發現,與我們直接使用JDBC template不同的是,這次我們引入了一個新的包:spring-boot-starter-data-jpa
,我們進一步檢視,就會發現,其主要是引入了一個spring-data-jpa
的包。
Spring Data簡介
Spring Data是為了簡化構建基於 Spring 框架應用的資料訪問技術,包括關係、非關係資料庫、
雲資料服務等等。SpringData為我們提供使用統一的API來對資料訪問層進行操作,讓我們在使用關係型或者非關係型資料訪問技術時都基於Spring提供的統一標準,標準包含了CRUD(建立、獲取、更新、刪除)、查詢、
排序和分頁的相關操作,同時為我們提供了統一的資料訪問類的模板,例如:MongoTemplate、RedisTemplate等。
JPA簡介
JPA(Java Persistence API)定義了一系列物件持久化的標準,它的出現主要是為了簡化現有的持久化開發工作和整合 ORM 技術,目前實現了這一規範的有 Hibernate、TopLink、JDO 等 ORM 框架。
Spring Data 與JPA
我們可以將Spring-data-jpa理解為Spring Boot對於JPA的再次封裝,使得我們通過Spring-data-jpa即實現常用的資料庫操作:
- JpaRepository實現基本功能: 編寫介面繼承JpaRepository既有crud及分頁等基本功能
- 定義符合規範的方法命名: 在介面中只需要宣告符合規範的方法,即擁有對應的功能
- 支援自定義查詢: @Query自定義查詢,定製查詢SQL
- Specifications查詢(Spring Data JPA支援JPA2.0的Criteria查詢)
使用Spring Data JPA的基本流程
建立實體類(entity)
package cn.newtol.springboot07.entity;
import javax.persistence.*;
/**
* @Author: 公眾號:Newtol
* @Description: JPA使用示例:使用JPA註解配置對映關係
* @Date: Created in 18:45 2018/9/24
*/
@Entity //表示一個實體類,和資料表進行對映
@Table(name = "t_user") //所對映的表的名字,可省略,預設為實體類名
public class User {
@Id //設定為主鍵
@GeneratedValue(strategy = GenerationType.IDENTITY) //定義為自增主鍵
private Integer id;
@Column(name = "t_name",nullable = false) //設定該欄位在資料表中的列名,並設定該欄位設定為不可為空
private String name;
@Column //預設則欄位名就為屬性名
private Integer phone;
@Transient //增加該註解,則在資料表中不會進行對映
private String address;
// //省略getter settet方法、構造方法,建立時自行補上
}
建立Dao層(Repository)
package cn.newtol.springboot07.repository;
import cn.newtol.springboot07.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
/**
* @Author: 公眾號:Newtol
* @Description:
* @Date: Created in 19:35 2018/9/24
*/
//定義為介面
public interface UserRepository extends JpaRepository<User,Integer>{
//直接繼承即可,不用編寫任何方法
}
編寫配置檔案
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/springboot?useSSL=false
username: root
password:
jpa:
hibernate:
ddl-auto: create
show-sql: true
建立Controller
package cn.newtol.springboot07.controller;
import cn.newtol.springboot07.entity.User;
import cn.newtol.springboot07.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import java.util.Optional;
/**
* @Author: 公眾號:Newtol
* @Description:
* @Date: Created in 19:37 2018/9/24
*/
@RestController
public class UserController {
@Autowired
UserRepository userRepository;
//根據Id查詢使用者
@GetMapping("/user/{id}")
public User getUser(@PathVariable("id") Integer id){
User user = userRepository.getOne(id);
return user;
}
//插入使用者
@GetMapping("/user")
public User addUser(User user){
userRepository.save(user);
return user; //返回插入的物件及其自增ID
}
}
直接啟動專案,看到Spring Boot自動幫我們在資料庫中自動建立了t_user表:
並且因為我們之前對address
屬相使用了@Transient註解,不進行對映,所以我們看到表中沒有address
欄位。現在我們將@Transient註釋後,再次啟動專案:
address
就也被成功的建立。
接著,我們嘗試著往資料庫中插入一條資料,我們在瀏覽器中輸入:localhost:8080/user?name=zhangsan&phone=123&address=beijing
再到資料庫中進行檢視:
插入成功。
現在我們來查詢剛剛插入的資料,瀏覽器輸入:http://localhost:8080/user/1
成功查詢到剛才插入的資料,並且我們可以在控制檯看到Spring Boot為我們列印的剛才執行的查詢語句:
JPA常用註解說明
雖然我們在上面的示例中已經使用了常用了的註解,但是為了方便和理解,我們在這個地方歸納一下關於JPA的一些常用的註解及其對應的引數。
-
@Entity:表示該類是一個的實體類。@Entity標註是必需的 ,name屬性為可選;需要注意的是:@Entity標註的實體類至少需要有一個無參的構造方法。
-
@Table:表示該實體類所對映的資料表資訊,需要標註在類名前,不能標註在方法或屬性前。引數如下:
引數 說明 name 實體所對應表的名稱,預設表名為實體名 catalog 實體指定的目錄名 schema 表示實體指定的資料庫名 uniqueConstraints 該實體所關聯的唯一約束條件,一個實體可以有多個唯一的約束,預設沒有約束條件。建立方式:uniqueConstraints = {@uniqueConstraint(columnNames = {“name”,“phone”})} indexes 該實體所關聯的索引。建立方式:indexes = { @Index(name = “index_name”, columnList = “t_name”)} -
@Column:表示該屬性所對映的欄位,此標記可以標註在Getter方法或屬性前。它有如下引數:
引數 說明 unique 欄位是否為唯一標識,預設為false nullable 欄位是否可以為null值,預設為true insertable 使用“INSERT” SQL語指令碼插入資料時,是否需要插入該欄位的值。 updatable 使用“UPDATE”指令碼插入資料時,是否需要更新該欄位的值 table 當對映多個表時,指定表中的欄位。預設值為主表的表名。 length 當欄位的型別為varchar時的欄位的長度,預設為255個字元。 precision 用於表示精度,數值的總長度 scale 用於表示精度,小數點後的位數。 columnDefinition 用於建立表時新增該欄位所需要另外執行的SQL語句 -
@Id:表示該屬性為主鍵,每一個實體類至少要有一個主鍵(Primary key)。
引數 說明 strategy 表示生成主鍵的策略 ,有4種類型:GenerationType.TABLE 、 GenerationType.SEQUENCE 、 GenerationType.IDENTITY 、 GenerationType.AUTO 預設為:AUTO,表示自動生成。 generator 生成規則名,不同的策略有不同的配置 -
@Basic: 實體屬性設定載入方式為惰性載入
引數 說明 fetch 表示獲取值的方式,它的值定義的列舉型,可選值為LAZY(惰性載入)、EAGER(即時載入),預設為即時載入。 optional 屬性是否可以為null,不能用於java基本資料型( byte、int、short、long、boolean、char、float、double )
自定義查詢
我們剛剛在上面使用的是JPA已經封裝好的一些預設的查詢方式,但是我們在實際的專案中,可能需要自定義一些符合實際需求的查詢,那麼我們就需要用到自定義查詢。
關鍵字查詢
JPA已經幫我們做好了關鍵字,我們只需要將這些關鍵字組成方法名,就可以實現相對應的查詢。
關鍵字 | 示例 | 同功能JPQL |
---|---|---|
And |
findByLastnameAndFirstname |
where x.lastname = ?1 and x.firstname = ?2 |
Or |
findByLastnameOrFirstname |
where x.lastname = ?1 or x.firstname = ?2 |
Is,Equals |
findByFirstname,findByFirstnameIs,findByFirstnameEquals |
where x.firstname = 1? |
Between |
findByStartDateBetween |
where x.startDate between 1? and ?2 |
LessThan |
findByAgeLessThan |
where x.age < ?1 |
LessThanEqual |
findByAgeLessThanEqual |
where x.age <= ?1 |
GreaterThan |
findByAgeGreaterThan |
where x.age > ?1 |
GreaterThanEqual |
findByAgeGreaterThanEqual |
where x.age >= ?1 |
After |
findByStartDateAfter |
where x.startDate > ?1 |
Before |
findByStartDateBefore |
where x.startDate < ?1 |
IsNull |
findByAgeIsNull |
where x.age is null |
IsNotNull,NotNull |
findByAge(Is)NotNull |
where x.age not null |
Like |
findByFirstnameLike |
where x.firstname like ?1 |
NotLike |
findByFirstnameNotLike |
where x.firstname not like ?1 |
StartingWith |
findByFirstnameStartingWith |
where x.firstname like ?1 (引數前面加 %) |
EndingWith |
findByFirstnameEndingWith |
where x.firstname like ?1 (引數後面加 %) |
Containing |
findByFirstnameContaining |
where x.firstname like ?1 (引數兩邊加 %) |
OrderBy |
findByAgeOrderByLastnameDesc |
where x.age = ?1 order by x.lastname desc |
Not |
findByLastnameNot |
where x.lastname <> ?1 |
In |
findByAgeIn(Collection<Age> ages) |
where x.age in ?1 |
NotIn |
findByAgeNotIn(Collection<Age> age) |
where x.age not in ?1 |
True |
findByActiveTrue() |
where x.active = true |
False |
findByActiveFalse() |
where x.active = false |
IgnoreCase |
findByFirstnameIgnoreCase |
where UPPER(x.firstame) = UPPER(?1) |
我們除了使用find
關鍵字以外,還可以使用count
、delete
、get
等。
例如,我們在Controller中加入如下方法得:
/**
* @Author: 公眾號:Newtol
* @Description: 自定義關鍵字查詢
* @Date: Created in 19:37 2018/9/24
*/
@GetMapping("/test/{address}/{phone}")
public List<User> getUserByAddressAndPhone (@PathVariable("address") String address, @PathVariable("phone") Integer phone){
return userRepository.findByAddressEqualsAndPhoneNot(address,phone);
}
在userRepository中雞加入如下方法:
/**
* @Author: 公眾號:Newtol
* @Description:
* @Date: Created in 19:35 2018/9/24
*/
public interface UserRepository extends JpaRepository<User,Integer>{
public List<User> findByAddressEqualsAndPhoneNot (String address, Integer phone);
}
在資料庫中插入下面兩條資料:
INSERT INTO `t_user` VALUES ('2', 'beijing', 'zhangsi', '456');
INSERT INTO `t_user` VALUES ('3', 'beijing', 'wangwu', '123');
在瀏覽器輸入:http://localhost:8080/test/beijing/456
我們就可以看到返回瞭如下資料,查詢成功。
自定義SQL語句
通常如果使用JPA提供給我們的關鍵字組成的查詢方法仍然無法滿足需求,那麼我們就可以使用@Query
來實現自定的SQL語句。
原生SQL
JPA中支援使用原生的SQL語句,例如:
在userRepository中加入下面的方法:
//自定義SQL查詢
@Query(value = "select * from t_user WHERE phone = ? " ,nativeQuery = true)
List<User> getAllUser (Integer phone);
在controller中加入以下方法:
//自定義SQL查詢
@GetMapping("/test/{phone}")
public List<User> getAllUser(@PathVariable("phone") Integer phone){
return userRepository.getAllUser(phone);
}
在瀏覽器中輸入:http://localhost:8080/test/123
返回資料,查詢成功。
但是,如果我們需要執行UPDATE
、DELETE
操作時,除了使用@Query
外,還需要使用@Modifying
、
@Transactional
兩個註解。
例如:
在userRepository中加入下面的方法:
//自定義SQL刪除
@Query(value = "delete from t_user WHERE phone = ? ",nativeQuery = true)
@Modifying
@Transactional
void deleteUser(Integer phone);
在controller中加入以下方法:
//自定義SQL刪除
@GetMapping("/del/{phone}")
public void deleteUser(@PathVariable("phone") Integer phone){
userRepository.deleteUser(phone);
}
在瀏覽器中輸入:http://localhost:8080/del/456
,我們就會發現資料庫中phone為456的資料就已經被刪除了。
JPQL查詢
在userRepository中加入下面的方法:
//JPQL查詢
@Query("select u from User u where u.name = ?1")
User getUserByName(String name);
在controller中加入以下方法:
//JPQL查詢
@GetMapping("/get/{name}")
public User getUserByName(@PathVariable("name") String name){
return userRepository.getUserByName(name);
}
瀏覽器輸入:http://localhost:8080/get/zhangsan
,資料返回,查詢成功:
複雜查詢
分頁查詢
我們經常會遇到進行分頁查詢的情況,而JPA就很好的替我們解決了這一個問題。只需要幾行程式碼就可以解決:
例如:在controller中加入以下方法:
//分頁查詢
@GetMapping("/userList/{page}/{size}")
public Page<User> getUserList(@PathVariable("page") Integer page, @PathVariable("size") Integer size) {
Sort sort = new Sort(Sort.Direction.DESC,"id");
PageRequest pageRequest = PageRequest.of(page,size,sort);
return userRepository.findAll(pageRequest);
}
瀏覽器輸入:http://localhost:8080/userList/0/3
返回的資料格式為:
{
"content": [
{
"id": 3,
"name": "wangwu",
"phone": 123,
"address": "beijing"
},
{
"id": 2,
"name": "lisi",
"phone": 789,
"address": "shanghai"
},
{
"id": 1,
"name": "zhangsan",
"phone": 123,
"address": "beijing"
}
],
"pageable": {
"sort": {
"sorted": true,
"unsorted": false
},
"offset": 0,
"pageSize": 3,
"pageNumber": 0,
"paged": true,
"unpaged": false
},
"totalPages": 1,
"last": true,
"totalElements": 3,
"number": 0,
"size": 3,
"sort": {
"sorted": true,
"unsorted": false
},
"numberOfElements": 3,
"first": true
}
限制查詢
除了限制查詢以外,對於排行榜類的資料,或許我們只需要取出前面幾名即可,Jpa也對這種情況做了很好的支援:
例如:在userRepository中加入下面的方法:
//限制查詢
List<User> findFirst2ByAddressOrderByIdDesc (String address);
在controller中加入以下方法:
@GetMapping("/top/{address}")
public List<User> getTop2User(@PathVariable("address") String address){
return userRepository.findFirst2ByAddressOrderByIdDesc(address);
}
在瀏覽器輸入:http://localhost:8080/top/beijing
返回的資料為:
[{"id":3,"name":"wangwu","phone":123,"address":"beijing"},{"id":1,"name":"zhangsan","phone":123,"address":"beijing"}]
常見問題及其解決方案:
-
如果提示:No identifier specified for entity: xxx
解決方案:這是因為在建立實體類時,匯入的jar包錯誤導致的。我們將
import org.springframework.data.annotation.*;
更改為:import javax.persistence.*;
即可 -
如果提示: No default constructor for entity: :
解決方案:這是因為在實體類中建立了含引數的構造方法造成的,這是因為在使用類反射機制 Class.newInstance()方法建立例項時,需要有一個預設的無引數構造方法,否則會執出例項化異常(InstantiationException),所以將構造方法更改為無引數即可。
-
如果提示:Caused by: org.springframework.dao.InvalidDataAccessApiUsageException: Executing an update/delete query; nested exception is javax.persistence.TransactionRequiredException: Executing an update/delete query
解決方案:這是因為在JPA中執行DELETE/UPDATE操作時,需要使用
@Modifying
、
@Transactional
兩個註解,補上即可。 -
如果提示:Can not issue data manipulation statements with executeQuery()。
解決方案:如果提示這個錯誤,就需要查詢檢查自己的SQL語句是否書寫正確,如果正確,檢查是否加上
@Modifying
、@Transactional
兩個註解 -
如果提示:Ambiguous handler methods mapped for HTTP path
解決方案:這是由於URL對映重複引起的,將你所請求的URL重新進行定義即可。
-
如果在使用PageRequest方法顯示過期
解決方案:將構造方法更改為:PageRequest.of即可。在2.0版本以後,就使用of(…) 方法代替 PageRequest(…)構造器。
總結
我們首先介紹了spring Data
,然後演示了一遍使用JPA的基本流程,最後詳細的介紹了我們主要使用的幾個註解、注意事項等;以及除了使用預設的查詢方式外,如何去使用自定義查詢和複雜查詢。