Spring data neo4j изменение контекста базы данных во время выполнения - PullRequest
0 голосов
/ 28 мая 2020

Мне очень трудно использовать spring-data-neo4j с neo4j 4.0.0. В этой версии Neo4j есть возможность переключаться между базами данных, и мне нужно это сделать, чтобы реализовать многопользовательскую систему, в которой каждый клиент имеет свою собственную базу данных.

Однако, spring-data-neo4j, похоже, не дает возможности переключать базу данных во время выполнения с последней версией (это также должно быть потокобезопасным).

То, что я придумал, - это создать один sessionFactory для каждого клиента, делая это

    val sessionFactory = createTenantSessionFactory()
    val session = sessionFactory.openSession()
    val factory = Neo4jRepositoryFactory(session, mappingContext)
    val repository = factory.getRepository(DesiredNeo4jRepository::class.java)

Однако с этим решением у меня возникают проблемы с параллелизмом, даже если запросы выполняются один после другого в одном потоке результат в базе данных окажется неверным.

Я понял, что это может быть связано с тем, что я не использую поддержку управления транзакциями, предлагаемую Spring с этим решением, потому что Neo4jTransactionManager не создается.

Я понимаю, что это действительно сложный вопрос, но применил ли я хороший подход к решению проблемы или есть лучшая альтернатива, которую я не видел?

Если нет лучшего способа, как я могу поддержать управление транзакциями с помощью этого решения?

Спасибо за помощь!

1 Ответ

0 голосов
/ 29 мая 2020

Я наконец понял, как это исправить, используя решение из https://community.neo4j.com/t/spring-data-neo4j-4-0-release-multi-tenant/12920

Итак, я реализовал TenantContext

object ApplicationContext {

    private val currentTenant = ThreadLocal<String>()

    fun setCurrentTenant(tenantName: String) {
        currentTenant.set(tenantName)
    }

    fun getCurrentTenant(): String? {
        return currentTenant.get()
    }

    // Just a utility function
    fun withTenantContext(tenant: String, handler: (() -> Unit)) {
        val currentTenant = getCurrentTenant()
        setCurrentTenant(tenant)
        handler()

        if (currentTenant != null) {
            setCurrentTenant(currentTenant)
        } else {
            clearTenant()
        }
    }

    fun clearTenant() {
        currentTenant.remove()
    }
}

и переопределил фабрика сеансов. К сожалению, SessionFactory не предоставляет интерфейс, поэтому мне пришлось переопределить его и передать поддельный пакет и пустой драйвер (просто реализуйте класс org.neo4j.ogm.driver.Driver и оставьте его пустым).

Это скорее исправление, чем реальное решение в ожидании решения от spring-data-neo4j, но оно работает в многопоточном контексте и соответствует моим потребностям.

class MultiTenantSessionFactory(private val sessionFactories: ConcurrentMap<String, SessionFactory>) : SessionFactory(EmptyDriver(), "fake.package") {


    override fun openSession(): Session {
        return getSessionFactory(getCurrentTenantOrThrow()).openSession()
    }

    override fun close() {
        getSessionFactory(getCurrentTenantOrThrow()).close()
    }

    private fun getSessionFactory(tenantName: String): SessionFactory {
        return sessionFactories[tenantName] ?: throw UnregisteredSessionFactoryException(tenantName)
    }

    private fun getCurrentTenantOrThrow(): String {
        return ApplicationContext.getCurrentTenant() ?: throw EmptyTenantContextException()
    }
}

Наконец, просто создайте bean-компонент для этой фабрики сеансов при использовании @EnableNeo4jRepositories

    @Bean
    fun sessionFactory(tenantRepository: TenantRepository): SessionFactory {
        return MultiTenantSessionFactory(
                tenantRepository.findAll()
                        .map { tenant -> tenant.name to initTenantSessionFactory(tenant) }
                        .toMap(ConcurrentHashMap())
        )
    }

Теперь вы должны иметь возможность прозрачно использовать все свои Neo4jRepository в мультитенантном контексте. Надеюсь, это кому-то поможет!

...