How to skip an #Transactional annotation based on flag - java

I am using #Transactional annotation in my code having database as MongoDB. But the same code is throwing error with CosmosDB if I'm using #Transactional annotation on method. Now I want #Transactional annotation to be ignored for cosmosDB but want that annotation to be executed for MongoDB based on certain flag as I'm maintaining the same codebase for both. Is there any way to ignore the annotation?
#Transactional(if flag true) //should enable only when flag is true
public Customer updateCustomer(Customer customer){
//code
}

#Transactional(prefix = "boom", name = "listener", havingValue = "MongoDB")
public Customer updateCustomer(Customer customer){
//code
}
boom:
listener: MongoDB
try this!when the 'boom.listener' do not equals 'MongoDB',the annotation will be ignore

I was able to skip transactional use case for cosmos by using one property in properties file:
activedb: 'cosmos'(or mongo in case of MongoDB)
In addition to this I have added one config file
#ConditionalOnProperty(name = "activedb",havingValue = "mongo")
#Configuration
#EnableTransactionManagement
public class TransactionManagementConfig {
#Bean
MongoTransactionManager transactionManager(MongoDatabaseFactory dbFactory) {
return new MongoTransactionManager(dbFactory);
}
}
As we have added #ConditionalOnProperty annotation so this class will be applicable for mongo only and as we are using #EnableTransactionManagement in this class so #Transactional annotation will be application for MongoDB only.

Related

#AliasFor doesn't work on attribute in custom annotation

I'm using SpringBoot 2.4.2. And I'm struggling with #AliasFor with custom annotation.
I implemented below custom annotation.
#Target({ElementType.TYPE})
#Retention(RetentionPolicy.RUNTIME)
#Documented
public #interface CustomAnnotation {
#AliasFor("aliasAttribute")
String value() default "";
#AliasFor("value")
String aliasAttribute() "";
}
And using it like this.
#CustomAnnoatation("test")
#Component
public class TestClass() {
// codes here
}
And this test code fails.
#SpringBootTest(classes = TestClass.class)
public class CustomAnnotationTest {
#Autowired
TestClass testClass;
#Test
public void valueTest1() {
Annotation annotation = testClass.getClass().getAnnotation(CustomAnnotation.class);
assertThat(((CustomAnnotation) annotation).value()).isEqualTo(((CustomAnnotation) annotation).aliasAttribute());
}
}
with message
org.opentest4j.AssertionFailedError:
Expecting:
<"">
to be equal to:
<"test">
I don't know why, anyone know?
An annotation is static metadata for a class, field etc. so Spring cannot change anything about it. To make features as #AliasFor possible Spring uses, what they call, synthesized annotations. For those to be used/detected you need to utilize the Spring internals to obtain that synthesized annotation and for the #AliasFor to work. For this use AnnotationUtils.findAnnotation (Spring uses that internally as well).
#AliasFor is a Spring feature so without using the Spring components this won't work.
Your test method is basically the same as
#Test
public void valueTest1() {
Annotation annotation = TestClass.class.getAnnotation(CustomAnnotation.class);
assertThat(((CustomAnnotation) annotation).value()).isEqualTo(((CustomAnnotation) annotation).aliasAttribute());
}
Both this test and your test will fail, because they simply don't use the Spring infrastructure for detecting annotations and apply the features of Spring.
When using AnnotationUtils.findAnnotation the test will pass.
class CustomAnnotationTest {
#Test
void testStandardJava() {
CustomAnnotation annotation = TestClass.class.getAnnotation(CustomAnnotation.class);
assertThat(annotation.value()).isEqualTo(annotation.aliasAttribute());
}
#Test
void testWithSpring() {
CustomAnnotation annotation = AnnotationUtils.findAnnotation(TestClass.class, CustomAnnotation.class);
assertThat(annotation.value()).isEqualTo(annotation.aliasAttribute());
}
}
The testStandardJava will fail, the testWithSpring will pass because it uses the proper mechanisms.

Spring Multi datasource with similar schema

I have 4 databases with similar schema on PostgreSQL
My current code is like this
ressources
spring.datasource.url=jdbc:postgresql://localhost:5432/postgres
spring.datasource.username=postgres
spring.datasource.password=postgres
DAO
public interface AccountRepository extends JpaRepository<Account, Long>{}
Configuration
#Configuration
public class AccountServiceConfiguration {
#Autowired
private AccountRepository accountRepository;
#Bean
public AccountService accountService() {
return new AccountService(accountRepository);
}
}
Controller
#RestController
#RequestMapping("/accounts")
public class AccountController {
#Autowired
private AccountService accountService;
#RequestMapping(name = "/", method = RequestMethod.GET)
public Page<Account> getAccounts(Integer page, Integer size) {
return accountService.getAll(page, size);
}
}
Service
public class AccounttService {
public AccounttService(AccountRepository accountRepository) {
this.accountRepository = accountRepository;
}
public Page<Account> getAll(Integer page, Integer size) {
PageRequest pageRequest = new PageRequest(page, size);
return accountRepository.findAll(pageRequest);
}
}
I want to change like this
ressources
spring.db1.url=jdbc:postgresql://db1:5432/postgres
spring.db1.username=postgres1
spring.db1.password=postgres1
spring.db2.url=jdbc:postgresql://db2:5432/postgres
spring.db2.username=postgres2
spring.db2.password=postgres2
spring.db3.url=jdbc:postgresql://db3:5432/postgres
spring.db3.username=postgres3
spring.db3.password=postgres3
spring.db4.url=jdbc:postgresql://db4:5432/postgres
spring.db4.username=postgres4
spring.db4.password=postgres4
Controller
...
public Page<Account> getAccounts(Integer page, Integer size, string env) {
return accountService.getAll(page, size, env);
}
...
Service
public class AccounttService {
public AccounttService(Map<AccountRepository> mapAccountRepository) {
this.mapAccountRepository = mapAccountRepository;
}
public Page<Account> getAll(Integer page, Integer size, String env) {
PageRequest pageRequest = new PageRequest(page, size);
// search in specific env
}
}
How to load 4 data sources (may be on map) and search by environnement !
If i send env=db1 i want to run my request on db1
If you have other solution, i take it, but must use one repository and one entity to search in all databases.
Thank you :)
According to your comments you want a single Repository instance to switch between different schemata.
This won't work.
What you can do is provide a Facade for multiple Repository instance that delegates each call to on of the many instances according to some parameter/field/property.
But one way or the other you have to create a separate Repository instance with a different database connection for each.
What you are describing is called multi-tenancy using multiple databases.
To accomplish so you would need to manually configure the persistence layer and not to rely completely in Spring Boot auto-configuration capabilities.
The persistence layer configuration involves:
Hibernate, JPA and datasources properties
Datasources beans
Entity manager factory bean (in the case of Hibernate, with properties specifying this is a mult-tenant entity manager factory bean and tenant connection provider as well as tenant resolver)
Transaction manager bean
Spring Data JPA and transaction support configuration
In a blog post I recently published: Multi-tenant applications using Spring Boot, JPA, Hibernate and Postgres I cover in this exact problem with a detailed implementation.

ACL security in Spring Boot

I am having issues setting up ACL through Java config in a Spring Boot application. I have created one small project to reproduce the issues.
I have tried a few different approaches. First issue I had was with EhCache, and after I fixed that (I assume I did) I couldn't login any more, and it looks like all the data is gone.
There are 4 classes with different configurations:
ACLConfig1.class
ACLConfig2.class
ACLConfig3.class
ACLConfig4.class
All #PreAuthorize and #PostAuthorize annotations are working as expected, except hasPermission.
Controller holds 4 endpoints: one for User, one for Admin, one Public and the last one which gives me headache #PostAuthorize("hasPermission(returnObject,'administration')")
I am pretty sure that inserts in DB are correct. This class is one of four, the last one that I have tried:
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class ACLConfig4 {
#Autowired
DataSource dataSource;
#Bean
public EhCacheBasedAclCache aclCache() {
return new EhCacheBasedAclCache(aclEhCacheFactoryBean().getObject(), permissionGrantingStrategy(), aclAuthorizationStrategy());
}
#Bean
public EhCacheFactoryBean aclEhCacheFactoryBean() {
EhCacheFactoryBean ehCacheFactoryBean = new EhCacheFactoryBean();
ehCacheFactoryBean.setCacheManager(aclCacheManager().getObject());
ehCacheFactoryBean.setCacheName("aclCache");
return ehCacheFactoryBean;
}
#Bean
public EhCacheManagerFactoryBean aclCacheManager() {
return new EhCacheManagerFactoryBean();
}
#Bean
public DefaultPermissionGrantingStrategy permissionGrantingStrategy() {
ConsoleAuditLogger consoleAuditLogger = new ConsoleAuditLogger();
return new DefaultPermissionGrantingStrategy(consoleAuditLogger);
}
#Bean
public AclAuthorizationStrategy aclAuthorizationStrategy() {
return new AclAuthorizationStrategyImpl(new SimpleGrantedAuthority("ROLE_ADMINISTRATOR"));
}
#Bean
public LookupStrategy lookupStrategy() {
return new BasicLookupStrategy(dataSource, aclCache(), aclAuthorizationStrategy(), new ConsoleAuditLogger());
}
#Bean
public JdbcMutableAclService aclService() {
JdbcMutableAclService service = new JdbcMutableAclService(dataSource, lookupStrategy(), aclCache());
return service;
}
#Bean
public DefaultMethodSecurityExpressionHandler defaultMethodSecurityExpressionHandler() {
return new DefaultMethodSecurityExpressionHandler();
}
#Bean
public MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler expressionHandler = defaultMethodSecurityExpressionHandler();
expressionHandler.setPermissionEvaluator(new AclPermissionEvaluator(aclService()));
expressionHandler.setPermissionCacheOptimizer(new AclPermissionCacheOptimizer(aclService()));
return expressionHandler;
}
}
What am I missing here? Why I have no data if I use ACLConfig3.class or
ACLConfig4.class. Is there any example on how this should be configured programmatically in Spring Boot?
The reason why you have no data was a bit tricky to find out. As soon as you define a MethodSecurityExpressionHandler bean in your config, there is no data in the database tables. This is because your data.sql file isn't executed.
Before explaining why data.sql isn't executed I'd first like to point out that you're not using the file as intended.
data.sql is executed by spring-boot after hibernate has been initialized and normally only contains DML statements. Your data.sql contains both DDL (schema) statements and DML (data) statements. This isn't ideal as some of your DDL statements clash with hibernate's hibernate.hbm2ddl.auto behaviour (note that spring-boot uses 'create-drop' when an embedded DataSource is being used). You should put your DDL statements in schema.sql and your DML statements in data.sql. As you're manually defining all tables you should disable hibernate.hbm2ddl.auto (by adding spring.jpa.hibernate.ddl-auto=none to applciation.properties).
That being said, let's take a look at why data.sql isn't executed.
The execution of data.sql is triggered via an ApplicationEvent that's fired via a BeanPostProcessor. This BeanPostProcessor (DataSourceInitializedPublisher) is created as a part of spring-boot's Hibernate/JPA auto configuration (see org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration, org.springframework.boot.autoconfigure.orm.jpa.DataSourceInitializedPublisher and org.springframework.boot.autoconfigure.jdbc.DataSourceInitializer).
Normally the DataSourceInitializedPublisher is created before the (embedded) DataSource is created and everything works as expected but by defining a custom MethodSecurityExpressionHandler the normal bean creation order alters.
As you've configured #EnableGlobalMethodSecurity, your're automatically importing GlobalMethodSecurityConfiguration.
spring-security related beans are created early on. As your MethodSecurityExpressionHandler requires a DataSource for the ACL stuff and the spring-security related beans require your custom MethodSecurityExpressionHandler, the DataSource is created earlier than usual; in fact it's created so early on that spring-boot's DataSourceInitializedPublisher isn't created yet.
The DataSourceInitializedPublisher is created later on but as it didn't notice the creation of a DataSource bean, it also doesn't trigger the execution of data.sql.
So long story short: the security configuration alters the normal bean creation order which results in data.sql not being loaded.
I guess that fixing the bean creation order would do the trick, but as I don't now how (without further experimentation) I propose the following solution: manually define your DataSource and take care of data initialization.
#Configuration
public class DataSourceConfig {
#Bean
public EmbeddedDatabase dataSource() {
return new EmbeddedDatabaseBuilder().setType(EmbeddedDatabaseType.H2)
//as your data.sql file contains both DDL & DML you might want to rename it (e.g. init.sql)
.addScript("classpath:/data.sql")
.build();
}
}
As your data.sql file contains all DDL required by your application you can disable hibernate.hbm2ddl.auto. Add spring.jpa.hibernate.ddl-auto=none to applciation.properties.
When defining your own DataSource spring-boot's DataSourceAutoConfiguration normally back's out but if you want to be sure you can also exclude it (optional).
#SpringBootConfiguration
#EnableAutoConfiguration(exclude = DataSourceAutoConfiguration.class)
#ComponentScan
#EnableCaching
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
This should fix your 'no data' problem. But in order to get everything working as expected you need to make 2 more modifications.
First of all, you should only define one MethodSecurityExpressionHandler bean. Currently you're defining 2 MethodSecurityExpressionHandler beans. Spring-security won't know which one to use and will (silently) use it's own internal MethodSecurityExpressionHandler instead. See org.springframework.security.config.annotation.method.configuration.GlobalMethodSecurityConfiguration#setMethodSecurityExpressionHandler.
#Configuration
#EnableGlobalMethodSecurity(prePostEnabled = true, securedEnabled = true)
public class MyACLConfig {
//...
#Bean
public MethodSecurityExpressionHandler createExpressionHandler() {
DefaultMethodSecurityExpressionHandler securityExpressionHandler = new DefaultMethodSecurityExpressionHandler();
securityExpressionHandler.setPermissionEvaluator(new AclPermissionEvaluator(aclService()));
securityExpressionHandler.setPermissionCacheOptimizer(new AclPermissionCacheOptimizer(aclService()));
return securityExpressionHandler;
}
}
The last thing you need to do is make the getId() method in Car public.
#Entity
public class Car {
//...
public long getId() {
return id;
}
//...
}
The standard ObjectIdentityRetrievalStrategy will look for a public method 'getId()' when trying to determine an object's identity during ACL permission evaluation.
(Note that I've based my answer upon ACLConfig4.)

Spring #Transactional on #Bean declaration instead of class Implementation

I'd like to configure "transactional" beans from my Spring #Configuration class instead of annotating the class implementation itself with #Transactional.
Kind of like the old school way, configuring transactional advice from an XML file, but without needing a String reference to my class/method names to create pointcuts.
The reason is that the bean implementation is in another code base, and the module it belongs to doesn't depend on Spring. Read : I'm not touching the source code of that bean, just instanciating it. The class is final, can't extend it either to add Spring annotations to the child class.
Let's say that all the methods must be transactional, for simplicity.
The bean implementation :
/** This class has no Spring dependency... */
// #Transactional <- which means I can't use this here
public final class ComplexComponentImpl implements ComplexComponent {
private SomeRepository repo;
public ComplexComponentImpl(SomeRepository repository) { this.repo = repository }
public void saveEntities(SomeEntity e1, SomeEntity e2) {
repo.save(e1);
throw new IllegalStateException("Make the transaction fail");
}
What I want to do in my configuration class (and which doesn't work in my unit test) :
#Configuration
#EnableTransactionManagement
public class ComplexComponentConfig {
#Bean
#Transactional // <- Make the bean transactional here
public ComplexComponent complexComponent() {
return new ComplexComponentImpl(repository());
}
// ...
}
The example above doesn't work, indeed, as nothing gets "transactional" at runtime : entity e1 is persisted even though the exception is thrown.
Note that my transaction management setup works works perfectly well with an implementation class marked with #Transactional.
Question : Is is it possible to declare #Beans transactional from a #Configuration class, or is there any alternative taking into accounts the constraints above ?
Found something built-in that is the sum of #Mecon's and #Erik Gillespie's answers, with limited boilerplate.
Spring already provides a TransactionProxyFactoryBean that just sets up a transactional proxy on any object. Much of the setup could be refactored to some utility method :
#Configuration
#EnableTransactionManagement
public class ComplexComponentConfig {
/** NOT A #Bean, this object will be wrapped with a transactional proxy */
public ComplexComponent complexComponentImpl() {
return new ComplexComponentImpl(repository());
}
#Bean
public ComplexComponent complexComponent() {
TransactionProxyFactoryBean proxy = new TransactionProxyFactoryBean();
// Inject transaction manager here
proxy.setTransactionManager(txManager());
// Define wich object instance is to be proxied (your bean)
proxy.setTarget(complexComponentImpl());
// Programmatically setup transaction attributes
Properties transactionAttributes = new Properties();
transactionAttributes.put("*", "PROPAGATION_REQUIRED");
proxy.setTransactionAttributes(transactionAttributes);
// Finish FactoryBean setup
proxy.afterPropertiesSet();
return (ComplexComponent) proxy.getObject;
}
// ...
}
I think you probably can't use #Transactional in that manner. One of spring's in-built PostProcessors, are supposed to scan all classes (beans) that have that annotation, and the create Aspects accordingly.
About alternatives: I would write an Adapter class for each 3rd party class I have to use. And then make those Adapter classes be Spring Beans.
You can't use #Transactional in that way but you can programmatically configure aspects with Spring.
Spring documentation for programmatically defining aspects:
http://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/aop.html#aop-aspectj-programmatic
http://docs.spring.io/spring/docs/3.0.x/spring-framework-reference/html/aop-api.html
The examples in the documentation are very simple. Defining transaction aspects will likely be more complicated and I wouldn't be surprised if you find it easier to just use the convenience of XML-based proxies or take #Mecon's advice and write adapters.
You can use spring's AOP capabilities to add the transaction interceptor to your bean. Just create an Advisor bean that specifies a pointcut and adds a TranscationInterceptor.
#Bean
public ComplexComponent complexComponentImpl() {
return new ComplexComponentImpl(repository());
}
#Bean
public Advisor advisorBean(TransactionManager txManager) {
Class<?> targetClass = ComplexComponent .class;
int propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRED;
return allMethodsTxAdvice(txManager, targetClass, propagationBehavior);
}
/**
* Extracted method for reuse.
*/
private DefaultPointcutAdvisor allMethodsTxAdvice(TransactionManager txManager, Class<?> targetClass, int propagationBehavior) {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
String pointcutExpression = MessageFormat.format("execution(* {0}.*(..)))", targetClass.getName());
pointcut.setExpression(pointcutExpression);
MatchAlwaysTransactionAttributeSource tas = new MatchAlwaysTransactionAttributeSource();
TransactionAttribute transactionAttribute = new DefaultTransactionAttribute(propagationBehavior);
tas.setTransactionAttribute(transactionAttribute);
TransactionInterceptor transactionInterceptor = new TransactionInterceptor(txManager, tas);
return new DefaultPointcutAdvisor(pointcut, transactionInterceptor);
}
PS: You don't need to call afterPropertiesSet just return the FactoryBean and spring will handle all lifecycle callbacks. E.g.
#Bean
public FactoryBean<Object> complexComponent(TransactionManager tx) {
TransactionProxyFactoryBean proxyFactory = new TransactionProxyFactoryBean();
proxyFactory.setTransactionManager(tx);
proxyFactory.setTarget(complexComponentImpl());
Properties transactionAttributes = new Properties();
transactionAttributes.put("*", "PROPAGATION_REQUIRED");
proxyFactory.setTransactionAttributes(transactionAttributes);
return proxyFactory;
}

How to set MongoDB ReadPreference in Spring MVC's contextConfigLocation

I am connecting to a MongoDB sharding server via mongodb java driver in Spring MVC. I am using the following versions:
spring-webmvc-3.2.1.RELEASE
mongo-java-driver/2.10.0/mongo-java-driver-2.10.0
spring-data-mongodb-1.2.0.RELEASE
My Mongo options are set in the contextConfigLocation file mvc-dispatcher-servlet.xml
<mongo:mongo host="mongo.sample.com" port="30000">
<mongo:options auto-connect-retry="true"
slave-ok="true"/>
</mongo:mongo>
It works pretty well, but the slave-ok is deprecated by come.MongoDB.ReadPreference. I just wonder if there is any way to set the readPreference for Spring MVC in the contextConfiLocation file.
Declare the following bean
<bean id="readPreferenceSecondary" class="com.mongodb.TaggableReadPreference.SecondaryReadPreference">
</bean>
and
you inject this in your mongotemplate
<bean id="mongoTemplateProdDb" class="org.springframework.data.mongodb.core.MongoTemplate" >
<property name="readPreference" ref="readPreferenceSecondary"></property>
</bean>
Expanding #Trisha's response in to an answer: "Do it in MongoTemplate programmatically" by setting the ReadPreference to SECONDARY.
MongoTemplate template = new MongoTemplate(...);
template.setReadPreference(com.mongodb.ReadPreference.SECONDARY);
In case you are using spring-data-mongodb and have some requirement to use multiple Read Preferences based on find query, you can create multiple Mongo Templates and/or Repositories like
#EnableMongoRepositories(basePackages = {
"com.you.repo.package" }, mongoTemplateRef = "mongoTemplateOne")
#Configuration
public class MongoConfig {
#Bean(name="mongoTemplateOne")
public MongoTemplate getMongoTemplateOne() throws UnknownHostException {
MongoTemplate templateOne = new MongoTemplate(new SimpleMongoDbFactory(new MongoClientURI("YOUR_MONGO_URL")));
templateOne.setReadPreference(ReadPreference.secondaryPreferred());
//setting WriteConcern but not relevant for this thread
templateOne.setWriteConcernResolver(yourWriteConcernResolver());
return templateOne;
}
#Bean(name = "mongoTemplateTwo")
public MongoTemplate getMongoTemplateTwo() throws UnknownHostException {
MongoTemplate templateTwo = new MongoTemplate(new SimpleMongoDbFactory(new MongoClientURI("YOUR_MONGO_URL")));
templateTwo.setReadPreference(ReadPreference.secondaryPreferred());
return templateTwo;
}
private WriteConcernResolver yourWriteConcernResolver() {
return action -> {
if (action.getCollectionName()
.equals("your_collecton")
&& (action.getMongoActionOperation() == MongoActionOperation.SAVE
|| action.getMongoActionOperation() == MongoActionOperation.UPDATE)) {
return WriteConcern.MAJORITY;
}
return action.getDefaultWriteConcern();
};
}
In case you have more than one secondary (replica-set) you can be more specific and tell the mongo driver explicitly which of the secondaries you want to read from, using tags.
On the mongo side you run this command:
db.getMongo().setReadPref('secondaryPreferred',
[{"tagName":"TagVal1"},
{"tagName":"TagVal2"},
{}])
In the code it looks like this:
MongoTemplate template = new MongoTemplate(...)
template.setReadPreference(ReadPreference.secondaryPreferred("your DBObject that reflect your mongo tag names");
Hope it helps.
Here is one more way to do this using Mongo Repositories
#Configuration
#EnableMongoRepositories
class ApplicationConfig extends AbstractMongoClientConfiguration {
#Autowired
private Environment env;
#Value("${spring.data.mongodb.uri}")
public String mongoUri;
#Override
protected String getDatabaseName() {
return env.getProperty("spring.data.mongodb.database");
}
#Override
protected void configureClientSettings(MongoClientSettings.Builder builder) {
builder.applyConnectionString(new ConnectionString(mongoUri)).readPreference(ReadPreference.secondary());
}
}
sample app available #
https://github.com/prashanthmadi/cosmosdb-mongodb-readpreference-springboot-java/blob/main/src/main/java/azure/cosmosdb/mongodb/spring/cosmosdbmongodb/ApplicationConfig.java
If there is a need to mix between primary and secondary for reads depending on the collection, you can set the ReadPreference on the DBCollection object. This helps to avoid complex multiple MongoTemplate configuration. Instead, set collection level preference like below once in the application lifetime. All the reads for that specific collection will go to secondary, while for other collections it goes to primary.
DBCollection dbCollection = mongoTemplate.getCollection(mongoTemplate.getCollectionName(collection));
dbCollection.setReadPreference(ReadPreference.secondaryPreferred());
If you want to know different options to achieve it, please check Spring data mongodb secondary reads
As of spring-mongo-2.0.xsd, slave-ok has been entirely removed, but support has been added for XML config of ReadPreference. Here's the XML from the original question translated for the current XSD:
<mongo:mongo-client host="mongo.sample.com" port="30000">
<mongo:client-options read-preference="SECONDARY_PREFERRED" />
</mongo:mongo-client>

Categories

Resources