前言
JPA是Java Persistence API的簡稱,中文名Java持久層API,是JDK 5.0註解或XML描述對象-關係表的映射關係,並將運行期的實體對象持久化到數據庫中。
Sun引入新的JPA ORM規範出於兩個原因:其一,簡化現有Java EE和Java SE應用開發工作;其二,Sun希望整合ORM技術,實現天下歸一。
–以上內容摘抄自百度百科
JPA Hibernate SpringDatAJpa 三者的關係
JPA 是一種規範而 Hibernate 和 SpringDataJpa 是 JPA 的具體實現, 另外Spring Data JPA 是 Spring 在 Hibernate 的基礎上構建的 JPA 使用解決方案。值得一提的是在早期的時候 Hibernate是不支持JPA,因爲Sun在提出 JPA 的時候就已經有 Hibernate 了。
SpirngBoot JPA 操作實戰
內置查詢接口
第一步:引入 SpirngBoot JPA 的 starter 依賴,同時需要引入mysql 驅動依賴。具體代碼如下:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
第二步:添加Mysql 數據源的配置
在 SpringBoot 的配置文件 application.yml 中添加數據庫配置信息。
spring:
datasource:
url: jdbc:mysql://127.0.0.1:3306/learn?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
# Ddl-auto : Create: 自動創建表 Update:自動修改表 Create-drop:應用停下來會把表刪除掉 None:什麼都不做 Validate:類和表明是否一致
jpa:
show-sql: true
hibernate:
ddl-auto: update
properties:
hibernate:
format_sql: true
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
在啓動的時候報如下錯誤
java.sql.SQLException: The server time zone value ‘Öйú±ê׼ʱ¼ä’ is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utilize time zone support.
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:129) ~[mysql-connector-java-8.0.13.jar:8.0.13]
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:97) ~[mysql-connector-java-8.0.13.jar:8.0.13]
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:89) ~[mysql-connector-java-8.0.13.jar:8.0.13]
at com.mysql.cj.jdbc.exceptions.SQLError.createSQLException(SQLError.java:63) ~[mysql-connector-java-8.0.13.jar:8.0.13]
原來的url內容是: url: jdbc:mysql://127.0.0.1:3306/learn
將url 內容修改爲
url: jdbc:mysql://127.0.0.1:3306/learn?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true 問題解決。
第三步:根據 JPA 規範的註解配置映射實體
添加映射實體通過JPA規範的註解,具體代碼如下:
package cn.lijunkui.springbootlearn.test.model;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
@Entity
public class User {
@Id
@GeneratedValue
private Long id;
private String name;
private Integer age;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
private String sex;
private String address;
public User(){
}
public User(Long id,String name,Integer age,String sex,String address){
this.id = id;
this.name = name;
this.age = age;
this.address = address;
this.sex = sex;
}
// 此處省略get 和set 方法
}
第四步:使用Spring Data Jpa 內置查詢接口 CrudRepository 充當DAO,具體操作方式是定義接口然後繼承CrudRepository即可。具體代碼如下:
package cn.lijunkui.springbootlearn.test.dao;
import cn.lijunkui.springbootlearn.test.model.User;
import org.springframework.data.repository.CrudRepository;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
public interface UserCrudRepository extends CrudRepository<User,Long>{
}
你肯定會驚訝 這就完成啦,我可以確定的告訴我們的Dao開發完畢 基本的增刪改查搞定。
查看CrudRepository源碼我們發現他有的方法如下:
<S extends T> S save(S entity);//新增
<S extends T> Iterable<S> saveAll(Iterable<S> entities);//批量新增
Optional<T> findById(ID id);//查詢通過id
boolean existsById(ID id);//id是否存在
Iterable<T> findAll();//查詢所有
Iterable<T> findAllById(Iterable<ID> ids);//查詢多個id的數據
long count();//數據的總數
void deleteById(ID id);//根據id進行刪除
void delete(T entity);//根據實例進行刪除
void deleteAll(Iterable<? extends T> entities);//批量刪除
void deleteAll();//刪除所有
第五步:編寫測試用例。
SpringBoot 的單元測試 需要我們聲明 @SpringBootTest 和 @RunWith 註解
這裏只是簡單寫啦幾個測試。
package cn.lijunkui.springbootlearn.test.dao;
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserCrudRepositoryTest {
@Autowired
private UserCrudRepository userCrudRepository;
/**
* 添加用戶 測試
*/
@Test
public void add(){
User user = new User();
user.setName("ljk2");
user.setSex("1");
user.setAge(18);
user.setAddress("beijing");
User result = userCrudRepository.save(user);
Assert.assertNotNull(result);
}
/**
* 修改用戶
*/
@Test
public void edit(){
User user = new User();
user.setId(1l);
user.setName("ljk2edit");
user.setSex("1");
user.setAge(18);
user.setAddress("beijing");
User result = userCrudRepository.save(user);
Assert.assertNotNull(result);
}
/**
* 通過id 進行查找
*/
@Test
public void findById(){
Optional<User> userOptional = userCrudRepository.findById(1l);
User result = userOptional.get();
Assert.assertNotNull(result);
}
/**
* 查詢所有
*/
@Test
public void findAll(){
List<User> userList = (List<User>)userCrudRepository.findAll();
Assert.assertTrue(userList.size()>0);
}
@Test
public void count(){
long count = userCrudRepository.count();
System.out.println(count);
}
}
到這裏最簡單SpringDataJAP 介紹完畢。接下來讓我們繼續深入SpringDataJAP其他內置接口
PagingAndSortRepository 使用介紹
CrudRepository 只是具有增刪改查的一些基本功能,接下來 PagingAndSortingRepository是具有分頁和排序的功能 同時他繼承啦 CrudRepository。
編寫測試用例:
package cn.lijunkui.springbootlearn.test.dao;
import cn.lijunkui.springbootlearn.test.model.User;
import org.hibernate.criterion.Order;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.List;
import static org.junit.Assert.*;
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserPageRepositoryTest {
@Autowired
private UserPageRepository userPageRepository;
@Test
public void findAllBySort(){
List<User> userList = (List<User>)userPageRepository.findAll(new Sort(Sort.Direction.ASC,"age"));
System.out.println(userList.size());
}
@Test
public void findAllByPageable(){
Page<User> userPage = userPageRepository.findAll(new PageRequest(0, 20));
userPage.getNumber();//頁數
userPage.getContent();//分頁的數據
userPage.getTotalPages();//總共的頁數
System.out.println("number:"+userPage.getNumber()
+"Countet"+userPage.getContent().size()
+"TotalPages"+userPage.getTotalPages());
}
}
JpaRepository 使用介紹
JpaRepository 不僅繼承啦 PagingAndSortingRepository 同時繼承啦 QueryByExampleExecutor(示例匹配器)
通過我們的測試用例查詢期詳細的用法
package cn.lijunkui.springbootlearn.test.dao;
import cn.lijunkui.springbootlearn.test.model.ResultDTO;
import cn.lijunkui.springbootlearn.test.model.User;
import org.junit.Assert;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.data.domain.*;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import static org.junit.Assert.*;
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserJpaRepositoryTest {
@Autowired
private UserJpaRepository userJpaRepository;
/**
* 執行秒數:49422 49145
* 批量保存數據
*/
@Test
public void BatchSave(){
long startTime = System.currentTimeMillis();
List<User> list = new ArrayList<User>();
for (int i = 0; i < 60000; i++) {
User user = new User();
user.setName("ljk"+i);
user.setAge(i);
user.setAddress("address"+i);
list.add(user);
if(i%100 == 0){
userJpaRepository.saveAll(list);
list.clear();
}
}
long endTime = System.currentTimeMillis();
System.out.println("執行秒數:"+ (endTime - startTime));
}
/**
* 執行秒數:48053 48394 執行速度比BatchSave 要快
* 批量保存數據 (高效處理方式)減少大事物的提交
*/
@Test
public void BatchSaveBest(){
long startTime = System.currentTimeMillis();
List<User> list = new ArrayList<User>();
for (int i = 0; i < 60000; i++) {
User user = new User();
user.setName("ljk"+i);
list.add(user);
if(i%100 == 0){
userJpaRepository.saveAll(list);
userJpaRepository.flush();
list.clear();
}
}
long endTime = System.currentTimeMillis();
System.out.println("執行秒數:"+ (endTime - startTime));
}
/**
* 查詢所有數據
*/
@Test
public void findALL(){
List<User> userlists = userJpaRepository.findAll();
Assert.assertTrue(userlists.size() > 0);
}
/**
* 根據 age 排序查詢
*/
@Test
public void findALLSortAge(){
List<User> lists = userJpaRepository.findAll(Sort.by(Sort.Direction.ASC ,"age"));
for (User list : lists) {
System.out.println(list);
}
}
/**
* 分頁查詢
*/
@Test
public void findAllByPage(){
PageRequest pageRequest = new PageRequest(0,1);
Page<User> userPage = userJpaRepository.findAll(pageRequest);
Assert.assertTrue(userPage.getContent().size() == 1);
}
/**
* 分頁排序查詢
*/
@Test
public void findAllByPageAndSort(){
PageRequest pageRequest = new PageRequest(0,3,Sort.by(Sort.Direction.ASC ,"age"));
Page<User> userPage = userJpaRepository.findAll(pageRequest);
List<User> userList= userPage.getContent();
for (User user : userList) {
System.out.println(user);
}
}
/**
* 根據id 的集合獲取所有數據
*/
@Test
public void findAllByIds(){
List<Long> ids = new ArrayList<Long>();
ids.add(1l);
ids.add(2l);
ids.add(3l);
ids.add(4l);
List<User> userList = userJpaRepository.findAllById(ids);
Assert.assertTrue(userList.size()>0);
}
/**
* 批量刪除所有數據
*/
@Test
public void deleteAllInBatch(){
userJpaRepository.deleteAllInBatch();
}
/**
* 保存數據並刷新緩存
*/
@Test
public void saveAndFlush(){
User user = new User();
user.setName("ljk");
user.setAge(18);
user.setAddress("beijing");
user.setSex("1");
User result = userJpaRepository.saveAndFlush(user);
Assert.assertNotNull(result);
}
/**
* 批量刪除
*/
@Test
public void deleteInBatch(){
List<User> userList = new ArrayList<User>();
User user = new User();
user.setId(1l);
userList.add(user);
User user2 = new User();
user2.setId(2l);
userList.add(user2);
User user3 = new User();
user3.setId(3l);
userList.add(user3);
User user4 = new User();
user4.setId(4l);
userList.add(user4);
userJpaRepository.deleteInBatch(userList);
}
/**
* 根據id 獲取數據 延遲加載
*/
@Test
public void getOne(){
User result = userJpaRepository.getOne(1l);
Long id = result.getId();
String name = result.getName();
System.out.println(id);
System.out.println(name);
Assert.assertNotNull(result);
}
/**
* 示例匹配器 ExampleMatcher
*/
@Test
public void findUserByExam(){
User user = new User();
user.setName("ljk");
List<User> list = userJpaRepository.findAll(Example.of(user));
System.out.println(list.size());
}
@Test
public void findUserByExamQuery(){
User user = new User();
user.setName("ljk");
user.setAddress("address8");
user.setAge(8);
ExampleMatcher matcher = ExampleMatcher.matching()
.withMatcher("name", ExampleMatcher.GenericPropertyMatchers.startsWith())//模糊查詢匹配開頭,即{username}%
.withMatcher("address" ,ExampleMatcher.GenericPropertyMatchers.contains())//全部模糊查詢,即%{address}%
.withIgnorePaths("id");//忽略字段,即不管id是什麼值都不加入查詢條件
Example<User> example = Example.of(user ,matcher);
List<User> userList = userJpaRepository.findAll(example);
Assert.assertTrue(userList.size() > 0);
}
}
方法名稱創建查詢
雖然 SpringDataJPA 提供內置查詢接口,自定義查詢方式它提供了方法名稱的方式進行查詢,只需要定義個接口方法你就可以進行查詢你想要的數據。
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserJpaRepositoryTest {
@Autowired
private UserJpaRepository userJpaRepository;
@Test
public void findByNameAndAge(){
List<User> userList = userJpaRepository.findByNameAndAge("ljk",18);
Assert.assertTrue( userList.size()>0 );
}
public void findByNameOrAge(){
List<User> userList = userJpaRepository.findByNameOrAge("ljk",18);
Assert.assertTrue( userList.size()>0 );
}
}
快速自定以查詢方法:示例如下
Keyword | Sample | JPQL snippet |
---|---|---|
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(parameter bound with appended %) |
EndingWith | findByFirstnameEndingWith | … where x.firstname like ?1(parameter bound with prepended %) |
Containing | findByFirstnameContaining | … where x.firstname like ?1(parameter bound wrapped in %) |
OrderBy | findByAgeOrderByLastnameDesc | … where x.age = ?1 order by x.lastname desc |
Not | findByLastnameNot | … where x.lastname <> ?1 |
In | findByAgeIn(Collection ages) | … where x.age in ?1 |
NotIn | findByAgeNotIn(Collection ages) | … 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) |
計數查詢
SpringDataJap 提供針對某個字段數量統計使用 countBy+數據庫映射實體字段名稱即可完成,代碼如下所示:
long countByName(String name);
計數刪除
也可以通過 removeBy+數據庫映射實體字段名稱進行計數刪除。
@Transactional
List<User> reomveByName(String name);
List 用於接收刪除數據的信息
同時也可以通過 deleteBy+數據庫映射實體字段名稱進行數據的刪除。
@Transactional
List<User> deleteByName(String name);
@Query註解查詢
Spring Data JPA 不僅提供內置接口和方法名稱查詢方式同時還提供了通過 @Query 註解的方式拼寫查詢語句,對於經常使用Hibernate HQL 查詢的福音啊。
具體使用方式代碼如下:
public interface UserJpaRepository extends JpaRepository<User,Long>{
/**
* 根據姓名查詢用戶
* @param name
* @return
*/
@Query("select u from User u where u.name = ?1")
public List<User> findUserByNameByQuery(String name);
/**
* 根據姓名(like)和年齡查詢用戶
* @param name
* @param age
* @return
*/
@Query("select u from User u where u.name like CONCAT('%',?1,'%') and u.age = ?2" )
public List<User> findUserByLikeNameByQuery(String name,Integer age);
/**
* 根據姓名(like)和年齡查詢用戶
* 命名參數 進行查詢
*/
@Query("select u from User u where u.name like CONCAT('%',:name,'%') and u.age = :age")
public User findUserByNameAndAgeWithQery(@Param("name") String name,@Param("age") Integer age);
/**
* 根據姓名(like)和年齡查詢用戶
* 命名參數 原生方式進行查詢
*/
@Query(value = "select * from user u where u.name like CONCAT('%',:name,'%') and u.age = :age",nativeQuery = true)
public List<User> findUserByNameAndAgeWithQeryNative(@Param("name") String name,@Param("age") Integer age);
/**
* 查詢每個地區的人的個數
* @return
*/
@Query("select new cn.lijunkui.springbootlearn.test.model.ResultDTO(u.address,count(u.id)) from User u group by u.address")
public List<ResultDTO> findCountGroupByAddress();
}
測試用例:
@SpringBootTest
@RunWith(SpringRunner.class)
public class UserJpaRepositoryTest {
@Autowired
private UserJpaRepository userJpaRepository;
@Test
public void findUserByNameByQuery(){
List<User> userList = userJpaRepository.findUserByNameByQuery("ljk");
Assert.assertNotNull(userList.size()>0);
}
@Test
public void findUserByLikeNameByQuery(){
List<User> userList = userJpaRepository.findUserByLikeNameByQuery("jk",18);
Assert.assertNotNull(userList.size()>0);
}
@Test
public void findUserByNameAndAgeWithQery(){
User user = userJpaRepository.findUserByNameAndAgeWithQery("jk",18);
Assert.assertNotNull(user);
}
@Test
public void findUserByNameAndAgeWithQeryNative(){
List<User> userList = userJpaRepository.findUserByNameAndAgeWithQeryNative("jk",18);
Assert.assertNotNull(userList.size()>0);
}
/**
* 零散參數的接收
*/
@Test
public void findCountGroupByAddress(){
List<ResultDTO> results = userJpaRepository.findCountGroupByAddress();
System.out.println(results);
}
}
小結
SpringDataJpa 相對於Hibernate 的使用更爲簡潔,更容易快速上手SQL 邏輯不是很複雜的業務。如果你想更靈活的編寫SQL 可以考慮使用Mybaties。 如果你還沒有上手過 SpringDataJpa,還等什麼抓緊跟着本文操作一遍吧。
代碼示例
我本地環境如下:
- SpringBoot Version: 2.1.0.RELEASE
- Apache Maven Version: 3.6.0
- Java Version: 1.8.0_144
- IDEA:Spring Tools Suite (STS)
整合過程如出現問題可以在我的GitHub 倉庫 springbootexamples 中模塊名爲 spring-boot-2.x-spring-data-jpa 項目中進行對比查看
GitHub:https://github.com/zhuoqianmingyue/springbootexamples
參考文獻
https://docs.spring.io/spring-data/jpa/docs/2.0.10.RELEASE/reference/html/