11package com.softeno.template.app.permission.db
22
3+ import com.softeno.template.app.permission.BaseEntity
34import com.softeno.template.app.permission.Permission
5+ import kotlinx.coroutines.reactor.awaitSingle
46import org.springframework.data.domain.PageRequest
57import org.springframework.data.domain.Pageable
68import org.springframework.data.domain.Sort
79import org.springframework.data.r2dbc.repository.Query
810import org.springframework.data.r2dbc.repository.R2dbcRepository
11+ import org.springframework.data.relational.core.mapping.Column
12+ import org.springframework.data.relational.core.mapping.Table
913import org.springframework.data.repository.query.Param
14+ import org.springframework.r2dbc.core.DatabaseClient
15+ import org.springframework.r2dbc.core.bind
1016import org.springframework.stereotype.Repository
1117import reactor.core.publisher.Flux
1218import reactor.core.publisher.Mono
19+ import java.util.*
20+ import kotlin.reflect.KProperty1
21+ import kotlin.reflect.full.findAnnotation
22+ import kotlin.reflect.full.memberProperties
23+ import kotlin.reflect.jvm.javaField
1324
1425@Repository
15- interface PermissionRepository : R2dbcRepository <Permission , Long > { // , QuerydslPredicateExecutor <Permission> {
26+ interface PermissionRepository : R2dbcRepository <Permission , Long >, BatchPermissionRepository <Permission > {
1627 fun findBy (pageable : Pageable ): Flux <Permission >
1728
1829 @Query(" SELECT p.version FROM permissions as p WHERE p.id = :id" )
1930 fun findVersionById (@Param(" id" ) id : Long ): Mono <Long >
2031}
2132
33+ /* *
34+ * Batch operations for @Table annotated entities
35+ */
36+ interface BatchPermissionRepository <T >
37+
38+ /* *
39+ * Inserts all @Column annotated props except id and uuid
40+ * Returns map of inserted entities to their db ids
41+ */
42+ suspend fun <T : BaseEntity > BatchPermissionRepository<T>.saveAllReturningIdsByUuid (
43+ entities : List <T >,
44+ databaseClient : DatabaseClient
45+ ): Map <T , Long > {
46+ if (entities.isEmpty()) return emptyMap()
47+
48+ val sample = entities.first()
49+ val tableName = sample::class .findAnnotation<Table >()?.value
50+ ? : error(" Missing @Table annotation on ${sample::class .simpleName} " )
51+
52+ val properties = sample::class .memberProperties
53+ .filter { it.javaField?.isAnnotationPresent(Column ::class .java) == true }
54+ .map { prop -> prop to prop.javaField!! .getAnnotation(Column ::class .java).value }
55+
56+ val activeProps = properties.filter { (prop, _) ->
57+ entities.any { (prop as KProperty1 <T , * >).get(it) != null }
58+ }
59+
60+ val columnNames = activeProps.map { it.second }
61+ val valuePlaceholders = entities.mapIndexed { idx, _ ->
62+ " (" + activeProps.joinToString(" , " ) { (prop, _) -> " :${prop.name}$idx " } + " )"
63+ }.joinToString(" , " )
64+
65+ val sql =
66+ " INSERT INTO $tableName (${columnNames.joinToString(" , " )} ) VALUES $valuePlaceholders RETURNING id, uuid"
67+
68+ var spec = databaseClient.sql(sql)
69+
70+ entities.forEachIndexed { idx, entity ->
71+ activeProps.forEach { (prop, _) ->
72+ val value = (prop as KProperty1 <T , * >).get(entity)
73+ spec = spec.bind(" ${prop.name}$idx " , value)
74+ }
75+ }
76+
77+ return specEntitiesToIds(spec, entities)
78+ }
79+
80+ /* *
81+ * Updates all @Column annotated props except id and uuid, entities must have the same uuid
82+ * Returns map of updated entities to their db ids
83+ */
84+ suspend fun <T : BaseEntity > BatchPermissionRepository<T>.updateAllReturningIdsByUuid (
85+ entities : List <T >,
86+ databaseClient : DatabaseClient
87+ ): Map <T , Long > {
88+ if (entities.isEmpty()) return emptyMap()
89+
90+ val sample = entities.first()
91+ val tableName = sample::class .findAnnotation<Table >()?.value
92+ ? : error(" Missing @Table annotation on ${sample::class .simpleName} " )
93+
94+ val properties = sample::class .memberProperties
95+ .filter { it.javaField?.isAnnotationPresent(Column ::class .java) == true }
96+ .map { prop -> prop to prop.javaField!! .getAnnotation(Column ::class .java).value }
97+ .filter { (_, colName) -> colName != " id" && colName != " uuid" }
98+
99+ val setClauses = properties.mapNotNull { (prop, colName) ->
100+ val cases = entities.mapIndexed { idx, entity ->
101+ val value = (prop as KProperty1 <T , * >).get(entity)
102+ if (value != null ) {
103+ " WHEN :uuid$idx THEN :${prop.name}$idx "
104+ } else null
105+ }.filterNotNull()
106+
107+ if (cases.isEmpty()) null
108+ else " $colName = CASE uuid ${cases.joinToString(" " )} ELSE $colName END"
109+ }
110+
111+ if (setClauses.isEmpty()) return emptyMap()
112+
113+ val sql = """
114+ UPDATE $tableName
115+ SET ${setClauses.joinToString(" , " )}
116+ WHERE uuid IN (${entities.indices.joinToString(" , " ) { " :uuid$it " }} )
117+ RETURNING id, uuid
118+ """ .trimIndent()
119+
120+ var spec = databaseClient.sql(sql)
121+
122+ entities.forEachIndexed { idx, entity ->
123+ spec = spec.bind(" uuid$idx " , entity.uuid)
124+ properties.forEach { (prop, _) ->
125+ val value = (prop as KProperty1 <T , * >).get(entity)
126+ if (value != null ) {
127+ spec = spec.bind(" ${prop.name}$idx " , value)
128+ }
129+ }
130+ }
131+
132+ return specEntitiesToIds(spec, entities)
133+ }
134+
135+ private suspend inline fun <T : BaseEntity > specEntitiesToIds (
136+ spec : DatabaseClient .GenericExecuteSpec ,
137+ entities : List <T >
138+ ): Map <T , Long > {
139+ val rows = spec.map { row, _ ->
140+ val uuid = row.get(" uuid" , UUID ::class .java)!!
141+ val id = row.get(" id" , java.lang.Long ::class .java)!! .toLong()
142+ uuid to id
143+ }
144+ .all()
145+ .collectList()
146+ .awaitSingle()
147+
148+ val uuidToId = rows.toMap()
149+ return entities.associateWith { uuidToId[it.uuid]!! }
150+ }
151+
152+
153+
22154fun getPageRequest (page : Int , size : Int , sort : String , direction : String ) =
23155 Sort .by(Sort .Order (if (direction == " ASC" ) Sort .Direction .ASC else Sort .Direction .DESC , sort))
24156 .let { PageRequest .of(page, size, it) }
0 commit comments