Spring Boot之集成JPA訪問數據庫

爲了解決這種情況(當然也不可能完全解決啦),我們會借用一些ORM框架來減少我們的工作負擔。本章我們來學習如何在Spring Boot中集成JPA框架來訪問數據庫。

JPA全稱Java Persistence API.JPA通過JDK 5.0註解或XML描述對象-關係表的映射關係,並將運行期的實體對象持久化到數據庫中。

JPA的查詢語言是面向對象而非面向數據庫的,它以面向對象的自然語法構造查詢語句,可以看成是Hibernate HQL的等價物。JPA定義了獨特的JPQL(Java Persistence Query Language),JPQL是EJB QL的一種擴展,它是針對實體的一種查詢語言,操作對象是實體,而不是關係數據庫的表,而且能夠支持批量更新和修改、JOIN、GROUP BY、HAVING 等通常只有 SQL 才能夠提供的高級查詢特性,甚至還能夠支持子查詢。

爲了抽象出不同對象的“增刪改查”操作,通常我們會寫一個模板Dao來簡化我們的開發,而Spring-data-jpa可以更加減輕我們的開發工作量,使得數據訪問層變成只是一層接口的編寫方式,如下所示:

 

package com.bluecoffee.repository;

import com.bluecoffee.domain.Book;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

/**
 * Created by qianlong on 16/9/27.
 */
public interface BookDao extends JpaRepository<Book,Long> {

    Book findByTitle(String title);

    Book findByTitleAndAuthor(String title,String author);

    @Query("from Book b where b.title=:title")
    Book findBook(@Param("title") String bookTitle);
}

我們只需要通過編寫一個繼承自JpaRepository的接口就能完成數據訪問,下面以一個具體實例來體驗Spring-data-jpa給我們帶來的強大功能。

使用示例

Spring-data-jpa依賴於Hibernate。如果您已經對Hibernate有一定了解,那你可以毫不費力的看懂並上手使用Spring-data-jpa。如果您還是Hibernate新手,您可以先按如下方式入門,再建議回頭學習一下Hibernate以幫助這部分的理解和進一步使用。

引入JPA依賴

pom.xml中添加JPA和MySQL數據庫驅動的依賴

 

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.39</version>
        </dependency>

配置數據庫連接信息

 

spring.datasource.url=jdbc:mysql://localhost:3306/spring_boot
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.jdbc.Driver

#spring.jpa.properties.hibernate.hbm2ddl.auto=create
#spring.jpa.properties.hibernate.hbm2ddl.auto=create-drop
spring.jpa.properties.hibernate.hbm2ddl.auto=update
#spring.jpa.properties.hibernate.hbm2ddl.auto=validate

spring.jpa.properties.hibernate.hbm2ddl.auto是hibernate的配置屬性,其主要作用是:自動創建、更新、驗證數據庫表結構。該參數的幾種配置如下:

  • create:每次加載hibernate時都會刪除上一次的生成的表,然後根據你的model類再重新來生成新表,哪怕兩次沒有任何改變也要這樣執行,這就是導致數據庫表數據丟失的一個重要原因。
  • create-drop:每次加載hibernate時根據model類生成表,但是sessionFactory一關閉,表就自動刪除。
  • update:最常用的屬性,第一次加載hibernate時根據model類會自動建立起表的結構(前提是先建立好數據庫),以後加載hibernate時根據model類自動更新表結構,即使表結構改變了但表中的行仍然存在不會刪除以前的行。要注意的是當部署到服務器後,表結構是不會被馬上建立起來的,是要等應用第一次運行起來後纔會。
  • validate:每次加載hibernate時,驗證創建數據庫表結構,只會和數據庫中的表進行比較,不會創建新表,但是會插入新值。

創建實體

 

package com.bluecoffee.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import java.util.Date;

/**
 * Created by qianlong on 16/10/2.
 */
@Entity
public class Book {

    @Id
    @GeneratedValue
    private Long bookId;

    @Column(nullable = false)
    private String title;

    @Column(nullable = true)
    private String author;

    @Column(name = "create_time" ,nullable = true)
    private Date createTime;

    public Book(){}

    public Book(String title,String author,Date createTime){
        this.title = title;
        this.author = author;
        this.createTime = createTime;
    }

    public Long getBookId() {
        return bookId;
    }

    public void setBookId(Long bookId) {
        this.bookId = bookId;
    }

    public String getTitle() {
        return title;
    }

    public void setTitle(String title) {
        this.title = title;
    }

    public String getAuthor() {
        return author;
    }

    public void setAuthor(String author) {
        this.author = author;
    }

    public Date getCreateTime() {
        return createTime;
    }

    public void setCreateTime(Date createTime) {
        this.createTime = createTime;
    }
}

在上述代碼中,註解@GeneratedValue表示bookId我們設置爲主鍵從1開始自增,但是通常我們在實際業務開發過程中,主鍵需要從自定義序列中獲取,我們會建一張表,然後在表中維護不同表主鍵的自增序列,做法如下:

新建序列號生成表

 

DROP TABLE IF EXISTS `sequence_generator`;
CREATE TABLE `sequence_generator` (
  `id` decimal(10,0) NOT NULL,
  `sequence_name` varchar(255) NOT NULL,
  `sequence_value` decimal(10,0) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

對book_store表新建自增序列

 

INSERT INTO `sequence_generator` VALUES ('1', 'book_store_pk', '10000000');

修改實體類中主鍵生成規則

 

 package com.bluecoffee.domain;

import javax.persistence.*;

/**
 * Created by qianlong on 16/10/2.
 */
@Entity
@Table(name="book_store")
public class BookStore {

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE, generator="pk_gen")
    @TableGenerator(name = "pk_gen",
            table="sequence_generator",
            pkColumnName="sequence_name",
            valueColumnName="sequence_value",
            pkColumnValue="book_store_pk",
            allocationSize=1
    )
    private Long bookId;

    @Column(nullable = true)
    private String address;

    @Column(nullable = true)
    private String storeManager;

    public BookStore(){};

    public BookStore(String address,String storeManager){
        this.address = address;
        this.storeManager = storeManager;
    }

    public Long getBookId() {
        return bookId;
    }

    public void setBookId(Long bookId) {
        this.bookId = bookId;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }

    public String getStoreManager() {
        return storeManager;
    }

    public void setStoreManager(String storeManager) {
        this.storeManager = storeManager;
    }
}

@GeneratedValue(strategy = GenerationType.TABLE, generator="pk_gen") 代表主鍵生成策略是從表中獲取序列,序列號生成器名稱爲“pk_gen”

@TableGenerator的定義(Java代碼)

 

@Target({TYPE, METHOD, FIELD})   

@Retention(RUNTIME)  

public @interface TableGenerator {  

  String name();  

  String table() default "";  

  String catalog() default "";  

  String schema() default "";  

  String pkColumnName() default "";  

  String valueColumnName() default "";  

  String pkColumnValue() default "";  

  int initialValue() default 0;  

  int allocationSize() default 50;  

  UniqueConstraint[] uniqueConstraints() default {};  

}  

name:序列號生成器名稱,與@GeneratedValue中的generator一致
table:表生成策略所持久化的表名,例如,這裏表使用的是數據庫中的“sequence_generator”。
pkColumnName:表示在持久化表中,該主鍵生成策略所對應鍵值的名稱。例如在“sequence_generator”中將“sequence_name”作爲主鍵的鍵值
valueColumnName:表示在持久化表中,該主鍵當前所生成的值,它的值將會隨着每次創建累加。例如,在“sequence_generator”中將“sequence_value”作爲主鍵的值
pkColumnValue:表示在持久化表中,該生成策略所對應的主鍵。例如在“sequence_generator”表中,將“sequence_name”的值爲“book_store_pk”。
allocationSize:表示每次主鍵值增加的大小,例如設置成1,則表示每次創建新記錄後自動加1,默認爲50。
initialValue:表示主鍵初識值,默認爲0。
UniqueConstraint:與@Table標記中的用法類似。

創建數據訪問dao

 

package com.bluecoffee.repository;

import com.bluecoffee.domain.Book;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

/**
 * Created by qianlong on 16/9/27.
 */
public interface BookDao extends JpaRepository<Book,Long> {

    Book findByTitle(String title);

    Book findByTitleAndAuthor(String title,String author);

    @Query("from Book b where b.title=:title")
    Book findBook(@Param("title") String bookTitle);
}

BookDao繼承了==JpaRepository==,我們通過查看JpaRepository的API接口文檔可以看到該接口已經實現了創建(save)、更新(save)、刪除(delete)、查詢(findAll、findOne)等基本操作的函數,因此對於這些基礎操作的數據訪問就不需要我們再開發了。

  • Book findByTitle(String title);
  • Book findByTitleAndAuthor(String title,String author);

findByTitle和findByTitleAndAuthor方法實現了根據書名和作者查詢的方法,可以看到我們並沒有編寫任何SQL就完成了查詢功能,這就是spring-data-jpa的一大特色:通過解析方法名創建查詢。 但是我們需要注意,查詢訪問名稱必須遵循JPA的規範,查詢方法名稱必須以find開頭,否則就需要自己通過@Query 註解來創建查詢,需要編寫JPQL語句,並通過類似“:title”來映射@Param指定的參數,如下所示:

 

    @Query("from Book b where b.title=:title")
    Book findBook(@Param("title") String bookTitle);

分頁查詢

 

    @Query("select book from Book book where book.author = :author")
    Page findBookPage(Pageable pageable,@Param("author") String author);

單元測試

最後我們來通過編寫單元測試來驗證,如下代碼所示:

 

package com.bluecoffee;

import com.bluecoffee.Repository.BookDao;
import com.bluecoffee.Repository.BookStoeDao;
import com.bluecoffee.domain.Book;
import com.bluecoffee.domain.BookStore;
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.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.util.Date;
import java.util.Iterator;

/**
 * Created by qianlong on 16/9/27.
 */
@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(classes = Application.class)
public class BookDaoTest {

    @Autowired
    private BookDao bookDao;

    @Autowired
    private BookStoeDao bookStoeDao;

    @Test
    public void testBook(){
        try{
            //先清空已有數據
            bookDao.deleteAll();

            //生成10本書
            for(int i=1;i<=10;i++){
                bookDao.save(new Book("book"+i,"author"+i,new Date()));
            }

            // 測試findAll, 查詢所有記錄
            Assert.assertEquals(10, bookDao.findAll().size());

            // 測試findByTitle, 查詢書名爲book5的書
            Assert.assertEquals("author5", bookDao.findByTitle("book5").getAuthor());

            // 測試findBook, 查詢書名爲book7的書
            Assert.assertEquals("author7", bookDao.findBook("book7").getAuthor());

            // 測試findByTitleAndAuthor, 查詢書名爲book1,作者爲author1的書
            Assert.assertEquals("author1", bookDao.findByTitleAndAuthor("book1","author1").getAuthor());

            // 測試findByTitleAndAuthor, 查詢書名爲book2,作者爲author1的書
            Assert.assertEquals(null, bookDao.findByTitleAndAuthor("book2", "author1"));

            //測試刪除book3
            bookDao.delete(bookDao.findBook("book3"));

            //測試刪除是否成功
            Assert.assertEquals(9, bookDao.findAll().size());

            //分頁查詢
            bookDao.deleteAll();
            for(int i=1;i<=10;i++){
                bookDao.save(new Book("book"+i,"Alex Qian",new Date()));
            }
            Sort sort = new Sort(Sort.Direction.DESC, "bookId");
            int page = 1;
            int size = 5;
            Pageable pageable = new PageRequest(page, size, sort);
            Page<Book> pages = bookDao.findBookPage(pageable,"Alex Qian");

            Iterator<Book> it= pages.iterator();

            Assert.assertEquals(size,pages.getSize());
            Assert.assertEquals(2,pages.getTotalPages());

            while(it.hasNext()){
                Book book = (Book)it.next();
                System.out.println("title/author/createTime:"+book.getTitle()+"/"+book.getAuthor()+"/"+book.getCreateTime());
            }

        }catch (Exception ex){
            Assert.fail(ex.getMessage());
        }
    }

    @Test
    public void testBookStore(){
        try{
            bookStoeDao.deleteAll();

            //生成5個書店
            for(int i=1;i<=5;i++){
                bookStoeDao.save(new BookStore("address_"+i,"manager_"+i));
            }
            // 測試findAll, 查詢所有記錄
            Assert.assertEquals(5, bookStoeDao.findAll().size());
            Assert.assertEquals(1, bookStoeDao.getBookStoreByManager("manager_2").size());
            Assert.assertEquals("manager_2", bookStoeDao.getBookStoreByManager("manager_2").get(0).getStoreManager());

        }catch (Exception ex){
            Assert.fail(ex.getMessage());
        }
    }

}


完整代碼戳這裏: Chapter 4-1-1 - Spring Boot集成JPA訪問數據庫

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