헤드퍼스트자바를 어느정도 읽고 나니까.. 확실히 이 헤드퍼스트자바 책을 좀 빨리, 그리고 많이 읽었으면 좋겠다고 생각했다.
여타 개발 책들과는 구성도 다르고, 개념과 코드를 같이 익힐 수 있다고해야하나
아니면 내가 스프링 CRUD(엉터리긴하지만)를 통해 자바 지식을 좀 획득해서 내용이 재밌고 잘 읽히는 것일수도 있다.
그래도 너무 헤드퍼스트자바만 읽기는 좀 그래서 읽은지 좀 된 스프링 인 액션 책을 읽기로 했다.
두 책을 합치면 77000원이다. 내가 하루 알바가도 다 못버는 돈...... 솔직히 IT책들 너무 비싸요
JDBC
동적인 애플리케이션을 정적인 웹사이트와 차별화하는 것은 사용자에게 보여주고 저장하는 데이터다.
이전에 DDD를 하면서 Repository 를 배웠다가 Entity 어노테이션을 통한 DB연결을 배웠다.
이 때 사용되는게 JPA로, JPA는 자바객체를 활용한 ORM 기술이다. JPA는 같은 3장이지만 좀 이따가 할 예정이다.
반면 JDBC는 수동으로 SQL을 짜야하는 도구이다.
고찰1에서 정리한 내용 중에 ORM을 사용할 때 단순히 사용하는게 아니라 그의 SQL 내용이나 코드들까지 전부 알고 있어야한다고 했는데, 시간이 된다면 SQL의 문법에 대해서도 알아볼 생각이다.
* 코드가 길기도하고 다 쓸 수도 없어서 자세한 코드는 스프링 인 액션 3장 73페이지 참고
JDBC는 JdbcTemplate 클래스를 기반으로 한다. 물론 JdbcTemplate를 쓰지 않고도 간단한 SQL 쿼리는 수행할 수 있다. 다만 DB를 연결하고, 명령문을 만들고, 연결을 닫고 SQLException 예외처리를 위해 try-catch 문을 쓰다보면 간단한 쿼리문인데도 책의 한바닥이 필요할 정도로 코드 길이가 길다. 심지어 SQL문 자체에 오류가 있을 경우 catch로도 잡히지 않는다.
JdbcTemplate에서는 이를 해결한다. JdbcTemplate가 자동으로 진행시켜주며, 쿼리를 수행하고 결과를 생성하는 것에 초점을 둔다.
https://congsong.tistory.com/46
스프링 부트(Spring Boot) - 5분 안에 H2 Database와 JPA 연동해보기
1. H2 Database란? H2 DB는 자바 기반의 오픈소스 관계형 데이터베이스 관리 시스템(RDBMS)입니다. 보통 테스트 단계 또는 작은 규모의 프로젝트에서 사용되며, Gradle 또는 Maven에 의존성만 추가해 주면
congsong.tistory.com
우선 JDBC를 배우기 앞서 JPA를 활용해 간단한 DB연결을 했다.
H2DB를 우선 활용하고, 나중에 mySQL을 배울 생각이다.

성공
H2 리포지토리 정의하기
package com.hongchelin.Repository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
@Repository
public class MemberRepository implements MemberRepositoryInterface {
private JdbcTemplate jdbcTemplate;
@Autowired
public MemberRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
}
이런식으로 생성자를 통해 JdbcTemplate를 불러왔다.
package com.hongchelin.Repository;
import com.hongchelin.Domain.Member;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import java.sql.ResultSet;
import java.sql.SQLException;
@Repository
public class MemberRepository implements MemberRepositoryInterface {
private JdbcTemplate jdbcTemplate;
@Autowired
public MemberRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
@Override
public Iterable<Member> findAll() {
return jdbcTemplate.query("select id, name, role, refreshToken from Member",
this::mapRowToMember);
}
@Override
public Member findById(Long id) {
return jdbcTemplate.queryForObject(
"select id, name, role, refreshToken from Member where id=?",
this::mapRowToMember, id
);
}
@Override
public Member save(Member member) {
return null;
}
private Member mapRowToMember(ResultSet rs, int rowNum) throws SQLException {
return Member.builder()
.id(rs.getLong("id"))
.name(rs.getString("name"))
.role(rs.getString("role"))
.refreshToken(rs.getString("refreshToken"))
.build();
}
}
이런 식으로..
JPA를 사용하면 findAll, save, findById 메서드는 자동으로 구현되지만, 나는 JDBC를 사용 중이라 (JPA는 이따가 할 예정) 수동으로 그 메서드를 구현해줬다.
코드를 한 번 뜯어보자면..
jdbc.query()에서 맨 처음 들어가는 인자는 SQL문이다. SQL문에서 SELECT문을 실행할 때 필요하며, DB조회용이다.
수정이나 업데이트 등은 이따가 나올 예정
뒤에 들어가는 this::mapRowToMember는 JDBC가 DB에서 떼온 날 것의 결과세트 객체를 을 Member 객체로 변환해준다.
mapRowToMember의 메서드에 있는 rs.getString ,rs.getLong 메서드들이 인자로 받은 요소를 찾아서 반환해준다.
이 메서드는 결과 세트의 행 개수만큼 호출되며, 모두 변환해서 List 형태로 저장한다.
jdbc.query() 메서드는 여러 행을 조회해 List형태로 반환한다.
반면 jdbc.queryForObject 메서드는 인자로 준 값 (여기서는 id)을 찾아서 딱 1개의 행만 반환한다. 정확히는 SQL문에 마지막에 있는 물음표 대신 인자로 받은 값이 들어간다. 따라서 행이 없을 경우와 행이 2개 이상인 경우 모두 에러가 발생한다.
저기서 예외처리를 위해서는 responseDTO에 담아야하는데..
그냥 저 Repository를 호출하는 곳에서 예외처리 해야할 것 같기도 하고?
참고로 인자에 메서드 참조를 전달할 수 있게 된 것은 자바8에서 추가된 기능이라고하며, 이전에는 인자로 그냥 new를 호출했다. 업데이트 되어서 다행이다.
DB에 값 저장하기 - JdbTemplate.update
@Override
public Member save(Member member) {
jdbcTemplate.update(
"insert into Member (id, name, role, refreshToken) values (?, ?, ?, ?)",
member.getId(),
member.getName(),
member.getRole(),
member.getRefreshToken()
);
return member;
}
이렇게 save 메서드도 구현했다.
save 등 DB에 값을 저장하는 메서드는 JdbcTemplate.update를 사용해야하며 인자로는 SQL 쿼리문, 매개변수에 지정할 값들이 들어간다.
여기까지 진행하고 테스트를 한 번 해봤다. 그 내용은 [GDG]홍대 맛집 아카이빙 프로젝트 #10 을 참고하면 된다. 여기서는 배운 내용들만 들어갈 예정
스키마
스키마란.. DB의 구조와 조건에 대한 전반적 명세서라고 한다.
스프링 인 액션 3장 85페이지에 스키마와 테이블을 생성하는 SQL문이 존재한다.
create table if not extists member (
id varchar(4) not null,
name varchar(25) not null
)
뭐 이런 식으로..
JDBC에서는 이렇게 테이블을 정의하고 src/main/resources 폴더에 SQL문을 저장하면 된다. 또한 그 테이블에 미리 insert 명령을 사용할 수 있다. (이 때는 다른 SQL 문서 사용하기)
나는 @Entity 어노테이션을 사용하고 있었는데, 이는 JDBC가 아닌 JPA오 관련된 것으로 자동으로 resources에 SQL문이 없더라도 테이블을 만들어준다.
KeyHolder
어제 회원정보와 관련된 개발을 할 때, 아이디와 비밀번호를 가지고 로그인을하면 입력받은 아이디를 가지고 비밀번호를 가져와야한다. 근데 비밀번호를 가져올 때 입력받은 아이디 말고 실제로 DB에 저장되는 ID로 가져올 수는 없을까? 이러면 update 문을 약간 수정해야한다.
여기까지했는데...
@Override
public Member save(Member member) {
long memberId = saveMemberInfo(member);
member.setId(memberId);
saveMemberToDB(member, memberId);
return member;
}
private long saveMemberInfo(Member member) {
PreparedStatementCreator psc =
new PreparedStatementCreatorFactory(
"insert into MEMBER (email, nickname, password, user_Id) values (?, ?, ?, ?)",
Types.VARCHAR, Types.VARCHAR, Types.VARCHAR, Types.VARCHAR
).newPreparedStatementCreator(
Arrays.asList(
member.getEmail(),
member.getNickname(),
member.getPassword(),
member.getUserId()
));
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(psc, keyHolder);
return keyHolder.getKey().longValue();
}
private void saveMemberToDB(Member member, long memberId) {
jdbcTemplate.update(
"insert into MEMBER (id, email, nickname, password, user_Id) values (?, ?, ?, ?, ?)",
memberId, member.getEmail(), member.getNickname(), member.getPassword(), member.getUserId()
);
}
대충 책 보고 따라만들었는데 잘못 만든 코드
왜냐하면.. 책에서는 타코에 들어갈 재료들을 저장하는 일대다 방식의 saveIngredientToTaco, 타코 도메인 그 자체를 저장하고 id 값을 반환하는 일대일 방식의 saveTacoInfo가 있었는데, 나는 회원가입 기능이라 일대일 방식이지만 무작정 두 방식을 따라해서 DB에 값이 두 번씩 저장되었다.
그래서 내 코드 (로그인 기능)에는 별 사용할 이유가 없지만... 나중에 또 필요할 수 있으니까 몇 가지 더 공부해보려고한다.
1. 저 코드들은 무얼하는건가?
책이 소개는 잘하는데 설명은 약간 불친절하다고 해야하나.. 내용이 약간 어렵다. 책에서 '골치아픈 코드'라고 했으니 할 말 다했다
코드를 보다보니까 좀 책에서 나온 예제의 구조가 좀 헷갈리는데 2장 37페이지부터 참고
그러니까...
PreparedStatementCreator는 PreparedStatement를 만든다.
기존의 방식은
String sql = "INSERT INTO MEMBER (nickname, user_Id, password, email) VALUES ('" + nickname + "','" + userId "','" + password + "','" + email + "')";
이런 방식으로 SQL문을 짰다면.. PreparedStatement 방식은
이런 식으로 짜는 것이다.
PreparedStatementCreator psc =
new PreparedStatementCreatorFactory(
"INSERT INTO MEMBER (user_Id, password) values (?, ?)",
Types.VARCHAR, Types.VARCHAR)).newPreparedStatementCreator(
Arrays.asList(
member.getName(), //메서드 인자로 제공
member.getPassword()));
이런 방식으로..
코드에 대해 알아보
new PreparedStatementCreatorFactory(
"INSERT INTO MEMBER (user_Id, password) values (?, ?)",
Types.VARCHAR, Types.VARCHAR))
먼저 이 코드
PreparedStatementCreatorFactory는 SQL문과 파라미터의 타입이 들어간다. SQL문의 첫 번째 괄호에는 컬럼의 이름이, 두 번째 괄호에는 ?가 들어가고, ?를 파라미터로 한다.
.newPreparedStatementCreator(
Arrays.asList(
member.getName(), //메서드 인자로 제공
member.getPassword()));
은 위에서 만든 SQL문과 설정을 바탕으로 PreparedStatementCreator를 호출해, Arrays.asList에 들어가는 요소들 (getName, getPassword)를 받아서 ?에 인자로 집어넣는다.
최종적으로는 PreparedStatement라는 타입의 psc 변수가 만들어진다.
이후 추가로
KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(psc, keyHolder);
Long id = keyHolder.getKey().longvalue();
DB에서 ID값이 자동으로 지정되게 설정해두었다면 jdbcTemplate.update를 통해 psc로 바인딩된 SQL문을 넘기고, keyHolder를 통해서는 id값이 나간다.
이 방식은 keyHolder를 통해서 id값을 추출하기 쉬운데, id값을 추출하지 않을 예정이거나하면
String sql = "INSERT INTO MEMBER (user_Id, nickname, password, email) values (?, ?, ?, ?)",
UserId(),
Nickname(),
Password(),
Email());
그냥 이런 방식을 써도된다.
이것도 내부적으로는 PreparedStatement를 쓰는데, keyHolder가 필요하냐 필요하지않냐 여부에 따라서 원하는 것을 사용하면 된다. 후자는 keyHolder를 사용하지 못한다.
왜 쓸까?
String sql = "INSERT INTO MEMBER (nickname, user_Id, password, email) VALUES ('" + nickname + "','" + userId "','" + password + "','" + email + "')";
이런 기존의 방식은 nickname에 SQL문을 입력시키는 순간 SQL Injection이 일어나기 쉽다.
반면 PreparedStatement 방식은 이를 방지하면서 사용할 수 있다.
이러한 방식은 일대다 관계의 테이블에서 주로 쓰인다.
예를 들면 주문관계에서..
ORDER 테이블과 ORDERITEM 테이블이 있다고 칠 때, ORDER 테이블에는 결제시각, 결제자 정보 등이 들어가고, ORDERITEM 테이블에는 결제물품과 함께 ORDER 테이블의 id가 들어간다. 주문하면 그 주문에 해당되는 ORDER에 ID를 ORDERITEM에서 찾아서 결제하는 방식
이런 상황에서 ORDER에서 정보를 저장하고 id값을 그대로 떼와서 ORDERITEM에 넣어주면 된다.
한 테이블이 다른 테이블의 ID를 필요로할 때 사용할 수 있다.
'CS > 백엔드' 카테고리의 다른 글
| [2025백엔드] 스프링인액션 독서 #5 - 3장. 데이터로 작업하기 #3 (2) | 2025.08.04 |
|---|---|
| [2025백엔드] 스프링인액션 독서 #4 - 3장.데이터로 작업하기 2 (2) | 2025.08.01 |
| [2025 백엔드] 스프링 인 액션 독서 #2 - 2장. 웹 애플리케이션 개발하기 (7) | 2025.07.24 |
| [2025 백엔드] 도메인과 Model, 도메인 주도 개발 (DDD) (1) | 2025.07.22 |
| [2025 백엔드] 스프링 인 액션 독서 #1 - 1장. 스프링 시작하기 (2) | 2025.07.21 |