SpringBoot로 h2 DB와 연동해서 데이터 테이블 저장하기
1. 환경설정 하기
build.gradle 파일의 dependencies에 해당 내용을 추가한다. (Jdbc, h2)
// jdbc
implementation 'org.springframework.boot:spring-boot-starter-jdbc'
// h2 db
// implementation 'com.h2database:h2' // 2-1
runtimeOnly 'com.h2database:h2' // 2-2
Jdbc
1. Jdbc 사용을 위한 Spring Boot 의존성 추가. DataSource 구현체로 tomcat-jdbc을 제공해준다. JdbcTemplate 사용 가능.
h2 DB
2-1. SpringBoot가 h2 데이터 베이스를 기본 데이터 베이스로 사용한다는 의미. application.
2-2. 런타임 시점에만 의존하게 된다.
application.properties 파일에 해당 내용을 추가한다. (h2)
# DB
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.url=jdbc:h2:~/spring-qna-db
spring.datasource.username=sa
spring.datasource.password=
spring.h2.console.enabled=true
spring.h2.console.path=/h2-console
1. 드라이버는 h2를 쓰겠다.
2. 사용할 db 파일 경로 (= 최상위 디렉토리에 있는 spring-qna-db.mv.db 파일)
3. 디폴트 이름
4. 디폴트 비밀번호 (없음)
5. h2 console을 활성화 한다.
6. h2 console의 url 경로 (예시 = http://localhost:8080/h2-console)
2. h2 console에서 테이블 만들기
connect 버튼을 누른다.
- 첫 실행이라면 최상위 디렉토리에 spring-qna-db.mv.db 파일이 자동으로 만들어지게 된다.
🤯 오류가 났을 경우 (해당 경로에 파일이 없다, 잘못된 접근 등등..)
1. 해당 파일을 삭제한다. (.mv.db 확장자, 같은 이름의 trace 확장자도 있으면 삭제.)
rm spring-qna-db.mv.db
2. 똑같은 이름으로 직접 파일을 생성한다.
touch spring-qna-db.mv.db
sql 쿼리 문으로 테이블 만들기
1. id: long형 숫자, null 불가, 자동으로 카운트 되면서 숫자가 올라감
2. writer: 16자리 까지의 문자가 가능하며 가변적인 공간을 가짐, null 불가
3. title: 32자리 까지의 문자가 가능하며 가변적인 공간을 가짐, null 불가
4. contents: 255자리 까지의 문자가 가능하며 가변적인 공간을 가짐, null 불가
5. createdAt: 날짜와 시간을 가짐, null 불가
6. points: long형 숫자, null 불가
7. primary key(id): 테이블 당 하나의 필드에 기본 키를 설정한다. 이 쿼리에서 기본 키는 id가 된다.
CHAR vs VARCHAR :
- CHAR: 불변한 크기의 공간을 가짐. char(8) 일 때, 안에 설정한 값이 2글자라면 8개의 크기를 맞추기 위해서 6개의 공백이 자동으로 생성된다.
- VARCHAR: 가변적인 공간을 가짐. varchar(8) 일 때, 안에 설정한 값이 2글자라면 2글자 만큼의 공간만 생성됨.
DATE vs TIME vs DATETIME vs TIMESTAMP
- DATE: 날짜만 표시 (YYYY-MM-DD)
- TIME: 시간만 표시 (HH:MM:SS)
- DATETIME: 등록한 날짜와 시간을 표시 - 문자, 8byte (YYYY-MM-DD HH:MM:SS), 직접 입력해줘야만 날짜가 설정된다.
- TIMESTAMP: 등록한 날짜와 시간을 표시 - 숫자, 4byte (YYYY-MM-DD HH:MM:SS), timezone 기반으로 자동으로 설정된다. timezone이 변경 될 경우, 자동으로 업데이트 된다.
3. JdbcTemplate을 사용해서 h2 DB와 연결하기
게시글을 DB에 저장하기 위한 JdbcArticleRepository 클래스를 만든다.
@Repository
public class JdbcArticleRepository implements ArticleRepository{
private final JdbcTemplate jdbcTemplate;
public JdbcArticleRepository(DataSource dataSource){
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
JdbcArticleRepository는 ArticleRepository의 구현체다.
필드로 JdbcTemplate을 가지고, 생성자의 파라미터로 DataSource를 주입받는다. (SpringBoot의 힘!)
Article 객체를 h2 DB에 저장한다.
@Override
public Article save(Article article) {
// insert 쿼리???
SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
jdbcInsert.withTableName("articles_squad").usingGeneratedKeyColumns("id");
Map<String, Object> parameters = new ConcurrentHashMap<>();
parameters.put("writer", article.getWriter());
parameters.put("title", article.getTitle());
parameters.put("contents", article.getContents());
parameters.put("createdAt", article.getCreatedAt());
parameters.put("points", article.getPoints());
Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters));
article.setId(key.longValue());
return article;
}
insert를 쉽게 해주기 위해 SimpleJdbcInsert를 생성한다.
"articles_squad"라는 이름을 가진 테이블에 "id"를 기본키로 가진 애를 생성해 줄 것이다.
Map의 value는 반환타입이 여러개일 수 있으니 최상위 타입인 Object로 설정해주고, 동시성 이슈를 방지하기 위해 ConcurrentHashMap을 사용한다.
Number 클래스: Character, Boolean을 제외한 모든 Wrapper 클래스의 상위 클래스.
db에서 자동으로 생성(AUTO_INCREMENT)되는 primary key를 받아서 article의 id에 넣어준다.
🤔 insert를 jdbcTemplate.update(sql)로 하면 더 간단할 것 같은데 왜 이렇게 쓰는 걸까?
update() 메서드는 변경 된 행의 개수만 리턴한다.
update() 메서드로는 AUTO_INCREMENT로 설정된 Primary Key를 알아낼 수 없기 때문에 위처럼 SimpleJdbcInsert 클래스를 사용해서 테이블 요소의 insert를 진행해준다.
- 테이블 요소를 insert 해주는 방법은 SimpleJdbcInsert 클래스를 사용하는 방법 외에도 BeanPropertySqlParameterSource 클래스를 사용하는 방법 등이 있기 때문에 자신에게 맞는 방법을 참고 할 것!
Id(번호)로 게시글 찾기
@Override
public Optional<Article> findById(Long id) {
List<Article> result = jdbcTemplate.query("select * from articles_squad where id = ?", articleRowMapper(), id);
return result.stream().findAny();
}
articles_squad 테이블에서 (파라미터로 받은 id와) 동일한 id를 가진 Article을 찾아, Optional로 감싸서 반환해준다.
findAny()와 findFirst()의 차이점
- findAny(): 병렬처리에 용이하다. 제일 먼저 찾아지는 요소를 반환하기 때문에 결과값이 다를 수 있다.
- findFirst(): 찾은 요소들 중, 가장 앞의 요소를 반환한다. 결과값은 동일하다.
table에 있는 모든 요소(Article) 반환하기
@Override
public List<Article> findAll() {
return jdbcTemplate.query("select * from articles_squad", articleRowMapper());
}
articles_squad 테이블에 있는 모든 요소(Article)를 반환한다.
table에 있는 모든 요소(Article) 삭제하기
@Override
public void clearStore() {
jdbcTemplate.update("delete from articles_squad");
}
article_squad 테이블에 있는 모든 요소(Article)를 삭제한다.
RowMapper 클래스 활용해서 객체(Article) 조회하기
private RowMapper<Article> articleRowMapper(){
return (rs, rowNum) -> {
Article article = new Article();
article.setId(rs.getLong("id"));
article.setWriter(rs.getString("writer"));
article.setTitle(rs.getString("title"));
article.setContents(rs.getString("contents"));
article.setCreatedAt(rs.getTimestamp("createdAt").toLocalDateTime());
article.setPoints(rs.getLong("points"));
return article;
};
}
RowMapper를 활용해서 객체를 조회할 수 있다.
RowMapper는 데이터베이스의 반환 결과인 ResultSet을 객체로 변환해주는 클래스다.
원래는 행의 번호를 가져와서 직접 입력해 줘야 했는데, 각 columnLabel(= "id", "writer" 등..)에 해당되는 값을 찾아와서 넣어준다.
rowNum은 반복되는 루프 중, 현재 행의 번호를 나타낸다. 람다식으로 간결하게 표현 할 수 있다.
- RowMapper를 사용하는 방법 외에도 NamedParameterJdbcTemplate을 사용하는 방법도 있다!
전체 코드
package kr.codesqaud.cafe.repository;
import kr.codesqaud.cafe.domain.Article;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Repository;
import javax.sql.DataSource;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.ConcurrentHashMap;
@Repository
public class JdbcArticleRepository implements ArticleRepository{
private final JdbcTemplate jdbcTemplate;
public JdbcArticleRepository(DataSource dataSource){
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
@Override
public Article save(Article article) {
SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
jdbcInsert.withTableName("articles_squad").usingGeneratedKeyColumns("id");
Map<String, Object> parameters = new ConcurrentHashMap<>();
parameters.put("writer", article.getWriter());
parameters.put("title", article.getTitle());
parameters.put("contents", article.getContents());
parameters.put("createdAt", article.getCreatedAt());
parameters.put("points", article.getPoints());
Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters));
article.setId(key.longValue());
return article;
}
@Override
public Optional<Article> findById(Long id) {
List<Article> result = jdbcTemplate.query("select * from articles_squad where id = ?", articleRowMapper(), id);
return result.stream().findAny();
}
@Override
public List<Article> findAll() {
return jdbcTemplate.query("select * from articles_squad", articleRowMapper());
}
@Override
public void clearStore() {
jdbcTemplate.update("delete from articles_squad");
}
private RowMapper<Article> articleRowMapper(){
return (rs, rowNum) -> {
Article article = new Article();
article.setId(rs.getLong("id"));
article.setWriter(rs.getString("writer"));
article.setTitle(rs.getString("title"));
article.setContents(rs.getString("contents"));
article.setCreatedAt(rs.getTimestamp("createdAt").toLocalDateTime());
article.setPoints(rs.getLong("points"));
return article;
};
}
}
참고 블로그:
https://dahye-jeong.gitbook.io/spring/spring/2021-02-15-spring-boot/2021-02-16-boot-h2
H2 DB 설정 - spring
H2 DB는 컴퓨터에 내장된 램(RAM) 메모리에 의존하는 자바 기반의 RDBMS이다. 용량이 적고, 브라우저 기반의 콘솔 등을 지원해 장점이 많다. 또한, SpringBoot에서 별도 DB를 설치하지 않고 바로 사용할
dahye-jeong.gitbook.io
https://codechacha.com/ko/java8-stream-difference-findany-findfirst/
Java - Stream findAny()와 findFirst()의 차이점
Stream에서 어떤 조건에 일치하는 요소(element) 1개를 찾을 때, findAny()와 findFirst() API를 사용할 수 있습니다. findAny()는 Stream에서 가장 먼저 탐색되는 요소를 리턴하고, findFirst()는 조건에 일치하는
codechacha.com
https://hyeon9mak.github.io/easy-insert-with-simplejdbcinsert/
SimpleJdbcInsert를 통한 쉬운 Insert
```java private final JdbcTemplate jdbcTemplate;
hyeon9mak.github.io
https://code-lab1.tistory.com/277
[Spring] JdbcTemplate이란? JdbcTemplate 사용법, RowMapper란?
JdbcTemplate이란? JdbcTemplate은 JDBC 코어 패키지의 중앙 클래스로 JDBC의 사용을 단순화하고 일반적인 오류를 방지하는데 도움이 된다. 개발자가 JDBC를 직접 사용할 때 발생하는 다음과 같은 반복 작
code-lab1.tistory.com