springmongodbspring-bootkotlin

MongoDB Indexes Not Created Automatically in Spring Boot with Multi-Tenant Database


I am using Spring Boot with MongoDB and implementing a multi-tenant architecture where each tenant has a separate database. I set the database dynamically using a custom MongoDatabaseFactory.

Even though I have @Indexed annotations in my entity classes and spring.data.mongodb.auto-index-creation=true in application.properties, indexes are not being created automatically when a new tenant database is initialized.

Note: I have no problems with dynamically changing database, CRUD operations, everything works properly, only index creation does not work.

What I tried:

My Question:

How can I make Spring Boot automatically create indexes for new tenant databases when they are first accessed?

My dynamic database changing codes:

class MultiTenantMongoDbFactory(
    private val mongoClient: MongoClient
) : MongoDatabaseFactory {

    private val exceptionTranslator = MongoExceptionTranslator()

    override fun getSession(options: ClientSessionOptions): ClientSession {
        return mongoClient.startSession(options)
    }

    override fun getMongoDatabase(): MongoDatabase {
        val dbName = DatabaseContext.getDatabaseName()
        println("Switching to database: $dbName")
        return mongoClient.getDatabase(dbName)
    }

    override fun getMongoDatabase(dbName: String): MongoDatabase {
        return mongoClient.getDatabase(dbName)
    }

    override fun getExceptionTranslator(): PersistenceExceptionTranslator {
        return exceptionTranslator
    }

    override fun withSession(session: ClientSession): MongoDatabaseFactory {
        return MultiTenantMongoDbFactory(mongoClient).apply {
            session
        }
    }
}
@Configuration
class MongoConfiguration {

    @Value("\${SPRING_DATA_MONGODB_URI}")
    lateinit var mongoURI: String

    @Bean
    fun mongoClient(): MongoClient {
        return MongoClients.create(mongoURI)
    }

    @Bean
    @Primary
    fun multiTenantMongoDatabaseFactory(mongoClient: MongoClient): MultiTenantMongoDbFactory {
        return MultiTenantMongoDbFactory(mongoClient)
    }

    @Bean
    fun mongoTemplate(multiTenantMongoDatabaseFactory: MultiTenantMongoDbFactory): MongoTemplate {
        val mongoTemplate = MongoTemplate(multiTenantMongoDatabaseFactory)
        mongoTemplate.setReadPreference(ReadPreference.secondaryPreferred())
        return mongoTemplate
    }
}
object DatabaseContext {
    private val contextHolder = ThreadLocal<String>()
    private const val DEFAULT_DATABASE = "mydb"

    fun setDatabaseName(dbName: String) {
        contextHolder.set(dbName)
    }

    fun getDefaultDatabaseName(): String {
        return DEFAULT_DATABASE
    }

    fun getDatabaseName(): String {
        return contextHolder.get() ?: DEFAULT_DATABASE
    }

    fun clear() = contextHolder.remove()
}

Example data class;

@Document(collection = "users")
data class User(
    @Id
    val userId: String? = null,

    @Indexed(name = "user_uuid_1", unique = true)
    @Field("user_uuid")
    var userUuid: String
)

application.properties;

spring.data.mongodb.auto-index-creation=true
logging.level.org.springframework.data.mongodb=DEBUG
logging.level.org.springframework.data.mongodb.core.index=DEBUG
...
...

Solution

  • When Spring Boot first starts, it connects to a default database and creates indexes only for that database.

    However, when I dynamically change the database, it cannot apply these indexes to other databases. And there is no solution for this.

    So you have to manually create indexes for each database from MongoDB Compass or shell.

    Or as I am doing now, when a customer is created in CRM, I switch to the customer's database and create indexes manually with indexOps().

    You can solve the problem if you include this code it in a scenario where you create a new database.

    DatabaseContext.setDatabaseName(request.dbName)
    
    mongoTemplate.indexOps("devices").ensureIndex(
        Index().on("device_uuid", Sort.Direction.ASC).unique()
    )
    
    mongoTemplate.indexOps("devices").ensureIndex(
        Index().on("user_uuid", Sort.Direction.ASC)
    )
    
    mongoTemplate.indexOps("devices").ensureIndex(
        Index().on("is_active", Sort.Direction.ASC)
    )
    
    mongoTemplate.indexOps("users").ensureIndex(
        Index().on("user_uuid", Sort.Direction.ASC).unique()
    )