玩轉 SpringBoot 2.x 之使用 SpringDataJpa 篇

前言

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/

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章