mongodb multi tenacy spel with @Document

2020-02-29 04:09发布

问题:

This is related to MongoDB and SpEL Expressions in @Document annotations

This is the way I am creating my mongo template

@Bean
public MongoDbFactory mongoDbFactory() throws UnknownHostException {
    String dbname = getCustid();
    return new SimpleMongoDbFactory(new MongoClient("localhost"), "mydb");
}

@Bean
MongoTemplate mongoTemplate() throws UnknownHostException {
    MappingMongoConverter converter = 
            new MappingMongoConverter(mongoDbFactory(), new MongoMappingContext());
    return new MongoTemplate(mongoDbFactory(), converter);
}

I have a tenant provider class

@Component("tenantProvider")
public class TenantProvider {

    public String getTenantId() {
      --custome Thread local logic for getting a name
    }
}

And my domain class

    @Document(collection = "#{@tenantProvider.getTenantId()}_device")
     public class Device {
    -- my fields here
    }

As you see I have created my mongotemplate as specified in the post, but I still get the below error

Exception in thread "main" org.springframework.expression.spel.SpelEvaluationException: EL1057E:(pos 1): No bean resolver registered in the context to resolve access to bean 'tenantProvider'

What am I doing wrong?

回答1:

Finally figured out why i was getting this issue.

When using Servlet 3 initialization make sure that you add the application context to the mongo context as follows

    @Autowired
private ApplicationContext appContext;

public MongoDbFactory mongoDbFactory() throws UnknownHostException {
    return new SimpleMongoDbFactory(new MongoClient("localhost"), "apollo-mongodb");
}

@Bean
MongoTemplate mongoTemplate() throws UnknownHostException {
    final MongoDbFactory factory = mongoDbFactory();

    final MongoMappingContext mongoMappingContext = new MongoMappingContext();
    mongoMappingContext.setApplicationContext(appContext);

    // Learned from web, prevents Spring from including the _class attribute
    final MappingMongoConverter converter = new MappingMongoConverter(factory, mongoMappingContext);
    converter.setTypeMapper(new DefaultMongoTypeMapper(null));

    return new MongoTemplate(factory, converter);
}

Check the autowiring of the context and also mongoMappingContext.setApplicationContext(appContext);

With these two lines i was able to get the component wired correctly to use it in multi tenant mode



回答2:

If you have the mongoTemplate configured as in the related issue, the only thing i can think of is this:

<context:component-scan base-package="com.tenantprovider.package" />

Or if you want to use annotations:

@ComponentScan(basePackages = "com.tenantprovider.package")

You might not be scanning the tenant provider package.

Ex:

@ComponentScan(basePackages = "com.tenantprovider.package")
@Document(collection = "#{@tenantProvider.getTenantId()}_device")
public class Device {
    -- my fields here
}


回答3:

The above answers just worked partially in my case.

I've been struggling with the same problem and finally realized that under some runtime execution path (when RepositoryFactorySupport relies on AbstractMongoQuery to query MongoDB, instead of SimpleMongoRepository which as far as I know is used in "out of the box" methods provided by SpringData) the metadata object of type MongoEntityMetadata that belongs to MongoQueryMethod used in AbstractMongoQuery is updated only once, in a method named getEntityInformation()

Because MongoQueryMethod object that holds a reference to this 'stateful' bean seems to be pooled/cached by infrastructure code @Document annotations with Spel not always work.

As far as I know as a developer we just have one choice, use MongoOperations directly from your @Repository bean in order to be able to specify the right collection name evaluated at runtime with Spel.

I've tried to use AOP in order to modify this behaviour, by setting a null collection name in MongoEntityMetadata but this does not help because changes in AbstractMongoQuery inner classes, that implement Execution interface, would also need to be done in order to check if MongoEntityMetadata collection name is null and therefore use a different MongoTemplate method signature. MongoTemplate is smart enough to guess the right collection name by using its private method

private <T> String determineEntityCollectionName(T obj)

I've a created a ticket in spring's jira https://jira.spring.io/browse/DATAMONGO-1043