본문 바로가기
  • 실행력이 모든걸 결정한다
Spring Series/Spring Framework

[Spring] 더 편리해진 Spring JDBC

by 김코더 김주역 2022. 6. 29.
반응형

1. Spring JDBC란?

- Spring JDBC는 DriverManager에 드라이버를 로드하고, Connection, Statement, ResultSet 관련 작업, 자원 해제 작업과 트랜잭션 작업을 모두 담당 해주기 때문에 상당한 소스코드를 줄일 수 있게 된다.

- Spring JDBC는 JDBC 작업 중에 발생하는 모든 예외를 처리해준다. DB별 에러 코드를 참고해서 SQLException 예외를 DataAccessException 타입의 계층구조 내의 예외로 변환해준다.

- Spring JDBC는 멀티 쓰레드 환경에 안전하다.

- Spring JDBC의 접근 방법 중에서 JdbcTemplate이 가장 대표적인데, JdbcTemplate 기능을 모두 갖고 있으면서 더 발전되고 편리한 사용 방법을 제공해주는 접근 방법들이 생겨났으며, 대표적으로 SimpleJdbcTemplate, SimpleJdbcInsert, SimpleJdbcCall이 있다.

※ JdbcTemplate 사용법 참고 : https://kimcoder.tistory.com/241

 

[Spring] JdbcTemplate

JdbcTemplate는 DriverManager에 드라이버를 로드하고, Connection, Statement, ResultSet 관련 작업, 자원 해제 작업을모두 담당 해주기 때문에 상당한 소스코드를 줄일 수 있게 된다. 이제 Spring JdbcTemplate..

kimcoder.tistory.com

※ JdbcTemplate에서 제공하는 세밀한 템플릿/콜백 방식을 직접 이용하고 싶다면 getJdbcOperation() 메소드를 이용하면 된다.

 

 

 

2. SimpleJdbcTemplate

1) SimpleJdbcTemplate란?

- JdbcTemplate의 기능을 향상시킨 것

- 실행, 조회, 배치(일괄 작업)으로 구분되어 제공된다.

 

 

2) SimpleJdbcTemplate의 생성

public class UserDao {
    SimpleJdbcTemplate simpleJdbcTemplate;
    
    public void setDataSource(DataSource dataSource) {
    	this.simpleJdbcTemplate=new SimpleJdbcTemplate(dataSource);
    }
}

 

 

3) 동적 SQL

- JdbcTemplate에서는 '?' 치환자를 이용하여 동적으로 SQL을 완성하는데, 이 방법은 SQL 주입 공격에 위험하다.

- SimpleJdbcTemplate에서는 더 편한 이름 치환자 기능을 제공한다. 이름을 이용하기 때문에 파라미터 바인딩에는 신경을 안써도 된다.

 INSERT INTO USER(ID, NAME, AGE) VALUES (:id, :name, :age);

- 이름 치환자를 사용하면 오브젝트나 맵을 활용할 수 있다. 오브젝트의 속성이나 맵의 key를 이용하여 이름을 바인딩 할 수 있는 것이다.

 

(1) BeanPropertySqlParameterSource

여기에서 말하는 bean은 spring bean이 아닌 java bean을 말한다.

public class User {
    int id;
    String name;
    int age;
    // getter, setter 메소드 생략
}

 

User user = new User(1, "Jooyeok", 25);
BeanPropertySqlParameterSource beanPropertySqlParameterSource
    = new BeanPropertySqlParameterSource(user);

 

(2) MapSqlParameterSource

MapSqlParameterSource mapSqlParameterSource = new MapSqlParameterSource()
    .addValue("id", 1)
    .addValue("name", "Jooyeok")
    .addValue("age", 25)

 

 

4) SQL 실행 메소드

- INSERT, UPDATE, DELETE문을 사용할 때는 SimpleJdbcTemplate의 update() 메소드를 사용한다. update() 메소드는 SQL 실행으로 영향 받은 레코드의 개수를 리턴해준다.

 

(1) 위치 치환자 사용

- args를 추가적으로 작성해주면 된다.

simpleJdbcTemplate.update("INSERT INTO USER(ID, NAME, AGE, args) VALUES(?,?,?)", 1, "Jooyeok", 25);

 

(2) Map

Map<String, Object> map = new HashMap<String, Object>();
...
simpleJdbcTemplate.update("INSERT INTO USER(ID, NAME, AGE, args) VALUES(:id, :name, :age)", map);

 

(3) MapSqlParameterSource

simpleJdbcTemplate.update("INSERT INTO USER(ID, NAME, AGE, args) VALUES(:id, :name, :age)", mapSqlParameterSource);

 

(4) BeanPropertySqlParameterSource

simpleJdbcTemplate.update("INSERT INTO USER(ID, NAME, AGE, args) VALUES(:id, :name, :age)", beanPropertySqlParameterSource);

 

 

5) SQL 조회 메소드

- SELECT문을 사용할 때는 사용하는 메소드

- 하나의 레코드를 조회하는 메소드의 경우에는 검색된 결과가 없다면 EmptyResultDataAccessException 예외가 발생하고, 그 외의 부적절한 결과의 수에 대해서는 IncorrectResultSizeDataAccessException 예외가 발생한다.

※ 전자의 예외는 후자의 예외를 상속 받음

 

(1) queryForInt

- 하나의 int 값을 조회할 때 사용

simpleJdbcTemplate.queryForInt("SELECT AGE FROM USER WHERE NAME=:name", mapSqlParameterSource);

 

(2) queryForLong

- 하나의 long 값을 조회할 때 사용

 

(3) quertForObject

- 하나의 레코드를 조회할 때 사용

- 결과를 가져올 오브젝트 타입을 직접 지정할 수 있음

<1> 클래스 사용

- 형식 : <T> T queryForObject(String, sql, Class<T> requiredType, [SQL parameters])

String name = simpleJdbcTemplate.queryForObject("SELECT NAME FROM USER WHERE ID=?", String.class, id);

- 하나의 결과값을 가져올 Object 타입을 직접 지정할 수 있음

<2> RowMapper 사용

- 형식 : <T> T queryForObject(String, sql, RowMapper<T> rm, [SQL parameters])

- 레코드를 하나의 오브젝트로 전환할 때 사용

※ RowMapper을 사용한 예시는 위에 올려둔 JdbcTemplate 사용법 포스팅에서 다뤘다.

 

(4) query

- 형식 : <T> List<T> query(String, sql, RowMapper<T> rm, [SQL parameters])

- 여러 개의 레코드를 조회할 때 사용

- List를 통해 여러 오브젝트를 저장할 수 있음

- 결과 레코드의 개수에 따른 예외는 발생하지 않음

 

(5) queryForMap

- 형식 : Map<String, object> queryForMap(String sql, [SQL parameters])

- 레코드를 하나의 Map으로 전환할 때 사용

- Map의 key에 컬럼 이름이 저장됨

 

(6) queryForList

- 형식 : List<Map<String, Object>> queryForList(String sql, [SQL parameters])

- 여러 개의 레코드를 여러 개의 Map으로 전환할 때 사용

- List를 통해 여러 Map을 저장할 수 있음

- 결과 레코드의 개수에 따른 예외는 발생하지 않음

 

 

6) SQL 배치 메소드

- update()로 실행하는 SQL문들을 일괄적으로 실행함으로써 DB 호출을 최소화하여 성능을 향상시킬 때 사용

- batchUpdate() 메소드를 사용하며, 내부적으로는 JDBC Statement의 addBatch()와 executeBatch() 메소드를 이용함

- 동일한 SQL을 파라미터만 바꿔가면서 실행하는 원리이기 때문에, 배치 메소드는 하나의 SQL과 SQL 파라미터 배열을 파라미터로 가짐

- 각 SQL을 실행했을 때 영향받은 레코드의 개수를 배열로 반환함

 

(1) Map 배열 사용

- 형식 : int[] batchUpdate(String sql, Map<String, ?>[] batchValues)

- 각 SQL에 대한 파라미터 정보를 Map 배열로 만듬

 

(2) SqlParameterSource 배열 사용

- 형식 : int[] batchUpdate(String sql, SqlParameterSource[] batchArgs)

- 각 SQL에 대한 파라미터 정보를 SqlParameterSource 타입 오브젝트로 만듬. SqlParameterSource 타입이기만 하면 섞어서도 사용 가능함

 

(3) List 사용

- 형식 : int[] batchUpdate(String sql, List<Object[]> batchArgs)

- 위치 치환자를 사용할 때 args로 전달 했던 SQL parameter들을 Object 배열에 넣고 이들을 List로 만듬

 

 

 

3. SimpleJdbcInsert

1) SimpleJdbcInsert란?

- 비슷한 구조의 SQL을 반복적으로 만들어야 하고, SQL이 일반 텍스트로 되어있기 때문에 빌드 시점에서 검증할 수 없고 실수가 나기 쉽다는 문제를 극복하기 위해 제공되는 클래스다.

- DB의 메타 정보를 활용하여 INSERT문의 작성을 간편하게 만들어준다.

 

 

2) SimpleJdbcInsert의 생성

SimpleJdbcInsert simpleJdbcInsert = new SimpleJdbcInsert(dataSource);

- SimpleJdbcInsert는 테이블별로 생성하는 클래스이기 때문에 오브젝트를 생성하고 난 후에 어떤 테이블을 적용할지 초기화해야 하며, 하나의 DAO에서 여러 개의 SimpleJdbcInsert를 사용할 수 있다.

- 초기화 메소드는 모두 SimpleJdbcInsert 자신을 반환하기 때문에 체인 형식으로 코드를 작성할 수 있다. 이제 초기화 메소드로는 어떤 것들이 있는지 살펴보자.

 

(1) withTableName

- 형식 : SimpleJdbcInsert withTableName(String tableName)

- 테이블 이름을 지정하여, 내부적으로 지정된 테이블 이름을 참고해서 DB의 테이블 메타 정보를 활용하도록 하는 메소드이다.

SimpleJdbcInsert simpleJdbcInsert = new SimpleJdbcInsert(dataSource).
    withTableName("user");

 

(2) withSchemaName

- 형식 : SimpleJdbcInsert withSchemaName(String schemaName)

- 스키마의 이름을 지정한다.

 

(3) withCatalogName

- 형식 : SimpleJdbcInsert withCatalogName(String catalogName)

- 카탈로그의 이름을 지정한다.

 

(4) usingColumns

- 형식 : SimpleJdbcInsert usingColumns(String... columnNames)

- 일부 컬럼들만 사용해서 INSERT문을 작성할 때 사용된다.

- 컬럼들의 이름을 파라미터에 String 타입의 가변 인자를 이용하여 작성한다.

 

(5) usingGeneratedKeyColumns

- 형식 : SimpleJdbcInsert usingGeneratedKeyColumns(String... columnNames)

- DB에 의해 자동으로 생성되는 key 컬럼을 지정하여, 지정된 컬럼을 INSERT문에서 제외시킬 때 사용한다.

 

(6) withoutTableColumnMetaDataAccess

- 형식 : SimpleJdbcInsertOperations withoutTableColumnMetaDataAccess()

- DB에서 테이블 메타데이터를 가져오지 않도록 한다.

- DB의 메타정보를 이용하지 않고 파라미터로 제공된 정보를 활용하고 싶을 때 사용한다.

 

 

3) SQL 실행 메소드

(1) execute

- 형식 : int execute(Map<String, Object> map) 또는 int execute(SqlParameterSource sqlParameterSource)

- SQL 파라미터 정보를 파라미터로 받아서 INSERT 작업을 수행한다.

 

(2) executeAndReturnKey

- 형식 : Number executeAndReturnKey(Map<String, Object> map) 또는 Number executeAndReturnKey(SqlParameterSource sqlParameterSource)

- execute() 작업을 수행하고 자동생성된 key값을 java.lang.Number 타입으로 반환해준다.

※ 자동생성 key 설정의 대표적인 예로, MySQL의 AUTO_INCREMENT 옵션이 있다.

- executeAndReturnKey() 메소드를 이용하려면 usingGeneratedKeyColumns() 메소드로 자동생성 key 컬럼을 지정해줘야 한다.

- 자동생성되는 id 컬럼에 대한 파라미터 정보는 생략하면 된다.

 

(3) executeAndReturnKeyHolder

- 형식 : KeyHolder executeAndReturnKeyHolder(Map<String, Object> map) 또는 KeyHolder executeAndReturnKeyHolder(SqlParameterSource sqlParameterSource)

- execute() 작업을 수행하고 2개 이상의 자동생성 key값을 반환받기 위해 사용된다.

- KeyHolder 클래스의 getKeyList() 메소드는 자동생성 key의 <컬럼명,값> 쌍들을 List<Map<String, Object>> 타입으로 반환해준다.

 

 

 

4. SimpleJdbcCall

1) SimpleJdbcCall 이란?

- DB에 저장된 procedure이나 function을 호출할 때 사용한다.

 

 

2) SimpleJdbcCall의 생성

SimpleJdbcCall simpleJdbcCall = new SimpleJdbcCall(dataSource);

- 기본적으로 실행할 procedure 또는 function 중 하나로 초기화해야 한다.

- 초기화 메소드는 모두 SimpleJdbcCall 자신을 반환하기 때문에 체인 형식으로 코드를 작성할 수 있다. 이제 초기화 메소드로는 어떤 것들이 있는지 살펴보자.

 

(1) withProcedureName

- 형식 : SimpleJdbcCallOperations withProcedureName(String procedureName)

- 실행할 procedure의 이름을 지정한다.

 

(2) withFunctionName

- 형식 : SimpleJdbcCallOperations withFunctionName(String functionName)

- 실행할 function의 이름을 지정한다.

 

(3) returningResultSet

- 형식 : SimpleJdbcCallOperations returningResultSet(String parameterName, ParameterizedRowMapper rowMapper)

- Procecure가 ResultSet을 반환하는 경우에 사용

 

 

3) 실행 메소드

(1) executeFunction

- 형식 : <T> T executeFunction(Class<T> returnType, [SQL parameters])

- Function을 실행할 때 사용

 

(2) executeObject

- 형식 : <T> T executeObject(Class<T> returnType, [SQL parameters])

- 출력 파라미터가 하나인 Procedure에 사용

 

(3) execute

- 형식 : Map<String, Object> execute([SQL parameters])

- 출력 파라미터가 하나 이상인 Procedure에 사용

 

 

 

5. JdbcDaoSupport

- JDBC를 이용하여 DAO를 만들 때 도움이 되는 추상 클래스로, DataSource의 주입과 JdbcTemplate의 생성과 같은 공통 기능을 담은 클래스다.

 

예시

public abstract class AbstractSimpleJdbcTemplate extends JdbcDaoSupport {
    protected SimpleJdbcTemplate simpleJdbcTemplate;
    
    protected void initTemplateConfig() {
    	this.simpleJdbcTemplate=new SimpleJdbcTemplate(getDataSource());
        initJdbcObjects();
    }
    
    protected void initJdbcObjects() {} // 상속 받는 클래스에서 사용하는 초기화 메소드
}

 

 

● 참고 자료 : 토비의 스프링 3.1 Vol.2 

 

반응형

'Spring Series > Spring Framework' 카테고리의 다른 글

[Spring] Hibernate  (0) 2022.07.06
[Spring] Spring이 제공하는 JPA  (0) 2022.07.02
[Spring] .gitignore 적용하기  (0) 2022.06.29
[Spring] Bean의 id와 name  (0) 2022.06.23
[Spring] Scope Proxy  (0) 2022.06.23

댓글