Skip to content

Commit

Permalink
fix: Import optimization
Browse files Browse the repository at this point in the history
  • Loading branch information
JanCizmar committed Dec 15, 2023
1 parent dea59b9 commit a2a0a57
Show file tree
Hide file tree
Showing 7 changed files with 204 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import io.tolgee.activity.data.EntityDescriptionWithRelations
import io.tolgee.activity.data.PropertyModification
import io.tolgee.activity.data.RevisionType
import io.tolgee.activity.propChangesProvider.PropChangesProvider
import io.tolgee.component.ActivityHolderProvider
import io.tolgee.dtos.cacheable.UserAccountDto
import io.tolgee.events.OnProjectActivityEvent
import io.tolgee.model.EntityWithId
Expand Down Expand Up @@ -274,10 +275,13 @@ class InterceptedEventsManager(
applicationContext.getBean(ActivityService::class.java)
}

private val activityHolder: ActivityHolder by lazy {
applicationContext.getBean(ActivityHolder::class.java)
private val activityHolderProvider: ActivityHolderProvider by lazy {
applicationContext.getBean(ActivityHolderProvider::class.java)
}

private val activityHolder
get() = activityHolderProvider.getActivityHolder()

private val userAccount: UserAccountDto?
get() = authenticationFacade.authenticatedUserOrNull
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ class TagsPropChangesProvider : PropChangesProvider {

override fun getChanges(old: Any?, new: Any?): PropertyModification? {
if (old is Collection<*> && new is Collection<*>) {
if(old === new){
return null
}

val oldTagNames = mapSetToTagNames(old)
val newTagNames = mapSetToTagNames(new)
if (oldTagNames.containsAll(newTagNames) && newTagNames.containsAll(oldTagNames)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package io.tolgee.component

import io.tolgee.activity.ActivityHolder
import jakarta.annotation.PreDestroy
import org.springframework.beans.factory.support.ScopeNotActiveException
import org.springframework.context.ApplicationContext
import org.springframework.stereotype.Component
import org.springframework.transaction.support.TransactionSynchronization
import org.springframework.transaction.support.TransactionSynchronizationManager

/**
* Class providing Activity Holder, while caching it in ThreadLocal.
* I also registers a transaction synchronization, which clears the ThreadLocal after transaction
* is completed, this enables us to be safe even when running activities in main thred.
*/
@Component
class ActivityHolderProvider(private val applicationContext: ApplicationContext) {
private val threadLocal = ThreadLocal<ActivityHolder?>()

fun getActivityHolder(): ActivityHolder {
// Get the activity holder from ThreadLocal.
var activityHolder = threadLocal.get()
if (activityHolder == null) {
// If no activity holder exists for this thread, fetch one and store it in ThreadLocal.
activityHolder = fetchActivityHolder()
threadLocal.set(activityHolder)
}
return activityHolder
}

private fun fetchActivityHolder(): ActivityHolder {
return try {
applicationContext.getBean("requestActivityHolder", ActivityHolder::class.java).also {
it.activityRevision
}
} catch (e: ScopeNotActiveException) {
val transactionActivityHolder =
applicationContext.getBean("transactionActivityHolder", ActivityHolder::class.java)
if (TransactionSynchronizationManager.isSynchronizationActive()) {
TransactionSynchronizationManager.registerSynchronization(
object : TransactionSynchronization {
override fun afterCompletion(status: Int) {
clearThreadLocal()
}
}
)
return transactionActivityHolder
}

throw IllegalStateException("Transaction synchronization is not active.")
}
}

@PreDestroy
fun clearThreadLocal() {
threadLocal.remove()
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package io.tolgee.configuration

import io.tolgee.activity.ActivityHolder
import io.tolgee.component.ActivityHolderProvider
import io.tolgee.configuration.TransactionScopeConfig.Companion.SCOPE_TRANSACTION
import org.springframework.beans.factory.annotation.Qualifier
import org.springframework.beans.factory.config.BeanDefinition
Expand Down Expand Up @@ -31,16 +32,16 @@ class ActivityHolderConfig {
return ActivityHolder(applicationContext)
}

/**
* This method for getting the activity holder is slow, since it
* needs to create new bean every time holder is requested. Which is pretty often.
*
* Use the activityHolderProvider when possible.
*/
@Bean
@Primary
@Scope(BeanDefinition.SCOPE_PROTOTYPE, proxyMode = ScopedProxyMode.TARGET_CLASS)
fun activityHolder(applicationContext: ApplicationContext): ActivityHolder {
return try {
applicationContext.getBean("requestActivityHolder", ActivityHolder::class.java).also {
it.activityRevision
}
} catch (e: ScopeNotActiveException) {
return applicationContext.getBean("transactionActivityHolder", ActivityHolder::class.java)
}
fun activityHolder(activityHolderProvider: ActivityHolderProvider): ActivityHolder {
return activityHolderProvider.getActivityHolder()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
package io.tolgee.service.dataImport

import jakarta.persistence.EntityManager
import org.hibernate.Session
import org.springframework.stereotype.Service

@Service
class ImportDeleteService(
private val entityManager: EntityManager
) {
fun deleteImport(importId: Long) {
entityManager.unwrap(Session::class.java).doWork { connection ->
deleteImportTranslations(connection, importId)
deleteImportLanguages(connection, importId)
deleteImportKeyMetaTags(connection, importId)
deleteImportKeyMetaComments(connection, importId)
deleteImportKeyMetaCodeReferences(connection, importId)
deleteImportKeyMeta(connection, importId)
deleteImportKeys(connection, importId)
deleteImportFileIssueParams(connection, importId)
deleteImportFileIssues(connection, importId)
deleteImportFiles(connection, importId)
deleteTheImport(connection, importId)
}
}

fun executeUpdate(connection: java.sql.Connection, query: String, importId: Long) {
@Suppress("SqlSourceToSinkFlow")
connection.prepareStatement(query).use { statement ->
statement.setLong(1, importId)
statement.executeUpdate()
}
}

fun deleteImportTranslations(connection: java.sql.Connection, importId: Long) {
val query =
"delete from import_translation " +
"where id in (" +
"select it.id from import_translation it " +
"join import_language il on il.id = it.language_id " +
"join import_file if on il.file_id = if.id where if.import_id = ?)"
executeUpdate(connection, query, importId)
}

fun deleteImportLanguages(connection: java.sql.Connection, importId: Long) {
val query =
"delete from import_language " +
"where id in (select il.id from import_language il " +
"join import_file if on il.file_id = if.id where if.import_id = ?)"
executeUpdate(connection, query, importId)
}

fun deleteImportKeys(connection: java.sql.Connection, importId: Long) {
val query =
"delete from import_key " +
"where id in (select ik.id from import_key ik " +
"join import_file if on ik.file_id = if.id where if.import_id = ?)"
executeUpdate(connection, query, importId)
}

fun deleteImportKeyMetaTags(connection: java.sql.Connection, importId: Long) {
val query =
"delete from key_meta_tags " +
"where key_metas_id in (select ikm.id from key_meta ikm " +
"join import_key ik on ikm.import_key_id = ik.id " +
"join import_file if on ik.file_id = if.id where if.import_id = ?)"
executeUpdate(connection, query, importId)
}

fun deleteImportKeyMetaComments(connection: java.sql.Connection, importId: Long) {
val query =
"delete from key_comment " +
"where key_meta_id in (select ikm.id from key_meta ikm " +
"join import_key ik on ikm.import_key_id = ik.id " +
"join import_file if on ik.file_id = if.id where if.import_id = ?)"
executeUpdate(connection, query, importId)
}

fun deleteImportKeyMetaCodeReferences(connection: java.sql.Connection, importId: Long) {
val query =
"delete from key_code_reference " +
"where key_meta_id in (select ikm.id from key_meta ikm " +
"join import_key ik on ikm.import_key_id = ik.id " +
"join import_file if on ik.file_id = if.id where if.import_id = ?)"
executeUpdate(connection, query, importId)
}

fun deleteImportKeyMeta(connection: java.sql.Connection, importId: Long) {
val query =
"delete from key_meta " +
"where id in (select ikm.id from key_meta ikm " +
"join import_key ik on ikm.import_key_id = ik.id " +
"join import_file if on ik.file_id = if.id where if.import_id = ?)"
executeUpdate(connection, query, importId)
}

fun deleteImportFileIssueParams(connection: java.sql.Connection, importId: Long) {
val query =
"delete from import_file_issue_param " +
"where import_file_issue_param.issue_id in (select ifi.id from import_file_issue ifi " +
"join import_file if on ifi.file_id = if.id where if.import_id = ?)"
executeUpdate(connection, query, importId)
}

fun deleteImportFileIssues(connection: java.sql.Connection, importId: Long) {
val query =
"delete from import_file_issue " +
"where id in (select ifi.id from import_file_issue ifi " +
"join import_file if on ifi.file_id = if.id where if.import_id = ?)"
executeUpdate(connection, query, importId)
}

fun deleteImportFiles(connection: java.sql.Connection, importId: Long) {
val query = "delete from import_file " +
"where id in (select if.id from import_file if where if.import_id = ?)"
executeUpdate(connection, query, importId)
}

fun deleteTheImport(connection: java.sql.Connection, importId: Long) {
val query = "delete from import where id = ?"
executeUpdate(connection, query, importId)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,8 @@ class ImportService(
private val keyMetaService: KeyMetaService,
private val removeExpiredImportService: RemoveExpiredImportService,
private val entityManager: EntityManager,
private val businessEventPublisher: BusinessEventPublisher
private val businessEventPublisher: BusinessEventPublisher,
private val importDeleteService: ImportDeleteService
) {
@Transactional
fun addFiles(
Expand Down Expand Up @@ -251,16 +252,9 @@ class ImportService(
)
}

@Transactional
fun deleteImport(import: Import) {
this.importTranslationRepository.deleteAllByImport(import)
this.importLanguageRepository.deleteAllByImport(import)
val keyIds = this.importKeyRepository.getAllIdsByImport(import)
this.keyMetaService.deleteAllByImportKeyIdIn(keyIds)
this.importKeyRepository.deleteByIdIn(keyIds)
this.importFileIssueParamRepository.deleteAllByImport(import)
this.importFileIssueRepository.deleteAllByImport(import)
this.importFileRepository.deleteAllByImport(import)
this.importRepository.delete(import)
importDeleteService.deleteImport(import.id)
}

@Transactional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,13 +121,6 @@ class KeyMetaService(

fun save(meta: KeyMeta): KeyMeta = this.keyMetaRepository.save(meta)

fun deleteAllByImportKeyIdIn(importKeyIds: List<Long>) {
tagService.deleteAllByImportKeyIdIn(importKeyIds)
keyCommentRepository.deleteAllByImportKeyIds(importKeyIds)
keyCodeReferenceRepository.deleteAllByImportKeyIds(importKeyIds)
this.keyMetaRepository.deleteAllByImportKeyIdIn(importKeyIds)
}

fun deleteAllByKeyIdIn(ids: Collection<Long>) {
tagService.deleteAllByKeyIdIn(ids)
keyCommentRepository.deleteAllByKeyIds(ids)
Expand Down

0 comments on commit a2a0a57

Please sign in to comment.