Custom DSL

DSL

Jpql ํด๋ž˜์Šค๋ฅผ ์ƒ์†ํ•˜๊ณ  ๋‚˜๋งŒ์˜ ํ•จ์ˆ˜๋ฅผ ์ถ”๊ฐ€ํ•˜๋Š” ๊ฒƒ์œผ๋กœ ๋‚˜๋งŒ์˜ DSL์„ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

Jpql์€ Kotlin JDSL์ด ์ œ๊ณตํ•˜๋Š” ๋ชจ๋“  ๊ธฐ๋ณธ DSL ํ•จ์ˆ˜๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๋ฅผ ์ด์šฉํ•ด ๋‚˜๋งŒ์˜ ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๊ทธ๋ฆฌ๊ณ  Expression ํ˜น์€ Predicate๋ฅผ ๊ตฌํ˜„ํ•œ ๋‚˜๋งŒ์˜ Model ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค๊ณ , ์ด๋ฅผ ๋ฐ˜ํ™˜ํ•˜๋Š” ํ•จ์ˆ˜๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด ๊ฒฝ์šฐ JpqlSerializer๋ฅผ ๊ตฌํ˜„ํ•˜์—ฌ Model์„ String์œผ๋กœ ๋žœ๋”๋งํ•˜๋Š” ๋ฐฉ๋ฒ•์„ Kotlin JDSL์—๊ฒŒ ์•Œ๋ ค์ค„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

์ด๋ ‡๊ฒŒ ๋งŒ๋“ค์–ด์ง„ ๋‚˜๋งŒ์˜ DSL์„ jpql()์— ์ „๋‹ฌํ•˜๊ธฐ ์œ„ํ•œ ๋‘ ๊ฐ€์ง€ ๋ฐฉ๋ฒ•์ด ์žˆ์Šต๋‹ˆ๋‹ค. ์ฒซ ๋ฒˆ์งธ๋Š” DSL ๊ฐ์ฒด๋ฅผ ์ƒ์„ฑํ•˜๋Š” JpqlDsl.Constructor๋ฅผ companion object๋กœ ๊ตฌํ˜„ํ•˜๋Š” ๊ฒƒ์ด๊ณ , ๋‘ ๋ฒˆ์งธ๋Š” DSL ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์ž…๋‹ˆ๋‹ค.

JpqlDsl.Constructor

์ด ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋ฉด ํด๋ž˜์Šค๋งŒ ์„ ์–ธํ•ด ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ์ฟผ๋ฆฌ ์ƒ์„ฑ ์‹œ๋งˆ๋‹ค ์ƒˆ๋กœ์šด ์ธ์Šคํ„ด์Šค๋ฅผ ์ž๋™์œผ๋กœ ์ƒ์„ฑํ•ฉ๋‹ˆ๋‹ค.

class MyJpql : Jpql() {
    companion object Constructor : JpqlDsl.Constructor<MyJpql> {
        override fun newInstance(): MyJpql = MyJpql()
    }

    fun myFunction(value: String): Expression<String> {
        return function(String::class, "myFunction", listOf(value(value)))
    }

    fun Expressionable<String>.regexLike(value: String): Predicate {
        return MyRegexLike(this.toExpression(), value)
    }
}

val query = jpql(MyJpql) {
    select(
        entity(Book::class)
    ).from(
        entity(Book::class)
    ).where(
        myFunction("test").regexLike(".*")
    )
}

Jpql Instance

์ด ๋ฐฉ๋ฒ•์„ ์‚ฌ์šฉํ•˜๋ฉด ์ฟผ๋ฆฌ ์ƒ์„ฑ์— ํ•˜๋‚˜์˜ ์ธ์Šคํ„ด์Šค๋ฅผ ์žฌํ™œ์šฉํ•  ์ˆ˜ ์žˆ์œผ๋ฉฐ ์˜์กด์„ฑ ์ฃผ์ž…์„ ํ™œ์šฉํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

class MyJpql(
    private val encryptor: Encryptor,
) : Jpql() {
    fun Path<String>.equalToEncrypted(value: String): Predicate {
        val encrypted = encryptor.encrypt(value)
        return this.equal(encrypted)
    }
}

val encryptor = Encryptor()
val instance = MyJpql(encryptor)

val query = jpql(instance) {
    select(
        entity(Book::class)
    ).from(
        entity(Book::class)
    ).where(
        path(Book::title).equalToEncrypted("plain")
    )
}

Serializer

๋‚˜๋งŒ์˜ Model์„ String์œผ๋กœ ๋žœ๋”๋งํ•˜๊ธฐ ์œ„ํ•ด JpqlSerializer๋ฅผ ๊ตฌํ˜„ํ•˜๊ณ  ์ด๋ฅผ RenderContext์— ์ถ”๊ฐ€ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.

JpqlSerializer๋Š” ๋žœ๋”๋ง ๋กœ์ง์„ ๊ตฌํ˜„ํ•  ์ˆ˜ ์žˆ๋„๋ก JpqlWriter์™€ RenderContext๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค. RenderContext๋ฅผ ํ†ตํ•ด JpqlRenderSerializer๋ฅผ ์–ป์–ด ๋‚˜์˜ Model์ด ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๋‹ค๋ฅธ Model์„ ๋žœ๋”๋งํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ๋˜ RenderContext๋ฅผ ํ†ตํ•ด JpqlRenderStatement์™€ JpqlRenderClause๋ฅผ ์–ป์–ด ํ˜„์žฌ ์–ด๋–ค statement์™€ clause ์•ˆ์—์„œ ๋žœ๋”๋งํ•˜๊ณ  ์žˆ๋Š”์ง€ ์•Œ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค. ์ด๊ฒƒ๋“ค์„ ์ด์šฉํ•ด์„œ ๋‚˜๋งŒ์˜ Model์„ String์œผ๋กœ ๋žœ๋”๋งํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.

class MyRegexLikeSerializer : JpqlSerializer<MyRegexLike> {
    override fun handledType(): KClass<MyRegexLike> {
        return MyRegexLike::class
    }

    override fun serialize(part: MyRegexLike, writer: JpqlWriter, context: RenderContext) {
        val delegate = context.getValue(JpqlRenderSerializer)

//        val statement = context.getValue(JpqlRenderStatement)
//        val clause = context.getValue(JpqlRenderClause)
//
//        statement.isSelect()
//        clause.isWhere()

        writer.write("REGEXP_LIKE")
        writer.writeParentheses {
            delegate.serialize(part.expr, writer, context)
            writer.write(", ")
            delegate.serialize(part.pattern, writer, context)
        }
    }
}

JpqlRenderContext๋Š” JpqlSerializer ๊ตฌํ˜„์ฒด๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์žˆ๋„๋ก registerModule()๋ฅผ ์ œ๊ณตํ•ฉ๋‹ˆ๋‹ค.

val myModule = object : JpqlRenderModule {
    override fun setupModule(context: JpqlRenderModule.SetupContext) {
        context.addSerializer(MyRegexLikeSerializer())
    }
}

val myContext = JpqlRenderContext().registerModule(myModule)

val jpqQuery = entityManager.createQuery(query, myContext)

Last updated