서버 개발 발 들이기 6 에서 생겼던 문제와 해결 방법이다.
Post Api를 통해 받아온 책 제목 (title)을 DB 내 Book table에 저장하고자 한다.
이전에는 List에 저장하는 방식을 통해 메모리에 저장했다. 이제는 JdbcTemplate를 통해 DB에 저장한다.
Service, Repository를 Spring Bean에 등록하지 않고 생성자를 통해 JdbcReopsitory를 주입받는다.
기존 코드는 다음과 같다.
//Controller
package com.group.libraryapp.controller.book;
import com.group.libraryapp.service.book.BookService;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class BookController {
private final BookService bookService = new BookService(new JdbcTemplate());
@PostMapping("/book")
public void saveBook(@RequestParam String title){
bookService.saveBook(title);
}
}
//Service
package com.group.libraryapp.service.book;
import com.group.libraryapp.repository.book.BookMysqlRepository;
import com.group.libraryapp.repository.book.BookRepository;
import org.springframework.jdbc.core.JdbcTemplate;
public class BookService {
private final JdbcTemplate jdbcTemplate;
private final BookMysqlRepository bookRepository;
public BookService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
this.bookRepository = new BookMysqlRepository(jdbcTemplate);
}
public void saveBook(String title){
System.out.println("title = " + title);
bookRepository.saveBook(title);
}
}
//Repository
package com.group.libraryapp.repository.book;
import org.springframework.jdbc.core.JdbcTemplate;
public class BookMysqlRepository {
private final JdbcTemplate jdbcTemplate;
public BookMysqlRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
public void saveBook(String title){
String sql = "INSERT INTO book (title) VALUES (?)";
jdbcTemplate.update(sql, title);
}
}
다음과 같이 Layered Architecture의 형태이다.
1> @RestController로 선언된 Controller에서 JdbcTemplete객체를 받아 BookService에 생성자로 넘겨준다
2> Service는 BookMysqlRepository 생성자로 jdbcTemplate를 넘겨준다.
3> Repository는 Service에서 받은 jdbcTemplate을 통해 title을 저장한다.
하지만, 해당 코드는 서버 내부 에러가 발생하였다. 메모리에 저장한는 BookRepository에서는 정상 작동하나, BookMysqlRepository에서는 에러가 발생하였다. 그 이유는 내가 DI에 대해 정확하게 이해하지 못했던 것이다. 문제가 되는 부분은 다음과 같다.
@RestController
public class BookController {
private final BookService bookService = new BookService(new JdbcTemplate());
@PostMapping("/book")
public void saveBook(@RequestParam String title){
bookService.saveBook(title);
}
}
BookService의 생성자로 JdbcTemplate 객체를 넣어주면 되기에 다음과 같은 코드를 작성했었다.
하지만,BookContorller 클래스도 생성자를 통해 JdbcTemplate 객체를 주입 받아야 한다.
BookController의 생성자에서 JdbcTemplate를 인자로 받는 이유는 Spring의 의존성 주입(Dependency Injection, DI) 메커니즘을 이용하기 위함입니다. Spring이 애플리케이션을 초기화할 때, @RestController로 표시된 BookController 클래스에 필요한 빈을 생성하고 주입합니다. 이 과정에서 JdbcTemplate 또한 Spring이 관리하는 빈이므로 BookController의 생성자에 주입될 수 있습니다.
최종적인 해결 코드이다.
package com.group.libraryapp.controller.book;
import com.group.libraryapp.service.book.BookService;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class BookController {
private final JdbcTemplate jdbcTemplate;
private final BookService bookService;
public BookController(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
bookService = new BookService(jdbcTemplate);
}
@PostMapping("/book")
public void saveBook(@RequestParam String title){
bookService.saveBook(title);
}
}
따라서, 전체적인 흐름은 다음과 같다
1> BookController 클래스의 생성자를 통해 Spring 컨테이너에서 관리되고 있는 JdbcTemplate 객체를 주입받는다
2> jdbcTemplate 객체는 BookService의 생성자를 통해 수동으로 의존성을 주입한다.
추가적으로 spring이 JdbcTemplate 객체를 주입하기 위해 BookController(new JdbcTempalte()); 같은 작업을 수행하는 것은 아니다.
아니요, Spring Framework에서는 new JdbcTemplate()와 같이 직접적으로 객체를 생성하지 않습니다. 대신, Spring의 애플리케이션 컨텍스트가 관리하는 빈(Bean)을 통해 의존성을 주입합니다. 이러한 빈은 일반적으로 설정 파일이나 애너테이션을 통해 미리 정의됩니다.
또한, 내가 앞서 작성한 BookController에서도 JdbcTemplate 객체를 주입받았었는데, 이 JdbcTemplate 객체는 스프링 컨테이너에 등록되어있는 스프링 빈을 주입하는 것이다. 때문에 동일한 객체이다.
Spring Framework에서는 기본적으로 빈(Bean)을 싱글턴(Singleton) 스코프로 관리합니다. 즉, 애플리케이션 컨텍스트 내에서 하나의 빈 인스턴스만 존재하게 됩니다. 따라서 여러 컨트롤러에서 JdbcTemplate 빈을 의존성 주입 받는다면, 그것은 동일한 JdbcTemplate 객체 인스턴스를 공유하게 됩니다.