I am running an application with SpringBoot 2.1.1.RELEASE.
I have a yml file with list of elements configured in the default profile and also in a "local" profile
listOfSimpleObjects:
one: oneOne, oneTwo
three: nzerjpeojr
listOfObjects:
- id: idOne
name: nameOne
---
spring:
profiles: local
listOfSimpleObjects:
two: twoOne,twoTwo
listOfObjects:
- id: idTwo
name: nameTwo
I want to map that configuration into a properties file whose definition is
#ConfigurationProperties
public class MyProperties {
private Map<String, List<String>> listOfSimpleObjects = new HashMap<String, List<String>>();
private List<SubConfig> listOfObjects = new ArrayList<>();
public Map<String, List<String>> getListOfSimpleObjects() {
return listOfSimpleObjects;
}
public void setListOfSimpleObjects(Map<String, List<String>> listOfSimpleObjects) {
this.listOfSimpleObjects = listOfSimpleObjects;
}
public List<SubConfig> getListOfObjects() {
return listOfObjects;
}
public void setListOfObjects(List<SubConfig> listOfObjects) {
this.listOfObjects = listOfObjects;
}
}
public class SubConfig {
private String id;
private String name;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Running with the profile "local" I was expecting to have a MyProperties object with three elements in the listOfSimpleObjects and two in the listOfObjects but it is not the case.
Below a Junit test that tells me that there is only one element in the listOfObjects.
#RunWith(SpringRunner.class)
#ActiveProfiles("local")
#SpringBootTest
public class MyPropertiesTest {
#Autowired
private MyProperties props;
#Test
public void testOnListOfStrings() {
// this assertion is ok :)
assertThat(props.getListOfSimpleObjects()).hasSize(3);
}
#Test
public void testOnListOfObjects() {
// this assertion fails :(
assertThat(props.getListOfObjects()).hasSize(2);
}
}
I asked a colleague of mine who that it was all about the key of the elements as the yml file is at first represented in a big HashMap.
So I guess there is no real answer to the question I could ask, but anyway:
is there any way to have a merge version of the listOfObject ?
Could SpringBoot be enhanced in order to support such feature (ie in case of detection of a list of items the merge is possible)
Thanks for any kind of answer :)
is there any way to have a merge version of the listOfObject ?
Out of the box, no as the documentation states :
When lists are configured in more than one place, overriding works by
replacing the entire list.
About :
Could SpringBoot be enhanced in order to support such feature (ie in
case of detection of a list of items the merge is possible)
You can open an issue/request on the Spring Boot Git.
And you guessed it works for Map as the doc states :
For Map properties, you can bind with property values drawn from
multiple sources. However, for the same property in multiple sources,
the one with the highest priority is used.
As simple and limited workaround (it works with only one specific profile. With two you will still have the overriding issue) you could specify a new property name for the list in the yaml of the specific profile.
So you would have two lists but it doesn't matter as finally you can merge it when the bean was completely loaded from the #PostConstruct annotated method.
Sample :
private List<SubConfig> listOfObjects = new ArrayList<>();
private List<SubConfig> listOfObjectsFromProfile = new ArrayList<>();
//... getters and setters
#PostConstruct
public void mergeList() {
listOfObjects.addAll(listOfObjectsFromProfile);
}
Related
I've provided a sample project to elucidate this problem: https://github.com/nmarquesantos/spring-mongodb-reactive-indexes
According to the spring mongo db documentation (https://docs.spring.io/spring-data/mongodb/docs/current/reference/html/#mapping-usage):
the #Indexed annotation tells the mapping framework to call createIndex(…) on that property of your document, making searches faster. Automatic index creation is only done for types annotated with #Document.
In my Player class, we can observe the both the #Document and #Indexed annotation:
#Document
public class Player {
#Id
private String id;
private String playerName;
#Indexed(name = "player_nickname_index", unique = true)
private String nickname;
public Player(String playerName, String nickname) {
this.id = UUID.randomUUID().toString();
this.playerName = playerName;
this.nickname = nickname;
}
public String getPlayerName() {
return playerName;
}
public void setPlayerName(String playerName) {
this.playerName = playerName;
}
public String getNickname() {
return nickname;
}
public void setNickname(String nickname) {
this.nickname = nickname;
}
}`
And in my application class, i'm inserting oneelement to check the database is populated successfully:
#PostConstruct
public void seedData() {
var player = new Player("Cristiano Ronaldo", "CR7");
playerRepository.save(player).subscribe();
}
If I check MongoDb after running my application, I can see the collection and the element created successfully.
The unique index for nickname is not created. I can only see an index created for the #Id attribute. Am I missing anything? Did I mis-interpret the documentation?
The Spring Data MongoDB version come with Spring Boot 2.3.0.RELEASE is 3.0.0.RELEASE. Since Spring Data MongoDB 3.0, the auto-index creation is disabled by default.
To enable auto-index creation, set spring.data.mongodb.auto-index-creation = true or if you have custom Mongo configuration, override the method autoIndexCreation
#Configuration
public class CustomMongoConfig extends AbstractMongoClientConfiguration {
#Override
public boolean autoIndexCreation() {
return true;
}
// your other configuration
}
I've faced this problem when upgrading the spring boot version to 2.3.x and overriding this method on the config class solved it (what #yejianfengblue said above)
#Override
public boolean autoIndexCreation() {
return true;
}
I went through the link: How to pass a Map<String, String> with application.properties and other related links multiple times, but still its not working.
I'm using Spring Boot and Spring REST example. Link Question: How to by default execute the latest version of endpoint in Spring Boot REST?.
I've created mapping something like this and simply read the mapping
get.customers={GET: '/app-data/customers', VERSION: 'v1'}
post.customers={POST: '/app-data/customers', VERSION: 'v1'}
get.customers.custId={GET: '/app-data/customers/{custId}', VERSION: 'v2'}
Code:
private String resolveLastVersion() {
// read from configuration or something
return "2";
}
Code:
#Component
#ConfigurationProperties
#PropertySource("classpath:restendpoint.properties")
public class PriorityProcessor {
private final Map<String, String> priorityMap = new HashMap<>();
public Map<String, String> getPriority() {
return priorityMap;
}
}
Code:
I suggest the following implementation:
#ConfigurationProperties(prefix="request")
public class ConfigurationProps {
private List<Mapping> mapping;
public List<Mapping> getMapping() {
return mapping;
}
public void setMapping(List<Mapping> mapping) {
this.mapping = mapping;
}
}
Class Mapping will denote the information about the single mapping:
public class Mapping {
private String method;
private String url;
private String version;
public Mapping(String method, String url, String version) {
this.method = method;
this.url = url;
this.version = version;
}
public Mapping() {
}
// getters setters here
}
On the Configuration or spring boot application class (the one with main method):
#EnableConfigurationProperties(ConfigurationProps.class)
In the properties file put:
request.mapping[0].method=get
request.mapping[0].url=/customers
request.mapping[0].version=1
request.mapping[1].method=post
request.mapping[1].url=/students
request.mapping[1].version=2
In Filter (I assume you followed my suggestion from the linked question):
#Component
#Order(1)
public class LatestVersionFilter implements Filter {
private List<Mapping> mappings;
public LatestVersionFilter(ConfigurationProps props) {
this.mappings = props.getMapping();
}
}
I'm following the 24.8.3 Merging Complex Types section of Spring Boot's 24. Externalized Configuration documentation.
I have this config.yaml file:
acme:
list:
- name: my name
description: my description
- name: another name
description: another description
The Properties file looks like this:
#ConfigurationProperties("acme")
#YamlPropertySource(value = { "classpath:/config.yaml" })
public class AcmeProperties {
private final List<MyPojo> list = new ArrayList<>();
public List<MyPojo> getList() {
return this.list;
}
}
The MyPojo class:
public class MyPojo {
private String name;
private String description;
public MyPojo(String name, String description) {
this.name = name;
this.description = description;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
}
The test, which fails, looks like this:
#RunWith(SpringJUnit4ClassRunner.class)
#ContextConfiguration(classes = { AcmeProperties.class })
public class AcmePropertiesTest {
#Autowired
private AcmeProperties properties;
#Test
public void getOpScoringClusters() {
Assert.assertEquals(2, properties.getList().size()); // FAIL!
}
}
Spring Boot version 1.5.6.
Basically I want to have a list of typed properties. What am I doing wrong?
Several comments have highlighted multiple issues with the code presented.
Firstly, the fields inside a configuration properties can't be final as spring uses the setter to set the value.
Secondly, #YamlPropertySource is not something provided by spring so won't do anything in this context.
Thirdly, even if you did use the spring PropertySource annotation, unfortunately you can't use it with yaml files.
YAML files cannot be loaded by using the #PropertySource annotation.
I've created a sample project that uses the code you presented and has been modified so that it passes the unit test. It's using spring boot 2.x instead of 1.x but the only significant difference should be the annotations used in the test class.
https://github.com/michaelmcfadyen/spring-boot-config-props-demo
I am having Spring Boot application and having application.yml with different properties and loading as below.
#Configuration
#ConfigurationProperties(prefix="applicationprops")
public class ApplicationPropHolder {
private Map<String,String> mapProperty;
private List<String> myListProperty;
//Getters & Setters
}
My Service or Controller Class in which I get this properties like below.
#Service
public ApplicationServiceImpl {
#Autowired
private ApplicationPropHolder applicationPropHolder;
public String getExtServiceInfo(){
Map<String,String> mapProperty = applicationPropHolder.getMapProperty();
String userName = mapProperty.get("user.name");
List<String> listProp = applicationPropHolder.getMyListProperty();
}
}
My application.yml
spring:
profile: dev
applicationprops:
mapProperty:
user.name: devUser
myListProperty:
- DevTestData
---
spring:
profile: stagging
applicationprops:
mapProperty:
user.name: stageUser
myListProperty:
- StageTestData
My questions are
In my Service class i am defining a variable and assigning Propertymap for every method invocation.Is it right appoach?
Is there any other better way I can get these maps without assigning local variable.
There are three easy ways you can assign the values to instance variables in your bean class.
Use the #Value annotation as follows
#Value("${applicationprops.mapProperty.user\.name}")
private String userName;
Use the #PostConstruct annotation as follows
#PostConstruct
public void fetchPropertiesAndAssignToInstanceVariables() {
Map<String, String> mapProperties = applicationPropHolder.getMapProperty();
this.userName = mapProperties.get( "user.name" );
}
Use #Autowired on a setter as follows
#Autowired
public void setApplicationPropHolder(ApplicationPropHolder propHolder) {
this.userName = propHolder.getMapProperty().get( "user.name" );
}
There may be others, but I'd say these are the most common ways.
Hope, you are code is fine.
Just use the below
#Configuration
#ConfigurationProperties(prefix="applicationprops")
public class ApplicationPropHolder {
private Map<String,String> mapProperty;
private List<String> myListProperty;
public String getUserName(){
return mapProperty.get("user.name");
}
public String getUserName(final String key){
return mapProperty.get(key);
}
}
#Service
public ApplicationServiceImpl {
#Autowired
private ApplicationPropHolder applicationPropHolder;
public String getExtServiceInfo(){
final String userName = applicationPropHolder.getUserName();
final List<String> listProp = applicationPropHolder.getMyListProperty();
}
}
I'm investigating Neo4j and have a question with regards to object eager/lazy loading. Lets say I have class Trolley with has Set<Item> (with getters/setters). If I do the following:
Trolley t = new Trolley(...); // create empty trolley
t.addItem(f); // add one item to the trolley
t.persist(); // persist the object
I then later find the trolley based on the nodeId:
repo.findOne(xxx); // returns the trolley successfully
When I do something like:
trolley.getItems().size()
this is empty. I guess this is the intended behaviour. Is there any mechanism similar to JPA where is the session/tx is open to load the collection dynamically.
Code:
#NodeEntity
public class Trolley
{
#Indexed
private String name;
#RelatedTo
private Set<Item> items;
public Trolley(){}
public Trolley(String name)
{
this.name = name;
}
public void addItem(Item item)
{
this.items.add(item);
}
public Set<Item> getItems()
{
return items;
}
}
#NodeEntity
public class Item
{
private String name;
public Item(){}
public Item(String name)
{
this.name = name;
}
public String getName()
{
return name;
}
}
#Test
public void trolleyWithItemPersist()
{
Trolley trolley = new Trolley("trolley1").persist();
// Persisting - however I would've expected a cascade to
// occur when adding to the collection.
Item item = new Item("item1").persist();
// now add to the trolley
trolley.addItem(item);
// persist
trolley.persist();
// Now use repo to get trolley
Trolley loadedTrolley = trolleyRepository.findOne(trolley.getNodeId());
// should have one item
assertEquals(1, loadedTrolley.getItems().size());
}
Afaik, in Spring Data Jpa, to populate an lazy loaded field you need to annotate the method which call the findOne(xxx) with
#Transactional
from (org.springframework.transaction.annotation.Transactional)
Maybe it works also with neo4j...
I'm not really an skilled developper on Spring Data but this is the only way I know to retrieve lazy loaded fields. If someone has a better solution, feel free to write it!