I have this dependency:
<dependency>
<groupId>org.hibernate.validator</groupId>
<artifactId>hibernate-validator</artifactId>
</dependency>
Which have it's version managed by
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
</parent>
And I have this piece:
import javax.validation.constraints.NotNull;
//other imports ommited
#Component
#ConfigurationProperties(prefix = "collector")
public class CollectorProperties {
#NotNull
private String urlCDI;
//getters and setters
}
And my SpringApplication.run class has this pice:
#SpringBootApplication
#EnableConfigurationProperties
#ComponentScan({ "otherPackages", "packageWhereCollectorPropertiesIs" })
When I have my application.properties with this line
collector.urlCDI=https://www.cetip.com.br/Home
It works as it was supposed inside other spring beans:
//#Component class variables:
#Autowired
private CollectorProperties props;
//inside some method
URL url = new URL(props.getUrlCDI());
But when I remove it or alter property key I get lots of NPE instead of validations errors. What I'm doing wrong? Doesn't hibernate-validator contains an implementation of javax.validation.constraints.NotNull interface?
Add ´#Validated' annotation to your properties class
Related
I have developed a test project to reproduce this issue.
This is a project structure:
pom.xml file:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.7.1</version>
<relativePath/>
</parent>
<modelVersion>4.0.0</modelVersion>
<groupId>org.example</groupId>
<artifactId>value-updated-after-fail-spring</artifactId>
<version>1.0-SNAPSHOT</version>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-lang3</artifactId>
</dependency>
</dependencies>
</project>
Persone.java file:
#Entity
#NoArgsConstructor(access = AccessLevel.PROTECTED)
#RequiredArgsConstructor
#AllArgsConstructor
#Builder
#Getter
#Setter
#ToString
#FieldDefaults(level = AccessLevel.PRIVATE)
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
Long id;
#Column(nullable = false)
#NonNull
String name;
}
PersonRepository.java file:
#Repository
public interface PersonRepository extends JpaRepository<Person, Long> {}
PersonService.java file:
#Component
public class PersonService {
private final PersonRepository repository;
public PersonService(PersonRepository repository) {
this.repository = repository;
}
#Transactional
public Person create(String name) {
return repository.save(new Person(name));
}
#Transactional
public Person save(Person person) {
if(StringUtils.isBlank(person.getName())) {
throw new RuntimeException();
}
Person personFromDB = getById(person.getId());
personFromDB.setName(person.getName());
return repository.save(personFromDB);
}
#Transactional
public Person getById(Long id) {
return repository.findById(id)
.orElseThrow(NullPointerException::new);
}
#Transactional
public void deleteAll() {
repository.deleteAll();
}
}
application.properties file:
spring.datasource.url=jdbc:h2:mem:test;DB_CLOSE_DELAY=-1
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=create-drop
spring.jpa.show-sql=true
spring.h2.console.enabled=true
spring.datasource.driver-class-name=org.h2.Driver
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
TestApplicationConfiguration.java file
#SpringBootConfiguration
#EnableAutoConfiguration
#EnableJpaRepositories
#EntityScan("net.example.model")
#ComponentScan(basePackages = "net.example")
public class TestApplicationConfiguration {}
PersonServiceTest.java file:
#DataJpaTest
#AutoConfigureTestDatabase(replace = Replace.NONE)
class PersonServiceTest {
#Autowired
private PersonService service;
#AfterEach
void tearDownEach() {
service.deleteAll();
}
#Test
void rename() {
String expected = "name";
Person person = service.create(expected);
Person personFromDB = service.getById(person.getId());
personFromDB.setName("");
assertThrows(RuntimeException.class, () -> service.save(personFromDB));
assertEquals(expected, service.getById(personFromDB.getId()).getName());
}
}
The issue: Last assertion fails
org.opentest4j.AssertionFailedError:
Expected :name
Actual :
What I already tried to fix this?
I tried to remove the #Transactional annotation for the PersonService#getById method to avoid getting the entity from the cache. - This didn't fix the issue
I tried to add spring.cache.type=none to the application.properties file to disable the cache. - This didn't fix the issue
Why do I think it's the cache?
When I debugged this, I found that the PersonService#getById() method doesn't return actual data, but the method returns a cached object with a changed title.
The database isn't changed after calling the PersonService#save method because it throws an exception
Perhaps I'm not developing the tests correctly.
Maybe I should change the method of saving changed data.
Please share best practices and articles to better understand how to update data and how to properly configure and write tests for Spring Boot applications.
Thanks a lot for the comment from Andrey B. Panfilov.
I investigated the #Transactional and the first level cache of Hibernate.
Indeed, each test method call in a class annotated with the #DataJpaTest annotation creates, runs, and rollbacks a transaction.
Each transaction creates and closes the Hibernate session. As we know, the first level cache exists until the session closes. That's why it's also called session cache.
You can see the evidence in the following screenshots:
In the first screenshot, you can see that SpringExtension, which is defined in the #DataJpaTest annotation, opens a new session before each test is called.
In the second screenshot, you can see that SpringExtension closes the session after each test is called.
I decided to override the default transaction propagation:
#Transactional(propagation = Propagation.NEVER) it doesn't create a transaction when the method is called and throw an exception if the method is called in an existing transaction
Links that helped me:
Data Access
Transaction Propagation and Isolation in Spring #Transactional
Transaction Propagation with illustrations
Hibernate Caching - First Level Cach
I am using JpaRepository, here is my code
public Interface EmpRepository extends JpaRepository<Employee, Integer> {}
class EmployeeServicImpl {
private EmpRepository empRepository;
#Autowired
EmployeeServicImpl (EmpRepository theRepository) {
this.empRepository = theRepository;
}
}
added below dependencies in my pom.xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
While start the application, resolution of declared constructor of bean from class loader : NoClassDeffFoundError : /org/springframework/data/jpa/repository/Repository
Some points,
Keep your repository interface in a separate package.
Use #Repository on your repository.
Use #Service or #Component annotation at EmployeeServicImpl class.
It appears as when you're starting the application, Spring is trying to find EmpRepository dependency to instantiate your service but is not able to find the repository since it is not declared as a #Repository by you.
Further, reason for using #Service is so that EmployeeServicImpl becomes available to Spring too.
#Configuration
#EnableStateMachineFactory
public class StateMachineConfig extends EnumStateMachineConfigurerAdapter
<States, Events> {
// configuring...
}
public enum Events {
CONFIRM_RESET,
CANCEL_RESET
// other events
}
public enum States {
INITIAL,
STARTING_ORDER
// other states
}
#Service
#Slf4j
public class OrderService {
#Autowired
private StateMachineFactory<States, Events> stateMachineFactory;
// Could not autowire. No beans of 'StateMachineFactory<States, Events>' type found.
}
#EnableStateMachineFactory annotation does not work. Could not autowire. No beans of StateMachineFactory<States, Events>' type found.
In the same time after using #EnableStateMachine I can autowire 1 statemachine.
oh, this is a version of boot starter problem, i change my version from 2.2.1 to 2.5.4 and the problem get away
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<version>2.5.4</version>
</dependency>
<dependency>
<groupId>org.springframework.statemachine</groupId>
<artifactId>spring-statemachine-starter</artifactId>
<version>3.0.1</version>
</dependency>
This solved my problem
I was following this JavaBrains tutorials of Spring Boot.
My project structure is as follows:
CourseApiApp.java:
#SpringBootApplication
#ComponentScan(basePackages = {
"com.bloodynacho.rishab.topics"
})
#EntityScan("com.bloodynacho.rishab.topics")
public class CourseApiApp {
public static void main(String[] args) {
SpringApplication.run(CourseApiApp.class, args);
}
}
TopicController.java:
#RestController
public class TopicController {
#Autowired
private TopicService topicService;
#RequestMapping(
value = "/topics"
)
public List<Topic> getAllTopcs() {
return topicService.getAllTopics();
}
}
TopicService.java:
#Service
public class TopicService {
#Autowired
private TopicRepository topicRepository;
public List<Topic> getAllTopics() {
List<Topic> topics = new ArrayList<>();
this.topicRepository
.findAll()
.forEach(topics::add);
return topics;
}
}
Topic.java:
#Entity
public class Topic {
#Id
private String id;
private String name;
private String description;
}
TopicRepository.java:
#Repository
public interface TopicRepository extends CrudRepository<Topic, String>{
}
pom.xml:
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.apache.derby</groupId>
<artifactId>derby</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
I was using the lombok #Getter, #Getter and #AllArgsConstructor in Topic.java but I removed it after reading one of the answers here.
I read this1, this2, this3
Still, I get
***************************
APPLICATION FAILED TO START
***************************
Description:
Field topicRepository in com.bloodynacho.rishab.topics.TopicService required a bean of type 'com.bloodynacho.rishab.topics.TopicRepository' that could not be found.
The injection point has the following annotations:
- #org.springframework.beans.factory.annotation.Autowired(required=true)
Action:
Consider defining a bean of type 'com.bloodynacho.rishab.topics.TopicRepository' in your configuration.
Process finished with exit code 1
EDIT: I read this explaining how even without actually implementing the interface the #Autowired works. I understand the solution, but I don't understand how to solve my issue. Clearly, there is some problem with the way Spring Data is set up and configured (as mentioned in the answer)
Because if your other packages hierarchies are below your main application with the #SpringBootApplication annotation, you’re covered by implicit components scan.
Therefore, one simple solution can be done by following 2 steps:
Rename the package of main class to be com.bloodynacho.rishab.
(That is what I suggest that the complete package name of main app. is supposed to be root of other packages.)
Remove #ComponentScan and #EntityScan annotation.
(Although #ComponentScan is different from #EntityScan, it can be also removed in my experience.)
How mock 2 beans when one is into second ?
public class A {
...
}
public class B {
private A a;
}
I tried:
#MockBean
private A a;
#InjectMocks
private B b;
#Before
public void executedBeforeEach() {
MockitoAnnotations.initMocks(this);
}
but had exception:
org.mockito.exceptions.base.MockitoException:
Cannot instantiate #InjectMocks field named 'B'.
You haven't provided the instance at field declaration so I tried to construct the instance.
However, I failed because: the type 'B' is an interface.
spring version:
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.8.RELEASE</version>
<relativePath/>
</parent>
test dependency:
<dependencies>
...
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
how to do it right ?where did I make a mistake ?
You just need to autowire B. By using the annotation #MockBean you are telling the test Spring context to replace the actual bean of type A with a mock and this will automatically be injected into everywhere A in included (i.e. in your B bean).
#MockBean
private A a;
#Autowire
private B b;
This is under the assumption that you are annotating your test class with #SpringBootTest