JdbcTemplate 사용하여 게시판 쿼리 연동하기
JdbcTemplate으로 자바와 데이터베이스를 연동할 때, 즉 JDBC를 할 때, 게시판 글 전체를 불러오는 경우 어떤 메서드를 써야 할까?
일단 xml파일에 bean 등록을 다음과 같이 한다.
<!-- DataSource 설정 -->
<context:property-placeholder location="classpath:config/database.properties" />
<!-- property 파일 주소로 placeholder 만들어줬으므로 가져다가 쓴다. -->
<bean id="dataSource"
class="org.apache.commons.dbcp2.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<!-- Spring JDBC 설정 -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dataSource" />
</bean>
한 파일 내에 직접 정보를 기술하지 않고, properties 파일을 만들어서 다음과 같이 입력해주었다.
jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://127.0.0.1:3306/schema_name?serverTimezone=UTC
jdbc.username=username
jdbc.password=password
username과 password자리에는 해당하는 계정명과 비밀번호를 적으면 되고, schema_name에도 연동할 스키마이름을 적으면 되겠다.
classpath:는 프로젝트 설정의 build path에 들어가서 살펴보면 알 수 있다.
기본적으로 src/main/resources/ 와 src/main/java/가 설정되어있다.
사실 위의 설정은 본론이 아니고, 위와 같이 설정한 후에 JdbcTemplate 클래스의 메서드 중 어느 것을 써야 글 목록을 불러오는데 적당한지가 본론이다.
아래와 같이, 게시판의 data를 연동하는 DAO가 있다고 생각해보자.
package com.cozle.board.impl;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import com.cozle.board.BoardVO;
// DAO(Data Access Object)
@Repository
public class BoardDAO {
@Autowired
private JdbcTemplate jdbcTemplate;
// SQL 명령어들
private final String BOARD_INSERT = "insert into board(seq, title, writer, content) "
+ "values((select ifnull(max(seq), 0)+1 from board temp),?,?,?)";
private final String BOARD_UPDATE = "update board set title=?, content=? where seq=?";
private final String BOARD_DELETE = "delete from board where seq=?";
private final String BOARD_GET = "select * from board where seq=?";
private final String BOARD_LIST = "select * from board order by seq desc";
private final String BOARD_LIST_T = "select * from board where title like CONCAT('%', ?, '%') order by seq desc";
private final String BOARD_LIST_C = "select * from board where content like CONCAT('%', ?, '%') order by seq desc";
// CRUD 기능의 메소드 구현 ========================================
// 글 등록 create
public void insertBoard(BoardVO vo) {
System.out.println("===> Spring JDBC로 insertBoard() 기능 처리");
jdbcTemplate.update(BOARD_INSERT, vo.getTitle(), vo.getWriter(), vo.getContent());
}
// 글 수정 update
public void updateBoard(BoardVO vo) {
System.out.println("===> Spring JDBC로 updateBoard() 기능 처리");
jdbcTemplate.update(BOARD_UPDATE, vo.getTitle(), vo.getContent(), vo.getSeq());
}
// 글 삭제 delete
public void deleteBoard(BoardVO vo) {
System.out.println("===> Spring JDBC로 deleteBoard() 기능 처리");
jdbcTemplate.update(BOARD_DELETE, vo.getSeq());
}
// 글 상세 조회 read
public BoardVO getBoard(BoardVO vo) {
System.out.println("===> Spring JDBC로 getBoard() 기능 처리");
Object[] args = { vo.getSeq() }; // args말고 object 하나만 하고 싶어도, 메서드 인자로 배열이 들어가야해서 불가
return jdbcTemplate.queryForObject(BOARD_GET, args, new BoardRowMapper());
}
// 글 목록 조회 read
public List<BoardVO> getBoardList(BoardVO vo) {
System.out.println("===> Spring JDBC로 getBoardList() 기능 처리");
Object[] args = { vo.getSearchKeyword() };
// 검색조건을 vo에서 가져와서 검색 조건에 따른 쿼리 설정
if (vo.getSearchCondition().equals("TITLE")) {
return jdbcTemplate.query(BOARD_LIST_T, args, new BoardRowMapper());
} else if (vo.getSearchCondition().equals("CONTENT")) {
return jdbcTemplate.query(BOARD_LIST_C, args, new BoardRowMapper());
}
return null;
}
}
지금 살펴볼 것은 글 상세 조회와 글 목록 조회 두가지 메서드 뿐이다.
BoardVO getBoard(BoardVO vo)에서는 쿼리를 읽고 결과 객체를 매핑하고 리턴하는 메서드로 queryForObject()를 사용한다. F3을 눌러 원본 소스를 보면 다음과 같다.
@Override
@Nullable
public <T> T queryForObject(String sql, @Nullable Object[] args, RowMapper<T> rowMapper) throws DataAccessException {
List<T> results = query(sql, args, new RowMapperResultSetExtractor<>(rowMapper, 1));
return DataAccessUtils.nullableSingleResult(results);
}
여러가지 overloading 버젼들이 있지만, 객체와 결과값 1개 row를 매핑하기 위해 RowMapper를 사용하고, ?에 들어갈 값이 존재하기에 위의 메서드를 사용했다.
row 한 줄, 즉 객체 하나만을 결과로 한다면 queryForObject()가 가장 적합하지만, 글 목록 보기를 위해서는 어떤 메서드를 써야 할까?
JdbcTemplate 클래스에는 다양한 경우를 위해 query... 로 시작하는 여러 메서드를 구현해 놓았다.
List를 리턴해야 하는 경우에는 query()와 queryForList() 중에서 고민하게 된다.
결론부터 말하자면 query()를 써야한다.
왜 그런지 원본 소스를 살펴보자.
@Override
public <T> List<T> queryForList(String sql, @Nullable Object[] args, Class<T> elementType) throws DataAccessException {
return query(sql, args, getSingleColumnRowMapper(elementType));
}
일단 결과가 nullable하지 않다는 것을 확인할 수 있다.
설명을 보자.
Query given SQL to create a prepared statement from SQL and a list of arguments to bind to the query, expecting a result list.
The results will be mapped to a List (one entry for each row) of result objects, each of them matching the specified element type.
Specified by: queryForList(...) in JdbcOperations
Type Parameters:<T> Parameters:
sql the SQL query to execute
elementType the required type of element in the result list (for example, Integer.class)
args arguments to bind to the query (leaving it to the PreparedStatement to guess the corresponding SQL type); may also contain SqlParameterValue objects which indicate not only the argument value but also the SQL type and optionally the scaleReturns: a List of objects that match the specified element type
Throws:DataAccessException - if the query fails
queryForList()를 사용하면 결국 query()메서드를 사용하는 것과 같은데, RowMapper가 single column을 위한 것이라는 사실을 메서드 이름에서 벌써 읽어낼 수 있다. class 타입을 암만 BoardVO.class처럼 설정해줘도 오류가 나는 것이다. 매핑 문제 이전에 컬럼이 1개를 초과하기 때문에 에러가 난다. 딱 하나의 컬럼을 가진 테이블로부터 여러 row를 읽을 때에나 쓸 수 있는 메서드이다.
결국 위에 구현해둔 BoardDAO 클래스와 같이, RowMapper 인터페이스를 구현한 BoardRowMapper를 파라미터로 가지는 query(BOARD_LIST_C, args, new BoardRowMapper())를 사용해야 한다.
끝으로 BoardRowMapper 클래스를 올려둔다.
package com.cozle.board.impl;
import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;
import com.cozle.board.BoardVO;
// resultSet에서 sql query 실행결과 받아서 vo에 mapping해주는 RowMapper객체..
// 결국 테이블 하나당 하나의 RowMapper 클래스 필요..
public class BoardRowMapper implements RowMapper<BoardVO> {
@Override
public BoardVO mapRow(ResultSet rs, int rowNum) throws SQLException {
BoardVO board = new BoardVO();
board.setSeq(rs.getInt("SEQ"));
board.setTitle(rs.getString("TITLE"));
board.setWriter(rs.getString("WRITER"));
board.setContent(rs.getString("CONTENT"));
board.setRegDate(rs.getDate("REGDATE"));
board.setCnt(rs.getInt("CNT"));
return board;
}
}