簡體   English   中英

使用 Jooq 創建通用存儲庫

[英]Create a generic repository with Jooq

我們正在使用 JOOQ 來 map 一個現有的數據庫。 此數據庫中的每個表(第 1 條)共享一組相似的列:

resource_id:   Int (FK)
id:            Int (PK)
identifier:    UUID
date_created:  DateTime
date_modified: DateTime

數據庫中的所有操作都有一個通用規則,如下所示:

select   * 
from     table t
join     resource r on r.id = t.resource_id
where    r.is_archived = false 

使用 JOOQs 生成功能,我可以成功創建實現基本查詢操作的存儲庫 class:


@Repository
class UserRepository(val dsl:DSLContext): IRepository {
    val record = Tables.AUTHENTICATION_USER!!   //generated by jooq
    val resource = Tables.RESOURCE!!            //generated by jooq
    val pojo = AuthenticationUser::class.java   //generated by jooq

    // this is the general query logic that should be applied to all the things
    override fun selectQuery(): SelectConditionStep<Record> {
        return dsl
            .select(record.asterisk())
            .from(record)
            .join(resource).on(resource.ID.eq(record.RESOURCE_ID))
            .where(resource.IS_ARCHIVED.eq(false))
    }

    override fun asyncCount(): Mono<Int> = Mono.fromCallable {
        selectQuery()
            .count()
    }

    fun asyncGetPage(from: Int,
                     size: Int,
                     orderBy: SortField<*> = record.DATE_CREATED.asc(),
    ) = Mono.fromCallable {
        selectQuery()
            .orderBy(orderBy)
            .limit(size)
            .offset(from)
            .fetchInto(pojo)
            .let { Collections.unmodifiableList(it) }
    }

現在,這一切都很好,但是我無法將其轉換為可以

  • 接受類型Ttable
  • 可以識別表上的這 5 個常見字段/屬性,因此我們可以使我們的查詢通用。

我可以讓它工作:

class GeneralRepository<T: Table<R>, R: Record>(val table: T, val record: R, val dsl:DSLContext) {
    fun asyncGetAll(): Mono<List<R>> = Mono.fromCallable {
        dsl
            .select()
            .from(table)
            .fetchInto(record::class.java)
    }
}

// manually construct it ignoring Springs DI for development
val resourceRepository = GeneralRepository(
    Tables.RESOURCE,
    ResourceRecord(),
    JooqConfiguration().dsl(),
)

當我嘗試對table T做任何事情時,問題就來了。 由於T是通用表類型,編譯器無法知道字段ididentifierresource_iddate_created等,這意味着這樣的查詢無法編譯:

    fun asyncGetAllNonArchived() = 
        dsl
            .select()
            .from(table)
            .where(table.DATE_CREATED > ...)

問題:有沒有辦法告訴編譯器或聲明 generics 以使公共字段可用。


編輯:感謝下面的 Lukas Eder 的建議。 調整該解決方案,我們能夠創建一個通用存儲庫。

首先,將生成器策略更改為 Kotlin 讓Auditable接口正確運行:)

其次,我們找不到使用通用 class 的方法來根據Record類型來實現Table類型,因此您必須稍微擴展簽名。

interface Auditable {
    fun RESOURCE_ID(): TableField<Record, Int> =
        (this as Table<Record>).field("resource_id") as TableField<Record, Int>

    fun ID(): TableField<Record, Int> =
        (this as Table<Record>).field("id") as TableField<Record, Int>

    fun IDENTIFIER(): TableField<Record, UUID> =
        (this as Table<Record>).field("identifier") as TableField<Record, UUID>

    fun DATE_CREATED(): TableField<Record, LocalDateTime> =
        (this as Table<Record>).field("date_created") as TableField<Record, LocalDateTime>

    fun DATE_MODIFIED(): TableField<Record, LocalDateTime> =
        (this as Table<Record>).field("date_modified") as TableField<Record, LocalDateTime>
}


open class GeneralRepository<R, T>(val table: T) where R: Record, T: Table<R>, T: Auditable {
    val resource = Resource.RESOURCE

    fun selectQuery() =
        dsl
            .select(table.asterisk())
            .from(table)
            .join(resource).on(resource.ID.eq(table.RESOURCE_ID()))
            .where(resource.IS_ARCHIVED.eq(false))

然后,您可以輕松實現通用擴展:

class UserRepo: GeneralRepository<UserRecord, User>(table = User.USER) {

    fun getEmail(email: String) = Mono.fromCallable {
        selectQuery().and(table.EMAIL.eq(email))
    }
}

您可以生成一個接口來提供對這些列的訪問:

interface Auditable {
  fun RESOURCE_ID(): TableField<Record, Int?> = 
   (this as Table<Record>).field("resource_id") as TableField<Record, Int?>
  fun ID(): TableField<Record, Int?> = 
   (this as Table<Record>).field("id") as TableField<Record, Int?>
  fun identifier(): TableField<Record, UUID?> = 
   (this as Table<Record>).field("identifier") as TableField<Record, UUID?>
  fun DATE_CREATED(): TableField<Record, LocalDateTime?> = 
   (this as Table<Record>).field("date_created") as TableField<Record, LocalDateTime?>
  fun DATE_MODIFIED(): TableField<Record, LocalDateTime?> = 
   (this as Table<Record>).field("date_modified") as TableField<Record, LocalDateTime?>
}

然后使用生成器策略將接口附加到所有生成的表:

<strategy>
    <matchers>
        <tables>
            <table>
                <expression>T_.*</expression>
                <tableImplements>com.example.Auditable</tableImplements>
            </table>
        </tables>
    </matchers>
</strategy>

目前無法在界面中聲明屬性,因為無法檢測是否需要override修飾符,請參閱https://github.com/jOOQ/jOOQ/issues/10776

但這目前可能已經足夠了。 然后,您可以創建如下一般條件:

fun <T> dateCreatedCondition(table: T, dt: LocalDateTime): Condition
where T : Table<*>, T : Auditable = table.DATE_CREATED().gt(dt)

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM