하위 태스크 1
JDBC 드라이버 의존성 추가
build.gradle에 데이터베이스 드라이버 추가
In-memory 데이터베이스를 사용하기 위해 H2 데이터베이스 엔진을 의존성에 추가한다.
build.gradle:
dependencies {
implementation 'com.h2database:h2:2.2.224'
// ...
}이후 Gradle 프로젝트를 동기화한다.
하위 태스크 2
데이터베이스 연결 구현
DriverManager를 사용한 Connection 획득
DriverManager.getConnection 메서드를 호출해 Connection 객체를 획득한다. H2 데이터베이스를 In-memory 방식으로 사용하기 때문에 jdbc:h2:mem:으로 시작하는 URL을 쓴다.
public class Main {
public static void main(String[] args) throws SQLException {
String url = "jdbc:h2:mem:mydb";
String user = "sa";
String password = "";
Connection conn = DriverManager.getConnection(url, user, password);
System.out.println("Connection 객체 → " + conn);
conn.close();
}
}main 메서드 실행 결과는 다음과 같다.
Connection 객체 → conn0: url=jdbc:h2:mem:mydb user=SA
하위 태스크 3
Member 테이블 생성
데이터베이스에 테이블 생성
데이터베이스에 id 열, name 열, email 열로 구성된 테이블을 생성한다. 생성된 테이블을 SHOW TABLES 문장으로 확인한다.
public class Main {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
Class.forName("org.h2.Driver");
String url = "jdbc:h2:mem:mydb";
String user = "sa";
String password = "";
Connection conn = DriverManager.getConnection(url, user, password);
Statement stmt = conn.createStatement();
stmt.execute("CREATE TABLE member (id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255), email VARCHAR(255))");
ResultSet rs = stmt.executeQuery("SHOW TABLES");
while (rs.next()) {
System.out.println(rs.getString("TABLE_NAME"));
}
stmt.close();
conn.close();
}
}main 메서드 실행 결과는 다음과 같다.
MEMBER
하위 태스크 4
Statement를 사용한 INSERT
기본 INSERT 작업 구현
member 테이블에 하나의 행을 삽입하는 INSERT 문장을 실행한다. 이후 member 테이블에서 영향을 받은 행을 출력한다.
public class Main {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
Class.forName("org.h2.Driver");
String url = "jdbc:h2:mem:mydb";
String user = "sa";
String password = "";
Connection conn = DriverManager.getConnection(url, user, password);
// 테이블 생성
Statement stmt = conn.createStatement();
stmt.execute("CREATE TABLE member (id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255), email VARCHAR(255))");
// 행 삽입
int result = stmt.executeUpdate("INSERT INTO member (name, email) VALUES ('홍길동', '[email protected]')");
System.out.println(result + "개의 행이 갱신되었습니다.");
stmt.close();
conn.close();
}
}main 메서드 실행 결과는 다음과 같다.
1개의 행이 갱신되었습니다.
하위 태스크 5
Statement를 사용한 SELECT
SELECT 및 ResultSet 처리 구현
member 테이블의 모든 데이터를 조회하는 문장을 실행한다. 이후 name 열의 값을 차례로 순회하며 출력한다.
public class Main {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
Class.forName("org.h2.Driver");
String url = "jdbc:h2:mem:mydb";
String user = "sa";
String password = "";
Connection conn = DriverManager.getConnection(url, user, password);
// 테이블 생성
Statement stmt = conn.createStatement();
stmt.execute("CREATE TABLE member (id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255), email VARCHAR(255))");
// 행 삽입
stmt.executeUpdate("INSERT INTO member (name, email) VALUES ('홍길동', '[email protected]')");
// 테이블 조회
ResultSet rs = stmt.executeQuery("SELECT * FROM member");
while (rs.next()) {
System.out.println(rs.getString("name"));
}
stmt.close();
conn.close();
}
}main 메서드 실행 결과는 다음과 같다.
홍길동
하위 태스크 6
Statement를 사용한 UPDATE/DELETE
UPDATE, DELETE 작업 구현
member 테이블을 생성하고 두 개의 행을 삽입한 시점의 member 테이블의 모습은 다음과 같다.
| id | name | |
|---|---|---|
| 1 | 홍길동 | [email protected] |
| 2 | 김철수 | [email protected] |
id 열이 2인 행의 name을 김수호로 변경하고, id 열이 1인 행을 제거하는 SQL 문을 실행하는 문장을 추가했다. |
public class Main {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
Class.forName("org.h2.Driver");
String url = "jdbc:h2:mem:mydb";
String user = "sa";
String password = "";
try (Connection conn = DriverManager.getConnection(url, user, password); Statement stmt = conn.createStatement()) {
// 테이블 생성
stmt.execute("CREATE TABLE member (id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255), email VARCHAR(255))");
// 행 삽입
stmt.executeUpdate("INSERT INTO member (name, email) VALUES ('홍길동', '[email protected]'), ('김철수', '[email protected]')");
// 행 수정
stmt.executeUpdate("UPDATE member SET name = '김수호' WHERE id = 2");
stmt.executeUpdate("DELETE FROM member WHERE id = 1");
// 테이블 조회
try (ResultSet rs = stmt.executeQuery("SELECT * FROM member")) {
while (rs.next()) {
System.out.println(rs.getString("name"));
}
}
}
}
}main 메서드 실행 결과는 다음과 같다.
김수호
하위 태스크 7
PreparedStatement INSERT 구현
파라미터 바인딩을 사용한 INSERT
member 테이블에 데이터를 삽입하는 코드에서 Statement를 사용해 SQL 문을 실행하던 것을 PreparedStatement로 대체했다.
public class Main {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
Class.forName("org.h2.Driver");
String url = "jdbc:h2:mem:mydb";
String user = "sa";
String password = "";
try (
Connection conn = DriverManager.getConnection(url, user, password);
Statement stmt = conn.createStatement();
) {
// 테이블 생성
stmt.execute("CREATE TABLE member (id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255), email VARCHAR(255))");
// 행 삽입
try (PreparedStatement pstmt = conn.prepareStatement("INSERT INTO member (name, email) VALUES (?, ?)")) {
pstmt.setString(1, "홍길동");
pstmt.setString(2, "[email protected]");
pstmt.executeUpdate();
pstmt.setString(1, "김철수");
pstmt.setString(2, "[email protected]");
pstmt.executeUpdate();
}
// 테이블 조회
try (ResultSet rs = stmt.executeQuery("SELECT * FROM member")) {
while (rs.next()) {
System.out.println(rs.getString("name"));
}
}
}
}
}main 메서드 실행 결과는 다음과 같다.
홍길동
김철수
하위 태스크 8
PreparedStatement SELECT 구현
파라미터 바인딩을 사용한 SELECT
member 테이블을 조회하는 SELECT 문에 PreparedStatement를 사용했다. WHERE 절에 파라미터를 사용해 id 열에 대한 조건을 설정할 수 있다. 다음 코드는 id 열이 1인 행을 탐색하고 name 열의 값을 출력한다.
public class Main {
public static void main(String[] args) throws SQLException, ClassNotFoundException {
Class.forName("org.h2.Driver");
String url = "jdbc:h2:mem:mydb";
String user = "sa";
String password = "";
try (
Connection conn = DriverManager.getConnection(url, user, password);
Statement stmt = conn.createStatement();
) {
// 테이블 생성
stmt.execute("CREATE TABLE member (id BIGINT PRIMARY KEY AUTO_INCREMENT, name VARCHAR(255), email VARCHAR(255))");
// 행 삽입
try (PreparedStatement pstmt = conn.prepareStatement("INSERT INTO member (name, email) VALUES (?, ?)")) {
pstmt.setString(1, "홍길동");
pstmt.setString(2, "[email protected]");
pstmt.executeUpdate();
pstmt.setString(1, "김철수");
pstmt.setString(2, "[email protected]");
pstmt.executeUpdate();
}
// 테이블 조회
try (PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM member WHERE id = ?")) {
pstmt.setLong(1, 1L);
try (ResultSet rs = pstmt.executeQuery()) {
while (rs.next()) {
System.out.println(rs.getString("name"));
}
}
}
}
}
}main 메서드 실행 결과는 다음과 같다.
홍길동
하위 태스크 9
Spring Data JDBC 설정
application.properties 설정 및 의존성 확인
build.gradle에 H2 의존성을 명시하고 Gradle 프로젝트를 동기화한다.
dependencies {
implementation 'com.h2database:h2:2.2.224'
// ...
}application.properties를 H2 데이터베이스 사용 환경에 따라 수정한다.
spring.datasource.url=jdbc:h2:mem:mydb
spring.datasource.username=sa
spring.datasource.password=
spring.datasource.driver-class-name=org.h2.Driver스프링 부트 실행 시 member 테이블을 생성하기 위해 resources 폴더 안에 schema.sql을 작성한다.
CREATE TABLE member (
id BIGINT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(255),
email VARCHAR(255)
);
하위 태스크 10
Member 엔티티 클래스 생성
@Table, @Id 어노테이션 사용
Member 클래스를 선언한다. @Table("MEMBER") 애노테이션은 Member 클래스가 데이터베이스의 member 테이블에 대응함을 알린다. @Id 애노테이션은 id 속성이 기본 키임을 알린다.
@Table("MEMBER") // H2 데이터베이스는 테이블 이름을 대문자로 관리한다.
@Data // Getter, Setter 등의 메서드를 만든다.
@AllArgsConstructor // 모든 속성을 생성자 매개변수로 하는 생성자를 만든다.
public class Member {
@Id
private Long id;
private String name;
private String email;
}하위 태스크 11
MemberRepository 인터페이스 생성
CrudRepository 상속
CrudRepository 인터페이스를 상속 받는 MemberRepository 인터페이스를 생성한다. CrudRepository 인터페이스를 상속 받으면 이전 하위 태스크에서 구현했던 여러 JDBC 코드(조회, 삽입 등)를 대체할 수 있다.
public interface MemberRepository extends CrudRepository<Member, Long> {
}하위 태스크 12
Repository를 사용한 CRUD
Spring Data JDBC를 통한 데이터베이스 작업
memberRepository 인스턴스로 CRUD 작업을 한다.
- Create:
Member클래스로 홍길동, 김철수 인스턴스를 생성하고 데이터베이스에 삽입한다. - Read:
member테이블의 모든 행을 가져오고, 그 내용과 개수를 출력한다. - Update: 홍길동 인스턴스의
name속성을 홍신우로 바꾼다. - Delete: 김철수 인스턴스를 제거하고
member테이블에 남은 행 개수를 출력한다.
@Component
@RequiredArgsConstructor
@Slf4j
public class SpringJdbcApplication implements ApplicationRunner {
private final MemberRepository memberRepository;
@Override
public void run(ApplicationArguments args) throws Exception {
// Create
Member gildongHong = new Member(null, "홍길동", "[email protected]");
Member chulsuKim = new Member(null, "김철수", "[email protected]");
memberRepository.save(gildongHong);
memberRepository.save(chulsuKim);
// Read
var members = memberRepository.findAll();
log.info("members = {}", members);
long count = memberRepository.count();
log.info("count = {}", count);
// Update
gildongHong.setName("홍신우");
memberRepository.save(gildongHong);
var sinwooHong = memberRepository.findById(1L);
log.info("updatedGildongHong = {}", sinwooHong);
// Delete
memberRepository.delete(chulsuKim);
long countAfterDelete = memberRepository.count();
log.info("countAfterDelete = {}", countAfterDelete);
}
}스프링 부트 실행 결과는 다음과 같다.
members = [Member(id=1, name=홍길동, [email protected]), Member(id=2, name=김철수, [email protected])]
count = 2
updatedGildongHong = Optional[Member(id=1, name=홍신우, [email protected])]
countAfterDelete = 1