세 줄 요약
- DB를 사용하기 위해선 스키마 및 테이블을 생성해야 한다.
- ApplicationRunner를 빈으로 등록해 어플리케이션 시작과 함께 커넥션 풀을 초기화한다.
- JdbcTemplate를 통해 쿼리문의 매개변수와 인스턴스 필드들을 매핑시킨다.
스키마 및 테이블 구성
상품관리 어플리케이션에 사용되는 ProductDatabaseRepository에 관한 예제입니다.
DB 컨테이너는 지난 게시글에서 생성한 'some-mysql' 컨테이너를 사용합니다.
스키마(SCHEMA) 생성
도커 데스크탑 실행 후 bash 쉘에 접속해 아래와 같이 입력해 스키마를 생성합니다.
bash 쉘은 명령 프롬프트에서 접속해도 되지만, 간편하게 도커 데스크탑에서 바로 접속할 수 있습니다.
CREATE SCHEMA product_management;
DB 인스턴스의 스키마 목록은 'SHOW DATABASES;' 를 통해 확인할 수 있습니다.
'USE product_management;' 를 입력해 해당 스키마를 사용할 수 있습니다.
테이블 생성
아이디(id), 이름(name), 가격(price), 재고(amount) 네 가지 컬럼을 갖는 테이블을 생성합니다.
CREATE TABLE products {
id BIGINT PRIMARY KEY NOT NULL AUTO_INCREMENT,
name VARCHAR(100) NOT NULL,
price INT NOT NULL,
amount INT NOT NULL
}
BIGINT : 자바의 Long 자료형과 같은 범위의 자료형
PRIMARY KEY : 중복을 허용하지 않으면서 '인덱스'를 생성해, 해당 컬럼을 기준으로 조회시 빠르게 조회 가능
어플리케이션 - DB 연결
의존성 추가
MySQL에 접속하기 위한 JDBC 관련 의존성을 추가합니다.
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.33</version>
</dependency>
spring-boot-starter-jdbc : 스프링 부트에서 제공하는 JDBC 관련 의존성 모음
mysql-connector-java : MySQL DB에 접속하기 위한 의존성
접속 정보 설정
application.properties 파일을 다음과 같이 설정합니다.
spring.datasource.url=jdbc:mysql://localhost:3306/product_management
spring.datasource.username=root
spring.datasource.password=userpassword
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
url : DB 접속을 위한 url (컨테이너 생성 시 설정한 포트번호와 스키마 이름이 포함)
username : 접속할 사용자 이름 (root 계정으로 접속)
password : 접속할 사용자의 비밀번호 (컨테이너 생성 시 MYSQL_ROOT_PASSWORD 변수에 설정한 pw)
driver-class-name : DB 연결에 사용될 드라이버
어플리케이션 - DB 연결
Application 클래스에 다음과 같이 'ApplicationRunner' 빈을 설정합니다.
해당 빈은 스프링 부트 어플리케이션이 시작한 직후 실행하려는 코드를 추가할 수 있는 의존성입니다.
@SpringBootApplication
public class Application {
(생략)
@Bean
public ApplicationRunner runner(DataSource dataSource) {
return args -> {
// 시작과 동시에 실행할 코드를 적는다.
Connection connection = dataSource.getConnection(); // 커넥션 풀 초기화
};
}
}
매개변수로 받고 있는 'DataSource'는 DB와의 연결을 담당하는 인터페이스입니다.
해당 인터페이스를 통해 DB와의 커넥션(connection)을 가져올 수 있고, 가져오는데 성공하면 DB와 연결됩니다.
실행결과
시작과 동시에 커넥션 풀을 초기화 하는 이유
spirng-boot-starter-jdbc 의존성을 추가할 때 자동으로 커넥션 풀이 생성되지만, 첫 번째 요청이 오기 전까진 커넥션 풀을 만들지 않습니다.
만약 커넥션 풀을 처음에 초기화하지 않는다면 어플리케이션이 받는 첫 번째 요청은 커넥션 풀이 생성되는 시간만큼 지연되어 클라이언트가 응답을 느리게 받게됩니다.
기능 구현 (상품 추가)
PostMapping
요청 본문(RequestBody)에 데이터를 담아 보낼 때 사용하는 HTTP 메소드입니다.
항목이 많거나 민감한 경우 해당 메소드를 통해 요청을 보냅니다.
update( )
CRUD 중에 Read를 제외한 나머지(Create, Update, Delete)를 위한 메소드입니다.
리턴하는 int 값은 해당 쿼리문이 적용된 컬럼의 숫자를 나타냅니다.
→ 만약 리턴 값이 '0' 이라면 update된 사항이 없다는 의미로 적절한 예외를 던지도록 설정합니다.
NamedParameterJdbcTemplate 클래스의 update() 메소드는 DataAccessException 예외를 던집니다.
JdbcTemplate
DB에 SQL 쿼리문을 전송하기 위한 의존성입니다.
update() 메소드에서 VALUES 인자들을 '물음표(?)'를 통해 받아옵니다.
ProductDatabaseRepository
@Repository
public class ProductDatabaseRepository {
private JdbcTemplate jdbctemplate;
@Autowired
public ProductDatabaseRepository(JdbcTemplate jdbctemplate) {
this.jdbctemplate = jdbctemplate;
}
public Product add(Product product) {
jdbcTemplate.update("INSERT INTO products (name, price, amount) VALUES (?, ?, ?)",
product.getName(), product.getPrice(), product.getAmount());
return product;
}
}
그러나 VALUES의 인자의 개수가 늘어나거나 순서가 변경되는 경우 헷갈릴 수 있다는 단점이 있습니다.
NamedParameterJdbcTemplate
물음표가 아닌, 매개변수의 이름을 통해 SQL 쿼리와 값을 매핑시킵니다.
SqlParameterSource와 KeyHolder 인스턴스를 함께 넘길 수 있습니다.
ProductDatabaseRepository
public Product add(Product product) {
KeyHolder keyHolder = new GeneratedKeyHolder();
SqlParameterSource namedParameter = new BeanPropertySqlParameterSource(product);
namedParameterJdbcTemplate.update(
"INSERT INTO products (name, price, amount) VALUES (:name, :price, :amount)",
namedParameter,
keyHolder);
Long generatedId = keyHolder.getKey().longValue();
product.setId(generatedId);
return product;
}
KeyHolder : update() 메소드의 인자로 함께 넘기면 id 값이 담기며, getKey() 메소드로 값을 받아올 수 있음
BeanPropertySqlParameterSource : 함께 넘기는 인스턴스의 필드들을 getter를 통해 쿼리의 매개변수와 매핑시킴
Controller & Service
ProductController
@PostMapping("/products")
public ProductDTO createProduct(@Valid @RequestBody ProductDTO productDTO) {
return productService.add(productDTO);
}
ProductService
public ProductDTO add(ProductDTO productDTO) {
Product product = ProductDTO.toEntity(productDTO);
validationService.checkValid(product);
Product savedProduct = productRepository.add(product); // ProductDatabaseRepository는 ProductRepository 인터페이스를 구현
ProductDTO savedProductDTO = ProductDTO.toDTO(savedProduct);
return savedProductDTO;
}
ProductService가 의존하는 ProductRepository 인터페이스에 대한 내용은 해당 게시글에서 확인할 수 있습니다.
Postman 전송 결과
'Spring-Java > Spring' 카테고리의 다른 글
인터페이스에 의존하는 Repository (0) | 2024.05.09 |
---|---|
DB를 사용하는 Repository 구성 2 (0) | 2024.05.08 |
전역 예외 핸들러와 예외 처리 전략 (@ControllerAdvice, @ExceptionHandler) (1) | 2024.04.26 |
@Valid, @Validated (0) | 2024.04.25 |
@RequiredArgsConstructor (1) | 2023.12.21 |