SpringBoot check if injected Properties are set NotNull - spring-boot

I know I can easily inject a property file in SpringBoot 2.2 with the following construct
#ConstructorBinding
#ConfigurationProperties(prefix = "example")
#Data
#AllArgsConstructor
public final class MyProps {
#NonNull
private final String neededProperty;
#NonNull
private final List<SampleProps> lstNeededProperty;
public String getFirstSample(){
return lstNeededProperty.get(0); //throws NPE
}
}
#ConstructorBinding
#AllArgsConstructor
#Data
public class SampleProps {
String key;
String label;
}
and yml file like:
expample:
neededProperty: test1
lstNeededProperty:
-key: abc
label: input
The #NonNull works quite well for the String but fails for the List - since the NPE is thrown even when the list will be set.
Is there a simple way to check if the List is initialized? I've tried #Postconstruct but this isn't called at all.

Try to check for the size and intialize the list:
#ConstructorBinding
#ConfigurationProperties(prefix = "example")
public final class MyProps {
#NonNull
private final String neededProperty;
#Size(min=1)
private final List<String> lstNeededProperty = new ArrayList<>();
}

Related

How to exclude/disable #Entity Annotation for particular class

I want to disable #Entity Annotation for particular class.
Here is my sample code.
#Component
public class GenericDropDown{
private Integer id;
private String key;
private String value;
// Standard getter and setter
The above class is used for fetching data from multiple table for rendering different dropdown list from different tables.
How I can achieve this without #Entity Annotation
Here is my sample code.
#Component
public class GenericDropDown{
private Integer id;
private String key;
private String value;
// Standard getter and setter
#Repository
public class DropDownDao {
#Autowired
private EntityManager entityManager;
public Object runNativeQuery() {
#SuppressWarnings("unchecked")
List<Priority> o= entityManager.createNativeQuery("select Id,PRKEY,PRVALUE from Priority",Priority.class)
.getResultList();
return o;
}
}
**Error:**Unknown entity: com.min.test.Project.entity.Priority; nested exception is org.hibernate.MappingException: Unknown entity: com.min.test.Project.entity.Priority
You can select List of Objects array and map them yourself.
List<Object[]> o = entityManager.createNativeQuery("select Id,PRKEY,PRVALUE from Priority").getResltList();
List<MyClass> result = o.stream().map(arr -> new MyClass((Long) arr[0], (String) arr[1])).collect(Collectors.toList());
Or you also can use a JdbcTemplate instead of EntityManager:
#Autowired
private JdbcTemplate jdbcTemplate;
public List<MyClass> runQuery() {
String select = "select Id,yourParameterHere from Priority";
return jdbcTemplate.query(select, (rs, rowNum) -> new MyClass(rs.getLong("Id"), rs.getString("yourParameterHere")));
}

Mapstruct - How to convert a DTO String parameter to an Entity object?

I'm new to Mapstruct and I'm trying to understand it properly.
What I want to achieve is converting from a DTO String parameter (carModel) to his Entity, retrieve using Service and Repository.
The problem is that Mapper class generated by Mapstruct is trying to inject the Service class with #Autowired annotation, but it's not working. The service is null.
Here's my #Mapper class:
#Mapper(componentModel = "spring", uses = CarModelService.class)
public interface KitMapper extends EntityMapper<KitDTO, Kit> {
KitMapper INSTANCE = Mappers.getMapper(KitMapper.class);
#Mapping(source = "weight", target = "systemWeight")
#Mapping(source = "carModel", target = "carModel")
Kit toEntity(KitDTO kitDTO);
}
public interface EntityMapper<D, E> {
E toEntity(D dto);
List<E> toEntity(List<D> dtoList);
}
The #Service class:
#Service
#Transactional
public class CarModelService {
private final CarModelRepository carModelRepository;
#Transactional(readOnly = true)
public CarModel findByName(String name) {
return carModelRepository.findByName(name).orElse(null);
}
}
The #Repository class:
#Repository
public interface CarModelRepository extends JpaRepository<CarModel, Long> {
Optional<CarModel> findByName(String carModelName);
}
The DTO and Entity classes:
public class KitDTO {
private String id;
private String carModel; // e.g. "Ferrari Monza"
....
}
#Entity
#Table(name = "kit")
public class Kit implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#Column(name = "id")
private Long id;
#ManyToOne
private CarModel carModel;
...
}
#Entity
#Table(name = "car_model")
public class CarModel implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "sequenceGenerator")
#SequenceGenerator(name = "sequenceGenerator")
#Column(name = "id")
private Long id;
#Column(name = "name")
private String name;
...
}
The build work properly but the application stop when I try to use the Mapper. It says that carModelService is null.
Here's the mapper generated implementation class:
#Component
public class KitMapperImpl implements KitMapper {
#Autowired // <-- this seems not working
private CarModelService carModelService;
#Override
public Kit toEntity(KitDTO kitDTO) {
if ( kitDTO == null ) {
return null;
}
Kit kit = new Kit();
kit.setSystemWeight( String.valueOf( kitDTO.getWeight() ) );
kit.carModel( carModelService.findByName(kitDTO.getCarModel()) ); // <-- carModelService is null!
// other setters
return kit;
}
}
I've tried many things, using Decorator, #Context, expression, inject the #Mapper class into the #Service class.
I've found many questions but actually no one helped me:
Mapstruct - How can I inject a spring dependency in the Generated Mapper class
#Service Class Not Autowired in org.mapstruct.#Mapper Class
MapStruct mapper not initialized with autowired when debug
Any help would be appreciated! Thanks in advance!
Found the solution!
Instead of calling directly the Mapper method toEntity() from the #RestController class, I injected the mapper in the CarModelService class and created a method that call the mapper.
In this way the flow is:
Controller --> Service --> Mapper
#Service
#Transactional
public class KitService {
private final KitRepository kitRepository;
private final KitSearchRepository kitSearchRepository;
private final KitMapper kitMapper; // <-- mapper declaration
public KitService(KitRepository kitRepository, KitSearchRepository kitSearchRepository, KitMapper kitMapper) {
this.kitRepository = kitRepository;
this.kitSearchRepository = kitSearchRepository;
this.kitMapper = kitMapper; // <-- mapper initilization
}
// here the method which calls mapper
public Kit convertDTOToEntity(KitDTO kitDTO) {
return kitMapper.toEntity(kitDTO);
}
In this way, the generated class by Mapstruct doesn't give error on the CarModelService.
Seems like this approach is the only way to achieve this, create a king of "bridge" between services and mappers.
(You can use also the #Autowired annotation instead of constructor)
Can you please share the error message?
From the information that you shared, I can see the carModel in KitDto is String and in Entity is CarModel class. Not sure how mapstruct's auto generated implementation class implemented this: kit.carModel( carModelService.findByName(kitDTO.getCarModel()) );.
But I would like to share another approach,Don't know this is a best practice or not. In this approach you can create a abstarct class of mapper, in which you can #Autowired repository can manually add those mapping.
I shared the snippet for it. Hopefully this will help you.
#Mapper(componentModel = "spring", nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE)
public abstract class ProductMapper {
#Autowired
private CarModelService carModelService;
public abstract Kit convertDTOToEntity(KitDTO kitDTO);
public Kit toEntity(KitDTO kitDTO);
{
Kit kit = convertDTOToEntity(kitDTO);
kit.setCarModel(carModelService.findByName(kitDTO.getCarModel()));
return kit;
}
}
Curious about the other approaches, will follow this thread. We can discuss the best practices

Reading property values in constructor using prefix

How to use the prefix in the below code?
Properties:
height.customer.feet=10
height.customer.eu.timezone=UTC
#Configuration
#EnableConfigurationProperties
#ConfigurationProperties(prefix = "height.customer")
public class Customer {
private final int age;
private final String timezone;
public Customer(int age, String timezone){
this.age = age;
this.timezone = timezone;
}
}
Here i want to set the default value for both age and timezone. Default values are read from application.properties file. Can someone help me please?
I could use like below.
#Value("${height.customer.age}")
private final int age;
#Value("${height.customer.eu.timezone}")
private final String timezone;
But if i use like this, i may not able to use constructor injection
There is no relationship between the #ConfigurationProperties and #Value annotation. Check here. What you should be using is the #PropertySource annotation. If you use #ConfigurationProperties then you should have hierarchical properties
#Configuration
#ConfigurationProperties(prefix = "height.customer")
public class Customer {
private final int age; // This maps to height.customer.age
private final String timezone; // This does NOT map to height.customer.eu.timezone but maps to height.customer.timezone
public Customer(int age, String timezone){
this.age = age;
this.timezone = timezone;
}
}
Use the #PropertySource with this example
#Configuration
#PropertySource("classpath: demo.properties") // your properties file
public class Customer {
#Value("${height.customer.age}")
private final int age;
#Value("${height.customer.eu.timezone}")
private final String timezone;
public Customer(int age, String timezone){
this.age = age;
this.timezone = timezone;
}
public Customer(){}
}
And there won't be a conflict with the existing constructor because, values injecting via #PropertySource will be default values. If you provide values in the constructor, these will get overridden.

Redis - Why details are saving both as HASH and SET using Spring Data Redis?

I am new to Redis and developing Spring Boot + Spring Data Redis example. I am using CrudRepository, Example and ExampleMatchers API to do the searching from the Redis Key value store DB.
Now when I simply run my code, I saw that persons data saved as SET and HASH as well. Is this correct ? What's the use of saving the Person details both as SET and HASH
Showing all my code
public enum Gender {
MALE, FEMALE {
#Override
public String toString() {
return "Superwoman";
}
}
}
Species.java
#Builder
#Data
#AllArgsConstructor
#NoArgsConstructor
public class Species {
#Indexed
private String name;
}
Person.java
#Data
#Builder
#AllArgsConstructor
#NoArgsConstructor
#RedisHash("persons")
public class Person {
#Id
private String id;
#Indexed
private String firstname;
private String lastname;
#Indexed
private Gender gender;
private List<String> nicknames;
#Indexed
private Integer age;
private Map<String, String> physicalAttributes;
#Reference
private Person relative;
private Species species;
}
PersonRepository.java
public interface PersonRepository extends CrudRepository<Person, String>, QueryByExampleExecutor<Person> {
}
RedisExampleDemoApplication.java
#SpringBootApplication
public class RedisExampleDemoApplication implements CommandLineRunner{
RedisMappingContext mappingContext = new RedisMappingContext();
ExampleQueryMapper mapper = new ExampleQueryMapper(mappingContext, new PathIndexResolver(mappingContext));
#Autowired
private PersonRepository personRepository;
public static void main(String[] args) {
SpringApplication.run(RedisExampleDemoApplication.class, args);
}
#Override
public void run(String... args) throws Exception {
Person person = Person.builder().firstname("Walter").gender(Gender.MALE).age(50).build();
Person person1 = Person.builder().firstname("Savani").gender(Gender.FEMALE).age(35).build();
personRepository.save(person);
personRepository.save(person1);
// [firstname:Walter, gender:MALE, age:50]
RedisOperationChain operationChain = mapper.getMappedExample(Example.of(person, ExampleMatcher.matchingAny()));
System.out.println(operationChain.getOrSismember());
System.out.println("----------------------------------------------");
Person p = Person.builder().lastname("Foo").build();
RedisOperationChain roc = mapper.getMappedExample(Example.of(p));
System.out.println(" == "+roc.getOrSismember());
System.out.println("-- "+roc.getSismember());
}
}
May be it is late to answer now , the reason that SET is visible is because of the secondary Index. I.e in your example First name is annotated as Indexed. Redis consider this as secondary index which is default a SET.

Spring Data JPA Redis : Cannot write custom method based query

I have configured Spring Data JPA with Redis and using RedisRepositories with provides methods like find(), findAll() etc. All these methods seem to be working just fine, but I am not able to write my custom method like.
RedisEntity findByGenderAndGrade(String gender, String grade);
RedisEntity is a simple POJO Entity class. If you want any more info, please let me know in messages.
Following is my entity:
#Data
#RedisHash("test1")
public class RedisEntity implements Serializable {
#Id
#GeneratedValue
private String id;
private String name;
private String gender;
private Integer grade;
}
Repository:
#Repository
public interface TestRepository extends JpaRepository<RedisEntity, String> {
List<RedisEntity> findAllByGender(String gender);
List<RedisEntity> findAllByGrade(Integer grade);
}
Service/Controller:
#Override
public List<RedisEntity> getById(String id) {
return testRepository.findById(id); //returns data perfectly.
}
#Override
public List<RedisEntity> getAllByGender(String gender) {
return testRepository.findAllByGender(gender); //returns []
}
#Override
public void saveEntity(RedisEntity redisEntity) {
testRepository.save(redisEntity); // saves it in redis perfectly.
}
Also,
findByGender and findAllByGender both give [], although I can see data in my redis database and save it as well.
As requested by FrançoisDupire,
#Configuration
public class RedisConfig {
#Autowired
private DeploymentProperties deploymentProperties;
private static Logger logger = LoggerFactory.getLogger(RedisConfig.class);
#Bean
JedisConnectionFactory jedisConnectionFactory() {
RedisStandaloneConfiguration redisStandaloneConfiguration = new RedisStandaloneConfiguration("localhost", 6379);
redisStandaloneConfiguration.setPassword(RedisPassword.of("root"));
return new JedisConnectionFactory(redisStandaloneConfiguration);
}
#Bean
public RedisTemplate<String, Object> redisTemplate() {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(jedisConnectionFactory());
return template;
}
}
Also, I had referred this article: Baeldung article on Spring data redis
As mentioned by #JoshJ and verified by myself and others,
The solution to the problem is:
Adding #Indexed annotation
to all those columns/fields which need to be used with all finds.
#Data
#RedisHash("EmployeeDetails")
public class RedisEntity {
#Id
private String employeeId;
private String firstName;
private String lastName;
#Indexed
private String gender;
#Indexed
private String grade;
}
We have the Spring Data Redis Library which provides the scope to write the custom method.Attaching Sample code.
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-redis</artifactId>
<version>2.0.8.RELEASE</version>
</dependency>
Entity Definition
#Data
#RedisHash("EmployeeDetails")
public class RedisEntity {
#Id
private String employeeId;
private String firstName;
private String lastName;
private String gender;
private String grade;
}
Repository Definition
#Repository
public interface RedisEntityRepository extends CrudRepository<RedisEntity, String>{
List<RedisEntity> findAllByGenderAndGrade(String gender, String grade);
}
Implementation
#Component
public class RedisEntityImpl implements RedisEntityService {
#Autowired
private RedisEntityRepository redisEntityRepository;
#Override
public List<RedisEntity> getAllByGenderAndGrade(String gender, String grade) {
return redisEntityRepository.findAllByGenderAndGrade(gender,grade);
}
}
Properties
spring.cache.type = redis
spring.redis.host = localhost
spring.redis.port = 6379

Resources