일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | ||||||
2 | 3 | 4 | 5 | 6 | 7 | 8 |
9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 | 17 | 18 | 19 | 20 | 21 | 22 |
23 | 24 | 25 | 26 | 27 | 28 |
- Packet Network
- Git
- draganddrop
- unity
- Digital Ocean
- mongoDB
- Unity IAP
- linux
- nodejs
- screencapture
- SDK upgrade
- Google Refund
- server
- Spring Boot
- express
- Camera Zoom
- Camera Movement
- --watch
- spread 연산자
- MySQL
- rpg server
- docker
- springboot
- Google Developer API
- react
- java
- critical rendering path
- Unity Editor
- css framework
- OverTheWire
- Today
- Total
우당탕탕 개발일지
[SpringBoot] 3. 리팩토링 & 스프링 컨테이너 본문
리팩토링하기
이전에 UserController
에서 유저생성, 조회, 수정,삭제 API를 구현하였다. 이것을 리팩토링 해보고, 스프링컨테이너에 대해 알아보자. 컨트롤러에서 하던 작업들은 다음 3개로 분리될것이다.
Controller : API의 진입지점으로써 HTTP Body를 객체로 변환한다
Service : 현재 유저가 있는지등을 확인하여 예외처리 진행
Repository : SQL을 사용해 실제 db와 통신을담당.
총 3개의 역할군으로 나누어서 작업을 하도록한다. 그럼 각 코드를 보도록 하자UserController.java
@RestController
public class UserController {
private UserService userService;
public UserController(JdbcTemplate jdbcTemplate){
userService = new UserService(jdbcTemplate);
}
@PostMapping("/user")
public void saveUser(@RequestBody UserCreateRequest request){
userService.saveUser(request);
}
@GetMapping("/user")
public List<UserResponse> getUsers(){
return userService.getUsers();
}
@PutMapping("/user")
public void updateUser(@RequestBody UserUpdateRequest request){
userService.updateUser(request);
}
@DeleteMapping("/user")
public void deleteUser(@RequestParam String name){
userService.deleteUser(name);
}
}
UserService.java
//로직을 담당함.
public class UserService {
private final UserRepository userRepository;
public UserService(JdbcTemplate _template){
userRepository = new UserRepository(_template);
}
public void saveUser(UserCreateRequest request){
userRepository.saveUser(request.getName(), request.getAge());
}
public List<UserResponse> getUsers()
{
return userRepository.getUsers();
}
public void updateUser(UserUpdateRequest request){
if(!userRepository.isUserNotExist(request.getId())){
throw new IllegalArgumentException("invalid id");
}
userRepository.updateUserName(request.getName(), request.getId());
}
public void deleteUser(String name){
if(!userRepository.isUserNotExist(name)) throw new IllegalArgumentException("invalid name");
userRepository.deleteUser(name);
}
}
UserRepository.java
//실제db와의 통신을 담당함.
public class UserRepository {
JdbcTemplate jdbcTemplate;
public UserRepository(JdbcTemplate _template){
jdbcTemplate =_template;
}
public boolean isUserNotExist(int uid){
String querySQL = "select * from user where id = ?";
return jdbcTemplate.query(querySQL, (res,rowNum)->0,uid).isEmpty();
}
public boolean isUserNotExist(String name){
String querySQL = "select * from user where name = ?";
return jdbcTemplate.query(querySQL, (res,rowNum)->0,name).isEmpty();
}
public void saveUser(String name, int age){
String sql = "insert info user (name, age) values (?,?)";
jdbcTemplate.update(sql, name, age);
}
public List<UserResponse> getUsers(){
String sql = "select * from user";
return jdbcTemplate.query(sql, (rs, rowNum) -> {
long id = rs.getLong("id");
String name = rs.getString("name");
int age = rs.getInt("age");
return new UserResponse(id,name,age);
});
}
public void updateUserName(String name, int uid){
String sql = "update user set name = ? where id= ?";
jdbcTemplate.update(sql,name, uid);
}
public void deleteUser(String name){
String sql = "delete from user where name = ?";
jdbcTemplate.update(sql,name);
}
}
스프링 컨테이너
userController.java
의 생성자에서 JdbcTemplate을 받아오고 있는데, 이것은 어디에서 자동으로 오는걸까?? 그리고 UserController.java
와 UserService.java
에서는 이 jdbcTemplate을 사용하고있지 않다. 그렇다면 UserRepository.java
에서 바로 받아올수는 없는걸까?
이는 바로 UserController 위에 있는 @RestController
의 역할이다. 이 어노테이션은 UserController를 스프링 빈으로 설정한다.
스프링 빈??
서버가 시작되면, 스프링 서버 내부에 거대한 컨테이너를 만든다. 그리고 이 컨테이너 안에는 여러가지 클래스가 들어간다. 예를들면, 실습에 만든 UserController같은것들. 이때 다양한 정보도 들어있고, 인스턴스화도 진행된다. 즉, 스프링 컨테이너 안에 내가 만든 클래스들이 들어간다. 이때 스프링 컨테이너 안으로 들어간 클래스들을 스프링 빈
이라고 한다.
UserController
에서는 JdbcTemplate이 필요하다. 그런데 스프링 컨테이너 안에는 이미 jdbctemplate이 들어있다. 따라서 인스턴스화 하면서 jdbcTemplate을 넣어주는 것이다. jdbcTemplate은 build.gradle의 dependencies 에 넣어줬기 때문에 스프링 컨테이너에 들어간다.
현재 UserRepostory 에서 JdbcTemplate을 가져오지 못하는 이유
jdbtemplate을 바로 가져오려면 UserRepository 가 스프링빈이어야 하는데, 일반 클래스이기 때문. UserRepository 를 스프링빈으로 설정하기 위해서는 클래스이름에 @Repository
어노테이션을 추가한다. UserService를 스프링빈으로 설정하기 위해서는 클래스 이름에 @Service
어노테이션을 추가한다.
UserRepository가 스프링빈이 되면 생성자에서 jdbcTemplate을 받아올 수 있다. userController 와 userService에서는 사용하지 않는 jdbcTemplate을 제거할 수 있게된다.
스프링 컨테이너를 사용하는 이유
만약 db가 변경되어, userRepositoryA 를 userRepositoryB 로 변경되었다고 가정해보자.
userService는 userRepositoryA 에 의존되어있었기 때문에, userService의 코드 또한 변경해주어야 한다.
이를 방지하기 위해서는 보통 java의 interface를 사용한다. userService에는 interface IUserRepository
를 사용하고, userRepositoryA와 userRepositoryB는IUserRepository를 상속받아 구현하는 것이다. 이렇게 하면 userService를 수정하지 않아도 된다. 마치 모듈을 갈아끼우는 느낌이다. 하지만 이것도, 수정이 된다면 모듈을 변경해줘야한다. 코드를 수정하는 것은 피할 수 없는것이다.
이를 위해 나온 것이 스프링 컨테이너이다. 컨테이너가 userService를 대신 인스턴스화하고, 그때 알아서 userRepository를 결정해준다. userRepositoryA, userRepositoryB , userService가 모두 스프링빈이라면 , 인스턴스화 과정에서 userService가 어떤 repository를 받을 지 결정하기 때문에 코드를 수정하지 않아도 된다.
@Service
public class UserService{
IUserRepository repository
public UserService(IUserRepository repo){
repository = repo;
}
}
@Primary
@Repository
public class UserRepositoryA implements IUserRepository{
@Override
public void saveUser(){}
}
public class UserRepositoryB implements IUserRepository{
@Override
public void saveUser(){}
}
이렇게 제작할 경우, 스프링 빈인 UserRepositoryA 가 자동으로 UserService에 전달된다.
결과적으로 우리는 모듈이 변경되더라도 UserService의 코드를 수정하지 않아도 된다. 둘다 @Repository로 설정되어있을 경우, 추가적으로 @Primary 를 통해 어떤 IUserRepository를 사용할지 결정할 수 있다.
스프링 빈 등록하기
다양한 어노테이션을 이용해 등록가능하다.
@Service , @Repository
예시에서 사용된 방법이다. @Service, @Repository 는 개발자가 직접 만든 클래스를 스프링빈으로 등록할 때 사용한다. UserService, UserRepository 가 여기에 해당한다.
@Configuration 과 @Bean의 조합
@Configuration 과 @Bean은 라이브러리, 프레임워크에서 만든 클래스를 등록할 때 사용한다. JdbcTemplate 이 여기에 해당한다.
@Configuration : 클래스에 붙인다. @Bean 을 사용할때 함께 사용해야 한다.
@Bean : 메소드에 붙인다.
예시는 다음과 같다. 보통 configuration 은 config라는 폴더를 만들어 따로 관리한다. userRepository 에 @Repository 가 붙지 않아도 스프링빈으로 등록되고, userService에서 받아올 수 있다.
@Configuration
public class UserConfiguration {
@Bean
public UserRepository userRepository(JdbcTemplate jdbcTemplate){
return new UserRepository(jdbcTemplate);
}
}
@Component
주어진 클래스를 컴포넌트로 간주하고, 스프링 서버 생성 시 자동으로 감지된다. 다른 모든 어노테이션들 모두 @Component 덕분이다. 어노테이션들의 어머니인 느낌이다. 이는 Controller, Service, Repository 모두에 해당되지않는데 스프링빈으로 등록할 필요가 있을 경우 사용한다.
스프링 빈 주입받는방법
1.생성자를 이용해 주입받는 방식. 현재 사용중인 방식이다.(권장)
2.setter 와 @Autowired 를 사용하는 방법 - 다른 인스턴스로 교체될 가능성이 있음.
public class UserService{
private JdbcTemplate jdbcTemplate;
@Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate){
this.jdbcTemplate = jdbcTemplate;
}
}
3.필드에 직접 @Autowired 를 사용 - 테스트가 어렵다
public class UserService{
@Autowired
private JdbcTemplate jdbcTemplate;
}
@Qualifier
여러개의 후보군이 있을때 그중 하나를 특정해서 가져오고 싶을 때 사용한다.
IUserRepository 에 UserRepositoryA, B, C 가 있을때 다음처럼 원하는 서비스를 특정할 수 있다. qualifier은 주입하는쪽과 주입받는쪽 모두에서 사용가능하다. 우선순위는 @Qualifier > @Primary 이다.
public class UserService{
IUserService service
public UserService(@Qualifier("UserRepositoryA") IUserService service){
this.service = service;
}
}
'Server' 카테고리의 다른 글
[SpringBoot] 4. JPA 적용하기 ( Repository 계층) (0) | 2025.01.06 |
---|---|
[SpringBoot] vscode 에서 import ... not exist 현상 (0) | 2025.01.06 |
[SpringBoot] 2. MySQL 연결하기 (0) | 2025.01.04 |
[SpringBoot] 1. spring boot 시작하기 (1) | 2025.01.03 |
[SpringBoot] Dependency requires at least JVM runtime version 17. This build uses a Java 11 JVM. (0) | 2025.01.02 |