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的基本流程,最後詳細的介紹了我們主要使用的幾個註解、注意事項等;以及除了使用默認的查詢方式外,如何去使用自定義查詢和複雜查詢。