Spring boot Build issue - java

**Custom configuration for mail sender **
#Configuration
public class EmailConfig {
private EmailProperties emailProp;
#Autowired
ConstantRepository constantRepository;
public EmailConfig(EmailProperties emailProp) {
this.emailProp = emailProp;
}
#Bean
public JavaMailSender getJavaMailSender() {
Constants cons = constantRepository.findByConstantKeyAndStatus("DEFAULT_MAIL_ACCOUNT_CREDENTIAL",true);
String password = cons.getValue();
}
here I am trying to fetch the password from the database but the problem is while building the app it calls the repository which leads to failure as IP whitelisting issue it is getting error: unable to acquire JDBC connection.
How to stop these repo calls while building the app

You can use #Lazy annotation at the Configuration level. So that the beans will be created at runtime when requested for First-time.
#Configuration
#Lazy
public class EmailConfig {
private EmailProperties emailProp;
#Autowired
ConstantRepository constantRepository;
public EmailConfig(EmailProperties emailProp) {
this.emailProp = emailProp;
}
#Bean
public JavaMailSender getJavaMailSender() {
Constants cons = constantRepository.findByConstantKeyAndStatus("DEFAULT_MAIL_ACCOUNT_CREDENTIAL",true);
String password = cons.getValue();
}
Reference: https://www.baeldung.com/spring-lazy-annotation

Related

Spring boot jdbc data repository by multi data source

please help me.
I use multi data source in my project
data source properties:
spring.datasource.url=jdbc:sqlserver://localhost:1433;databaseName=db
spring.datasource.username=xxxxx
spring.datasource.password=xxxxx
spring.datasource.driverClassName=com.microsoft.sqlserver.jdbc.SQLServerDriver
spring.datasource2.url=jdbc:mysql://localhost:3306/db2
spring.datasource2.username=xxxx
spring.datasource2.password=xxx
spring.datasource2.driver-class-name=com.mysql.cj.jdbc.Driver
config class:
#Configuration
#EnableJdbcRepositories(
jdbcOperationsRef = "mysqlNamedParameterJdbcOperations",
basePackages = "com.example.demo.mysqlModels"
)
public class Config extends AbstractJdbcConfiguration {
#Bean("mysqlDataSource")
#ConfigurationProperties(prefix = "spring.datasource2")
public DataSource mysqlDataSource() {
return DataSourceBuilder.create()
.build();
}
#Bean(name = "mysqlNamedParameterJdbcOperations")
NamedParameterJdbcOperations mysqlNamedParameterJdbcOperations(#Qualifier("mysqlDataSource") DataSource mysqlDataSource) {
return new NamedParameterJdbcTemplate(mysqlDataSource);
}}
#Configuration
#EnableJdbcRepositories(
jdbcOperationsRef = "mssqlNamedParameterJdbcOperations",
basePackages = "com.example.demo.mssqlModels"
)
public class Config2 extends AbstractJdbcConfiguration {
#Bean("mssqlDataSource")
#ConfigurationProperties(prefix = "spring.datasource")
public DataSource mssqlDataSource() {
return DataSourceBuilder.create()
.build();
}
#Bean(name = "mssqlNamedParameterJdbcOperations")
NamedParameterJdbcOperations mssqlNamedParameterJdbcOperations(#Qualifier("mssqlDataSource") DataSource mssqlDataSource) {
return new NamedParameterJdbcTemplate(mssqlDataSource);
}}
repository in com.example.demo.mssqlModels:
public interface MssqlRepository extends PagingAndSortingRepository<MyEntity, Integer> {}
repository in com.example.demo.mysqlModels:
public interface MysqlRepository extends PagingAndSortingRepository<MyEntity, Integer> {}
my service:
#Slf4j
#Service
public class MyService {
#Autowired
private final MssqlRepository mssqlRepository;
#Autowired
private final MysqlRepository mysqlRepository;
#PostConstruct
public void init() {
log.info("mssql result {}", mssqlRepository.findAll());
log.info("mysql result {}", mysqlRepository.findAll());
}}
but result is same and both repositories read data from mysql datasource
thanks
You might be interested in looking at my question I raised recently here regarding 2 data sources but each applied to a different repository.
In your configuration classes you should also create two TransactionManagers with unique names.
In each repository annotate it with #Transactional(transactionManager = 'transaction manager name') replacing the transaction name with the appropriate name
You'll probably need to override the default methods such as saveAll() with the same annotation as in (2).
However as per my question I found that an incorrect data source is sometimes used (I've since found that having the Postgres classes as the primary resolved my problem but I don't know why this worked)

Spring Data Cassandra config change on the fly

I am trying to do a similar thing with my application. I am using following versions of Spring boot and Cassandra:
spring-data-cassandra - 2.0.8.RELEASE
spring-boot-starter-parent - 2.0.4.RELEASE
I need to change some properties(mostly hostnames) of Cassandra on the fly and want it to make a new connection with the application. For config change we have internal Cloud Config Change Management and it runs fine on changes and listens to it.
This is my class :
#Configuration
#Order(Ordered.HIGHEST_PRECEDENCE)
#RefreshScope
#EnableCassandraRepositories(basePackages = {"com.*.*.*.dao.repo"})
public class AppConfig {
private static final Logger LOGGER = LoggerFactory.getLogger(AppConfig.class);
#Value("${application['cassandraPort']}")
private String cassandraPort;
#Value("${application['cassandraEndpoint']}")
private String cassandraEndpoint;
#Value("${application['keyspaceName']}")
private String keyspaceName;
#Value("${application['cassandraConsistency']}")
private String cassandraConsistency;
#Value("${application['cassandraUserName']}")
private String cassandraUserName;
#Autowired
private AppConfig appConfig;
public AppConfig() {
System.out.println("AppConfig Constructor");
}
public String getCassandraPort() {
return cassandraPort;
}
public void setCassandraPort(String cassandraPort) {
this.cassandraPort = cassandraPort;
}
public String getCassandraEndpoint() {
return cassandraEndpoint;
}
public void setCassandraEndpoint(String cassandraEndpoint) {
this.cassandraEndpoint = cassandraEndpoint;
}
public String getKeyspaceName() {
return keyspaceName;
}
public void setKeyspaceName(String keyspaceName) {
this.keyspaceName = keyspaceName;
}
public String getCassandraConsistency() {
return cassandraConsistency;
}
public void setCassandraConsistency(String cassandraConsistency) {
this.cassandraConsistency = cassandraConsistency;
}
public String getCassandraUserName() {
return cassandraUserName;
}
public void setCassandraUserName(String cassandraUserName) {
this.cassandraUserName = cassandraUserName;
}
#Bean
// #RefreshScope
public CassandraConverter converter() {
return new MappingCassandraConverter(this.mappingContext());
}
#Bean
// #RefreshScope
public CassandraMappingContext mappingContext() {
return new CassandraMappingContext();
}
#Bean
//#RefreshScope
public CassandraSessionFactoryBean session() {
CassandraSessionFactoryBean session = new CassandraSessionFactoryBean();
session.setCluster(this.cluster().getObject());
session.setKeyspaceName(appConfig.getKeyspaceName());
session.setConverter(this.converter());
session.setSchemaAction(SchemaAction.NONE);
return session;
}
#Bean
//#RefreshScope
public CassandraClusterFactoryBean cluster() {
CassandraClusterFactoryBean cluster = new CassandraClusterFactoryBean();
cluster.setContactPoints(appConfig.getCassandraEndpoint());
cluster.setPort(Integer.valueOf(appConfig.getCassandraPort()));
cluster.setUsername(appConfig.getCassandraUserName());
cluster.setPassword("password");
cluster.setQueryOptions(new QueryOptions().setConsistencyLevel(ConsistencyLevel.LOCAL_QUORUM));
return cluster;
}
}
However, when I try to use #RefreshScope with that Configuration class, the application fails to start. This is what it shows in console :
***************************
APPLICATION FAILED TO START
***************************
Description:
Parameter 2 of constructor in org.springframework.boot.autoconfigure.data.cassandra.CassandraDataAutoConfiguration required a bean of type 'com.datastax.driver.core.Cluster' that could not be found.
- Bean method 'cassandraCluster' not loaded because auto-configuration 'CassandraAutoConfiguration' was excluded
Action:
Consider revisiting the entries above or defining a bean of type 'com.datastax.driver.core.Cluster' in your configuration.
Is there some guidelines on using #RefreshScope with Cassandra Bean? If anyone has done that earlier can you share the same?
You're mixing a couple of things here.
The config carries properties and bean definitions.
#RefreshScope on AppConfig causes some interference with Spring Boot's auto-configuration and the declared beans aren't used (that's why you see Parameter 2 of constructor…).
To clean up, we will reuse what Spring Boot provides as much as possible, and only declare what's really needed.
Follow these steps to solve the issue (based on your code above):
Create a #ConfigurationProperties bean that encapsulates your properties, or better, reuse CassandraProperties.
Re-enable CassandraAutoConfiguration and remove your own MappingContext and CassandraConverter beans, keep only Cluster and Session bean definitions
Declare Cluster and Session beans as needed and make them use #RefreshScope. Your #Configuration class should look like:
Example Configuration:
#Configuration
public class MyConfig {
#Bean(destroyMethod = "close")
#RefreshScope
public Cluster cassandraCluster(CassandraProperties properties) {
Cluster.Builder builder = Cluster.builder().addContactPoints(properties.getContactPoints().toArray(new String[0]))
.withoutJMXReporting();
return builder.build();
}
#Bean(destroyMethod = "close")
#RefreshScope
public Session cassandraSession(CassandraProperties properties, Cluster cluster) {
return cluster.connect(properties.getKeyspaceName());
}
}

#Configuration class with empty #Value

I have a Maven Spring Boot application with an external configuration.
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.0.RELEASE</version>
</parent>
I have a config class with #PropertySource annotation, which uses both #Value and Environment:
#Configuration
#PropertySource("file:/etc/koshka/application.properties")
public class Config
{
#Autowired
private Environment env;
#Bean
public static PropertySourcesPlaceholderConfigurer configurer() {
return new PropertySourcesPlaceholderConfigurer();
}
#Value("${socmess.pass}")
private String pass;
public String getUser() {
return env.getProperty("socmess.user");
}
public String getPass() {
return pass;
}
}
In the nutshell, I need just #Value, but I tested Environment option as well.
I have configuration file in the appropriate folder:
$ cat /etc/koshka/application.properties
socmess.user="testuser"
socmess.pass="testpass"
In the controller I'm instantiating the configuration:
#RestController
public class Sender {
private final Logger logger;
public Sender()
{
logger = (Logger)LogManager.getLogger("KoshkaBot");
}
#GetMapping("/vk")
#CrossOrigin(origins = "*")
public ResponseEntity<String> vk(
#RequestParam(value="username") String username,
#RequestParam(value="password") String password
) {
Config conf = new Config();
logger.info(conf.getUser());
logger.info(conf.getPass());
return ResponseEntity.ok().body("OK");
}
}
Unfortunately, I see nulls in the log.
Don't create new Config() object. This one is managed by spring container and as such you should, at most, inject it.
Recap
Spring is dependency injection framework, meaning that if you need some dependency, it is provided to you by the container and you shouldn't create it. Those dependencies are generally spring beans, objects that spring know how to create and manage.
Back to us
#Configuration is annotation that spring uses to define beans, through #Bean annotation that can be, later on, used by other components using the #Inject annotation. What I would do is define a pojo like:
public class User(){
private String username;
private String password;
//getters,setters etc ...
}
and then in the Config class define the bean like:
#Bean
public User getUser(){
//..load user and password
return new User(user, password);
}
and finally when you need to use the user you can do:
#Inject User user;
//now the properties will be not-null

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

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";
}

Inject Spring repository into Spring #Configuration class

I need to read my db to load a custom setting inside my Spring #Configuration class.
I have something like:
#Configuration
public MyConfigClass implements ApplicationContextAware{
#Bean(initMethod = "start", destroyMethod = "stop")
public ServerSession serverSession() throws Exception {
ServerSession serverSession = new ServerSession(urlGateway, useSsl, hostGateway, portGateway);
return serverSession;
}
I should read parameters from DB instead from property file. I know that I can't #Inject my repository directly into this class, but there is a trick or something that permit me to do this or at least make a query on db?
I'm using Hibernate + Spring + Spring Data.
I prefer injecting the necessary dependencies as a parameter. Using #Autowired in a field looks unnatural to me in a #Configuration class (just using stateful fields, as configuration is supposed to be stateless). Just provide it as a parameter for the bean method:
#Bean(initMethod = "start", destroyMethod = "stop")
public ServerSession serverSession(MyRepo repo) throws Exception {
repo.loadSomeValues();
ServerSession serverSession = new ServerSession(urlGateway, useSsl, hostGateway, portGateway);
return serverSession;
}
This might require using #Autowired itself at method level, depending on the Spring version:
#Bean(initMethod = "start", destroyMethod = "stop")
#Autowired
public ServerSession serverSession(MyRepo repo) throws Exception {
repo.loadSomeValues();
ServerSession serverSession = new ServerSession(urlGateway, useSsl, hostGateway, portGateway);
return serverSession;
}
See also:
Autowire a bean within Spring Java configuration
Autowiring and DI work in #Configuration classes. If you're experiencing difficulties then it may be because you're trying to use the injected instance too early in the app startup lifecycle.
#Configuration
public MyConfigClass implements ApplicationContextAware{
#Autowired
private MyRepository repo;
#Bean(initMethod = "start", destroyMethod = "stop")
public ServerSession serverSession() throws Exception {
// You should be able to use the repo here
ConfigEntity cfg = repo.findByXXX();
ServerSession serverSession = new ServerSession(cfg.getUrlGateway(), cfg.getUseSsl(), cfg.getHostGateway(), cfg.getPortGateway());
return serverSession;
}
}
public interface MyRepository extends CrudRepository<ConfigEntity, Long> {
}

Categories