Mybatis annotation how to return hashmap key item if value is null - spring-boot

springboot mybatis example:
#Select({"select id,xx from table where id=#{id}"})
Map<String,Object> queryById(#Param("id") Long id);
if table xx is null, the returned map does not contains xx key.
I search for a long time,
mapper.xml could configure <setting name="callSettersOnNulls" value="true"/>
could solve my problem, but I do not use xml configuration mode, how to config param callSettersOnNulls using mybatis annotation??

i say no, callSettersOnNulls now only support the global configuration, it can't support on specific method
see the source code of mybatis:
org.apache.ibatis.executor.resultset.DefaultResultSetHandler
applyPropertyMappings() or applyAutomaticMappings()

I've had the same problem right now. I don't solve it yet
Answering the question
how to config param callSettersOnNulls using mybatis annotation
, the configuration would be something like this.
import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
#Configuration
public class MyBatisConfiguration {
#Bean
ConfigurationCustomizer mybatisConfigurationCustomizer() {
return new ConfigurationCustomizer() {
#Override
public void customize(org.apache.ibatis.session.Configuration configuration) {
configuration.setCallSettersOnNulls(true);
}
};
}
}

Related

How to read secret key and value from Kubernetes volume mount using Spring Boot

I have mounted one volume which contained username and password inside pod. If I do:
kubectl exec -it my-app -- cat /mnt/secrets-store/git-token
{"USERNAME":"usernameofgit","PASSWORD":"dhdhfhehfhel"}
I want to read this USERNAME and PASSWORD using Spring Boot.
Assuming:
the file (git_token) format is fixed (JSON).
the file may not have an extension suffix (.json).
... we have some Problems!
I tried 2.3.5. Importing Extensionless Files like:
spring.config.import=/mnt/secrets-store/git-token[.json]
But it works only with YAML/.properties yet!(tested with spring-boot:2.6.1))
Same applies to 2.8. Type-safe Configuration Properties. ;(;(
In Spring-Boot we can (out-of-the box) provide JSON-config (only) as SPRING_APPLICATION_JSON environment/command line property, and it has to be the json string, and cannot be a path or file (yet).
The proposed (baeldung) article shows ways to "enable JSON properties", but it is a long article with many details, shows much code and has decent lacks/outdates (#Component on #ConfigurationProperties is rather "unconventional")..
I tried the following (on local machine, under the mentioned assumptions):
package com.example.demo;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
#SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
#Value("""
#{#jacksonObjectMapper.readValue(
T(java.nio.file.Files).newInputStream(
T(java.nio.file.Path).of('/mnt/secrets-store/git-token')),
T(com.example.demo.GitInfo)
)}""" // watch out with #Value and text blocks! (otherwise: No converter found capable of converting from type [com.example.demo.GitInfo] to type [java.lang.String])
)
GitInfo gitInfo;
#Bean
CommandLineRunner runner() {
return (String... args) -> {
System.out.println(gitInfo.getUsername());
System.out.println(gitInfo.getPassword());
};
}
}
#Data
class GitInfo {
#JsonProperty("USERNAME")
private String username;
#JsonProperty("PASSWORD")
private String password;
}
With (only) spring-boot-starter-web and lombok on board, it prints the expected output.
Solution outline:
a pojo for this
the upper case is little problematic, but can be handled as shown.
a (crazy) #Value - (Spring-)Expression, involving:
(hopefully) auto-configured #jacksonObjectMapper bean. (alternatively: custom)
ObjectMapper#readValue (alternatives possible)
java.nio.file.Files#newInputStream (alternatives possible)
java.nio.file.Path#of
When you have your volume mounted, then all you need to do is to read a JSON file from the Spring Boot application. I recommend reading Load Spring Boot Properties From a JSON File.
In short, you can create a class corresponding to your JSON file, something like this one.
#Component
#PropertySource("file:/mnt/secrets-store/git-token")
#ConfigurationProperties
public class GitToken {
private String username;
private String password;
// getters and setters
}
Then, you need to add it to componentScan and you can autowire your class.

Table name configured with external properties file

I build a Spring-Boot application that accesses a Database and extracts data from it. Everything is working fine, but I want to configure the table names from an external .properties file.
like:
#Entity
#Table(name = "${fleet.table.name}")
public class Fleet {
...
}
I tried to find something but I didn't.
You can access external properties with the #Value("...") annotation.
So my question is: Is there any way I can configure the table names? Or can I change/intercept the query that is sent by hibernate?
Solution:
Ok, hibernate 5 works with the PhysicalNamingStrategy. So I created my own PhysicalNamingStrategy.
#Configuration
public class TableNameConfig{
#Value("${fleet.table.name}")
private String fleetTableName;
#Value("${visits.table.name}")
private String visitsTableName;
#Value("${route.table.name}")
private String routeTableName;
#Bean
public PhysicalNamingStrategyStandardImpl physicalNamingStrategyStandard(){
return new PhysicalNamingImpl();
}
class PhysicalNamingImpl extends PhysicalNamingStrategyStandardImpl {
#Override
public Identifier toPhysicalTableName(Identifier name, JdbcEnvironment context) {
switch (name.getText()) {
case "Fleet":
return new Identifier(fleetTableName, name.isQuoted());
case "Visits":
return new Identifier(visitsTableName, name.isQuoted());
case "Result":
return new Identifier(routeTableName, name.isQuoted());
default:
return super.toPhysicalTableName(name, context);
}
}
}
}
Also, this Stackoverflow article over NamingStrategy gave me the idea.
Table names are really coming from hibernate itself via its strategy interfaces. Boot configures this as SpringNamingStrategy and there were some changes in Boot 2.x how things can be customised. Worth to read gh-1525 where these changes were made. Configure Hibernate Naming Strategy has some more info.
There were some ideas to add some custom properties to configure SpringNamingStrategy but we went with allowing easier customisation of a whole strategy beans as that allows users to to whatever they need to do.
AFAIK, there's no direct way to do config like you asked but I'd assume that if you create your own strategy you can then auto-wire you own properties to there. As in those customised strategy interfaces you will see the entity name, you could reserve a keyspace in boot's configuration properties to this and match entity names.
mytables.naming.fleet.name=foobar
mytables.naming.othertable.name=xxx
Your configuration properties would take mytables and within that naming would be a Map. Then in your custom strategy it would simply be by checking from mapping table if you defined a custom name.
Spring boot solution:
Create below class
#Configuration
public class CustomPhysicalNamingStrategy extends SpringPhysicalNamingStrategy{
#Value("${table.name}")
private String tableName;
#Override
public Identifier toPhysicalTableName(final Identifier identifier, final JdbcEnvironment jdbcEnv) {
return Identifier.toIdentifier(tableName);
}
}
Add below property to application.properties:
spring.jpa.properties.hibernate.physical_naming_strategy=<package.name>.CustomPhysicalNamingStrategy
table.name=product

#RequestBody is getting null values

I have created a simple REST service (POST). But when i call this service from postman #RequestBody is not receiving any values.
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;
#RestController
public class Add_Policy {
#ResponseBody
#RequestMapping(value = "/Add_Policy", headers = {
"content-type=application/json" }, consumes = MediaType.APPLICATION_JSON_VALUE, method = RequestMethod.POST)
public Policy GetIPCountry( #RequestBody Policy policy) {
System.out.println("Check value: " + policy.getPolicyNumber());
return policy;
}
}
My java Bean object is like below:
public class Policy {
private String PolicyNumber;
private String Type;
private String Tenture;
private String SDate;
private String HName;
private String Age;
public String getPolicyNumber() {
return PolicyNumber;
}
public void setPolicyNumber(String policyNumber) {
PolicyNumber = policyNumber;
}
public String getType() {
return Type;
}
public void setType(String type) {
Type = type;
}
public String getTenture() {
return Tenture;
}
System.out.println is printing a null as a value for PolicyNumber.
Please help me to resolve this issue.
JSON which i am passing in request body is
{
"PolicyNumber": "123",
"Type": "Test",
"Tenture": "10",
"SDate": "10-July-2016",
"HName": "Test User",
"Age": "10"
}
I have even set Content-Type to application/json in postman
Check the #RequestBody import,
wrong that will cause the problem in most cases.
import io.swagger.v3.oas.annotations.parameters.RequestBody;
to solve problem It should be
import org.springframework.web.bind.annotation.RequestBody;
Try setting the first character of the properties in your JSON to lower case. Eg.
{
"policyNumber": "123",
"type": "Test",
"tenture": "10",
"sDate": "10-July-2016",
"hName": "Test User",
"age": "10"
}
Basically, Spring uses getter and setter to set the properties of the the bean object. And it takes the property of the JSON object, matches it with the setter of the same name. eg to set the policyNumber property it tries to find a setter with the name setpolicyNumber() in your bean class and use that to set the value of your bean object.
Setter would have been missed. So, Object values do not get set.
If you are not in power to change the JSON format and still want to fix this problem, try adding
#JsonNaming(PropertyNamingStrategy.UpperCamelCaseStrategy.class)
annotation before your DTO (Policy in example) class.
Java convention demands the name of variable in a POJO (attribute of a class) must to be the first character in lowercase.
You have uppercase letters in your JSON properties, which is what is causing the failure.
I had lombok in my pom, and lombok annotations on my bean. I did not properly installed lombok with my STS yet, and had similar issue, my bean was not populated.
When I removed lombok annotations, my bean was properly populated.
Seems like a combination of lomboc not properly installed on STS + lomboc annotations on my bean.
if you are using Lombok Api then there are no Getters and Setters publicly visible or available to the #ResponseBody and #RequestBody annotation.
That is why we read the JSON request with null values.
So you need to comment those #Getter, #Setter annotation to Receive JSON response and Read JSON request object and generate the same getters and setters.
Restart or Hot Load (using Spring-Boot-Devtools) server and it should work for sure.
You can still use your lombok api for other Entities or BO's.
In my case was a Lombok issue. I removed all the lombok annotations and created the constructor, setter and getter manually.
As an advise, I would also set the JSON to lowercase to follow the convention.
Use the annotation org.springframework.web.bind.annotation.RequestBody and not org.springframework.web.bind.annotation.ResponseBody
In my case, empty constructor must be defined.
public MyClass(){}
Apart from lowerCamelCasing, for me what additionally needed was applying #JsonProperty(value="your expected JSON KEY name") for each of the getter and setter methods and using this operator under the POJO/Bean/DTO class.
Sample Code:
#JsonProperty(value="policyNumber")
public void setPolicyNumber(String policyNumber) {
this.policyNumber = policyNumber;
}
Had the same issue but for my case only one field was not being set. A log on the request body object showed it was being recieved as null. deleted getters and setters for the field and autogenerated them using the IDE and all worked fine.
I highly suspect a mismatch in the getter and setter definition can also cause this
I have been having this issue too, but the best way i solve mine was checking on spaces after the first quotes in every initialization of fields in my json values
see spring PropertyNamingStrategy(UPPER_CAMEL_CASE,LOWER_CAMEL_CASE ,LOWER_CASE
etc... defalult SNAKE_CASE).
Spring will auto change http contorller class parameter by naming strategy, which may be not consistant with your request json
take SNAKE_CASE as a ex, when "myToken" in java controller class, you client should send my_token instead of myToken
If you are using Lombok you need compileOnly and annotationProcessor
In my case I missed the 2nd one. So I got all null values
compileOnly 'org.projectlombok:lombok:1.18.24'
annotationProcessor 'org.projectlombok:lombok:1.18.24'
1-Make Entity class properties start with lowercase.
2-Check for Annotations.
3-Check for Constructor--> **Entity classes should have two constructor.
4-Check for Getter and Setters.
In my case, date format was given incorrectly

Spring cloud config server. Environment variables in properties

I configured Spring Cloud Config server like this:
#SpringBootApplication
#EnableAutoConfiguration
#EnableConfigServer
public class ConfigServer {
public static void main(String[] args) {
SpringApplication.run(ConfigServer.class, args);
}
}
I'm using 'native' profile so properties are picked up from the file system:
server.port=8888
spring.profiles.active=native
spring.cloud.config.server.native.search-locations: classpath:/global
Now the tricky part is that some properties contain environmental variables. Properties in 'global/application-production.properties' are configured like this:
test=${DOCKER_HOST}
When I start up Config Server - everything works fine. However when I access http://localhost:8888/testapp/production I see this:
{
name: "testapp",
profiles: [
"production"
],
label: null,
version: null,
propertySources: [
{
name: "classpath:/global/application-production.properties",
source: {
test: "${DOCKER_HOST}"
}
}
]
}
So value from ENV variable is not replacing ${DOCKER_HOST} put rather returned as is.
But if I access http://localhost:8888/application-production.properties then result is non JSON but rather plain text:
test: tcp://192.168.99.100:2376
Spring documentation says:
The YAML and properties representations have an additional flag (provided as a boolean query parameter resolvePlaceholders) to signal that placeholders in the source documents, in the standard Spring ${…​} form, should be resolved in the output where possible before rendering. This is a useful feature for consumers that don’t know about the Spring placeholder conventions.
For some reason resolvePlaceholders is not working for JSON representation thus server config clients need to be aware of all ENV variables configured on server.
Is it possible to force JSON representation resolvePlaceholders same way as plain text (properties) representation?
I faced the same issue. After looking into Spring Cloud Config Repository I have found the following commit:
Omit system properties and env vars from placeholders in config
It looks like such behavior is not supported.
You can try the Property Overrides feature to override properties from git Environment Repository.
To override property foo at runtime, just set a system property or an environment variable spring.cloud.config.server.overrides.foo before starting the config server.
There was an update in order to accomplish this, in the following merge. 1 I found an implementation for resolvePlaceholders. Which gave me the idea of just creating a new controller which uses the EnvironmentController. This will allow you to resolve configuration, this is a good bootstrap.
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.config.server.environment.EnvironmentController;
import org.springframework.cloud.config.server.environment.EnvironmentRepository;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping(method = RequestMethod.GET, path = "resolved/${spring.cloud.config.server.prefix:}")
public class ReplacedEnvironmentController {
private EnvironmentController environmentController;
#Autowired
public ReplacedEnvironmentController(EnvironmentRepository repository) {
environmentController = new EnvironmentController(repository, new ObjectMapper());
}
public ReplacedEnvironmentController(EnvironmentRepository repository,
ObjectMapper objectMapper) {
environmentController = new EnvironmentController(repository, objectMapper);
}
#RequestMapping("/{name}/{profiles:.*[^-].*}")
public ResponseEntity<String> resolvedDefaultLabel(#PathVariable String name,
#PathVariable String profiles) throws Exception {
return resolvedLabelled(name, profiles, null);
}
#RequestMapping("/{name}/{profiles}/{label:.*}")
public ResponseEntity<String> resolvedLabelled(#PathVariable String name, #PathVariable String profiles,
#PathVariable String label) throws Exception {
return environmentController.labelledJsonProperties(name, profiles, label, true);
}
}

Spring JPA Auditing not updating rows

I'm trying to configure Spring JPA to update timestamp columns using the JPA auditing framework.
I think I've got it configured correctly, but whenever I create or update a row it just sets null on all the auditable fields. (note the fields are created in the database, and if I manually write a value, it will be overwritten with null).
What am I missing here? Do I need to explicitly set the last modified date etc?
Also my auditor bean isn't being triggered, I set a break point and it's never entered, which leads me to suspect I'm missing some configuration for the auditing service.
So far I have these definitions:
#Configuration
#EnableTransactionManagement
#EnableJpaAuditing(auditorAwareRef = "auditorBean")
#EnableJpaRepositories(basePackages="com.ideafactory.mvc.repositories.jpa")
public class PersistenceConfig
{...
And the auditor aware class:
#Component
public class AuditorBean implements AuditorAware<Customer> {
private static final Logger LOGGER= LoggerFactory.getLogger(AuditorBean.class);
private Customer currentAuditor;
#Override
public Customer getCurrentAuditor() {
// Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
//
// if (authentication == null || !authentication.isAuthenticated()) {
// return null;
// }
//
// return ((MyUserDetails) authentication.getPrincipal()).getUser();
LOGGER.debug("call AuditorAware.getCurrentAuditor(");
return currentAuditor;
}
public void setCurrentAuditor(Customer currentAuditor) {
this.currentAuditor = currentAuditor;
}
}
And my entity configuration:
#Entity
#Table(name= "contact_us_notes")
public class ContactUsNote extends AbstractAuditable<Customer, Long> {...
========================== Updated ============================
Ok so i went back over the docs, and it seems I'd missed configuring the entity listener. So it's kind of working now.
But now my question becomes how in java configuration do I configure the listener as a default for all entities? (Similar to the way the docs recommend in orm.xml).
I added the entity listeners annotation below.
#Entity
#Table(name= "contact_us_notes")
#EntityListeners({AuditingEntityListener.class})
public class ContactUsNote extends AbstractAuditable<Customer, Long> {
Have you create an orm.xml file in /resources/META-INF? I don't see it posted in your question.

Resources