Why isn't my spring boot (mongo) bean being created / used? - java

I'm trying to use SpringBoot to talk to a Mongo database.
It is working using spring-boot-starter-data-mongodb and auto configuring a default bean which does allow my MongoRepository classes to talk to the DB ok.
However, I want to override the defaults. I could use application.properties but I need to be able to pass the connection parameters as options on the command line as the application starts up.
I've tried changing the port to break it, I've added debug to the Mongo config and it seems whatever I do the default spring config is being used regardless. It's as if the #Configuration annotation is ignored.
I've tried various flavours of configuring the main application class (specifying conf location, adding #Configuration to main class, with and without #SpringBootApplication ...), but here is where I am at the moment....
package somepackage
#EnableAutoConfiguration
#ComponentScan
public class MyApplication {
public static void main(String[] args) {
ApplicationContext ctx = SpringApplication.run(MyApplication.class, args);
....
}
package somepackage.conf; // should be picked up by ComponentScan, no?
#Configuration
public class MongoConf {
#Bean
public MongoClientFactoryBean mongo() throws Exception {
MongoClientFactoryBean mongo = new MongoClientFactoryBean();
/*
setting to silly values to try to prove it is trying to create connections using this bean - expected to see errors because can't create connection... */
mongo.setHost("flibble");
mongo.setPort(345);
return mongo;
}
}

You should actually use built in Spring Boot MongoDb Starter features and related auto configuration through application properties. Custom host, port, passwords etc. can and should be set via dedicated Spring Boot MongoDB Properties:
spring.data.mongodb.authentication-database= # Authentication database name.
spring.data.mongodb.database=test # Database name.
spring.data.mongodb.field-naming-strategy= # Fully qualified name of the FieldNamingStrategy to use.
spring.data.mongodb.grid-fs-database= # GridFS database name.
spring.data.mongodb.host=localhost # Mongo server host.
spring.data.mongodb.password= # Login password of the mongo server.
spring.data.mongodb.port=27017 # Mongo server port.
spring.data.mongodb.repositories.enabled=true # Enable Mongo repositories.
spring.data.mongodb.uri=mongodb://localhost/test # Mongo database URI. When set, host and port are ignored.
spring.data.mongodb.username= # Login user of the mongo server.
And link to the full list of supported properties is here.

In addition to RafalG's suggestion about MongoProperties, I combined that with the ApplicationArguments class and now I'm getting somewhere....
#Bean
#Primary
public MongoProperties mongoProperties(ApplicationArguments args) {
MongoProperties props = new MongoProperties();
String[] mongoHostAndPort = args.getSourceArgs()[3].split(":");
props.setHost(mongoHostAndPort[0]);
props.setPort(Integer.parseInt(mongoHostAndPort[1]));
return props;
}
#Bean
public MongoClientFactoryBean mongo() {
return new MongoClientFactoryBean();
}
Of course there's lots of error handling to add (nulls, non-ints etc) but hopefully if may help someone else.

#Configuration
#EnableAutoConfiguration(exclude = { EmbeddedMongoAutoConfiguration.class })
#Profile("!testing")
public class TestMongoConfig extends AbstractMongoConfiguration {
private static final MongodStarter starter = MongodStarter.getDefaultInstance();
private MongodExecutable _mongodExe;
private MongodProcess _mongod;
private MongoClient _mongo;
#Value("${spring.data.mongodb.host}")
private String host;
#Value("${spring.data.mongodb.port}")
private Integer port;
#Override
protected String getDatabaseName() {
return "test";
}
#Bean
public Mongo mongo() throws Exception {
_mongodExe = starter.prepare(new MongodConfigBuilder()
.version(Version.Main.PRODUCTION)
.net(new Net(port, Network.localhostIsIPv6()))
.build());
_mongod = _mongodExe.start();
return new MongoClient(host, port);
}
#Override
public String getMappingBasePackage() {
return "com.test.domain";
}

Related

CloudException: No suitable cloud connector found

My project has dependencies on spring-boot-starter-cloud-connectors and spring-cloud-localconfig-connector. Here is my code:
#Configuration
class MyConfig {
#Bean
public CloudFactory cloudFactory() {
CloudFactory cf = new CloudFactory();
cf.registerCloudConnector(new LocalConfigConnector());
return cf;
}
}
#Component
class MyComponent {
#Autowired
CloudFactory cf;
#EventListener(value = ApplicationStartedEvent.class)
public void postConstruct() {
Cloud cloud = cf.getCloud();
}
}
When I try to run the above code locally, I get an exception saying:
org.springframework.cloud.CloudException: No suitable cloud connector found
Although, the parent's version is irrelevant but I am using 2.1.8.RELEASE.
Can someone point out what's wrong with the above code?
You have to add the default datasource:
mysql://user:password#localhost:3306/databasename
You can set it using the run configuration or create a seperate properties file for it.

How to pass arguments to Spring Repository when using it with a Spring Application or while testing

I've created a Spring repository, that allows the user of this repository to add and remove from a MySQL table. Now to be able to use this repository, the application/tester would need to pass in the datasource variables (viz URL of the database, usermame, and password).
I tried having arguments to my constructor for the repository, but it seems that that is not allowed.
From what I've read online, one way is to use #Value annotation, and use that to pass it in. However, does this also work if the applciation.properties is not in the same file as the repository? By that I mean, like, the person making the application would put the application.properties file in the directory of the application right? And not in the directory of the repository? How do I do it in that case?
Any help is appreciated, thank you
Here is the code for my repository right now:
#Repository
public class SigningKeyDao implements IDao<SigningKeyModel> {
private DataSource dataSource;
private JdbcTemplate jdbcTemplate;
SigningKeyRowMapper wrapper = new SigningKeyRowMapper();
private String tableName = "signingKey";
public SigningKeyDao(String url, String username, String password) {
super();
this.dataSource =
DataSourceBuilder.create().url(url).username(username).password(password).build();
}
#PostConstruct
private void postConstruct() {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
}
And this is how I am using this repository in my test class
#EnableJpaRepositories("com.supertokens.repository")
#SpringBootTest(classes = SigningKeyDao.class)
#EntityScan("com.supertokens.model")
public class SigningKeyTableTest {
SigningKeyDao dao;
public SigningKeyTableTest() {
this.dao =
new SigningKeyDao("jdbc:mysql://localhost:3306/st_maven", "root", <password>);
}
}
To set an application property to a certain value in a particular test, have a look at the #TestPropertySource annotation which allows you to change the value of one or more properties from the application.properties file for the scope of the annotated test-class.
Example:
#EnableJpaRepositories("com.supertokens.repository")
#SpringBootTest(classes = SigningKeyDao.class)
#EntityScan("com.supertokens.model")
#TestPropertySource(properties = {
"spring.datasource.url=custom_url_just_for_this_test",
"spring.datasource.username=user",
"spring.datasource.password=secret"
})
public class SigningKeyTableTest {

Dynamically add property sources to SpringBootTest

Similar to Springboot unit test set #Configuration Properties dynamically but the context is different.
In my case I have a TestContainer running a custom MySQL database that is prepopulated with a lot of data (not using the SQL batch loading approach because the data is an anonymized copy of production and doing it through SQLs makes the boot up time of the container 20 minutes vs 2 minutes).
So far my test looks like this
#RunWith(SpringRunner.class)
#SpringBootTest(
classes = {
Bootstrap.class
}
)
public class ITFakeDB {
#ClassRule
public static final GenericContainer DB = new GenericContainer("devdb")
.withExposedPorts(3306);
#Autowired
private DataSource dataSource;
#Autowired
private Users users;
#Test
public void testDatabaseIsUp() {
assertTrue(DB.getMappedPort(3306) != 0);
}
#Test
public void testUser() {
Optional<User> user = users.findByLoginName("mimi");
assertTrue(users.isPresent());
}
}
What I want to do is somehow set the spring.datasource.url (or in my case datasources.schema1.url because I did the routing datasource) to the one used by DB
You can manually override the property from within your Spring-boot test by using ContextConfiguration and ApplicationContextInitializer.
Override the property - define a static inner class:
static class Initializer implements ApplicationContextInitializer<ConfigurableApplicationContext> {
#Override
public void initialize(ConfigurableApplicationContext configurableApplicationContext) {
String url = "jdbc:mysql://" + DB.getContainerIpAddress() + ":" + DB.getMappedPort(3306) + "/my_db";
TestPropertyValues
.of("datasources.schema1.url=" + url)
.applyTo(configurableApplicationContext.getEnvironment());
}
}
Note: I have assumed that the url is derived from the ip address, port and db name. You may change that part as needed but the core idea remains.
ApplicationContextInitializer can be used for programmatically initializing a Spring context before context refresh. Now, wire up the context initializer class by annotating at test class level with ContextConfiguration:
#ContextConfiguration(initializers = Initializer.class)
Docs:
ApplicationContextInitializer
ContextConfiguration
While the previous answer should work, Spring Framework 5.2.5 (that is included into Spring Boot 2.2.6) has introduced a new #DynamicPropertySource annotation exactly for that case:
#DynamicPropertySource
static void initializeDatasource(DynamicPropertyRegistry registry) {
String ip = DB.getContainerIpAddress();
Integer port = DB.getMappedPort(3306);
String url = String.format("jdbc:mysql://%s:%d/my_db", ip, port);
registry.add("datasources.schema1.url", url);
}
See for details:
Blog: #DynamicPropertySource in Spring Framework 5.2.5 and Spring Boot 2.2.6
Documentation: Context Configuration with Dynamic Property Sources

spring boot entityManagerFactory initialization after login

I develop a JavaFX application with spring boot. This application uses local database (H2), however the database connection properties like database name, username, password are entered by the user at the login screen.
But spring boot initializes all beans defined in AppConfig.java, and i'm not able to initialize javafx scene to show the UI.
Putting exclude to #EnableAutoConfiguration annotation doesn't help.
#SpringBootApplication
#EnableAutoConfiguration(exclude= {DataSourceAutoConfiguration.class, DataSourceTransactionManagerAutoConfiguration.class, HibernateJpaAutoConfiguration.class})
public class MyApplication extends Application {
spring boot still tries to connect to the database.
Of course once the user enters db name, username/password, spring boot should initialize database related beans or somehow to force spring boot for the initialization, because later on they're required for the application functionality.
Thanks for any hint!
You could try to start the login screen before spring boot and then set the entered values as system properties. And after that start Spring Boot:
public static void main(String[] args) {
// Start login screen
// For all properties the user entered
System.setProperty("spring.database.xy", "<value from UI>");
SpringApplication.run(Application.class, args);
}
You may provide your own DataSource bean using a wrapper which initialize datasource only when connection is needed.
Your DataSource wrapper may provide setUsername() and setPassword() you can use on your login attempt.
I didn't tried this out, but let me know if there's something wrong.
public class AppDataSourceWrapper implements DataSource {
private final DataSourceProperties dataSourceProperties;
private String username;
private String password;
private DataSource dataSource;
public AppDataSourceWrapper(DataSourceProperties dataSourceProperties) {
this.dataSourceProperties = dataSourceProperties;
}
public void setUsername(String username) {
this.username = username;
}
public void setPassword(String password) {
this.password = password;
}
private DataSource getDataSource() {
if (dataSource == null) {
initDataSource();
}
return dataSource;
}
private void initDataSource() {
this.dataSource = dataSourceProperties.initializeDataSourceBuilder()
.username(username)
.password(password)
.build();
}
#Override
public Connection getConnection() throws SQLException {
return getDataSource().getConnection();
}
// other override methods omitted for brevity
}
Add to your config:
#Bean
public DataSource dataSource(DataSourceProperties dataSourceProperties) {
return new AppDataSourceWrapper(dataSourceProperties);
}
On user access request you can do something like:
AppDataSourceWrapper dsWrapper = (AppDataSourceWrapper) dataSource;
dsWrapper.setUsername(username);
dsWrapper.setPassword(password);
Then DataSource should be initialized on your first connection attempt.
UPDATE
Spring Boot JPA autoconfiguration tries to connect to the database during beans initialization (i.e. JpaVendorAdapter bean in JpaBaseConfiguration), thus providing a data source wrapper won't do the job.
Possible solution can be found here.
Sounds to me like you want a sort of dynamic/lazy datasource bean.
You could try to accomplish this by creating a custom RoutingDatasource implementation extending org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource.
You could search on SessionRoutingDataSource, DynamicRoutingDatasource or MultitenantDatasource if you want some examples.
You could try working with some sort of NoopDatasource in your RoutingDatasource for scenario's where you need a datasource to be there but the user has not yet provided the creds.
I would suggest avoiding spring boot starter JPA and datasource properties for initialization.
Keep the properties for datasource but name it to something that spring boot doesn't look for.Then I would initialize datasource at login by creating DataSource object.
Another option would be to define a dataSource bean and mark it lazy.

cant config correct mongo db name in spring

Im currently diving in MongoDB and Spring.
Although another db is configured, it still tries to create/read from the wrong db.
Here's my code:
#Configuration
#EnableMongoRepositories
public class MongoConfig {
#Bean
public MongoClientFactoryBean mongo() {
MongoClientFactoryBean mongo = new MongoClientFactoryBean();
mongo.setHost("localhost");
return mongo;
}
#Bean
public MongoOperations mongoTemplate(Mongo mongo) {
return new MongoTemplate(mongo, "gabble");
}
}
Handler:
#Component
public class SomeHandler {
private static final Logger log = Logger.getLogger(SomeHandler.class);
private MongoOperations mongo;
#Autowired
public SomeHandler(MongoOperations mongo) {
this.mongo = mongo;
}
public void registerNewUser(User user, Credential credential) {
log.info(mongo.getCollectionNames());
mongo.save(user,"user");
mongo.save(credential,"credential");
log.info("count: "+mongo.getCollection("user").count());
log.info("content: "+mongo.getCollection("user").find());
log.info("stored new user in database");
}
}
the output of log.info():
2016-08-03 14:46:13 INFO SomeHandler:29 - count: 1
2016-08-03 14:46:13 INFO SomeHandler:30 - content: Cursor id=0, ns=test.user, query={ }, numIterated=0, readPreference=primary
As you can see, mongo object refers to test.user, but why ?
Is there more configuration needed ? The db test gets also created by spring.
Since you are using spring boot, you can just use its auto-configuration capabilities - add the following line to the application.properties file:
spring.data.mongodb.uri=mongodb://localhost/gabble
and remove the MongoConfig class entirely.

Categories