public void good(U u){if (u.a <= 60 && u.a > 10 && u.b > 130) { c();}}
public class U {int a; int b;}
public void getOnTheRideIfPossible(User user) {
if (user.canGetOnTheRide()) {
ride(); }
}
public class User {
private int age;
private int height;
public boolean canGetOnTheRide() {
return this.age > 10 && this.age <= 60 && this.height > 130;
} }
controller@PutMapping("/user") // PUT /user
public void updateUser(@RequestBody UserUpdateRequest request) {
// 수정하고자 하는 User가 데이터베이스에 존재하는지 검증
String sqlCheck = "SELECT * FROM user WHERE id = ?";
// user가 존재하지 않는다면 빈 리스트가 반환될 것임.
boolean isUserNotExist = jdbcTemplate.query(sqlCheck, (rs, rowNum) -> 0, request.getId()).isEmpty();
if (isUserNotExist) {
throw new IllegalArgumentException();
}
String sql = "UPDATE user SET name = ? WHERE id = ?";
jdbcTemplate.update(sql, request.getName(), request.getId());
}
Controller, Service, Repository
Controller : API의 진입 지점으로, HTTP body를 객체로 변환(DTO) 하는 역할만 수행 @PutMapping("/user") // PUT /user
public void updateUser(@RequestBody UserUpdateRequest request) {
userService.updateUser(jdbcTemplate, request);
}
Service : 요청 중인 사용자가 존재하는지 아닌지를 검증하고 예외처리하는 역할을 수행package com.group.libraryapp.service.user;
import com.group.libraryapp.dto.user.request.UserUpdateRequest;
import com.group.libraryapp.repository.user.UserRepository;
import org.springframework.jdbc.core.JdbcTemplate;
public class UserService {
private final UserRepository userRepository;
public UserService(JdbcTemplate jdbcTemplate) {
userRepository = new UserRepository(jdbcTemplate);
}
public void updateUser(UserUpdateRequest request) {
if (userRepository.isUserNotExist(request.getId())) {
throw new IllegalArgumentException();
}
userRepository.updateUserName(request.getName(), request.getId());
}
}
Repository : SQL을 사용해서 데이터베이스와 통신하는 역할을 수행package com.group.libraryapp.repository.user;
import org.springframework.jdbc.core.JdbcTemplate;
public class UserRepository {
private final JdbcTemplate jdbcTemplate;
public UserRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public boolean isUserNotExist(long id) {
String sqlCheck = "SELECT * FROM user WHERE id = ?";
return jdbcTemplate.query(sqlCheck, (rs, rowNum) -> 0, id).isEmpty();
}
public void updateUserName(String name, long id) {
String sql = "UPDATE user SET name = ? WHERE id = ?";
jdbcTemplate.update(sql, name, id);
}
}
JdbcTemplate은 데이터베이스에 접근할 때만 필요한데, 이걸 controller에서 선언하면 계속 파라미터로 넘겨서 내려줘야하는 불편함이 있으므로 UserController와 스프링 컨테이너지금까지 작성한 코드의 의문점들:
클래스 안의 함수를 사용하기 위해서는 인스턴스화(new 클래스)가 필요하다. 그러나 우리는 현재 클래스를 인스턴스화 하지 않았다. 이게 어떻게 가능한걸까?
UserController의 생성자는 JdbcTemplate라는 클래스를 필요로 한다(의존한다). 그러나 우리는 JdbcTemplate에 대해 한번도 처리한 적이 없음에도 가져와서 사용했다.
@RestController 덕분이다.@RestController 는 해당 클래스를 API의 진입지점으로 만들어줄 뿐 아니라, 해당 클래스를 Spring Bean으로 등록시킨다.
@SpringBootApplication을 main 클래스 위에 붙이면 서버 실행에 필요한 이런저런 설정들을 자동으로 해준다고 앞에서 이해했었는데, 이 과정을 조금 더 구체적으로 생각해보자.
우리가 인텔리제이에서 시작 버튼을 눌러서 main 클래스를 작동시키면, @SpringBootApplication은 자동으로 스프링 서버 내부에 컨테이너를 생성한다. 이렇게 생성된 서버 내부에는 스프링 빈으로 등록된 클래스들이 들어가있다. 이 때, 해당 클래스들의 인스턴스화도 이루어진다.


spring-boot-autoconfigure:2.7.6에 위치해있다.

이런 코드를 우리가 어떻게 추가한 걸까? build.gradle 파일의 dependencies 파트를 확인해보면 우리가 추가한 것을 확인할 수 있다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa' // 얘가 jdbcTemplate를 가져와줌
implementation 'org.springframework.boot:spring-boot-starter-web'
runtimeOnly 'com.h2database:h2'
runtimeOnly 'mysql:mysql-connector-java'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
그렇다면 왜 UserRepository는 JdbcTemplate를 가져오지 못할까?
JdbcTemplate를 가져오려면 UserRepository가 Spring bean이어야 하는데, UserRepository는 Spring bean이 아님
즉, UserRepository에서 JdbcTemplate을 바로 가져오기 위해서는 해당 클래스를 Spring Bean으로 만들어줘야 함.
이를 위해서는 @Repository라는 어노테이션을 붙여줌.
| 계층 | 어노테이션 |
|---|---|
| Controller 계층 | @RestController |
| Repository 계층 | @Repository |
| Service 계층 | @Service |
요구사항: 책 이름을 메모리(DB 사용하지 않음)에 저장하는 API 구현하기 (단,
Service,Repository는 스프링 빈이 아니어야 함)
Service, Repository 등을 스프링빈으로 등록하지 않았다고 가정하자.Repository 에서 ArrayList 등을 활용해 저장했다고 하자.Service 에서 private final <Repository명> <repository명> = new <Repository명>() 등 레포지토리를 불러오는 부분을 수정해줘야 한다.Repository를 가져다 사용했지만 실제 로직에서는 100군데, 1000군데에서 가져다 사용할 수 있다.부분적인 해결방법으로는, 자바의 interface 를 활용하는 것이다. Service 폴더 내에 interface 타입으로 해서 BookRepository 이런 코드를 만들어주고, 아래와 같이 수정해준다.
public class BookService {
private final BookRepository bookRepository = new BookMemoryRepository();
// 이후에 BookMemoryRepository() 부분만 바꿔주면 됨
}
public interface BookRepository {
public void save(String bookName);
}
public class BookMemoryRepository implements BookRepository {
private final List<String> books = new ArrayList();
@Override
public void save(String bookName) {
books.add(bookName);
}
}
public class BookMySqlRepository implements BookRepository {
@Override
public void save(String bookName) {
// jdbcTemplate.....
}
}
물론 수정해야 하는 부분이 적어지기는 했지만 여전히 Service 코드를 불러와 사용한 부분마다 찾아가서 바꿔줘야 한다는 사실은 변하지 않는다.
Service 코드를 대신 인스턴스화 하고, 그때 알아서 Repository를 결정함interface를 extends 한 클래스 중 하나를 선택하는 방식을 제어의 역전 (IoC, Inversion of Control)이라 부른다.interface를 extends 한 클래스 중 하나를 선택해서 넣어주는 과정을 의존성 주입(Dependency Injection)이라고 한다.@Primary 어노테이션을 이용해 우선권을 제어할 수 있다@Service
public class BookService {
private final BookRepository bookRepository;
public BookService(BookRepository bookRepository) {
this.bookRepository = bookRepository;
}
}
public interface BookRepository {
public void save(String bookName);
}
@Repository
public class BookMemoryRepository implements BookRepository {
@Override
public void save(String bookName) {
println("Memory Repository " + bookName);
}
}
@Repository
@Primary // 우선권을 부여하는 어노테이션!!
public class BookMySqlRepository implements BookRepository {
@Override
public void save(String bookName) {
println("MySQL Repository " + bookName);
}
}
@Configuration (클래스에 붙이는 어노테이션으로, Bean을 사용할 때 같이 사용해줘야 함), @Bean(메소드에 붙이는 어노테이션으로, 메소드에서 반환되는 객체를 컨테이너에 등록함)@Service, @Repository@Component: 주어진 클래스를 컴포넌트로 간주하여 이 클래스들은 컨테이너가 뜰 때마다 자동으로 감지되고, 스프링빈이 되게 됨. (@Service, @Repository, @RestController 에도 내장되어있음)@RestController
public class UserController {
private final UserService userService;
public UserController(UserService userService) { // 생성자 추가
this.userService = userService;
}
setter(), @Autowired 사용하기: 누군가 setter()를 사용할 위험이 있음@RestController
public class UserController {
// setter 방식
private UserService userService;
@Autowired
public void setUserService(UserService userService) {
this.userService = userService;
}
@Autowired 사용하기: 테스트가 어려워짐@Qualifier vs @Primary@Qualifier > @Primary