Spring Boot | MyBatis project structure can't wire layers - spring

Can't wire layers in Spring Boot | MyBatis application. The problem is probably happening when Service layer uses Mapper.
Controller method sample:
#Controller
#RequestMapping("demo")
public class MessageController {
#Autowired
private MessageService messageService;
#RequestMapping(value = "messages", method = RequestMethod.GET)
public String getMessages(ModelMap modelMap) {
modelMap.addAttribute(MESSAGE,
messageService.selectMessages());
return "messages";
}
Service class:
#Service
public class MessageService {
#Autowired // Not sure if I can use Autowired here.
private MessageMapper messageMapper;
public MessageService() {
}
public Collection<Message> selectMessages() { return
messageMapper.selectAll(); }
}
MyBatis Mapper:
#Mapper
public interface MessageMapper {
#Select("select * from message")
Collection<Message> selectAll();
}
UPDATE
It feels like I'm having some fundamental knowledge based mistake. Probably managing external libraries.
Here's maven pom.xml. Looks kind of overloaded, I faced a lot of errors managing different spring-boot packages. Starter for autoconfiguration included.
pom.xml
Here's the project structure:
UPDATE #2
I'm sure DB connection is working well, I'm able to track changes in MySQL Workbench while Spring Boot is executing schema.sql and data.sql. But somehow, MyBatis mapper methods throw NullPointerException and page proceeds with exit code 500. Seems like they can't connect.

MessageService isn't managed by spring.
You have to annotate the MessageService class with #Service annotation (also, after adding this annotation you can indeed use #Autowired inside the service class)
#Service
public class MessageService {
#Autowired
private MessageMapper messageMapper;
public Collection<Message> selectMessages() {
return messageMapper.selectAll();
}
}
and wire it to the controller with
#Autowired
private MessageService messageService
and use it in a method like this
#RequestMapping(value = "messages", method = RequestMethod.GET)
public String getMessages(ModelMap modelMap) {
modelMap.addAttribute(MESSAGE, messageService.selectMessages());
return "messages";
}

Related

Do java validation annotations run before AOP

I'm new in Spring Boot AOP.
Does an AOP method annotated with #Before run before java validation annotations (such as #NotNull)?
I have some other custom validations that need to run for every request but I need to run these validations after java validation annotations run.
Which one will run first?
my Controller:
#RestController
#RequestMapping("/users")
public class UserController {
private final UserService userService;
#Autowired
public UserController(UserService userService) {
this.userService = userService;
}
#PostMapping(value = "")
public List<User> getAllUsers(#Valid #RequestBody User user) {
return userService.getAllUsers();
}
}
and my advice:
#Aspect
#Component
public class AspectConfig {
#Pointcut(value = "within(#org.springframework.web.bind.annotation.RestController *)")
public void restControllers() {
}
#Before(value = "restControllers()")
public void logRequest(JoinPoint joinPoint) {
...
}
}
Does an AOP method annotated with #Before run before java validation annotations
No, it runs afterwards, just like you wish. See also this question. So you should be all set. Your logging advice should only be triggered if validation was successful, because only then the target method will be called.
You can implement a HandlerInterceptor if you wish to log/do something on the request level before validators kick in, see here.

Spring - how to #Autowired jpa repository if mock is working fine

I'm writing tests for my Controller class. I have configuration class which looks like this :
#Configuration
#Import(RoomRepository.class)
public class TestConfig {
#MockBean
RoomRepository roomRepository;
#Bean
ReservationService reservationService() {
return new ReservationService(roomRepository);
}
}
In controller test class I'm injecting config class :
#WebMvcTest(ReservationController.class)
#Import({TestConfig.class})
class ReservationControllerTest { ... }
Everything works well except one thing. I need "real" room repository - not mock. When I try to #Autowired that repository I'm receiving an error that it's interface, not class (which is quite obvious). How can I initialize working instance of RoomRepository ?
You can do it like this:
private RoomRepository roomRepository;
#Autowired
public YourClassName(RoomRepository roomRepository){
this.roomRepository = roomRepository
}

Is there a way to test nested objects without the web or persistence layer in Spring Boot?

I'm using JUnit5 to test a Spring Boot application. I want to test a #Service object, which uses #Autowired fields. I would like to mock another #Service object which is indirectly used by my test object. Concretely, I have the following (highly simplified) setup:
Object being tested:
#Service
public class MainService {
private #Autowired SubService subService;
public String test() {
return subService.test();
}
}
SubService:
#Service
public class SubService {
private #Autowired StringService stringService;
public String test() {
return stringService.test();
}
}
StringService:
#Service
public class StringService {
public String test() {
return "Real service";
}
}
Test class used:
#SpringBootTest
public class MainServiceTest {
private #Autowired MainService mainService;
private #MockBean StringService stringService;
#BeforeEach
public void mock() {
Mockito.when(stringService.test()).thenReturn("Mocked service");
}
#Test
public void test() {
assertEquals("Mocked service", mainService.test());
}
}
The above works if I run the test class as a #SpringBootTest, but this loads the full application and is very slow. I also want to avoid #WebMvcTest since I don't need the web server, or #DataJpaTest since I don't need persistence. I don't want to mock SubService, as it contains functionality I want to test together with the MainService.
I tried the following:
#ExtendWith(SpringExtension.class) => throws NoSuchBeanDefinitionException, it seems the autowiring does not work in this case
#ExtendWith(MockitoExtension.class) and using #InjectMocks and #Mock instead of the Spring annotations => as the StringService is not a direct field of the MainService being tested, this does not work.
Is there a way to use the spring dependency injection system without loading the web server or persistence layer, or alternatively not use Spring tests but allow for 'nested' dependency injection?
You can use profiling (i.e Spring #Profile) to avoid loading the whole application. It will look something like below:
#Profile("test")
#Configuration
public class TestConfiguration {
#Bean
public MainService mainService() {
return new MainService();
}
#Bean
public SubService subService() {
return new SubService();
}
// mock the StringService
#Bean
public StringService stringService() {
return Mockito.mock(StringService.class);
}
}
then use that profile with `#SpringBootTest(classes = TestConfiguration.class), it will look something like below:
#ActiveProfiles("test")
#SpringBootTest(classes = TestConfiguration.class)
class MainServiceTest {
#Autowired
private MainService mainService;
#Test
public void test() {
// configure behavior using apis like when(), basically however you
// want your mock to behave
}
}
This will load only the beans defined in the class TestConfiguration.
NOTE: Since your question is more about how to avoid loading the whole application, I've answered focusing on that. The above approach will get the job done, but I'd prefer constructor wiring over any other mode of dependency injection on any given day, it's easier to maintain and test(like cases where you want to mock).

Autowiring of Service and Service Implementation class

Following are my code
#RestController
public class EmployeeController {
#Autowired
EmployeeService empService;
public EmployeeController (EmployeeService Impl empServiceImpl) {
super();
this.empService = empServiceImpl;
}
}
#Service
public interface EmployeeService {
public List<EmployeeDTO> getAllEmployeeDetails()
}
public class EmployeeServiceImpl {
public List<EmployeeDTO> getAllEmployeeDetails(){
//methods business logic and repo call goes here
}
}
When I start my server I am getting below error.
Parameter 1 of constructor in
com.app.in.controller.EmployeeController required a bean of type
'com.app.in.service.EmployeeServiceImpl' that could not be found
My understanding might be wrong. If I annotate the EmployeeSeriveImpl class also with #Service then it working.Is that is the correct way to do it ? My question is the service interface is annotated with #Service still why its implementation is also required to annotation. Please let me know if I miss something in that ? What is the standard method to solve this issue ?
You can get your dependency injected using a constructor. And #Autowired is optional in this case.
This is your example, but with a few corrections:
#RestController
public class EmployeeController {
// private final is a good practice. no need in #Autowire
private final EmployeeService empService;
// this constructor will be used to inject your dependency
// #Autowired is optional in this case, but you can put it here
public EmployeeController (EmployeeService empServiceImpl) {
this.empService = empServiceImpl;
}
}
I assume you have an interface EmployeeService and class EmployeeServiceImpl which implements that interface and is Spring Bean.
Something like this:
#Service
public class EmployeeServiceImpl implements EmployeeService {}
Why this #Service is needed? When you put this annotation on your class, Spring knows this is a bean that Spring should manage for you (container will create an instance of it and inject it wherever it is needed).
Check Spring docs to get more details about Dependency Injection.
The Spring team generally advocates constructor injection, as it lets you implement application components as immutable objects and ensures that required dependencies are not null.

Spring constructor injection using java config

I have a Class that accepts the following constructor
public Student(int id, String name, Map<String, List<String>> mapInject) {
super();
this.id = id;
this.name = name;
this.mapInject = mapInject;
}
And from spring Java Config, I am injecting the constructor args like below..
#Configuration
public class JavaConfig {
#Bean
public Employee getEmployeeBean() {
Map<String,List<String>> mapInject = new HashMap<String,List<String>>();
//Add map element
return new Employee(3123,"John",mapInject);
}
}
Am i doing constructor injection here? Is this the right way to do so?
I wouldn't use Spring to handle this bean creation, unless you want ALL employees to have the same id and name which I doubt.
The power behind Spring is its Dependency Injection (DI) where you define beans for providers such as database managers, services, etc. and inject those into your components. Defining a #Bean like you have there serves no purpose as now you can only inject employees with an id of 3123 and name John.
It's important to understand that just because you are using Spring it doesn't mean EVERYTHING needs to be handled as a bean - you will always need standard POJOs for housing and passing around state (such as your Employee class) which doesn't need to have anything to do with Spring.
Down the line you might have an EmployeeService for example which houses business logic to fetch employees from a database or something, this could then be configured as a bean so it can be injected across the application.
EDIT
#Configuration
public class JavaConfig {
#Bean
#Autowired //assuming a sessionfactory been is configured elsewhere
public EmployeeService employeeService(final SessionFactory sessionfactory) {
return new EmployeeService(sessionFactory);
}
}
You could then inject this anywhere (maybe in a controller for example):
#RestController
public class EmployeeController {
private final EmployeeService employeeService;
#Autowired
public EmployeeController(final EmployeeService employeeService) {
this.employeeService = employeeService;
}
}
Where the EmployeeController doesn't need to know or care that the userService has a DB connection and doesn't need to worry about configuring it as Spring will handle all of that.

Resources