Skip to content

Commit

Permalink
fix: Ditch stateless session and use jdbcTemplate
Browse files Browse the repository at this point in the history
  • Loading branch information
JanCizmar committed Dec 18, 2023
1 parent fa477dc commit 7e9d1f7
Show file tree
Hide file tree
Showing 5 changed files with 70 additions and 85 deletions.
81 changes: 37 additions & 44 deletions backend/data/src/main/kotlin/io/tolgee/activity/ActivityService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,14 +13,13 @@ import io.tolgee.model.activity.ActivityRevision
import io.tolgee.model.views.activity.ProjectActivityView
import io.tolgee.repository.activity.ActivityModifiedEntityRepository
import io.tolgee.util.Logging
import io.tolgee.util.doInStatelessSession
import io.tolgee.util.flushAndClear
import jakarta.persistence.EntityManager
import org.hibernate.StatelessSession
import org.postgresql.util.PGobject
import org.springframework.context.ApplicationContext
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional

Expand All @@ -30,62 +29,56 @@ class ActivityService(
private val applicationContext: ApplicationContext,
private val activityModifiedEntityRepository: ActivityModifiedEntityRepository,
private val currentDateProvider: CurrentDateProvider,
private val objectMapper: ObjectMapper
private val objectMapper: ObjectMapper,
private val jdbcTemplate: JdbcTemplate
) : Logging {
@Transactional
fun storeActivityData(activityRevision: ActivityRevision, modifiedEntities: ModifiedEntitiesType) {
// let's keep the persistent context small
entityManager.flushAndClear()

val mergedActivityRevision = entityManager.doInStatelessSession { statelessSession ->
val mergedActivityRevision = statelessSession.persistAcitivyRevision(activityRevision)
statelessSession.persistedDescribingRelations(mergedActivityRevision)
mergedActivityRevision.modifiedEntities = statelessSession.persistModifiedEntities(modifiedEntities)
mergedActivityRevision
}
val mergedActivityRevision = persistAcitivyRevision(activityRevision)

persistedDescribingRelations(mergedActivityRevision)
mergedActivityRevision.modifiedEntities = persistModifiedEntities(modifiedEntities)
applicationContext.publishEvent(OnProjectActivityStoredEvent(this, mergedActivityRevision))
}

private fun StatelessSession.persistModifiedEntities(modifiedEntities: ModifiedEntitiesType): MutableList<ActivityModifiedEntity> {
private fun persistModifiedEntities(modifiedEntities: ModifiedEntitiesType): MutableList<ActivityModifiedEntity> {
val list = modifiedEntities.values.flatMap { it.values }.toMutableList()

this.doWork { connection ->
val describingRelationQuery = "INSERT INTO activity_modified_entity " +
jdbcTemplate.batchUpdate(
"INSERT INTO activity_modified_entity " +
"(entity_class, entity_id, describing_data, " +
"describing_relations, modifications, revision_type, activity_revision_id) " +
"VALUES (?, ?, ?, ?, ?, ?, ?)"
val preparedStatement = connection.prepareStatement(describingRelationQuery)
list.forEach { entity ->
preparedStatement.setString(1, entity.entityClass)
preparedStatement.setLong(2, entity.entityId)
preparedStatement.setObject(3, getJsonbObject(entity.describingData))
preparedStatement.setObject(4, getJsonbObject(entity.describingRelations))
preparedStatement.setObject(5, getJsonbObject(entity.modifications))
preparedStatement.setInt(6, RevisionType.values().indexOf(entity.revisionType))
preparedStatement.setLong(7, entity.activityRevision.id)
preparedStatement.addBatch()
}
preparedStatement.executeBatch()
"VALUES (?, ?, ?, ?, ?, ?, ?)",
list,
100
) { ps, entity ->
ps.setString(1, entity.entityClass)
ps.setLong(2, entity.entityId)
ps.setObject(3, getJsonbObject(entity.describingData))
ps.setObject(4, getJsonbObject(entity.describingRelations))
ps.setObject(5, getJsonbObject(entity.modifications))
ps.setInt(6, RevisionType.values().indexOf(entity.revisionType))
ps.setLong(7, entity.activityRevision.id)
}

return list
}


private fun StatelessSession.persistedDescribingRelations(activityRevision: ActivityRevision) {
this.doWork { connection ->
val describingRelationQuery = "INSERT INTO activity_describing_entity " +
private fun persistedDescribingRelations(activityRevision: ActivityRevision) {
jdbcTemplate.batchUpdate(
"INSERT INTO activity_describing_entity " +
"(entity_class, entity_id, data, describing_relations, activity_revision_id) " +
"VALUES (?, ?, ?, ?, ?)"
val preparedStatement = connection.prepareStatement(describingRelationQuery)
activityRevision.describingRelations.forEach { entity ->
preparedStatement.setString(1, entity.entityClass)
preparedStatement.setLong(2, entity.entityId)
preparedStatement.setObject(3, getJsonbObject(entity.data))
preparedStatement.setObject(4, getJsonbObject(entity.describingRelations))
preparedStatement.setLong(5, activityRevision.id)
preparedStatement.addBatch()
}
preparedStatement.executeBatch()
"VALUES (?, ?, ?, ?, ?)",
activityRevision.describingRelations,
100
) { ps, entity ->
ps.setString(1, entity.entityClass)
ps.setLong(2, entity.entityId)
ps.setObject(3, getJsonbObject(entity.data))
ps.setObject(4, getJsonbObject(entity.describingRelations))
ps.setLong(5, activityRevision.id)
}
}

Expand All @@ -96,10 +89,10 @@ class ActivityService(
return pgObject
}

private fun StatelessSession.persistAcitivyRevision(activityRevision: ActivityRevision): ActivityRevision {
private fun persistAcitivyRevision(activityRevision: ActivityRevision): ActivityRevision {
return if (activityRevision.id == 0L) {
activityRevision.timestamp = currentDateProvider.date
this.insert(activityRevision)
entityManager.persist(activityRevision)
entityManager.flushAndClear()
activityRevision
} else {
entityManager.getReference(ActivityRevision::class.java, activityRevision.id)
Expand Down
45 changes: 22 additions & 23 deletions backend/data/src/main/kotlin/io/tolgee/batch/BatchJobService.kt
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import org.springframework.context.ApplicationContext
import org.springframework.context.annotation.Lazy
import org.springframework.data.domain.Page
import org.springframework.data.domain.Pageable
import org.springframework.jdbc.core.JdbcTemplate
import org.springframework.stereotype.Service
import org.springframework.transaction.annotation.Transactional
import org.springframework.transaction.event.TransactionalEventListener
Expand All @@ -56,7 +57,8 @@ class BatchJobService(
private val currentDateProvider: CurrentDateProvider,
private val securityService: SecurityService,
private val authenticationFacade: AuthenticationFacade,
private val objectMapper: ObjectMapper
private val objectMapper: ObjectMapper,
private val jdbcTemplate: JdbcTemplate
) : Logging {

companion object {
Expand Down Expand Up @@ -132,31 +134,28 @@ class BatchJobService(
}

private fun insertExecutionsViaBatchStatement(executions: List<BatchJobChunkExecution>) {
entityManager.unwrap(Session::class.java).doWork { connection ->
val query = """
val sequenceIdProvider = SequenceIdProvider(SEQUENCE_NAME, ALLOCATION_SIZE)
jdbcTemplate.batchUpdate(
"""
insert into tolgee_batch_job_chunk_execution
(id, batch_job_id, chunk_number, status, created_at, updated_at, success_targets)
values (?, ?, ?, ?, ?, ?, ?)
"""
val statement = connection.prepareStatement(query)
val sequenceIdProvider = SequenceIdProvider(connection, SEQUENCE_NAME, ALLOCATION_SIZE)
val timestamp = Timestamp(currentDateProvider.date.time)
executions.forEach {
val id = sequenceIdProvider.next()
it.id = id
statement.setLong(1, id)
statement.setLong(2, it.batchJob.id)
statement.setInt(3, it.chunkNumber)
statement.setString(4, it.status.name)
statement.setTimestamp(5, timestamp)
statement.setTimestamp(6, timestamp)
statement.setObject(7, PGobject().apply {
type = "jsonb"
value = objectMapper.writeValueAsString(it.successTargets)
})
statement.addBatch()
}
statement.executeBatch()
""",
executions,
100
) { ps, execution ->
val id = sequenceIdProvider.next(ps.connection)
execution.id = id
ps.setLong(1, id)
ps.setLong(2, execution.batchJob.id)
ps.setInt(3, execution.chunkNumber)
ps.setString(4, execution.status.name)
ps.setTimestamp(5, Timestamp(currentDateProvider.date.time))
ps.setTimestamp(6, Timestamp(currentDateProvider.date.time))
ps.setObject(7, PGobject().apply {
type = "jsonb"
value = objectMapper.writeValueAsString(execution.successTargets)
})
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ import io.tolgee.service.key.KeyService
import io.tolgee.service.key.NamespaceService
import io.tolgee.service.security.SecurityService
import io.tolgee.service.translation.TranslationService
import io.tolgee.util.flushAndClear
import jakarta.persistence.EntityManager
import org.springframework.context.ApplicationContext

class StoredDataImporter(
Expand Down Expand Up @@ -46,6 +48,8 @@ class StoredDataImporter(
*/
private val translationService = applicationContext.getBean(TranslationService::class.java)

private val entityManager = applicationContext.getBean(EntityManager::class.java)

private val namespacesToSave = mutableMapOf<String?, Namespace>()

/**
Expand Down Expand Up @@ -89,6 +93,8 @@ class StoredDataImporter(
saveMetaData(keyEntitiesToSave)

translationService.setOutdatedBatch(outdatedFlagKeys)

entityManager.flushAndClear()
}

private fun saveMetaData(keyEntitiesToSave: MutableCollection<Key>) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,28 +3,27 @@ package io.tolgee.util
import java.sql.Connection

class SequenceIdProvider(
private val connection: Connection,
private val sequenceName: String,
private val allocationSize: Int
) {

private var currentId: Long? = null
private var currentMaxId: Long? = null

fun next(): Long {
allocateIfRequired()
fun next(connection: Connection): Long {
allocateIfRequired(connection)
val currentId = currentId
this.currentId = currentId!! + 1
return currentId
}

private fun allocateIfRequired() {
private fun allocateIfRequired(connection: Connection) {
if (currentId == null || currentMaxId == null || currentId!! >= currentMaxId!!) {
allocate()
allocate(connection)
}
}

private fun allocate() {
private fun allocate(connection: Connection) {
@Suppress("SqlSourceToSinkFlow")
val statement = connection.prepareStatement("select nextval('$sequenceName')")
val resultSet = statement.executeQuery()
Expand Down
12 changes: 0 additions & 12 deletions backend/data/src/main/kotlin/io/tolgee/util/entityManagerExt.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,15 +11,3 @@ fun EntityManager.flushAndClear() {
this.flush()
this.clear()
}


inline fun <reified T> EntityManager.doInStatelessSession(
crossinline block: (StatelessSession) -> T
): T {
return unwrap(Session::class.java).doReturningWork { connection ->
val statelessSession = unwrap(Session::class.java).sessionFactory.openStatelessSession(connection)
statelessSession.use { ss ->
block(ss)
}
}
}

0 comments on commit 7e9d1f7

Please sign in to comment.