Spring Boot 與 Kotlin使用Spring-data-jpa簡化數據訪問層

在《Spring Boot 與 Kotlin 使用JdbcTemplate連接MySQL》 中介紹了一種基本的數據訪問方式,結合構建RESTful API和使用Thymeleaf模板引擎渲染Web視圖的內容就已經可以完成App服務端和Web站點的開發任務了。

然而,在實際開發過程中,對數據庫的操作無非就“增刪改查”。就最爲普遍的單表操作而言,除了表和字段不同外,語句都是類似的,開發人員需要寫大量類似而枯燥的語句來完成業務邏輯。

爲了解決這些大量枯燥的數據操作語句,我們第一個想到的是使用ORM框架,比如:Hibernate。通過整合Hibernate之後,我們以操作Java實體的方式最終將數據改變映射到數據庫表中。

爲了解決抽象各個Java實體基本的“增刪改查”操作,我們通常會以泛型的方式封裝一個模板Dao來進行抽象簡化,但是這樣依然不是很方便,我們需要針對每個實體編寫一個繼承自泛型模板Dao的接口,再編寫該接口的實現。雖然一些基礎的數據訪問已經可以得到很好的複用,但是在代碼結構上針對每個實體都會有一堆Dao的接口和實現。

由於模板Dao的實現,使得這些具體實體的Dao層已經變的非常“薄”,有一些具體實體的Dao實現可能完全就是對模板Dao的簡單代理,並且往往這樣的實現類可能會出現在很多實體上。Spring-data-jpa的出現正可以讓這樣一個已經很“薄”的數據訪問層變成只是一層接口的編寫方式。比如,下面的例子:

import name.quanke.kotlin.chaper11_6_2.entity.User
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param


/**
 * Created by http://quanke.name on 2018/1/10.
 */

interface UserRepository : JpaRepository<User, Long> {

    fun findByUsername(username: String): List<User>

    @Query("from User u where u.username=:username")
    fun findUser(@Param("username") username: String): User

}

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

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

工程配置

build.gradle中添加相關依賴,加入以下內容:

compile "org.springframework.boot:spring-boot-starter-data-jpa:$spring_boot_version"

如果使用jpa必須增加kotlin-jpa插件

和kotlin-spring插件一樣,kotlin-jpa是一個包含在no-arg之上的插件。 該插件自動指定@Entity,@Embeddable和@MappedSuperclass no-arg註釋。

buildscript {
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-noarg:$kotlin_version"
    }
}

apply plugin: "kotlin-jpa"

如果使用的 Gradle plugins DSL

plugins {
  id "org.jetbrains.kotlin.plugin.jpa" version "1.2.20"
}

完整的build.gradle

group 'name.quanke.kotlin'
version '1.0-SNAPSHOT'

buildscript {
    ext.kotlin_version = '1.2.10'
    ext.spring_boot_version = '1.5.4.RELEASE'
    ext.springfox_swagger2_version = '2.7.0'
    ext.mysql_version = '5.1.21'
    repositories {
        mavenCentral()
    }
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath("org.springframework.boot:spring-boot-gradle-plugin:$spring_boot_version")

//        Kotlin整合SpringBoot的默認無參構造函數,默認把所有的類設置open類插件
        classpath("org.jetbrains.kotlin:kotlin-noarg:$kotlin_version")
        classpath("org.jetbrains.kotlin:kotlin-allopen:$kotlin_version")
    }
}

apply plugin: 'kotlin'
apply plugin: "kotlin-spring" // See https://kotlinlang.org/docs/reference/compiler-plugins.html#kotlin-spring-compiler-plugin
apply plugin: 'org.springframework.boot'
apply plugin: "kotlin-jpa"  //https://stackoverflow.com/questions/32038177/kotlin-with-jpa-default-constructor-hell
jar {
    baseName = 'chapter11-6-2-service'
    version = '0.1.0'
}
repositories {
    mavenCentral()
}


dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-jre8:$kotlin_version"
    compile("org.jetbrains.kotlin:kotlin-reflect:${kotlin_version}")


    compile "org.springframework.boot:spring-boot-starter-web:$spring_boot_version"
    compile "org.springframework.boot:spring-boot-starter-data-jpa:$spring_boot_version"
    compile "mysql:mysql-connector-java:$mysql_version"

    testCompile "org.springframework.boot:spring-boot-starter-test:$spring_boot_version"
    testCompile "org.jetbrains.kotlin:kotlin-test-junit:$kotlin_version"

}

compileKotlin {
    kotlinOptions.jvmTarget = "1.8"
}
compileTestKotlin {
    kotlinOptions.jvmTarget = "1.8"
}

application.yml中配置:數據庫連接信息(如使用嵌入式數據庫則不需要)、自動創建表結構的設置,例如使用mysql的情況如下:

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

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

  • create:每次加載hibernate時都會刪除上一次的生成的表,然後根據你的model類再重新來生成新表,哪怕兩次沒有任何改變也要這樣執行,這就是導致數據庫表數據丟失的一個重要原因。
  • create-drop:每次加載hibernate時根據model類生成表,但是sessionFactory一關閉,表就自動刪除。
  • update:最常用的屬性,第一次加載hibernate時根據model類會自動建立起表的結構(前提是先建立好數據庫),以後加載hibernate時根據model類自動更新表結構,即使表結構改變了但表中的行仍然存在不會刪除以前的行。要注意的是當部署到服務器後,表結構是不會被馬上建立起來的,是要等應用第一次運行起來後纔會。
  • validate:每次加載hibernate時,驗證創建數據庫表結構,只會和數據庫中的表進行比較,不會創建新表,但是會插入新值。
    至此已經完成基礎配置,如果您有在Spring下整合使用過它的話,相信你已經感受到Spring Boot的便利之處:JPA的傳統配置在persistence.xml文件中,但是這裏我們不需要。當然,最好在構建項目時候按照之前提過的最佳實踐的工程結構來組織,這樣以確保各種配置都能被框架掃描到。

創建實體

創建一個User實體,包含id(主鍵)、username(姓名)、password(密碼)屬性,通過ORM框架其會被映射到數據庫表中,由於配置了hibernate.hbm2ddl.auto,在應用啓動的時候框架會自動去數據庫中創建對應的表。

import javax.persistence.*

/**
 * Created by http://quanke.name on 2018/1/10.
 */

@Entity
data class User(
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        var id: Long = -1,
        @Column(nullable = false)
        var username: String = "",

        var password: String = ""
)

創建數據訪問接口

下面針對User實體創建對應的Repository接口實現對該實體的數據訪問,如下代碼:


import name.quanke.kotlin.chaper11_6_2.entity.User
import org.springframework.data.jpa.repository.JpaRepository
import org.springframework.data.jpa.repository.Query
import org.springframework.data.repository.query.Param


/**
 * Created by http://quanke.name on 2018/1/10.
 */

interface UserRepository : JpaRepository<User, Long> {

    fun findByUsername(username: String): List<User>

    fun findByUsernameAndPassword(username: String, password: String?): User

    @Query("from User u where u.username=:username")
    fun findUser(@Param("username") username: String): User

}

在Spring-data-jpa中,只需要編寫類似上面這樣的接口就可實現數據訪問。不再像我們以往編寫了接口時候還需要自己編寫接口實現類,直接減少了我們的文件清單。

下面對上面的UserRepository做一些解釋,該接口繼承自JpaRepository,通過查看JpaRepository接口的API文檔,可以看到該接口本身已經實現了創建(save)、更新(save)、刪除(delete)、查詢(findAll、findOne)等基本操作的函數,因此對於這些基礎操作的數據訪問就不需要開發者再自己定義。

在我們實際開發中,JpaRepository接口定義的接口往往還不夠或者性能不夠優化,我們需要進一步實現更復雜一些的查詢或操作。由於本文重點在spring boot中整合spring-data-jpa,在這裏先拋磚引玉簡單介紹一下spring-data-jpa中讓我們興奮的功能,後續再單獨開篇講一下spring-data-jpa中的常見使用。

在上例中,我們可以看到下面兩個函數:

  • fun findByName(name:String ):User
  • fun findByNameAndAge(name:String , age:Int ):User

它們分別實現了按name查詢User實體和按name和age查詢User實體,可以看到我們這裏沒有任何類SQL語句就完成了兩個條件查詢方法。這就是Spring-data-jpa的一大特性:通過解析方法名創建查詢。

除了通過解析方法名來創建查詢外,它也提供通過使用@Query 註解來創建查詢,您只需要編寫JPQL語句,並通過類似“:name”來映射@Param指定的參數,就像例子中的第三個findUser函數一樣。

Spring-data-jpa的能力遠不止本文提到的這些,由於本文主要以整合介紹爲主,對於Spring-data-jpa的使用只是介紹了常見的使用方式。諸如@Modifying操作、分頁排序、原生SQL支持以及與Spring MVC的結合使用等等內容就不在本文中詳細展開,這裏先挖個坑,後續再補文章填坑,如您對這些感興趣可以關注我博客或簡書,同樣歡迎大家留言交流想法。

單元測試

在完成了上面的數據訪問接口之後,按照慣例就是編寫對應的單元測試來驗證編寫的內容是否正確。這裏就不多做介紹,主要通過數據操作和查詢來反覆驗證操作的正確性。


import name.quanke.kotlin.chaper11_6_2.entity.User
import name.quanke.kotlin.chaper11_6_2.repository.UserRepository
import org.apache.commons.logging.LogFactory
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.test.context.junit4.SpringRunner

/**
 * Created by http://quanke.name on 2018/1/9.
 */
@RunWith(SpringRunner::class)
@SpringBootTest
class ApplicationTests {

    val log = LogFactory.getLog(ApplicationTests::class.java)!!

    @Autowired
    lateinit var userRepository: UserRepository

    @Test
    fun `jpa test"`() {

//        val username = "quanke"
//        val password = "123456"

        val user = User()

        user.username = "quanke.name"
        user.password = "12"
        userRepository.save(user)

        log.info("總共用戶: ${userRepository.count()}")

        log.info("名字爲quanke的用戶: ${userRepository.findByUsername("quanke.name")}")


    }

更多Spring Boot 和 kotlin相關內容

歡迎關注《Spring Boot 與 kotlin 實戰》

參考

全科龍婷

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