Support for multi-tenant repositories for Spring Boot Data MongoDB.
build.gradle.kts:
repositories {
mavenCentral() {
content {
excludeGroup("com.github.enablingflow") // improves speed
}
}
maven(url="https://jitpack.io") {
content {
includeGroup("com.github.enablingflow")
}
}
}
dependencies {
implementation("com.github.enablingflow:spring-boot-data-mongo-multitenant:0.0.7")
}
You need to use the MultiTenantMongoTemplate
in order to support the multi-tenant repositories:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.mongodb.MongoDatabaseFactory;
import org.springframework.data.mongodb.core.MongoTemplate;
import org.springframework.data.mongodb.core.MultiTenantMongoTemplate;
import org.springframework.data.mongodb.repository.config.EnableMongoRepositories;
import com.github.enablingflow.springbootdatamongomultitenant.MultiTenantContext;
import com.github.enablingflow.springbootdatamongomultitenant.MultiTenantMongoRepository;
@Configuration
@EnableMongoRepositories(repositoryBaseClass = MultiTenantMongoRepository.class)
public class MongoConfig {
@Bean
MultiTenantContext multiTenantContext() {
return new MultiTenantContext();
}
@Bean
MongoTemplate mongoTemplate(MongoDatabaseFactory mongoDbFactory, MultiTenantContext multiTenantContext) {
return new MultiTenantMongoTemplate(mongoDbFactory, multiTenantContext);
}
}
Mark your entity with @MultiTenant
annotation and mark the field that will be used to filter the data with @MultiTenantField
annotation:
import com.github.enablingflow.springbootdatamongomultitenant.MultiTenant;
import com.github.enablingflow.springbootdatamongomultitenant.MultiTenantField;
import org.springframework.data.mongodb.core.mapping.Document;
import org.springframework.data.annotation.Id;
import org.bson.types.ObjectId;
@Document
@MultiTenant
class Board {
@Id
private String id;
private String name;
private String description;
@MultiTenantField("tenantId")
private String tenantId;
// ...
}
another example with @DocumentReference
:
@Document("teams")
@MultiTenant
public class Team {
@Id
ObjectId id;
@DocumentReference
@MultiTenantField("organization.id")
Organization organization;
// ...
}
Now, we could use the BoardRepository
as a normal repository and when fetching data, the tenantId
field will be used to filter the data:
@DataMongoTest
@Testcontainers
public class MultiTenantTest {
@ServiceConnection
@Container
static MongoDBContainer mongoContainer = new MongoDBContainer("mongo:6.0.8");
@Autowired
private BoardRepository repository;
@Autowired
private MultiTenantContext multiTenantContext;
@BeforeEach
void setup() {
repository.save(new Board(null, "board 1", "", "tenant-A"));
repository.save(new Board(null, "board 2", "", "tenant-A"));
repository.save(new Board(null, "board 3", "", "tenant-B"));
repository.save(new Board(null, "board 4", "", "tenant-C"));
multiTenantContext.set("tenant-A");
}
@AfterEach
void clean() {
multiTenantContext.performAsRoot(() -> repository.deleteAll());
}
@Test
void receivesOnlyTenantA() {
var users = repository.findAll();
assert users.size() == 2;
}
@Test
void receivesOnlyTenantA() {
multiTenantContext.performAsRoot(() -> {
var users = repository.findAll();
assert users.size() == 4;
});
var users = multiTenantContext.performAsRoot(() -> repository.findAll());
assert users.size() == 4;
}
@Test
void receivesOnlyTenantA() {
multiTenantContext.performAsTenant("tenant-B", () -> {
var users = repository.findAll();
assert users.size() == 1;
});
var users = multiTenantContext.performAsTenant("tenant-B", () -> repository.findAll());
assert users.size() == 1;
}
}
In case of an Entity annotated with @MultiTenant
and @Document
without a field with @MultiTenantField
, it will throw a run time exception.
See MultiTenantTest for more examples.