JdbcTemplate

이 문서에서는 Spring에서 제공하는 JDBC 기능을 사용하는 방법에 대해서 다룬다.

Spring JDBC란

Spring에서 제공하는 JDBC(Java DataBase Connectivity)를 위한 도구들이다.

  • DriverManagerDataSource - 데이터베이스 연결 담당 객체

  • JdbcTemplate - 구문 실행 객체

필요 라이브러리

프로그램을 구현하기에 앞서 다음과 같은 라이브러리를 준비해야 한다.

  • 오라클

    • ojdbc8

  • Spring

    • spring-beans

    • spring-core

    • spring-tx

    • spring-jdbc

  • 기타

    • commons-logging-1.2

해당 라이브러리들은 Maven Central Repository에서 검색하여 다운로드 가능하다.

도구 준비

JDBC를 위한 도구를 준비하는 코드는 다음과 같다.

DriverManagerDataSource

DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("oracle.jdbc.OracleDriver");
dataSource.setUrl("jdbc:oracle:thin:@localhost:1521:xe");
dataSource.setUsername("계정이름");
dataSource.setPassword("비밀번호");

JdbcTemplate

JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);

테이블 준비

테이블은 Database에 생성되어 있어야 한다.

이 문서에서는 다음 테이블(table)을 생성하여 사용한다.

CREATE TABLE STUDENT(
NO NUMBER PRIMARY KEY,
NAME VARCHAR2(21) NOT NULL CHECK(REGEXP_LIKE(NAME, '^[가-힣]{2,7}$')),
KOREAN NUMBER DEFAULT 0 NOT NULL CHECK(KOREAN BETWEEN 0 AND 100),
ENGLISH NUMBER DEFAULT 0 NOT NULL CHECK(ENGLISH between 0 AND 100),
MATH NUMBER DEFAULT 0 NOT NULL CHECK(MATH between 0 AND 100)
);

번호 등록을 위한 시퀀스(sequence)도 생성한다.

CREATE SEQUENCE STUDENT_SEQ;

등록(INSERT)

등록을 하기 위한 SQL 구문은 다음과 같다.

INSERT INTO STUDENT(NO, NAME, KOREAN, ENGLISH, MATH)
VALUES(STUDENT_SEQ.NEXTVAL, '테스트', 50, 60, 70);

구문에서 변할 수 있는 정보는 이름, 국어점수, 영어점수, 수학점수이다. 따라서 해당하는 정보들을 자바에서 입력 받고 구문을 완성하여 DBMS에 전송해야 한다.

DriverManagerDataSourceJdbcTemplate을 먼저 준비한다.

이후 추가할 데이터를 다음과 같이 준비한다.

String name = "피카츄";
int korean = 50, english = 60, math = 70;

데이터는 Scanner 클래스 등을 이용하여 사용자에게 입력 받는 것도 가능하다.

Scanner sc = new Scanner(System.in);
System.out.print("이름 : ");     String naem = sc.next();
System.out.print("국어 : ");     int korean = sc.nextInt();
System.out.print("영어 : ");     int english = sc.nextInt();
System.out.print("수학 : ");     int math = sc.nextInt();
sc.close(); 

준비된 데이터를 추가할 수 있는 SQL 구문과 배열을 준비한다.

String sql = "INSERT INTO STUDENT(NO, NAME, KOREAN, ENGLISH, MATH) VALUES(STUDENT_SEQ.NEXTVAL, ?, ?, ?, ?)";
Object[] param = {name, korean, english, math};

JdbcTemplate을 실행하며 작성한 SQL 구문과 데이터를 전달한다.

jdbcTemplate.update(sql, param); 

완성 코드

//사용자 입력
Scanner sc = new Scanner(System.in);
System.out.print("이름 : ");     String naem = sc.next();
System.out.print("국어 : ");     int korean = sc.nextInt();
System.out.print("영어 : ");     int english = sc.nextInt();
System.out.print("수학 : ");     int math = sc.nextInt();
sc.close(); 

//연결 도구 생성 
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("oracle.jdbc.OracleDriver");
dataSource.setUrl("jdbc:oracle:thin:@localhost:1521:xe");
dataSource.setUsername("계정이름");
dataSource.setPassword("비밀번호");

//구문 실행 도구 생성
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);

//구문 및 데이터 배열 준비
String sql = "INSERT INTO STUDENT(NO, NAME, KOREAN, ENGLISH, MATH) VALUES(STUDENT_SEQ.NEXTVAL, ?, ?, ?, ?)";
Object[] param = {name, korean, english, math};

//실행
jdbcTemplate.update(sql, param); 

System.out.println("완료");

수정(UPDATE)

STUDENT 테이블에서 할 수 있는 수정은 다음과 같은 것들이 있다.

  • 특정 번호 학생에 대한 모든 정보를 변경한다

  • 특정 번호 학생에 대한 이름을 변경한다

  • 특정 번호 학생에 대한 점수 정보를 변경한다

번호가 PRIMARY KEY이기 때문에 특정 대상을 수정할 때는 반드시 번호가 필요하다. 여러 학생을 동시에 고치는 행위는 일반적이지 않으므로 번호를 수정하는 경우가 없도록 해야 한다.

특정 번호 학생에 대한 모든 정보를 변경하는 SQL 구문은 다음과 같다.

UPDATE STUDENT SET NAME='라이츄',KOREAN=60,ENGLISH=70,MATH=80 WHERE NO=1;

구문에서 변할 수 있는 정보는 이름, 국어점수, 영어점수, 수학점수, 번호이다. 해당 정보들은 자바에서 입력받고 구문에 합성하여 DBMS에서 처리할 수 있도록 프로그램을 구성해야 한다.

DriverManagerDataSourceJdbcTemplate을 먼저 준비하고 수정을 위한 데이터를 준비한다.

int no = 1;
String name = "라이츄";
int korean = 60, english = 70, math = 80;

Scanner를 이용하여 입력 받을 경우의 코드는 다음과 같다.

Scanner sc = new Scanner(System.in);

System.out.print("수정할 번호 : ");
int no = sc.nextInt();

System.out.print("수정할 이름 : ");
String name = sc.next();

System.out.print("수정할 국어점수 : ");
int korean = sc.nextInt();

System.out.print("수정할 영어점수 : ");
int english = sc.nextInt();

System.out.print("수정할 수학점수 : ");
int math = sc.nextInt();

sc.close();

준비한 데이터를 설정할 수 있도록 구문과 배열을 준비한다. 주의할 점은 구문에 작성된 홀더(?)와 동일한 순서로 배열의 데이터가 존재해야 한다는 것이다.

String sql = "UPDATE STUDENT SET NAME=?,KOREAN=?,ENGLISH=?,MATH=? WHERE NO=?";
Object[] param = {name, korean, english, math, no};

구문과 데이터를 JdbcTemplate에게 전달하여 실행을 지시한다. 단, 조건에 따라 수정된 데이터가 없을 수도 있으므로 결과를 받아서 확인해야한다.

int result = jdbcTemplate.update(sql, param);

결과에 따라 성공/실패 여부를 사용자에게 출력한다.

if(result > 0){
    System.out.println("정보 변경이 완료되었습니다");
}
else {
    System.out.println("해당하는 번호가 존재하지 않습니다");
}

완성 코드

//데이터 준비
Scanner sc = new Scanner(System.in);

System.out.print("수정할 번호 : ");
int no = sc.nextInt();

System.out.print("수정할 이름 : ");
String name = sc.next();

System.out.print("수정할 국어점수 : ");
int korean = sc.nextInt();

System.out.print("수정할 영어점수 : ");
int english = sc.nextInt();

System.out.print("수정할 수학점수 : ");
int math = sc.nextInt();

sc.close();

//연결 도구 생성 
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("oracle.jdbc.OracleDriver");
dataSource.setUrl("jdbc:oracle:thin:@localhost:1521:xe");
dataSource.setUsername("계정이름");
dataSource.setPassword("비밀번호");

//구문 실행 도구 생성
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);

//구문 및 데이터 배열 준비
String sql = "UPDATE STUDENT SET NAME=?,KOREAN=?,ENGLISH=?,MATH=? WHERE NO=?";
Object[] param = {name, korean, english, math, no};

//실행
int result = jdbcTemplate.update(sql, param); 

//결과에 따른 출력
if(result > 0){
    System.out.println("정보 변경이 완료되었습니다");
}
else {
    System.out.println("해당하는 번호가 존재하지 않습니다");
}

삭제(DELETE)

삭제는 PRIMARY KEY를 이용하여 한 개의 데이터를 삭제하는 것이 일반적이다.

DELETE STUDENT WHERE NO = 1;

DriverManagerDataSourceJdbcTemplate을 먼저 준비하고 삭제를 위한 데이터를 준비한다.

int no = 1;

다음과 같이 Scanner로 입력 처리할 수 있다.

Scanner sc = new Scanner(System.in);
System.out.print("삭제할 번호 : ");
int no = sc.nextInt();
sc.close();

데이터를 설정할 구문과 배열을 준비한다.

String sql = "DELETE STUDENT WHERE NO = ?";
Object[] param = {no};

JdbcTemplate에게 구문과 배열을 전달하며 실행을 명령한다. 실행 후 실제로 삭제된 개수를 알아야 사용자에게 성공과 실패를 알려줄 수 있으므로 반환값을 int 형태의 변수에 저장한다.

int result = jdbcTemplate.update(sql, param);

결과에 따라 사용자에게 성공/실패 여부를 출력한다.

if(result > 0){
    System.out.println("삭제 완료");
}
else {
    System.out.println("올바르지 않은 번호");
}

완성 코드

//데이터 준비
Scanner sc = new Scanner(System.in);

System.out.print("삭제할 번호 : ");
int no = sc.nextInt();

sc.close();

//연결 도구 생성 
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("oracle.jdbc.OracleDriver");
dataSource.setUrl("jdbc:oracle:thin:@localhost:1521:xe");
dataSource.setUsername("계정이름");
dataSource.setPassword("비밀번호");

//구문 실행 도구 생성
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);

//구문 및 데이터 배열 준비
String sql = "DELETE STUDENT WHERE NO = ?";
Object[] param = {no};

//실행
int result = jdbcTemplate.update(sql, param); 

//결과에 따른 출력
if(result > 0){
    System.out.println("삭제 완료");
}
else {
    System.out.println("올바르지 않은 번호");
}

조회(SELECT)

조회는 등록, 수정, 삭제와 구문의 목적과 명령이 다르다. 조회의 목적은 데이터베이스 테이블의 데이터를 프로그램으로 가져오는 것이다. 하지만 데이터베이스 테이블은 표 형태를 가지고 있고, 자바에서는 객체 형태로 표현해야 하기 때문에 변환이 필요하다. 예를 들어서 다음과 같은 테이블이 있을 경우 자바에서는 다섯 개의 객체가 있다고 본다.

데이터베이스 테이블

자바(객체 지향) 관점

  • 1번 학생(no=1,name=피카츄,korean=73,english=79,math=78)

  • 2번 학생(no=2,name=라이츄,korean=57,english=69,math=80)

  • 3번 학생(no=3,name=파이리,korean=93,english=82,math=80)

  • 4번 학생(no=4,name=꼬부기,korean=99,english=51,math=74)

  • 5번 학생(no=5,name=버터플,korean=85,english=98,math=67)

따라서 데이터베이스를 객체로 변환하고 List에 저장하여 순서를 유지하도록 관리하는 것이 자바에서 의미 있는 데이터로 사용하는 방법이라고 볼 수 있다.

이를 위해서 데이터베이스의 한 줄을 저장하기 위한 클래스를 설계한다. 이를 DTO(Data Transfer Object)라 부른다.

StudentDto.java
public class StudentDto {
    private int no;
    private String name;
    private int korean;
    private int english;
    private int math;
    
    //setter, getter, toString() 등을 추가로 생성
}

그리고 조회 결과를 StudentDto에 어떻게 복사할 것인지 알려주기 위한 클래스가 필요하며 이를 RowMapper<T> 라고 부른다.

RowMapper<StudentDto> mapper = new RowMapper<StudentDto>(){
    @Override
    public StudentDto mapRow(ResultSet rs, int idx) throws SQLException {
        StudentDto studentDto = new StudentDto();
        studentDto.setNo(rs.getInt("no"));
        studentDto.setName(rs.getString("name"));
        studentDto.setKorean(rs.getInt("korean"));
        studentDto.setEnglish(rs.getInt("english"));
        studentDto.setMath(rs.getInt("math"));
        return studentDto;
    }
};

RowMapper<T>는 함수형 인터페이스이기 때문에 Lambda 표현이 가능하다.

RowMapper<StudentDto> mapper = (rs, idx) -> {
    StudentDto studentDto = new StudentDto();
    studentDto.setNo(rs.getInt("no"));
    studentDto.setName(rs.getString("name"));
    studentDto.setKorean(rs.getInt("korean"));
    studentDto.setEnglish(rs.getInt("english"));
    studentDto.setMath(rs.getInt("math"));
    return studentDto;
};

위 두 가지가 준비되었다면 동일하게 DriverManagerDataSourceJdbcTemplate을 준비하여 query 메소드를 실행하도록 코드를 구성한다. 반환형은 List<StudentDto> 형태이다.

String sql = "SELECT * FROM STUDENT";
List<StudentDto> list = jdbcTemplate.query(sql, mapper);

반복문을 통해 출력을 수행한다.

for(StudentDto studentDto : list){
    System.out.println(studentDto.getName());
}

완성 코드

//변환 객체 준비
RowMapper<StudentDto> mapper = (rs, idx) -> {
    StudentDto studentDto = new StudentDto();
    studentDto.setNo(rs.getInt("no"));
    studentDto.setName(rs.getString("name"));
    studentDto.setKorean(rs.getInt("korean"));
    studentDto.setEnglish(rs.getInt("english"));
    studentDto.setMath(rs.getInt("math"));
    return studentDto;
};

//연결 도구 생성 
DriverManagerDataSource dataSource = new DriverManagerDataSource();
dataSource.setDriverClassName("oracle.jdbc.OracleDriver");
dataSource.setUrl("jdbc:oracle:thin:@localhost:1521:xe");
dataSource.setUsername("계정이름");
dataSource.setPassword("비밀번호");

//구문 실행 도구 생성
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);

//구문 준비
String sql = "SELECT * FROM STUDENT";

//실행 및 목록 가져오기
List<StudentDto> list = jdbcTemplate.query(sql, mapper);

//출력
for(StudentDto studentDto : list){
    System.out.print("번호 : " + studentDto.getNo());
    System.out.print("/");
    System.out.print("이름 : " + studentDto.getName());
    System.out.print("/");
    System.out.print("국어 : " + studentDto.getKorean());
    System.out.print("/");
    System.out.print("영어 : " + studentDto.getEnglish());
    System.out.print("/");
    System.out.print("수학 : " + studentDto.getMath());
    System.out.println();
}

Last updated