지금까지 DB연동시 JDBC API
를 사용해 왔다.
ojdbc6.jar
안의 DriverManager.getConnection(url, user, password)
메서드를 사용해 Connection
객체를 얻어 DB와 연동하였다.
Connection
객체를 이용해 Resultset
, PreparedStatement
등의 객체를 생성하고
쿼리를 삽입하고 실행하고 결과값을 반복문을 돌며 집어 넣고
모두 사용후에는 .close()
메서드를 통해 DB연결 객체를 모두 해제하는 반복코딩을 많이 사용하였다.
Spring
에서 jdbcTemplate
를 사용하면 위와같은 귀찮고 중복되는 코드를 없앨 수 있다.
jdbcTemplate
에 대해 알아보기 전 jdbcTemplate
을 사용하려면 꼭 필요한 DataSource
를 먼저 알아보자.
데이터 베이스와 연동하는 방법은 여러가지 인데 스프링 프레임워크에선 DataSource
방식을 사용하고 있다.
DataSource
: 커넥션풀에는 여러개의Connection
객체가 생성되어 운용되는데, 이를 직접 웹 애플리케이션에서 다루기 힘들기 때문에DataSource
라는 개념을 도입하여 사용, 커넥션 풀의Connection
을 관리하기 위한 객체이다.DataSource
객체를 통해서 필요한Connection
을 획득, 반납 등의 작업을 한다.
스프링 프레임 워크에서 DataSource
를 얻는 방법은 아래 3가지.
우리는 스프링 프레임 워크에서 JDBC를 지원하는 기능을 간단히 살펴볼 것 이기 때문에
3번째 방법인 DriverManager
를 사용해서 DriverManagerDataSource
를 가져오자.
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"></property>
<property name="username" value="scott"></property>
<property name="password" value="tiger"></property>
<property name="url" value="jdbc:oracle:thin:@172.17.107.68:1521:xe"></property>
</bean>
dataSource
라는 식별자로 DriverManagerDataSource
스프링 빈 객체를 생성!
만들어진 DataSource
객체는 스프링에서 JDBC를 위해 지원하는 여러 빈 객체들의 의존관계로 설정된다.
Connection
객체를 구하고 try-catch-finally
로 이루어진 중복된 코드를 제거하기 위해 스프링에선 각종 템플릿 클래스
를 제공하고 있다.
이중 JDBC에서 사용하는 템플릿 클래스는 아래와 같다.
JdbcTemplate
- 기본적인 JDBC 템플릿 클래스, JDBC를 이용해 DB연동하는 메서드를 제공
NamesParameterJdbcTemplate
- PreparedStatement에서 인덱스 기반의 파라미터가 아닌 이름 기반의파라미터를 사용할 수 있도록 지원하는 메서드 제공
SimpleJdbcInsert
- 데이터 삽입을 위한 인터페이스를 제공하는 클래스
SimpleJdbcCall
- 프로시저 호출을 위한 인터페이스를 제공하는 클래스
여기서 가장 많이 쓰이는 건 NamesParameterJdbcTemplate
템플릿 클래스이다.
NamesParameterJdbcTemplate
도 사실 내부적으로는 JdbcTemplate
을 사용하기 떄문에 JdbcTemplate
먼저 알아보자.
먼저 JdbcTemplate
를 일반 객체 혹은 빈 객체를 생성하기 위해선 생성자 혹은 set메서드
를 통해 DataSource
객체를 전달해야 한다.
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"></property>
<property name="url" value="jdbc:oracle:thin:@172.17.107.68:1521:xe"></property>
<property name="username" value="scott"></property>
<property name="password" value="tiger"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg ref="dataSource"></constructor-arg>
</bean>
설정 했으면 DAO
객체에 의존설정을 해주자.
@Autowired
어노테이션을 통해 자동 의존 설정 해주던가 xml설정을 통해 빈 객체 생성시 주입시켜주면 된다.
public class SampleDAO {
private JdbcTemplate jdbcTemplate;
@Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
...
...
}
JdbcTemplate
- query()
- 조회를 위한 메서드query()
메서드는 다음과 같이 오버로딩 되어있다.
List<T> query(String sql, RowMapper<T> rowMapper)
List<T> query(String sql, RowMapper<T> rowMapper, Object ... args)
List<T> query(String sql, Object[] args, RowMapper<T> rowMapper)
List<T> query(String sql, Object[] args, int[] argsTypes)
List<T> query(String sql, Object[] args, int[] argsTypes, RowMapper<T> rowMapper)
String sql
- 당연히 실행할 쿼리문, 위치기반 ?
를 사용하는 PreparedStatement용 쿼리를 사용할 수 있다.
RowMapper<T> rowMapper
- 조회결과 ResultSet
을 읽어와 원하는 객체형식으로 생성해주는 매퍼
Object[] args, int[] argsTypes
- args는 ?
에 바인딩할 파라미터 값 목록, argsTypes
은 파라미터를 바인딩 할 때 사용할 SQL 타입 목록
Object ... args
- 배열 형식으로도 바인딩할 파라미터를 지정 가능하지만 가변인자 형식으로도 바인딩 처리 가능하다.
RowMapper
는 인터페이스로 ResultSet
과 객체를 매핑시킬때 사용하는 mapRow()
를 구현해야 한다.
RowMapper
를 익명 인터페이스로 구현해도 되지만 여러 메서드에 같은형식의 쿼리가 등장해 같은 객체를 매핑해야 한다면 다음과 같이 RowMapper
를 구현한 클래스를 만들어 두자.
반환형이 List<T>
인 것 을 알수 있는데 검색된 행을 객체로 매핑해 리스트화 시켜 반환한다.
public class NoticeRowMapper implements RowMapper<Notice> {
@Override
public Notice mapRow(ResultSet rs, int rowNum) throws SQLException {
Notice notice = new Notice();
notice.setSeq(rs.getInt("seq"));
notice.setTitle(rs.getString("title"));
notice.setWriter(rs.getString("writer"));
notice.setRegdate(rs.getString("regdate"));
notice.setFilesrc(rs.getString("filesrc"));
notice.setContent(rs.getString("content"));
notice.setHit(rs.getInt("hit"));
}
}
public List<Notice> getNotices(int page, String field, String query) throws ClassNotFoundException, SQLException
{
int srow = 1 + (page-1)*15;
int erow = 15 + (page-1)*15;
StringBuffer sql = new StringBuffer();
sql.append("SELECT * FROM ");
sql.append("(SELECT ROWNUM NUM, N.* FROM (SELECT * FROM NOTICES WHERE "+field+" LIKE ? ORDER BY REGDATE DESC) N) ");
sql.append("WHERE NUM BETWEEN ? AND ? ");
List<Notice> n_list = this.jdbcTemplate.query(
sql,
new Object[] {"%" + query + "%", srow, erow},
new NoticeRowMapper()
);
return n_list;
}
Object[]
을 사용하지 않고 인덱스를 통해 바인딩 파라미터를 지정하고 싶다면 PreparedStatementSetter
을 사용
List<T> query(String sql, PreparedStatementSetter setter, RowMapper<T> rowMapper)
List<Notice> n_list = this.jdbcTemplate.query(
sql,
new PreparedStatementSetter() {
@Override
public void setValues(PreparedStatement ps) throws SQLException {
ps.setString(1, "%" + query + "%");
ps.setInt(2, srow);
ps.setInt(3, erow);
}
}
new NoticeRowMapper()
);
NoticeRowMapper
같은 클래스를 따로 만드는게 번거롭지 않은가?
이런 규칙성 있는 클래스는 스프링 MVC에서 자동으로 만들 수 있다.
RowMapper
인터페이스를 구현한 BeanPropertyRowMapper
가 바로 그 클래스이다!
기존에 정의했떤 NoticeRowMapper
를 없애버리고 다음과 같이 사용할 수 있다.
List<Notice> n_list = this.jdbcTemplate.query(
sql,
new Object[] {"%" + query + "%", srow, erow},
new BeanPropertyRowMapper<Notice>(Notice.class)
);
템플릿 클래스를 사용하면
try, catch, finally, close
등 귀찮은 코드가 생략됨을 알 수 있다.
JdbcTemplate
- queryForList()
- 칼럼 개수가 하나라면SELECT
쿼리로 조회한 데이터가 칼럼 하나라면 queryForList()
를 사용해 List형태의 값을 반환받을 수 있다.
List<T> queryForList(String sql, Class<T> elementType)
List<T> queryForList(String sql, Object[] args, Class<T> elementType)
List<T> queryForList(String sql, Object[] args, int[] argsTypes, Class<T> elementType)
Class<T> elementType
을 제외하고 형식은 query()
메서드와 똑같다.
정수형, 문자열에 따라 Integer.class
혹은 String.calss
등의 값이 매개변수로 전달될 수 있다.
JdbcTemplate
- queryForObject()
- 행의 개수가 하나라면SELECT
쿼리로 조회한 데이터가 한 행 이라면 queryForObject()
를 사용할 수 있다.
만약 쿼리 실행결과의 행 개수가 0개 혹은 2개 이상이라면 예외 발생.
T queryForObject(String sql, RowMapper<T> rowMapper)
T queryForObject(String sql, Object[] args, RowMapper<T> rowMapper)
T queryForObject(String sql, Object[] args, int[] argTypes, RowMapper<T> rowMapper)
T queryForObject(String sql, Class<T> requiredType)
T queryForObject(String sql, Object[] args, Class<T> requiredType)
T queryForObject(String sql, Object[] args, int[] argTypes, Class<T> requiredType)
T queryForObject(String sql, Object[] args, int[] argTypes, Object ... args)
query()
메서드와 비교해서 다른 매개변수가 딱 2개 있다.
Class<T> requiredType
, Object ... args
Class<T> requiredType
는 만약 행 뿐만 아니라 칼럼도 하나일 경우
그 하나의 칼럼 타입을 지정할 때 쓰인다. (queryForList()
의 Class<T> elementType
과 같음)
public int counts() {
String sql = "SELECT COUNT(*) FROM notice";
return jdbcTemplate.queryForObject(
sql,
Integer.class
);
}
JdbcTemplate
- update()
- insert, update. delete를 위한 메서드INSERT
, UPDATE
, DELETE
쿼리를 실행할 때 사용하는 메서드 ,
int update(String sql)
int update(String sql, Objet... args)
int update(String sql, Object[] args, int[] argTypes)
int update(String sql, PreparedStatementSetter pss)
int update(PreparedStatementCreator psc)
query()
메서드와 다를 것 이 없다.
똑같은 방법으로 바인딩 처리하고 리턴값으로 sql이 수행된 행의 개수를 반환한다.
NamedParameterJdbcTemplate
는 스프링에서 JDBC 템플릿 클래스중 가장 많이 사용되는 템플릿 클래스로
빈 객체로 생성하기 위해 JdbcTemplate
템플릿 클래스와 마찬가지로 DataSource
객체를 필요로 한다.
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"></property>
<property name="url" value="jdbc:oracle:thin:@172.17.107.68:1521:xe"></property>
<property name="username" value="scott"></property>
<property name="password" value="tiger"></property>
</bean>
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate">
<constructor-arg ref="dataSource"></constructor-arg>
</bean>
NamedParameterJdbcTemplate
역시 사용하려면 @Autowired
어노테이션을 설정하거나 <bean>
태그를 통해 DI 주입…
public class SampleDAO {
private NamedParameterJdbcTemplate jdbcTemplate;
@Autowired
public void setJdbcTemplate(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}
...
...
}
지금까지 PreparedStatement
의 sql 문자열의 ?
위치에 파라미터를 바인딩 하기 위해 인덱스를 사용하거나 Object[]
에 집어넣을 값을 순서대로 넣어 바인딩 처리 하였는데
NamedParameterJdbcTemplate
을 사용하면 이름기반으로 바인딩 가능하다.
NamedParameterJdbcTemplate
템플릿 클래스가 제공하는 메서드들을 알아보자.
NamedParameterJdbcTemplate
에서도 JdbcTemplate
에서 제공하는 query()
, queryForList()
, queryForObject()
, update()
등의 메서드 역시 제공된다.
List<T> query(String sql, Map<String, ?> paramMap, RowMapper<T> rowMapper)
List<T> queryForList(String sql, Map<String, ?> paramMap, Class<T> elementType)
T queryForObject(String sql, Map<String, ?> paramMap, RowMapper<T> rowMapper)
T queryForObject(String sql, Map<String, ?> paramMap, Class<T> elementType)
int update(String sql, Map<String, ?> paramMap)
int update(String sql, Map<String, ?> paramMap, keyHolder keyHolder)
중간의 Map<String, ?> paramMap
매개변수를 제외하곤 JdbcTemplate
의 메서드들과 차이점이 없어보인다.
Map<String, ?> paramMap
매개변수를 통해 PreparedStatement
의 쿼리문에 바인딩 시키는데 사용법은 아래와 같다.
public List<Notice> getNotices(int page, String field, String query) throws ClassNotFoundException, SQLException
{
int srow = 1 + (page-1)*15;
int erow = 15 + (page-1)*15;
StringBuffer sql = new StringBuffer();
sql.append("SELECT * FROM ");
sql.append("(SELECT ROWNUM NUM, N.* FROM (SELECT * FROM NOTICES WHERE "+field+" LIKE :query ORDER BY REGDATE DESC) N) ");
sql.append("WHERE NUM BETWEEN :srow AND :erow ");
Map<String, Object> params = new HashMap<>();
params.put("query", query);
params.put("srow", srow);
params.put("erow", erow);
List<Notice> n_list = this.jdbcTemplate.query(
sql,
params,
new BeanPropertyRowMapper<Notice>(Notice.class)
);
return n_list;
}
sql쿼리문을 보면 ?
대신 :바인딩변수명
이 들어가있다.
그리고 query()
메서드의 2번째 매개변수로 바인딩변수명과 같은 이름의 key
값으로 설정한 value
가 들어있는 Map객체를 전달.
이름을 식별자로 사용해 바인딩 하기 때문에 좀더 직관적인 코딩이 가능하다.
SqlParameterSource
를 이용한 파라미터 값 설정 메서드바인딩 처리를 위해 NamedParameterJdbcTemplate
에서 Map
객체 대신 SqlParameterSource
를 사용 가능하다.
List<T> query(String sql, SqlParameterSource paramSource, RowMapper<T> rowMapper)
List<T> queryForList(String sql, SqlParameterSource paramSource, Class<T> elementType)
T queryForObject(String sql, SqlParameterSource paramSource, RowMapper<T> rowMapper)
T queryForObject(String sql, SqlParameterSource paramSource, Class<T> elementType)
int update(String sql, SqlParameterSource paramSource)
int update(String sql, SqlParameterSource paramSource, keyHolder keyHolder)
스프링MVC에선 SqlParameterSource
는 인터페이스를 구현한 다음 두개의 클래스를 제공한다.
SqlParameterSource
에 어떤 기능이 있는지 위의 두 클래스를 알아보자.
JdbcTemplate
에서 Map
콜렉션 클래스를 사용해 바인딩 처리를 하였는데
NamedParameterJdbcTemplate
의 MapSqlParameterSource
매개변수도 똑같은 역할을 한다.
MapSqlParameterSource
역시 <이름, 값>
쌍을 이용해 파라미터의 값을 설정한다.
public List<Notice> getNotices(int page, String field, String query) throws ClassNotFoundException, SQLException
{
int srow = 1 + (page-1)*15;
int erow = 15 + (page-1)*15;
StringBuffer sql = new StringBuffer();
sql.append("SELECT * FROM ");
sql.append("(SELECT ROWNUM NUM, N.* FROM (SELECT * FROM NOTICES WHERE "+field+" LIKE :query ORDER BY REGDATE DESC) N) ");
sql.append("WHERE NUM BETWEEN :srow AND :erow ");
MapSqlParameterSource<String, Object> paramSource = new MapSqlParameterSource<>();
params.addValue("query", query);
params.addValue("srow", srow);
params.addValue("erow", erow);
List<Notice> n_list = this.jdbcTemplate.query(
sql,
paramSource,
new BeanPropertyRowMapper<Notice>(Notice.class)
);
return n_list;
}
BeanPropertySqlParameterSource
의 경우 바인딩 처리를 객체를 통해 처리한다.
스프링 MVC에서 커맨드 객체를 통해 파라미터를 자동으로 객체로 만들어저 매개변수로 받는다.
<form action="">
이메일: <input type="text" name="email"/>><br>
이름: <input type="text" name="name"/><br>
암호: <input type="text" name="pw1"/><br>
암호확인: <input type="text" name="pw2"/><br>
</form>
public class MemberRequest {
private String email;
private String name;
private String pw1;
private String pw2;
//get, set 메서드....
}
@RequestMapping("member_join.htm")
public String member_join(MemberRequest member) throws ClassNotFoundException, SQLException {
...
...
return "index.jsp";
}
이 정보를 DB에 INSERT
할때 일일이 객체안의 필드를 바인딩 시켜주어야 하는데 BeanPropertySqlParameterSource
객체를 사용하면 그럴필요 없다.
객체의 필드명과 DB 테이블 칼럼명이 같으면 자동으로 바인딩 시킨다.
@RequestMapping("member_join.htm")
public String member_join(MemberRequest member) throws ClassNotFoundException, SQLException {
StringBuffer sql = new StringBuffer();
sql.append(" INSERT INTO member (email, name, pw1, pw2) VALUES ");
sql.append(" (:email, :name, :pw1, :pw2) ");
SqlParameterSource paramSource = new BeanPropertySqlParameterSource(member);
jdbcTemplate.update(
sql,
paramSource
);
...
return "index.jsp";
}
BeanPropertySqlParameterSource
의 생성자로 member
객체를 넘겨주면 끝.
INSERT
의 경우 매우 간단하게 처리할 수 있다.
오히려 Map
클래스나 SqlParameterSource
를 구현한 클래스를 생성해서 사용하는게 번거로울 수 있다. (간단한 작업의 경우)
그럴 때 JdbcTemplate
의 메서드를 사용할 수 있도록
NamedParameterJdbcTemplate
는 getJdbcTemplate()
메서드를 제공한다.
getJdbcTemplate()
가 반환한 JdbcTemplate
을 사용하면 된다.