Prefix for nested configuration properties in spring - java

Spring boot 2.0.0.RELEASE
I have properties class:
#Configuration
#ConfigurationProperties(prefix="person")
public class PersonProperties {
private AddressProperties addressProperties;
public AddressProperties getAddressProperties() {
return addressProperties;
}
public void setAddressProperties(final AddressProperties addressProperties) {
this.addressProperties = addressProperties;
}
public static class AddressProperties {
private String line1;
public String getLine1() {
return line1;
}
public void setLine1(final String line1) {
this.line1 = line1;
}
}
}
And application.yml:
person:
address:
line1: line1OfAddress
It is not binding properly as my AddressProperties object is null.
When a class has the same name as yml properties AddressProperties -> Address it is working well. I tried to add Qualifier or ConfigurationProperties with a prefix address but it is not working. Unfortunately, I cannot find useful information about this case in spring docs.
How to specify a prefix for nested properties?

Property defined in yaml / property file should match with the variables defined in class.
Either change yaml file as
person:
# addressProperties will also work here
address-properties:
line1: line1OfAddress
Or define your bean as
#Configuration
#ConfigurationProperties(prefix = "person")
public class PersonProperties {
// here variable name doesn't matter, it can be addressProperties as well
// setter / getter should match with properties in yaml
// i.e. getAddress() and setAddress()
private AddressProperties address;
public AddressProperties getAddress() {
return address;
}
public void setAddress(AddressProperties address) {
this.address = address;
}
}
If you want to get all properties under address without defining them in separate bean you can define your PersonProperties class as
#Configuration
#ConfigurationProperties(prefix = "person")
public class PersonProperties {
private Map<String, Object> address;
public Map<String, Object> getAddress() {
return address;
}
public void setAddress(Map<String, Object> address) {
this.address = address;
}
}
Here PersonProperties#address will contain {line1=line1OfAddress}
Now All properties under address will be in the Map.

You could simply un-nest the two classes, allowing each to have it's own prefix.
First class:
#Configuration
#ConfigurationProperties(prefix="person")
public class PersonProperties {
private AddressProperties addressProperties;
public AddressProperties getAddressProperties() {
return addressProperties;
}
public void setAddressProperties(final AddressProperties addressProperties) {
this.addressProperties = addressProperties;
}
}
Second class:
#Configuration
#ConfigurationProperties(prefix="person.address")
public class PersonAddressProperties {
private String line1;
public String getLine1() {
return line1;
}
public void setLine1(final String line1) {
this.line1 = line1;
}
}
Edit: As was pointed out in the comments, you'd have to inject both of these classes if one block of code needed to refer to both sets of properties.

Related

Values not getting picked up from application.properties

I'm trying to access values from my application.properties file in the Neo4jConnectionFactory class. I am able to access the properties within my Setup beans class. But, I want to be able to access them from Neo4jConnectionFactory. When I try to do so I get null values. Is this possible?
Applications.properties
neo4j.url = bolt://localhost:7687
neo4j.user = neo4j
neo4j.password = neo4j
SetupBeans.java
#Component
public class SetupBeans {
#Bean
public Neo4jClient neo4jClient() {
return new Neo4jConnectionFactory().build();
}
}
Neo4jConnectionFactory.java
#Configuration("neo4j")
public class Neo4jConnectionFactory {
#Value("${neo4j.url}")
private String url;
#Value("${neo4j.user}")
private String user;
#Value("${neo4j.password}")
private String password;
public Neo4jClient build() {
return new Neo4jClient(getUrl(), getUser(), getPassword());
}
}
MyApplication.java
#SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
I think this automatically prepends the prefix neo4j
#Configuration("neo4j")
So your #Value statments should omit the prefix, like this:
#Value("${url}")
private String url;
#Value("${user}")
private String user;
#Value("${password}")
private String password;

#JsonCreator not working for #RequestParams in Spring MVC

#JsonCreator not deserialising #RequestParam of type enum
I am working on a Spring application where the controller is receiving list of request params that Spring is binding to a wrapper object. One of the params is of type enum where I am receiving it by some property name.
Endpoint example: http://localhost:8080/searchCustomers?lastName=Smith&country=Netherlands
#RequestMapping(value = "/search/customers", method = RequestMethod.GET)
public CustomerList searchCustomers(#Valid CustomerSearchCriteria searchCriteria)
public class CustomerSearchCriteria {
private String lastName;
private Country country;
}
public enum Country {
GB("United Kingdom"),
NL("Netherlands")
private String countryName;
Country(String countryName) {
countryName = countryName;
}
#JsonCreator
public static Country fromCountryName(String countryName) {
for(Country country : Country.values()) {
if(country.getCountryName().equalsIgnoreCase(countryName)) {
return country;
}
}
return null;
}
#JsonValue
public String toCountryName() {
return countryName;
}
}
I am expecting Spring to bind enum Country.Netherlands to CustomerSearchCriteria.country but its not doing it so. I tried similar annotations with #RequestBody and that works fine, so I am guessing he Spring binding is ignoring #JsonCreator.
Any helpful tips would be appreciated.
Here is the code that is behind #Mithat Konuk comment.
Put in your controller something like:
import java.beans.PropertyEditorSupport;
#RestController
public class CountryController {
// your controller methods
// ...
public class CountryConverter extends PropertyEditorSupport {
public void setAsText(final String text) throws IllegalArgumentException {
setValue(Country.fromCountryName(text));
}
}
#InitBinder
public void initBinder(final WebDataBinder webdataBinder) {
webdataBinder.registerCustomEditor(Country.class, new CountryConverter());
}
}
More information ca be found here: https://www.devglan.com/spring-boot/enums-as-request-parameters-in-spring-boot-rest.

Load external properties file with same keys

I have two properties file in a folder in file system.
The path of this folder is passed using system properties -D=path/to/properties/folder
ex: java -jar -DpropDir=abc/def app.jar
These are properties files.
Please note that both files have common key username,password.
mysql.properties
url=jdbc:mysql://localhost:3306
username=root
password=pass
vertica.properties
dburl=jdbc:vertica://123.123.123:4321/abcd
username=abcd
password=pass123
Now I want to access all these properties in respective classes.
MySqlProperties.java and VerticaProperties.java like this.
#Component
public class VerticaProperties {
#Value("${dburl}")
private String dburl;
#Value("${username}")
private String username;
#Value("${password}")
private String password;
public String getDbUrl() {
return dburl;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
}
and similarly MySqlProperties.java
#Component
public class MySqlProperties {
#Value("${url}")
private String url;
#Value("${username}")
private String username;
#Value("${password}")
private String password;
public String getDbUrl() {
return url;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
}
But as key is same value is overriding for username and password.
How to access mysql.properties in MySqlProperties.java and vertica.properties in VerticaProperties.java classes.
You import external properties using #PropertySource
Given the location is shared as
-Dmysql.properties=file:/path-to-mysql.properties -Dvertica.properties=file:/path-to-vertica.properties
#Component
#PropertySource("${vertica.properties}")
public class VerticaProperties {
.....
}
#Component
#PropertySource("${mysql.properties}")
public class MySqlProperties {
....
}
or Given
-Dmysql.properties=/path-to-mysql.properties -Dvertica.properties=/path-to-vertica.properties
#Component
#PropertySource("file:${vertica.properties}")
public class VerticaProperties {
.....
}
#Component
#PropertySource("file:${mysql.properties}")
public class MySqlProperties {
....
}
Additionally , you may also use prefix with #ConfigurationProperties along with #PropertySource.
The annotation works best when we have hierarchical properties that all have the same prefix, so we mention the prefix too as a part of the annotation.
Add prefix to keys like mysql.url , vertica.url in respective files
#Component
#PropertySource("${vertica.properties}")
#ConfigurationProperties(prefix="vertica")
public class VerticaProperties {
.....
}
#Component
#PropertySource("${mysql.properties}")
#ConfigurationProperties(prefix="mysql")
public class MySqlProperties {
....
}

How to group properties with Spring Boot Configuration Properties

According to Spring Boot documentation, properties can be grouped and a property may appear in more than one group. But at the moment when we create a property class marked with #ConfigurationProperties(prefix="test1") the group name will be the prefix which is test1. Now if I have another property class for example with prefix as "test2" how can I say this latter one has a property from the group test1?
--- UPDATE ---
Added nested class but it's not working
#Configuration
#Profile({"wmx"})
#EnableConfigurationProperties
#ConfigurationProperties(prefix = "myapp.wmx", locations = {"classpath:application-wmx.properties", "classpath:myapp-env.properties"})
public class WmxProperties {
/**
* The WMX implementation to be loaded.
*/
#NotNull(message = "Must be configured.")
private ProfileEnum profile;
//#ConfigurationProperties(locations = "classpath:myapp-env.properties")
public static class Env {
/**
* Host name for WMX.
*/
private String host;
/**
* Port number for WMX.
*/
//#Pattern(regexp = "^[1-9]\\d*$", message = "Positive port number only.")
private Integer port;
/**
* Provider name.
*/
#NotBlank
private String providerName;
public String getHost() {
return host;
}
public void setHost(String host) {
this.host = host;
}
public Integer getPort() {
return port;
}
public void setPort(Integer port) {
this.port = port;
}
public String getProviderName() {
return providerName;
}
public void setProviderName(String providerName) {
this.providerName = providerName;
}
}
public ProfileEnum getProfile() {
return profile;
}
public void setProfile(ProfileEnum profile) {
this.profile = profile;
}
}
The commented annotation #ConfigurationProperties on the inner class is done after failing my tests. Spring doesn't load those properties with or without the annotation unless they are in the same property file, in this case application-emx.properties. Why is that? I want to separate these properties
=== RESOLVED ====
I noticed that I had to add a field of type the nested class with getter/setter methods otherwise Spring won't load the properties in the nested class
You can compose them with help of inner classes:
Property file
test1.property1=...
test1.test2.property2=...
test1.test2.property3=...
Java/Spring mapping:
import javax.validation.constraints.NotNull;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Configuration;
#Getter
#Setter
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties(locations = "classpath:myapp.properties")
public class ApplicationProperties {
private String property1;
private Test2 test2;
#Getter
#Setter
#ConfigurationProperties(prefix = "test2")
public static class Test2 {
#NotNull
private String property2;
#NotNull
private String property3;
}
}
We had success with this approach, because java composition mimics structure of property file. Also properties are validatable, so you can fail fast if configuration is not right.
Downside of this approach is that properties are mutable.
If your properties file is getting too big, your application most probably has wider problems.
The annotation processor automatically considers inner classes as nested properties. Make sure you have getters and setters defined.
#ConfigurationProperties(prefix="server")
public class ServerProperties {
private String name;
private Host host;
// ... getter and setters !!!
public static class Host {
private String ip;
private int port;
// ... getter and setters !!!
}
}
The same effect can be achieved with non-inner class but you should use the #NestedConfigurationProperty annotation on a field to indicate that a regular (non-inner) class should be treated as if it were nested.
#ConfigurationProperties(prefix="server")
public class ServerProperties {
private String name;
#NestedConfigurationProperty
private Host host;
// ... getter and setters !!!
}
public class Host {
private String ip;
private int port;
// ... getter and setters
}

How to connect Spring with MySQL database?

I have a simple project, based on this guide. I created a simple REST interface and I want it to use my database. I added Hibernate to the dependencies and created the DAO class. I'm using Spring Tool-Suite for IDE. As far as I understand I should add some beans to tell the classes what to use but I don't understand how. Here are my classes.
Application.java
package com.learnspring.projectfirst;
#SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
Marker.java
package com.learnspring.projectfirst;
#Entity
public class Marker {
#Id
#Column
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
#Column
private double longitude;
#Column
private double latitude;
#Column
private String address;
public Marker() {
// Empty constructor
}
public Marker(long id, double longitude, double latitude, String address) {
this.id = id;
this.longitude = longitude;
this.latitude = latitude;
this.address = address;
}
//Getters and Setters
}
MarkerController.java
package com.learnspring.projectfirst.controller;
#Controller
public class MarkerController {
private Logger logger = Logger.getLogger(MarkerController.class.getName());
#Autowired
private MarkerServiceImplementation markerService;
#RequestMapping(value="/markers", method=RequestMethod.GET)
public #ResponseBody List<Marker> getMarkers(#RequestParam(value="city", defaultValue="") String city) {
return this.markerService.getAllMarkers();
}
#RequestMapping(value="/markers/new", method=RequestMethod.POST)
public #ResponseBody Marker addMarker(#RequestBody Marker marker) {
this.markerService.addMarker(marker);
return marker;
}
}
MarkerDaoImplementation.java
package com.learnspring.projectfirst.dao;
#Repository
public class MarkerDaoImplementation implements MarkerDaoInterface {
#Autowired
private SessionFactory sessionFactory;
#Override
public void addMarker(Marker marker) {
this.sessionFactory.getCurrentSession().save(marker);
}
#Override
public void deleteMarker(int markerId) {
this.sessionFactory.getCurrentSession().delete(this.getMarker(markerId));
}
#Override
public Marker getMarker(int markerId) {
return (Marker) this.sessionFactory.getCurrentSession().get(Marker.class, markerId);
}
#Override
public List<Marker> getAllMarkers() {
return this.sessionFactory.getCurrentSession().createQuery("from Marker").list();
}
}
MarkerServiceImplementation.java
package com.learnspring.projectfirst.service;
#Service
public class MarkerServiceImplementation implements MarkerServiceInterface {
#Autowired
private MarkerDaoImplementation markerDao;
#Transactional
public void addMarker(Marker marker) {
this.markerDao.addMarker(marker);
}
#Transactional
public void deleteMarker(int markerId) {
this.markerDao.deleteMarker(markerId);
}
#Transactional
public Marker getMarker(int markerId) {
return this.markerDao.getMarker(markerId);
}
#Transactional
public List<Marker> getAllMarkers() {
return this.markerDao.getAllMarkers();
}
}
And here is the file structure:
I understand that I should tell my program the database name and the columns using beans but I don't understand how. How can I link the java code to the beans? Sorry I pasted so much code, I just wanted to make sure you have everything needed. Thank you in advance!
This is the one you need: Spring Boot with MySQL
Refer this example : Spring MVC with JdbcTemplate Example
The annotations in your "Marker" class determine the MySQL table and column names (based on the class and class variable names). The tablename will be "marker", with the columns "id", "longitude", "latitude", "address".
You forgot the most important part in your code: your spring configuration. it determines how the SessionFactory instance will be initialized before being injected into your DAO class. Here you have to set an appropriate connection to the MySQL Server (e.g. via an JNDI Resource)

Categories