Day 81. Spring Framwork 4 : Spring JDBC

ho_c·2022년 6월 26일
0

국비교육

목록 보기
63/71
post-thumbnail

Spring JDBC

현재 사용하는 JDBC는 반복되고, 작성할 코드가 너무 많다. 그래서 개발자인 내가 해야될 일이 너무 많아서, 이번엔 스프링 전용 JDBC를 사용해 DB작업을 더 간소화시켜볼 것이다.

물론 DB전용 프레임워크에 비해 성능은 낮지만, 충분히 지금 단계에서는 써먹도 좋다.

1) Dependency 가져오기

먼저 Spring JDBC를 사용하기 위해 Maven에서 라이브러리 태그를 불러와서, pom.xml에 추가해주자.

<!-- Spring JDBC -->
<dependency>
    <groupId>org.springframework</groupId>
    <artifactId>spring-jdbc</artifactId>
    <version>${org.springframework-version}</version>
</dependency>

이때, <version> 은 문서 최상단의 <properties> 내부의 태그를 OGNL 방식으로 문서 안에서 일종의 ‘환경 변수’로 사용한다. 이 역시 버전의 통일성과 최적화, 유지 보수를 위해 기존에 세팅해둔 값을 사용한다.


2) Spring JDBC Bean

스프링 JDBC의 목적은 기존 DB작업을 더 간결화하는 것이다. 쉽게 말하면 최적화? 곧 스프링에서 DB작업 자체를 우리가 세팅해주는 값에 따라서 진행해주는 것이다.

그 결과로 우리가 하나, 하나 pstat.setString();으로 값을 세팅하고, DBCP에서 Connection 인스턴스 뽑아오는, 이런 과정이 간소화된다.


3) DBCP DI

그래서 첫째로 Spring JDBC bean에 DBCP bean을 DI해줘야 한다. 이로 인해 DBCP 역시 우리의 손을 떠나 Spring JDBC에서 연결을 뽑아와서 DB 작업을 진행한다. 방식은 Setter로 한다.

<!-- DBCP Bean -->
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource">
	<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"></property>
	<property name="url" value="jdbc:oracle:thin:@Localhost:1521:xe"></property>
	<property name="username" value="id"></property>
	<property name="password" value="pw"></property>
</bean>
	
<!-- Spring JDBC Bean -->
<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
	<property name="dataSource" ref="dataSource"></property>
</bean>

이제 톰캣이 실행하면서 root-context를 읽어들이면서 두 인스턴스를 만들고 서로 조립해서 메모리에 로딩해둔다. ( DBCP는 참조형이니 ref에 id값을 넣어준다 )


4) update

Spring JDBC에서는 insert, delete, update의 작업들은 update() 메서드로 한 번에 작업한다. 이때, update메서드에 오버로딩 목록을 보면 ‘가변 인자’가 존재한다.

이는 개발하는 사람 입장에서 DB에 몇 개의 값을 세팅할지 모르니까, 가변 인자로 넘겨준 것. 그래서 제일 첫 번째 인자로는 ‘SQL문’, 그 뒤로는 ? 개수에 맞는 값을 세팅해주면 된다.

@Component
public class MessagesDAO {

	@Autowired
	private JdbcTemplate jdbc; // DI로 DBCP를 갖고 있음

	public int insert(MessagesDTO dto) {

		String sql = "insert into messages values(messages_seq.nextval, ?, ?, sysdate)";
		return jdbc.update(sql, dto.getWriter(), dto.getContents()); // insert, update, delete
	}


	public int update(MessagesDTO dto) {

		String sql = "update Messages set writer=?, content=? where seq=?";
		return jdbc.update(sql, dto.getWriter(), dto.getContents(), dto.getSeq());

	}


	public int delete(int seq) {

		String sql = "delete from Messages where seq=?";
		return jdbc.update(sql, seq);
	}	

}

5) select

select문을 사용하게 되면 난이도가 오른다. 그 이유는 우리가 가져오는 데이터의 자료형이 경우마다 달라지기 때문인데, 일단 한번 만들어보자.

public MessagesDTO selectBySeq(int seq) {

	String sql = "select * from messages where seq=?";

	return jdbc.queryForObject(sql, new RowMapper<MessagesDTO>() {
		@Override
		public MessagesDTO mapRow(ResultSet rs, int rowNum) throws SQLException {

			MessagesDTO dto = new MessagesDTO();
			dto.setSeq(rs.getInt("seq"));
			dto.setWriter(rs.getString("writer"));
			dto.setContents(rs.getString("contents"));
			dto.setWrite_date(rs.getTimestamp("write_date"));
			return dto;
		}
	}, seq);

select으로 데이터를 뽑을 때 사용하는 메서드는 queryForObject(), query()라는 메서드들이다. 물론 이외에도 많지만, 이 둘을 주로 사용한다.

두 메서드의 차이는 가져오는 데이터가 단수인지, 복수인지의 차이이다.

  • queryForObject : int 값 하나 또는 record 한 줄 등의 데이터를 뽑아낼 때.
  • query : 여러 줄의 결과 또는 여러 데이터를 뽑아낼 때 ( 리스트 데이터 )

실제 동작 부분은 쿼리 결과를 ResultSet 인스턴스에 담아서 주는 것 똑같다. 다만 후자는 내부적으로 반복문을 수행하여서, List 형태로 반환해준다.

그리고 둘의 매개변수를 보면 첫째 인자는 ‘SQL문’, 두 번째는 ‘함수’, 세 번째부터는 ‘가변인자’이다. 일단 첫째와 셋째를 보면 당연한 과정이다. SQL문을 넣어주고, 그 값을 세 번째부터 하나씩 ?의 개수에 맞춰서 늘어나기 때문이다.

포인트는 쿼리를 넣고, 나오는 결과를 우리가 사용할 수 있도록 데이터를 담는 작업이다. 이걸 기존 jdbc에서는 다음과 같이 진행하였다.

List<MessagesDTO> list = new ArrayList<MessagesDTO>();

while(rs.next()) {
	MessagesDTO dto = new MessagesDTO();
	dto.setSeq(rs.getInt("seq"));
	dto.setWriter(rs.getString("writer"));
	dto.setContents(rs.getString("content"));
	dto.setWrite_date(rs.getTimestamp("write_date"));
				
	list.add(dto);
	
}

이런 식으로 우리가 반복을 돌려서 담아왔는데, Spring JDBC를 쓰게 되면 이 작업 또한 jdbc가 대신 처리해준다. 다만 그 방식을 ‘Call-back’으로 함수를 넣어서 전달해야 한다. 왜냐면 스프링이 사용자가 어떤 형식의 DTO 사용할지 모르게 때문이다.

그래서 스프링은 두 번째 변수로 DTO값을 세팅해줄, 다시 말하면 값을 담는 방식을 전달해달라고 한다.

근데 생각해보면 자바에서 콜백이 가능한 일인가?

실제로 가능하다. 다만 우리가 JS쓰는 것처럼 쉽고 편하지 않다. 애초에 함수 자체를 값으로 쓸 수 없기에, 자바에서는 그 방식을 바꿔야 할 뿐이다.

그리고 그 방식은 아주 간단히 함수를 담은 클래스를 넘기는 것이다. 생각해보자 함수를 넘길 수 없으면, 함수를 담은 클래스를 넘기고 이를 따라 진행하면 된다. 다만 그 기법? 문법을 우리가 알지 못하기 때문에 지금부터 알아보자.


RowMapper

넘길 클래스는 인터페이스인데, 이걸 new 한다. 근데 인터페이스를 new하는 것이 가능한 일인가?

가능하다. 앞서 말한 것처럼 인터페이스는 오버라이딩이 아직 되지 않아서 new할 수 없을 뿐 오버라이딩만 하면 사용할 수 있다. 그리고 그 방식이 우리가 자바에서 콜백을 사용하는 방법이다.

new RowMapper<MessagesDTO>() {
		@Override
		public MessagesDTO mapRow(ResultSet rs, int rowNum) throws SQLException {

			MessagesDTO dto = new MessagesDTO();
			dto.setSeq(rs.getInt("seq"));
			dto.setWriter(rs.getString("writer"));
			dto.setContents(rs.getString("contents"));
			dto.setWrite_date(rs.getTimestamp("write_date"));
			return dto;
		}

그래서 위 코드와 같이 스프링이 DTO를 옮겨담는 방식이 적히 메서드가 mapRow이다. 이 메서드를 @Override 어노테이션으로 명시해준 뒤, 채워서 전달하면 스프링이 ResultSet을 건네받아 DTO를 채워서 반환한다.


query()

이와 같이 query() 도 같은 방식으로 동작하지만, 반복의 과정까지 스프링이 해주기 때문에 메서드도 똑같이 건네주면 된다.

public List<MessagesDTO> selectAll(){

	String sql = "select * from messages";

	return jdbc.query(sql, new RowMapper<MessagesDTO>() { 
		@Override
		public MessagesDTO mapRow(ResultSet rs, int rowNum) throws SQLException {

			MessagesDTO dto = new MessagesDTO();
			dto.setSeq(rs.getInt("seq"));
			dto.setWriter(rs.getString("writer"));
			dto.setContents(rs.getString("content"));
			dto.setWrite_date(rs.getTimestamp("write_date"));
			return dto;
		}
	});
}

추가로 한 개의 숫자같은 데이터를 꺼내올 때는 굳이 콜백을 안쓰고, 반환하는 타입만을 매개변수로 준다.

public int selectCount() {
	String sql = "select count(*) from member";
	return jdbc.queryForObject(sql, Integer.class); 
}
profile
기록을 쌓아갑니다.

0개의 댓글