How To Configure MongoDb Collection Name For a Class in Spring Data
Solution 1
The only way you can currently achieve this is by annotating your domain class with @Document
using the collection
property to define the name of the collection instances of this class shall be persisted to.
However, there's a JIRA issue open that suggests adding a pluggable naming strategy to configure the ways class, collection and property names are handled in a more global way. Feel free to comment your use case and vote it up.
Solution 2
using answer from Oliver Gierke above, working on a project where I need to create multiple collections for one entity, I wanted to use the spring repositories and needed to specify the entity to use before using the repository.
I managed to modify the repository collection name on demand using this system, it using SPeL. You can only work on 1 collection at a time though.
Domain object
@Document(collection = "#{personRepository.getCollectionName()}")
public class Person{}
Default Spring Repository:
public interface PersonRepository
extends MongoRepository<Person, String>, PersonRepositoryCustom{
}
Custom Repository Interface:
public interface PersonRepositoryCustom {
String getCollectionName();
void setCollectionName(String collectionName);
}
implementation:
public class PersonRepositoryImpl implements PersonRepositoryCustom {
private static String collectionName = "Person";
@Override
public String getCollectionName() {
return collectionName;
}
@Override
public void setCollectionName(String collectionName) {
this.collectionName = collectionName;
}
}
To use it:
@Autowired
PersonRepository personRepository;
public void testRetrievePeopleFrom2SeparateCollectionsWithSpringRepo(){
List<Person> people = new ArrayList<>();
personRepository.setCollectionName("collectionA");
people.addAll(personRepository.findAll());
personDocumentRepository.setCollectionName("collectionB");
people.addAll(personRepository.findAll());
Assert.assertEquals(4, people.size());
}
Otherwise if you need to use configuration variables, you could maybe use something like this? source
@Value("#{systemProperties['pop3.port'] ?: 25}")
Solution 3
A little late, but I've found you can set the mongo collection name dynamically in spring-boot accessing the application configuration directly.
@Document(collection = "#{@environment.getProperty('configuration.property.key')}")
public class DomainModel {...}
I suspect you can set any annotation attribute this way.
Solution 4
The only comment I can add is that you have to add @ prefix to the bean name:
collection = "#{@beanName.method()}"
for the bean factory to inject the bean:
@Document(collection = "#{@configRepositoryCustom.getCollectionName()}")
public class Config {
}
I struggled to figure it out..
COMPLETE EXAMPLE:
@Document(collection = "#{@configRepositoryCustom.getCollectionName()}")
public class Config implements Serializable {
@Id
private String uuid;
private String profile;
private String domain;
private String label;
private Map<String, Object> data;
// get/set
}
public interface ConfigRepositoryCustom {
String getCollectionName();
void setCollectionName(String collectionName);
}
@Component("configRepositoryCustom")
public class ConfigRepositoryCustomImpl implements ConfigRepositoryCustom {
private static String collectionName = "config";
@Override
public String getCollectionName() {
return collectionName;
}
@Override
public void setCollectionName(String collectionName) {
this.collectionName = collectionName;
}
}
@Repository("configurations")
public interface ConfigurationRepository extends MongoRepository<Config, String>, ConfigRepositoryCustom {
public Optional<Config> findOneByUuid(String Uuid);
public Optional<Config> findOneByProfileAndDomain(String profile, String domain);
}
usage in serviceImpl:
@Service
public class ConfigrationServiceImpl implements ConfigrationService {
@Autowired
private ConfigRepositoryCustom configRepositoryCustom;
@Override
public Config create(Config configuration) {
configRepositoryCustom.setCollectionName( configuration.getDomain() ); // set the collection name that comes in my example in class member 'domain'
Config configDB = configurationRepository.save(configuration);
return configDB;
}
Solution 5
I use static class and method in SpEL;
public class CollectionNameHolder {
private static final ThreadLocal<String> collectionNameThreadLocal = new ThreadLocal<>();
public static String get(){
String collectionName = collectionNameThreadLocal.get();
if(collectionName == null){
collectionName = DataCenterApiConstant.APP_WECHAT_DOCTOR_PATIENT_COLLECTION_NAME;
collectionNameThreadLocal.set(collectionName);
}
return collectionName;
}
public static void set(String collectionName){
collectionNameThreadLocal.set(collectionName);
}
public static void reset(){
collectionNameThreadLocal.remove();
}
}
In Entity class ,@Document(collection = "#{T(com.test.data.CollectionNameHolder).get()}")
And then ,use
CollectionNameHolder.set("testx_"+pageNum)
in Service , and
CollectionNameHolder.reset();
Hope it helps you.
Danish
I design and write software which powers million dollar business, with the power to scale to billions!
Updated on October 14, 2020Comments
-
Danish over 3 years
I have a collection called
Products
in my MongoDB database, which is represented by the interfaceIProductPrice
in my Java code. The following repository declaration causes Spring Date to look to the collectiondb.collection: Intelliprice.iProductPrice
.I want it to configure it to look in
db.collection: Intelliprice.Products
using an external configuration rather than putting an@Collection(..)
annotation onIProductPrice
. Is this possible? How can I do this?public interface ProductsRepository extends MongoRepository<IProductPrice, String> { }
-
Danish over 11 yearsThanks, I'm aware of the @Document annotation and probably would end up using that. I basically wanted to externalize the config from the actual class. The JIRA issue you linked to is talking about a naming strategy and still suggests using the annotation for custom names.
-
norgence over 9 yearsThe
collection
attribute supports SpEL so that you can invoke arbitrary methods on other Spring beans to calculate the collection name by e.g using#{#bean.someMethod(T(your.fully.qualified.Type))}
if you have registered a component asbean
providing a methodsomeMethod(Class<?> type)
. -
alex about 8 yearshaven't tested this, and it's not very clean, but +1 just for being creative :)
-
thanosa75 about 8 yearsSeems that you're keeping "context" information within a repository that is potentially auto-wired in various places. My guess that this solution is not thread safe.
-
Jeremie about 8 years@thanosa75 you are right, I was just reusing that solution and thinking that having a repository where you always provide the collection name, would be much much safer: instead of repo.findAll() > repo.findAll("collectionName") . but I don't know how to do that elegantly (rather than recreate a class that reuses a mongo template, and always set the collection name before running the request)
-
thanosa75 almost 8 years@jeremie you could keep a ThreadLocal with this - so no context amongst threads- and wrap the call(s) with a helper that sets the local, something like: Query.for("person", x -> {}) where in the {} you would put the repo calls. The helper would set context, do the call and unset. To see this better in action you could read the Transaction annotation and helpers in Spring. Same concept.
-
thanosa75 almost 8 years@Jeremie I found the example I was thinking about here: doanduyhai.wordpress.com/2011/11/20/… and you need to check the actual code (look for "invoke" for the juicy bit that explains the aspect and how it works by encapsulating the invocation and setting the transaction). You could have a similar code, that uses a new annotation e.g. @WithCollection("Persons") to encapsulate method declarations and on invocation set the correct collection on the repository.
-
Jeremie almost 8 years@thanosa75 thank you for that :)! will have to try it out!
-
Rohitesh about 7 yearsThis is giving cirular dependency error and when i am removing #{notificationRepository.getCollectionName()} from the Person bean its getting resolved
-
Erdem Aydemir over 6 yearshow you can create a new collection in test class ? @Jeremie
-
Erdem Aydemir over 6 yearshow would the behavior of the application if the following scenario occur? => We will serve more than one application in practice. When inserting in a service, another serviced search query can be done. My question is, are the same repository of autowired 2 service. What happens if the repository "collection_a" is set when doing an insert, and if the "search" repository is set to "collection_b" before "insert" works?
-
Constantino Cronemberger almost 6 yearsIn the project I am working they have used this, but now it is not working anymore. The problem is that 'personRepository' is not defined by the time the expression is evaluated. I am not sure what is the cause of this problem, but I think it was working in a previous version of Spring (not sure if Boot, Data or what) and is not working anymore.
-
Jeremie almost 6 yearsvery late to answer to @forguta, I'm sorry. as I said it would work on 1 collection at a time, so might cause issues if doing parallel queries on 2 collections at a time. would need to set the collection before any query, and even though, if 2 are happening at the same time, might not work. This is just a hack really
-
Jeremie almost 6 yearssorry to hear that @ConstantinoCronemberger how did you find this out, you updated spring version and it stopped working?
-
Constantino Cronemberger almost 6 yearsMy problem seems to be in the MongoPersistentEntityIndexCreator which is trying to evaluate the expression before the application context is initialized.
-
Constantino Cronemberger almost 6 yearsFound my problem, it was a project issue because I was creating a custom MongoTemplate based on a brand new MongoMappingContext instead of using the provided MongoMappingContext.
-
FrVaBe about 4 yearsI also only manage to get this work with the '@' bean prefix. Not sure about that syntax as it is also not suggested in @Oliver Drotbohm|s mentioned Jira Issue where the solution is otherwise also documented.
-
FrVaBe about 4 years
-
Zon over 2 yearsThis doesn't work if you extend your document from parent interface. If this interface is declared in repository signature
ReactiveMongoRepository<iProductPrice, String>
, then even hardcoded collection name is ignored -@Document(collection = "specific_collection_name")
. Collection will beiproductprice
-
CᴴᴀZ over 2 years@Zon In that case you need to use set the collection name at base/parent class level using sepl approach. This solution is simple to employ.
-
Zon over 2 yearsI have tried SPEL, but then you will have one collection name for all the descendants. If you call some method or pass arguments to a prototype bean - anyway collection name is set only once when the bean is created. I have even tried setting annotation value dynamically - this hadn't helped either. The only option left - is to rewrite Spring Data repositories to MongoTemplate that allows passing collection name with queries.
-
iamfrank over 2 yearsFor some reason, this works for me but ONLY if the first letter of the bean name is lower case. So instead of
#{@ActualBeanName.method()}"
, it works only if I use#{@actualBeanName.method()}"