perf: retrieve one to many collections in bulk

This commit is contained in:
Gauthier Roebroeck 2022-07-18 11:54:56 +08:00
parent 5b6c1a56f7
commit 8e9d93f6f9
2 changed files with 71 additions and 49 deletions

View file

@ -27,12 +27,14 @@ import org.jooq.ResultQuery
import org.jooq.impl.DSL import org.jooq.impl.DSL
import org.jooq.impl.DSL.inline import org.jooq.impl.DSL.inline
import org.jooq.impl.DSL.noCondition import org.jooq.impl.DSL.noCondition
import org.springframework.beans.factory.annotation.Value
import org.springframework.data.domain.Page import org.springframework.data.domain.Page
import org.springframework.data.domain.PageImpl import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.PageRequest import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Pageable import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Sort import org.springframework.data.domain.Sort
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
import org.springframework.transaction.support.TransactionTemplate
import java.math.BigDecimal import java.math.BigDecimal
import java.net.URL import java.net.URL
@ -40,6 +42,8 @@ import java.net.URL
class BookDtoDao( class BookDtoDao(
private val dsl: DSLContext, private val dsl: DSLContext,
private val luceneHelper: LuceneHelper, private val luceneHelper: LuceneHelper,
@Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
private val transactionTemplate: TransactionTemplate,
) : BookDtoRepository { ) : BookDtoRepository {
private val b = Tables.BOOK private val b = Tables.BOOK
@ -298,8 +302,30 @@ class BookDtoDao(
.apply { if (joinConditions.selectReadListNumber) leftJoin(rlb).on(b.ID.eq(rlb.BOOK_ID)) } .apply { if (joinConditions.selectReadListNumber) leftJoin(rlb).on(b.ID.eq(rlb.BOOK_ID)) }
.apply { if (joinConditions.author) leftJoin(a).on(b.ID.eq(a.BOOK_ID)) } .apply { if (joinConditions.author) leftJoin(a).on(b.ID.eq(a.BOOK_ID)) }
private fun ResultQuery<Record>.fetchAndMap() = private fun ResultQuery<Record>.fetchAndMap(): MutableList<BookDto> {
fetch() val records = fetch()
val bookIds = records.getValues(b.ID)
lateinit var authors: Map<String, List<AuthorDto>>
lateinit var tags: Map<String, List<String>>
lateinit var links: Map<String, List<WebLinkDto>>
transactionTemplate.executeWithoutResult {
dsl.insertTempStrings(batchSize, bookIds)
authors = dsl.selectFrom(a)
.where(a.BOOK_ID.`in`(dsl.selectTempStrings()))
.filter { it.name != null }
.groupBy({ it.bookId }, { AuthorDto(it.name, it.role) })
tags = dsl.selectFrom(bt)
.where(bt.BOOK_ID.`in`(dsl.selectTempStrings()))
.groupBy({ it.bookId }, { it.tag })
links = dsl.selectFrom(bl)
.where(bl.BOOK_ID.`in`(dsl.selectTempStrings()))
.groupBy({ it.bookId }, { WebLinkDto(it.label, it.url) })
}
return records
.map { rec -> .map { rec ->
val br = rec.into(b) val br = rec.into(b)
val mr = rec.into(m) val mr = rec.into(m)
@ -307,25 +333,9 @@ class BookDtoDao(
val rr = rec.into(r) val rr = rec.into(r)
val seriesTitle = rec.into(sd.TITLE).component1() val seriesTitle = rec.into(sd.TITLE).component1()
val authors = dsl.selectFrom(a) br.toDto(mr.toDto(), dr.toDto(authors[br.id].orEmpty(), tags[br.id].orEmpty().toSet(), links[br.id].orEmpty()), if (rr.userId != null) rr.toDto() else null, seriesTitle)
.where(a.BOOK_ID.eq(br.id))
.fetchInto(a)
.filter { it.name != null }
.map { AuthorDto(it.name, it.role) }
val tags = dsl.select(bt.TAG)
.from(bt)
.where(bt.BOOK_ID.eq(br.id))
.fetchSet(bt.TAG)
val links = dsl.select(bl.LABEL, bl.URL)
.from(bl)
.where(bl.BOOK_ID.eq(br.id))
.fetchInto(bl)
.map { WebLinkDto(it.label, it.url) }
br.toDto(mr.toDto(), dr.toDto(authors, tags, links), if (rr.userId != null) rr.toDto() else null, seriesTitle)
} }
}
private fun BookSearchWithReadProgress.toCondition(): Condition { private fun BookSearchWithReadProgress.toCondition(): Condition {
var c: Condition = noCondition() var c: Condition = noCondition()

View file

@ -29,12 +29,14 @@ import org.jooq.impl.DSL.count
import org.jooq.impl.DSL.countDistinct import org.jooq.impl.DSL.countDistinct
import org.jooq.impl.DSL.lower import org.jooq.impl.DSL.lower
import org.jooq.impl.DSL.substring import org.jooq.impl.DSL.substring
import org.springframework.beans.factory.annotation.Value
import org.springframework.data.domain.Page import org.springframework.data.domain.Page
import org.springframework.data.domain.PageImpl import org.springframework.data.domain.PageImpl
import org.springframework.data.domain.PageRequest import org.springframework.data.domain.PageRequest
import org.springframework.data.domain.Pageable import org.springframework.data.domain.Pageable
import org.springframework.data.domain.Sort import org.springframework.data.domain.Sort
import org.springframework.stereotype.Component import org.springframework.stereotype.Component
import org.springframework.transaction.support.TransactionTemplate
import java.net.URL import java.net.URL
private val logger = KotlinLogging.logger {} private val logger = KotlinLogging.logger {}
@ -47,6 +49,8 @@ const val BOOKS_READ_COUNT = "booksReadCount"
class SeriesDtoDao( class SeriesDtoDao(
private val dsl: DSLContext, private val dsl: DSLContext,
private val luceneHelper: LuceneHelper, private val luceneHelper: LuceneHelper,
@Value("#{@komgaProperties.database.batchChunkSize}") private val batchSize: Int,
private val transactionTemplate: TransactionTemplate,
) : SeriesDtoRepository { ) : SeriesDtoRepository {
private val s = Tables.SERIES private val s = Tables.SERIES
@ -216,8 +220,40 @@ class SeriesDtoDao(
private fun readProgressConditionSeries(userId: String): Condition = rs.USER_ID.eq(userId).or(rs.USER_ID.isNull) private fun readProgressConditionSeries(userId: String): Condition = rs.USER_ID.eq(userId).or(rs.USER_ID.isNull)
private fun ResultQuery<Record>.fetchAndMap() = private fun ResultQuery<Record>.fetchAndMap(): MutableList<SeriesDto> {
fetch() val records = fetch()
val seriesIds = records.getValues(s.ID)
lateinit var genres: Map<String, List<String>>
lateinit var tags: Map<String, List<String>>
lateinit var sharingLabels: Map<String, List<String>>
lateinit var aggregatedAuthors: Map<String, List<AuthorDto>>
lateinit var aggregatedTags: Map<String, List<String>>
transactionTemplate.executeWithoutResult {
dsl.insertTempStrings(batchSize, seriesIds)
genres = dsl.selectFrom(g)
.where(g.SERIES_ID.`in`(dsl.selectTempStrings()))
.groupBy({ it.seriesId }, { it.genre })
tags = dsl.selectFrom(st)
.where(st.SERIES_ID.`in`(dsl.selectTempStrings()))
.groupBy({ it.seriesId }, { it.tag })
sharingLabels = dsl.selectFrom(sl)
.where(sl.SERIES_ID.`in`(dsl.selectTempStrings()))
.groupBy({ it.seriesId }, { it.label })
aggregatedAuthors = dsl.selectFrom(bmaa)
.where(bmaa.SERIES_ID.`in`(dsl.selectTempStrings()))
.filter { it.name != null }
.groupBy({ it.seriesId }, { AuthorDto(it.name, it.role) })
aggregatedTags = dsl.selectFrom(bmat)
.where(bmat.SERIES_ID.`in`(dsl.selectTempStrings()))
.groupBy({ it.seriesId }, { it.tag })
}
return records
.map { rec -> .map { rec ->
val sr = rec.into(s) val sr = rec.into(s)
val dr = rec.into(d) val dr = rec.into(d)
@ -227,40 +263,16 @@ class SeriesDtoDao(
val booksInProgressCount = rsr.inProgressCount ?: 0 val booksInProgressCount = rsr.inProgressCount ?: 0
val booksUnreadCount = sr.bookCount - booksReadCount - booksInProgressCount val booksUnreadCount = sr.bookCount - booksReadCount - booksInProgressCount
val genres = dsl.select(g.GENRE)
.from(g)
.where(g.SERIES_ID.eq(sr.id))
.fetchSet(g.GENRE)
val tags = dsl.select(st.TAG)
.from(st)
.where(st.SERIES_ID.eq(sr.id))
.fetchSet(st.TAG)
val sharingLabels = dsl.select(sl.LABEL)
.from(sl)
.where(sl.SERIES_ID.eq(sr.id))
.fetchSet(sl.LABEL)
val aggregatedAuthors = dsl.selectFrom(bmaa)
.where(bmaa.SERIES_ID.eq(sr.id))
.fetchInto(bmaa)
.filter { it.name != null }
.map { AuthorDto(it.name, it.role) }
val aggregatedTags = dsl.selectFrom(bmat)
.where(bmat.SERIES_ID.eq(sr.id))
.fetchSet(bmat.TAG)
sr.toDto( sr.toDto(
sr.bookCount, sr.bookCount,
booksReadCount, booksReadCount,
booksUnreadCount, booksUnreadCount,
booksInProgressCount, booksInProgressCount,
dr.toDto(genres, tags, sharingLabels), dr.toDto(genres[sr.id].orEmpty().toSet(), tags[sr.id].orEmpty().toSet(), sharingLabels[sr.id].orEmpty().toSet()),
bmar.toDto(aggregatedAuthors, aggregatedTags), bmar.toDto(aggregatedAuthors[sr.id].orEmpty(), aggregatedTags[sr.id].orEmpty().toSet()),
) )
} }
}
private fun SeriesSearchWithReadProgress.toCondition(): Condition { private fun SeriesSearchWithReadProgress.toCondition(): Condition {
var c = DSL.noCondition() var c = DSL.noCondition()