기존 UserService, UserRepository는 JdbcTemplate을 사용하기 위해서 의존성 주입을 사용하였다. 그런데 UserRepository는 그러지 않아도 된다?
build.gradle을 통해 dependency(의존성)을 설정하였으며,
의존성에 의해 JdbcTemplete이 스프링 컨테이너 안에 들어가게 된 것이다.
의존성이 특정 라이브러리, 프레임워크를 가져온다.
스프링 컨테이너는 필요한 클래스를 연결해준다.
UserRepository는 스프링 빈이 아니기에 -> JdbcTemplate를 바로 가져올 수 없는 것이다.
총 3단계를 통해 코드를 발전시켜가며, 스프링 컨테이너가 필요한 이유에 대해 알아보도록 하자.
책 이름을 받아 이를 저장하고자 한다. 다만, 책 데이터를 메모리 / MySql DB에 저장할지 정해지지 않았다.
BookController -> BookService -> BookRepository 세 클래스가 다음과 같은 의존관계를 가진다.
package com.group.libraryapp.controller.book;
@RestController
public class BookController {
private final BookService bookService = new BookService();
@PostMapping("/book")
public void saveBook(@RequestParam String title){
bookService.saveBook(title);
}
}
package com.group.libraryapp.service.book;
public class BookService {
private final BookRepository bookRepository = new BookRepository();
public void saveBook(String title){
bookRepository.saveBook(title);
}
}
package com.group.libraryapp.repository.book;
import com.group.libraryapp.domain.book.Book;
import java.util.ArrayList;
import java.util.List;
public class BookRepository {
private final List<Book> books = new ArrayList<>();
public void saveBook (String title){
Book book = new Book(title);
books.add(book);
for (int i = 0 ; i < books.size(); i++){
System.out.println("title = " + books.get(i).getTitle());
}
}
}
자바 코드만 가지고 '메모리에 저장하는 경우'의 코드이다.
만약, 회사 내에서 메모리에 저장하는 것이 아닌 (당연하지) DB에 저장한다고 가정하자.
mySQL 내 book 테이블에 저장하는 BookMySqlRepository 클래스를 생성한다.
package com.group.libraryapp.controller.book;
//...
@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);
}
}
package com.group.libraryapp.service.book;
public class BookService {
private final JdbcTemplate jdbcTemplate;
private final BookMysqlRepository bookRepository;
public BookService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
// this.bookRepository = new BookRepository(jdbcTempalte); 메모리에 저장하는 클래스
this.bookRepository = new BookMysqlRepository(jdbcTemplate);
}
public void saveBook(String title){
System.out.println("title = " + title);
bookRepository.saveBook(title);
}
}
package com.group.libraryapp.repository.book;
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);
}
}
해당 코드를 작성하며 JdbcTemplate 의존성 주입과 관련한 에러가 발생했었다. 그에 대한 정리글을 아래와 같다
의존성 주입 에러 정리글_서버 개발 발 들이기 5
해당 코드를 살펴보면, 메모리에 저장하는 경우 / DB에 저장하는 경우에 따라 코드가 변경된다.
중요한 것은 Service의 코드가 변경된다는 것이다.
private final JdbcTemplate jdbcTemplate;
// private final BookRepository bookRepository;
private final BookMysqlRepository bookRepository;
public BookService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
// this.bookRepository = new BookRepository(jdbcTempalte); 메모리에 저장하는 클래스
this.bookRepository = new BookMysqlRepository(jdbcTemplate);
}
이렇게 BookRepository의 종류가 변경된다면, 그 객체를 생성하는 코드를 모두 변경해주어야 한다. 이처럼 비효율적이라니!
다음으로는 Interface를 통해 BookRepository 인터페이스를 implement 하는 두 클래스를 생성한다.
//Service
package com.group.libraryapp.service.book;
public class BookService {
private final JdbcTemplate jdbcTemplate;
private final BookRepository bookRepository;
public BookService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
this.bookRepository = new BookMemoryRepository(jdbcTemplate);
}
public void saveBook(String title){
System.out.println("title = " + title);
bookRepository.saveBook(title);
}
}
//Interface
package com.group.libraryapp.repository.book;
public interface BookRepository {
public void saveBook(String title);
}
package com.group.libraryapp.repository.book;
public class BookMemoryRepository implements BookRepository{
private final List<Book> books = new ArrayList<>();
private final JdbcTemplate jdbcTemplate;
public BookMemoryRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public void saveBook (String title){
Book book = new Book(title);
books.add(book);
for (int i = 0 ; i < books.size(); i++){
System.out.println("title = " + books.get(i).getTitle());
}
}
}
package com.group.libraryapp.repository.book;
public class BookMysqlRepository implements BookRepository{
private final JdbcTemplate jdbcTemplate;
public BookMysqlRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public void saveBook(String title){
String sql = "INSERT INTO book (title) VALUES (?)";
jdbcTemplate.update(sql, title);
}
}
BookRepository Interface를 구현하는 메모리에 저장하는 BookMemoryRepository와 DB에 저장하는 BookMysqlRepository 클래스를 생성하였다.
Service에서는 타입은 동일하게 BookRepository이므로 구현체만 변경하면 된다.
private final JdbcTemplate jdbcTemplate;
private final BookRepository bookRepository;
public BookService(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
//this.bookRepository = new BookMemoryRepository(jdbcTemplate);
this.bookRepository = new BookMysqlRepository(jdbcTemplate);
}
두 구현체 모두 title 데이터를 받아 Book 객체를 생성한 뒤 저장하는 것을 확인하였다.
🚨 그럼에도 문제는 여전하다.
여전히 BookService에서는 객체 생성시 어떤 클래스를 사용하는지 선택해야 하며, 코드를 모두 수정해주어야 한다.
그렇다면, 과연 코드를 수정하지 않고도 구현체를 선택할 수 있을까?
앞선 의문에 대한 답변은 Repository, Service를 스프링 빈으로 등록한다!
@Service, @Repository 어노테이션을 통해 스프링 컨테이너에 스프링 빈으로 등록한다. 그렇다면 서버가 시작되면, 다음 과정이 이루어진다.
1> 서버 시작되면 스프링 컨테이너가 생성된다.
2> 기본적으로 많은 스프링 빈들이 등록된다.
3> 개발자가 설정해 둔 스프링 빈이 등록된다.
4> 이 때 필요한 의존성이 자동으로 설정된다.
해당 어노테이션을 통해서 우선순위를 설정할 수 있다.