日日操夜夜添-日日操影院-日日草夜夜操-日日干干-精品一区二区三区波多野结衣-精品一区二区三区高清免费不卡

公告:魔扣目錄網(wǎng)為廣大站長提供免費收錄網(wǎng)站服務(wù),提交前請做好本站友鏈:【 網(wǎng)站目錄:http://www.ylptlb.cn 】, 免友鏈快審服務(wù)(50元/站),

點擊這里在線咨詢客服
新站提交
  • 網(wǎng)站:51998
  • 待審:31
  • 小程序:12
  • 文章:1030137
  • 會員:747

 

最近我在做一個新項目,由于我們項目組一直使用的是 MongoDB 數(shù)據(jù)庫,所以新項目我就打算上 Spring Data MongoDB 嘗試一下,雖然我早就用過了 Spring Data JPA,對 Spring Data 的相關(guān) CRUD 和 動態(tài)查詢的封裝也比較熟悉,但是自帶的封裝顯然不能很好的滿足我們的需求,本篇帶大家講述我所遇到的問題以及解決方案。

注: MongoRepository / JPARepository 都繼承自
PagingAndSortingRepository,除了對應(yīng)的數(shù)據(jù)庫不同之外,功能都基本相同,所以本文的二次封裝也可以用于 JPARepository 上。

1. 我遇到的問題

問題一

在 Spring Data 中可以通過繼承 MongoRepository / JPARepository 接口的方式獲得 CRUD 和 分頁的能力,但是這種能力也僅僅滿足基礎(chǔ)的 CRUD 操作和 分頁,對于極其常用的兩個操作比如:針對數(shù)據(jù)庫某個字段進行更新 和 多條件查詢,這個接口并沒有提供。

準(zhǔn)確的來說,多條件查詢的能力是提供了,但是非常不宜用,它必須使用你的類做為查詢條件,這個類的變量名還必須和數(shù)據(jù)庫表中的字段名保持一致,這可以非常簡單的讓我們想到使用 PO 類當(dāng)作這個查詢條件。

但是在有些規(guī)范中,PO 類應(yīng)該是一個擁有全參構(gòu)造器的不可變類,這使得先創(chuàng)建這個類然后對應(yīng)的查詢字段進行賦值的操作變得不可行,這里我舉一個簡單的例子,我擁有一個數(shù)據(jù)表的映射對象:User,這就是俗稱的 PO

@Document("user")
class User (

    @Id
    val id : String,
?
    val account : String,
?
    val pwd : String,
?
    val name : String,
)
復(fù)制代碼

然后我如果想要單獨更新 name 這個字段時,我需要擁有整個 User 對象中的所有屬性,因為 Repository 接口所提供的能力是把新增操作和更新操作放在一起的 (save 方法),每次更新都是所有字段的更新,這是我不愿意看到的,也是極其麻煩的。

接著就是多條件查詢的問題,我們先來看下如果我想要使用多條件查詢,它的參數(shù)是什么:

 

可以明顯看到是一個叫 Example 的對象,如果我想使用,它應(yīng)該是這樣的:

    fun test() {
        
        val user = cssUser()
        
        user.name = "我要查詢的參數(shù)具體值"
?
        userRepository.findAll(Example.of(user))
    }
復(fù)制代碼

這里我定義了一個 CssUser 去當(dāng)它的查詢條件的類,而且這個類和 User 類的內(nèi)容幾乎一樣,因為我的 User 類是一個全參構(gòu)造器沒辦法直接創(chuàng)建一個空對象進行賦值,所以我不得不創(chuàng)建一個 CssUser 去當(dāng)查詢條件的類,對于程序員來講,這很煩

我想要的效果是什么樣的呢?是這樣的:

    fun test() {
?
        userRepository.listAll(Criteria
                                   .where("account").`is`("admin")
                                   .and("name").`is`("你的名字")
        )
?
    }
復(fù)制代碼

通過 lambda 的方式直接獲取到某個屬性的名字,然后作為查詢變量,然后跟著鏈?zhǔn)秸{(diào)用可以隨便在里面加上各樣的查詢條件,例子中的 Criteria 類是 Spring 已經(jīng)為我們做好的,但是 Repository 接口并沒有提供它,所以我們需要一層封裝。

問題二

從上面的例子中我們可以看到在組裝查詢條件時,需要硬編碼進去字段名,這對于程序員來說,是很煩的

所以我們應(yīng)該使用 lambda 的特性,幫助我們?nèi)カ@取某一個類的字段名,通常是 PO,因為它和數(shù)據(jù)庫屬性是一一對應(yīng)的,整體要達到的有點像 MyBatis-PLus 的效果,大概是這樣:

    fun test() {
?
        userRepository.listAll(Criteria
                                   .where(CssUser::account.mongoFiled()).`is`("admin")
                                   .and(CssUser::name.mongoFiled()).`is`("你的名字")
        )
?
    }
復(fù)制代碼

當(dāng)然我的這個效果還沒有 Mybatis-PLus 的效果好,它可以直接省略 .mongoFiled() 這個操作,這是因為我只加了三四行代碼就能達到這個效果,對我而言夠用了,而 Mybatis-PLus 則是有一套相關(guān)支持。

雖然我這是 Kotlin 示例,但隨后也會給出 JAVA 語法中的相關(guān)思路。

2. Repository 接口封裝

先來談?wù)剬?CRUD 的增強,正常情況下,我們只需要使用一個接口繼承 MongoRepository 接口,然后 Spring Data 就會幫我們生成一個動態(tài)代理類,并聲明為 Bean,直接注入就可以使用了,就像這樣(代碼中的 :語法是繼承的意思):

interface UserMongoRepository : MongoRepository<User, String> {
?
}
復(fù)制代碼

現(xiàn)在既然我們要對 Repository 進行增強,就需要再抽象出一個類,作為我們新的基類,之后的自己的業(yè)務(wù)類需要繼承這個接口,而非原來的 MongoRepository 接口,當(dāng)然,我們這個新的基類接口還會去繼承 MongoRepository 接口,然后在接口中定義我們需要的新操作即可:

@NoRepositoryBean
interface BaseMongoRepository<T, ID> : MongoRepository<T, ID> {
?
    fun listAll(condition: Criteria, pageable: Pageable): Page<T>
?
    fun updateById(id: ID, update: Update): Long
}
復(fù)制代碼

我創(chuàng)建了一個新的接口:BaseMongoRepository,用它來繼承 MongoRepository,接著定義我們需要的擴展的一些方法,這里我擴展類了兩個方法:新的多條件分頁方法和新的更新接口。

其中 listAll 方法的第一個參數(shù) Criteria 是 Spring Data 已經(jīng)給我們提供好的類,它廣泛運用于 MongoTemplate 里面,畢竟這層 CRUD 的封裝底層其實還是 MongoTemplate 來操作。

除了繼承接口外,我們還需要對這兩個方法進行實現(xiàn),再創(chuàng)建一個 BaseMongoRepository 的實現(xiàn)類去繼承 MongoRepository 的實現(xiàn)類——SimpleMongoRepository:

class BaseMongoRepositoryClass<T, ID>(
    private val metadata: MongoEntityInformation<T, ID>,
    private val mongoOperations: MongoOperations
) :
    SimpleMongoRepository<T, ID>(metadata, mongoOperations), BaseMongoRepository<T, ID> {
?
    private val clazz: Class<T> = metadata.javaType
?
    override fun listAll(condition: Criteria, pageable: Pageable): Page<T> {
        val list = mongoOperations.find(Query(condition).with(pageable), this.clazz, metadata.collectionName)
?
        return PageableExecutionUtils.getPage(list, pageable) {
            mongoOperations
                .count(
                    Query(condition).limit(-1).skip(-1),
                    clazz,
                    metadata.collectionName
                )
        }
    }
?
    override fun updateById(id: ID, update: Update): Long {
        if (update.updateObject.isEmpty()) return 0
        return mongoOperations.updateFirst(
            Query().addCriteria(Criteria.where("_id").`is`(id)),
            update,
            metadata.collectionName
        ).modifiedCount
    }
?
?
}
復(fù)制代碼

其中 BaseMongoRepositoryClass 需要兩個參數(shù),這兩個參數(shù)直接從 SimpleMongoRepository 里面拷貝過來然后通過構(gòu)造再傳遞給 SimpleMongoRepository 即可,反正都是從自動注入里面來。

兩個變量簡單講解一下都是什么意思:

  1. MongoEntityInformation:這個是 MongoEntity 的元信息,就是最上面用 @Document 注解標(biāo)記的 PO 類的元信息,我們可以通過它拿到 PO 類的類型和數(shù)據(jù)表的名字。
  2. MongoOperations:MongoTemplate 的實現(xiàn)類,這個我想不用多談。

接著就是方法實現(xiàn),方法實現(xiàn)就是就是通過 MongoTemplate 操作了這個這個方法要做什么事,代碼都比較簡單因為不包含什么邏輯,熟悉 MongoTemplate 的一眼就可看懂。

接下來就是最重要的一步,沒有這一步一切都是白費,還會造成項目啟動失敗,那就是把這個新的基類告訴 Spring,這是新的基類,你可以在項目的入口中加上這一句注解:

@EnableMongoRepositories(basePackages = ["com.xxx.*"], repositoryBaseClass = BaseMongoRepositoryClass::class)
class AdminApplication
?
fun main(args: Array<String>) {
    runApplication<AdminApplication>(*args)
}
復(fù)制代碼

指定一下 repositoryBaseClass,這樣生成動態(tài)代理的時候會以這個類為基類,我們動態(tài)代理類也就具有了我們定義的兩個方法的能力了,使用中和原來的一樣,只不過繼承的接口不同罷了:

interface UserRepository : BaseMongoRepository<User, String> {
?
}
復(fù)制代碼

到這一步,我們可以完成這個效果:

    fun test() {
?
        userRepository.listAll(Criteria
                                   .where("account").`is`("admin")
                                   .and("name").`is`("你的名字")
        )
?
    }
復(fù)制代碼

3. 實體類變量進行 lambda 封裝

接下來是對實體變量進行 lambda 封裝,這個東西我覺得可以分為 Kotlin 和 Java 兩個版本來說,兩者各有千秋。

先來說說Kotlin,因為 Kotlin 自身的語言特性的關(guān)系,實現(xiàn)起來比較簡單,但也會拖一個尾巴,Kotlin 具有一個擴展函數(shù)的能力,簡單點說就是直接給某個類加上一些自定義方法,比如 String 我們可以在不繼承的情況下直接給 String 類加上一個新的方法,然后它就會出現(xiàn)在 String 對象可調(diào)用的函數(shù)列表中。

所以我們?nèi)绻胍?User::account.mongoFiled() 這種效果,就得先知道 User::account 返回值是什么,在 Kotlin 中,它的返回值是一個 KProperty 類對象,那么我們直接給這個類加上擴展如下:

fun KProperty<*>.mongoFiled(): String {
    if (this.hasAnnotation<Id>()) return "_id"
    return this.findAnnotation<Field>()?.run {
        this.name.ifEmpty { this@mongoFiled.name }
    } ?: this.name
}
復(fù)制代碼

這樣在 lambda 調(diào)用下就可以再調(diào)用這個方法了,接著來看看方法內(nèi)容。

  1. 首先判斷了是否存在 ID 注解,這個 ID 注解是用來標(biāo)識 Mongo 的主鍵屬性的注解,這種注解標(biāo)識的變量在數(shù)據(jù)庫中統(tǒng)一叫做 "_id",所以這里我也返回這個名字。
  2. 接著判斷是否存在 Field 注解,它是用來標(biāo)識數(shù)據(jù)庫字段和類變量不一樣的情況,如果出現(xiàn)這種情況,我們使用注解所標(biāo)識的字段名。
  3. 最后,以上兩種情況排除后,我們直接使用這個字段的名字。

這樣就可以達到如下效果了:

    fun test() {
?
        userRepository.listAll(Criteria
                                   .where(CssUser::account.mongoFiled()).`is`("admin")
                                   .and(CssUser::name.mongoFiled()).`is`("你的名字")
        )
?
    }
復(fù)制代碼

接著我們可以來說說 Java 的做法,首先也需要一個方法通過 lambda 拿到字段名,這個方法網(wǎng)上有很多我不再贅述,但是拿到之后該怎么辦呢?

你當(dāng)然可以直接通過工具類的靜態(tài)方法去拿,就像這樣:

    fun test() {
?
        userRepository.listAll(Criteria
                                   .where(Util.getName(CssUser::account).`is`("admin")
                                   .and(Util.getName(CssUser::name).`is`("你的名字")
        )
?
    }
復(fù)制代碼

可能到這一步看起來還是略微不雅,追求極致的小伙伴這個時候就可以再度發(fā)揮封裝的本色,將 Criteria 類封裝出一個新的查詢條件類,比如叫 Condition,然后將 Criteria 裝在里面再封裝一下查詢時的相關(guān)常用方法,就像這樣(注意此處的 Funtion 入?yún)⒅皇且粋€例子,實際應(yīng)該是泛型):

public class Condition {
    
    private Criteria criteria = new Criteria();
?
    public Condition where(Function<String, String> function, String value) {
        criteria.andOperator(Criteria.where(Util.getName(function)).is(value));
        return this;
    }
}
復(fù)制代碼

除了 where 方法你還可以繼續(xù)封裝 gt、lt、or 等常用方法,并且它們還能形成鏈?zhǔn)秸{(diào)用,最終的效果是這樣的:

    public static void main(String[] args) {
        Criteria criteria = new Condition()
                .where(CssUser::getName, "你的名字")
                .where(CssUser::getAccount, "admin");
    }
復(fù)制代碼

是不是更優(yōu)雅了呢?

4. 最后

今天是滿滿的技術(shù)干貨,希望 Get 到新技能的小伙伴可以積極的點贊,有什么問題都可以再評論區(qū)留言,我會積極對線的,下篇見。

作者:和耳朵
鏈接:
https://juejin.cn/post/7168133740093243423

分享到:
標(biāo)簽:Spring
用戶無頭像

網(wǎng)友整理

注冊時間:

網(wǎng)站:5 個   小程序:0 個  文章:12 篇

  • 51998

    網(wǎng)站

  • 12

    小程序

  • 1030137

    文章

  • 747

    會員

趕快注冊賬號,推廣您的網(wǎng)站吧!
最新入駐小程序

數(shù)獨大挑戰(zhàn)2018-06-03

數(shù)獨一種數(shù)學(xué)游戲,玩家需要根據(jù)9

答題星2018-06-03

您可以通過答題星輕松地創(chuàng)建試卷

全階人生考試2018-06-03

各種考試題,題庫,初中,高中,大學(xué)四六

運動步數(shù)有氧達人2018-06-03

記錄運動步數(shù),積累氧氣值。還可偷

每日養(yǎng)生app2018-06-03

每日養(yǎng)生,天天健康

體育訓(xùn)練成績評定2018-06-03

通用課目體育訓(xùn)練成績評定