본문 바로가기

Spring

스프링 JDBC

1. 스프링 JDBC 개념

JDBC는 가장 오랫동안 자바 개발자들이 사용한 DB 연동 기술이다. JDBC를 이용하여 DB 연동 프로그램을 개발하면 데이터베이스에 비종속적인 DB 연동 로직을 구현할 수 있다. 그런데 JDBC 프로그램은 이용하려면 개발자가 작성해야 할 코드가 너무 많다.

 

스프링은 JDBC 기반의 DB 연동 프로그램을 쉽게 개발할 수 있도록 JDBCTemplate 클래스를 지원한다.

반복되는 DB 연동 로직은 JDBCTemplate 클래스의 템플릿 메소드가 제공하고, 개발자는 달라지는 SQL 구문과 설정값만 신경쓰면 된다.

 

2. 스프링 JDBC 설정

2.1 라이브러리 추가

먼저 JDBC를 이용하려면 BoardWeb 프로젝트에 있는 pom.xml 파일에 DBCP 관련 <dependency> 설정을 추가해야 한다.

 

[pom.xml]

		<!-- JDBC -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>${org.springframework-version}</version>
		</dependency>
		
		<!-- DBCP -->
		<dependency>
			<groupId>commons-dbcp</groupId>
			<artifactId>commons-dbcp</artifactId>
			<version>1.4</version>
		</dependency>

 

설정을 추가한 후에 Maven Dependencies에서 DBCP 라이브러리가 정상적으로 등록되었는지 확인한다.

 

2.2 DataSource 설정

JdbcTemplate 클래스가 JDBC API를 이용하여 DB 연동을 처리하려면 반드시 데이터베이스로부터 커넥션을 얻어야 한다. 따라서 JdbcTemplate 객체가 사용할 DataSource를 <bean> 등록하여 스프링 컨테이너가 생성하도록 해야 한다. 이때 PropertyPlaceholderConfigurer를 이용하면 외부의 프로퍼티 파일을 참조하여 DataSource를 설정할 수 있다. 실습을 위해 src/main/resources 소스 폴더에 config 폴더를 생성하고 database.properties 파일을 작성한다. database.properties파일을 작성하기 위해 config 폴더에 마우스 오른쪽 버튼을 클릭하고 [New] -> [Other] 메뉴를 선택한다. General 폴더에서 'Untitled Text File'을 선택하고 <Finish>를 클릭해 파일을 생성한 다음, 파일 내용을 작성하고 [File] -> [Save As]에서 파일명을 database.properties로 저장한다.

 

[database.properties]

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:4020/jdbcexam
jdbc.username=root
jdbc.password=1234

 

이제 Properties 파일에 설정된 프로퍼티들을 이용하여 DataSource를 설정하려면 다음과 같이 <context:property-placeholder> 엘리먼트를 사용한다.

 

[applicationContext.xml]

	<!-- DataSource 설정 -->
	<context:property-placeholder location="classpath:config/database.properties"/>
	
	<!-- DateSource 설정 -->
	<bean id="dataSource" class="org.apache.commons.dbcp.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>

 

프로퍼티 파일을 사용하려면 <context:property-placeholder> 엘리먼트로 프로퍼티 파일의 위치를 등록해야 한다. 그리고 "${}" 구문을 이용하여 프로퍼티 이름을 지정하면 프로퍼티 값으로 치환되어 실행된다.

 

3. JdbcTemplate 메소드

스프링 JDBC를 위한 기본 설정이 마무리 됐으면 이제 JdbcTemplate 객체를 이용하여 DB 연동을 간단하게 처리할 수 있다.

 

1) update() 메소드

첫번째 방식

SQL문의 '?' 수 만큼 값을 나열하는 방식

int update(String sql, Object args, Object args......)

두번째 방식

Object 배열 객체에 SQL 구문에 설정된 '?' 수 만큼 값을 세팅해 배열 객체를 인자로 전달하는 방식

int update(String sql, Obejct[] args)

 

2) queryForInt() 메소드

select 문으로 검색된 값 중에 정수를 리턴받을 때 사용한다.

int queryForInt(String sql) 
int queryForInt(String sql, Object args...) 
int queryForInt(String sql, Object[] args)

 

3) queryForObject() 메소드

select 문으로 검색된 결과에서 객체를 리턴받을 때 사용한다.

Object queryForObject(String sql) 
Object queryForObject(String sql, RowMapper<t> rowMapper) 
Object queryForObject(String sql, Object[] args, RowMapper<t> rowMapper)

 

4) query() 메소드

select 문으로 검색된 결과가 리스트일 때 사용한다.

List query(String sql) List query(String sql, RowMapper<t> rowMapper) 
List query(String sql, Object[] args, RowMapper<t> rowMapper)

4. DAO 클래스 구현

DAO 클래스에서 JdbcTemplate 객체를 얻는 방법은 두 가지이다.

 

4.1 첫 번째 방법 : JdbcDaoSupport 클래스 상속

[BoardDAOSpring.java]

package com.springquick.board.impl;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

import com.springquick.board.BoardVO;

// DAO(Data Access Object)
@Repository
public class BoardDAOSpring extends JdbcDaoSupport {
	
	// SQL 명령어들
	private final String BOARD_INSERT =
			"insert into board(seq, title, writer, content)"
			+ "values((select ifnull(max(seq), 0)+1 from board a),?,?,?)";
	//private final String BOARD_INSERT =
	//		"insert into board(seq, title, writer, content) values(?,?,?,?)";
	private final String BOARD_UPDATE =
			"update board set title=?, content=? where seq=?";
	private final String BOARD_DELETE =
			"delete 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";
	
	// getJdbcTemplate 객체 리턴을 위해 DataSource 주입
	// DataSource로 되어 있는 변수가 있으면 setter를 통해 스프링이 자동으로 주입함
	@Autowired
	public void setSuperDataSource(DataSource _dataSource) {
		super.setDataSource(_dataSource);
	}
	
	// CRUD 기능의 메소드 구현
	// 글 등록
	public void insertBoard(BoardVO _vo) {
		System.out.println("===> Spring JDBC로 insertBoard() 기능 처리");
		this.getJdbcTemplate().update(BOARD_INSERT, _vo.getTitle(),
								_vo.getWriter(), _vo.getContent());
	}
	
	// 글 수정
	public void updateBoard(BoardVO _vo) {
		System.out.println("===> Spring JDBC로 updateBoard() 기능 처리");
		this.getJdbcTemplate().update(BOARD_UPDATE, _vo.getTitle(),
								_vo.getContent(), _vo.getSeq());
	}
	
	// 글 삭제
	public void deleteBoard(BoardVO _vo) {
		System.out.println("===> Spring JDBC로 deleteBoard() 기능 처리");
		this.getJdbcTemplate().update(BOARD_DELETE, _vo.getSeq());
	}
	
	// 글 상세 조회
	public BoardVO getBoard(BoardVO _vo) {
		System.out.println("===> Spring JDBC로 getBoard() 기능 처리");
		Object[] args = {_vo.getSeq()};
		// 내가 만든 테이블의 컬럼명에 맞게끔 가져오기 위해서는 RowMapper 객체를 만듦
		return this.getJdbcTemplate().queryForObject(BOARD_GET, args,
									new BoardRowMapper());
	}
	
	// 글 목록 조회
	public List<BoardVO> getBoardList(BoardVO _vo) {
		System.out.println("===> Spring JDBC로 getBoardList() 기능 처리");
		return this.getJdbcTemplate().query(BOARD_LIST,
								new BoardRowMapper());
	}
}

// public 클래스는 파일 하나당 하나
class BoardRowMapper implements RowMapper<BoardVO> {
	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;
	}
}

 

DAO 클래스를 구현할 때, JdbcDaoSupport 클래스를 부모 클래스로 지정하면 getJdbcTemplate() 메소드를 상속받을 수 있다. 그리고 getJdbcTemplate() 메소드를 호출하면 JdbcTemplate 객체가 리턴되어 모든 메소드를 JdbcTemplate 객체로 구현할 수 있다.

 

그런데 문제는 getJdbcTemplate() 메소드가 JdbcTemplate 객체를 리턴하려면 DataSource 객체를 가지고 있어야 한다. 따라서 반드시 부모 클래스인 JdbcDaoSupport 클래스의 setDateSource() 메소드를 호출하여 DataSource  객체를 의존성 주입해야 한다. -> setSuperDataSource 메소드 위에 '@Autowired' 추가

 

@Autowired 어노테이션은 주로 변수 위에 선언하는 데 메소드 위에 선언해도 동작한다. 메소드 위에 @Autowired를 붙이면 해당 메소드를 스프링 컨테이너가 자동으로 호출해 주며, 이때 메소드 매개변수 타입을 확인하고 해당 타입의 객체가 메모리에 존재하면 그 객체를 파라미터로 넘겨준다.

 

4.2 두 번째 방법 : JdbcTemplate 클래스 <bean> 등록, 의존성 주입

DAO 클래스에서 JdbcTemplate 객체를 얻는 두 번째 방법은 JdbcTemplate 클래스를 <bean> 등록하고, 의존성 주입으로 처리하는 것이다. 일반적으로 이 방법을 사용한다. 먼저 스프링 설정 파일에 JdbcTemplate 클래스를 <bean> 등록한다.

 

[applicationContext.xml]

	<!-- Spring JDBC 설정 -->
	 <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
		<property name="dataSource" ref="dataSource"/>
	</bean>

 

[BoardDAOSpring.java]

package com.springquick.board.impl;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import javax.sql.DataSource;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

import com.springquick.board.BoardVO;

// DAO(Data Access Object)
@Repository
public class BoardDAOSpring {
	@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 a),?,?,?)";
	//private final String BOARD_INSERT =
	//		"insert into board(seq, title, writer, content) values(?,?,?,?)";
	private final String BOARD_UPDATE =
			"update board set title=?, content=? where seq=?";
	private final String BOARD_DELETE =
			"delete 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";
	
	// CRUD 기능의 메소드 구현
	// 글 등록
	public void insertBoard(BoardVO _vo) {
		System.out.println("===> Spring JDBC로 insertBoard() 기능 처리");
		
		jdbcTemplate.update(BOARD_INSERT, _vo.getTitle(),
				_vo.getWriter(), _vo.getContent());
		
	}
	
	// 글 수정
	public void updateBoard(BoardVO _vo) {
		System.out.println("===> Spring JDBC로 updateBoard() 기능 처리");
	
		jdbcTemplate.update(BOARD_UPDATE, _vo.getTitle(),
				_vo.getContent(), _vo.getSeq());
	}
	
	// 글 삭제
	public void deleteBoard(BoardVO _vo) {
		System.out.println("===> Spring JDBC로 deleteBoard() 기능 처리");
		
		jdbcTemplate.update(BOARD_DELETE, _vo.getSeq());
	}
	
	// 글 상세 조회
	public BoardVO getBoard(BoardVO _vo) {
		System.out.println("===> Spring JDBC로 getBoard() 기능 처리");
		Object[] args = {_vo.getSeq()};
		// 내가 만든 테이블의 컬럼명에 맞게끔 가져오기 위해서는 RowMapper 객체를 만듦
		
		return jdbcTemplate.queryForObject(BOARD_GET, args,
				new BoardRowMapper());
	}
	
	// 글 목록 조회
	public List<BoardVO> getBoardList(BoardVO _vo) {
		System.out.println("===> Spring JDBC로 getBoardList() 기능 처리");
		
		return jdbcTemplate.query(BOARD_LIST,
				new BoardRowMapper());
	}
}


// public 클래스는 파일 하나당 하나
class BoardRowMapper implements RowMapper<BoardVO> {
	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;
	}
}

 

JdbcTemplate 객체를 이용하여 BoardDAOSpring 클래스를 구현했으면  이제 BoardServiceImpl 클래스가 BoardDAOSpring 객체를 이용하여 DB 연동을 처리하도록 수정하자. 앞에서 작성했던 BoardServiceImpl 클래스에서 boardDAO 변수의 타입을 BoardDAO에서 BoardDAOSpring으로만 수정하면 된다.

 

[BoardServiceImpl.java]

package com.springquick.board.impl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import com.springquick.board.BoardVO;

@Service("boardService")
public class BoardServiceImpl implements BoardService {
	@Autowired
	private BoardDAOSpring boardDAO;
	
	@Override
	public void insertBoard(BoardVO _vo) {
//		if (_vo.getSeq() == 0) {
//			throw new IllegalArgumentException("0번 글은 등록할 수 없습니다.");
//		}
		boardDAO.insertBoard(_vo);
		//boardDAO.insertBoard(_vo);
	}
	
	@Override
	public void updateBoard(BoardVO _vo) {
		boardDAO.updateBoard(_vo);
	}
	
	@Override
	public void deleteBoard(BoardVO _vo) {
		boardDAO.deleteBoard(_vo);
	}
	
	@Override
	public BoardVO getBoard(BoardVO _vo) {
		return boardDAO.getBoard(_vo);
	}
	
	@Override
	public List<BoardVO> getBoardList(BoardVO _vo) {
		return boardDAO.getBoardList(_vo);
	}
}

 

이렇게 하면 기존에 JDBC 기반으로 동작했던 BoardDAO가 아닌 스프링 JDBC 기반의 BoardDAOSpring으로 DB 연동이 처리된다는 점만 다르고 실행 결과는 같다.

 

[결괏값]

'Spring' 카테고리의 다른 글

Spring pom.xml 오류 해결법  (0) 2022.08.06
RequestContextListener  (0) 2022.08.01
Annotation 기반 설정  (0) 2022.05.03
IoC, DI의 개념과 예제  (0) 2022.05.03
이클립스(Eclipse)에 스프링(Spring) 설치하기  (0) 2022.04.26