본문 바로가기
Spring 프레임워크/이론

Spring JDBC

by Hwanii_ 2023. 8. 16.
728x90

0.

그동안, Model 에서 DB와의 연결을 위해, JDBC 인터페이스를 구현 했었다.

기존 JDBC 코드 작업시, 로직이 반복 되거나, 유사함을 느꼈고,

이에 따라서, "반복되는 코드를 줄일 수 없을까 ?" 라는 생각을 했었다.

 

이렇게 반복되거나,

유사한 알고리즘을 캡슐화 해서,

재사용 하는 패턴을 Template 패턴 이라 한다.

 

코딩 순서가 정해져 있는 == 정형화 되어 있는

기술에서, 특히 유용하게 활용 된다.

 

예)

JDBC, 트랜잭션, Mybatis, JPA, ..

 

1. 

Spring JDBC 이란 ?

 

스프링 JDBC는 스프링 프레임워크에서 제공하는,

데이터베이스 연결과 관련된 기능을 간편하게 사용할 수 있게 해주는 모듈 이다.

 

데이터베이스와의 연결과 관련된 번거로운 작업들을 추상화하여,

개발자가 보다 편리하게 데이터베이스를 다룰 수 있도록 도와준다.

이를 통해, 데이터베이스와의 상호작용을 더 안전하고 효율적으로 처리 할 수 있게 된다.

 

2.

스프링 JDBC의 주요 특징은 아래와 같다.

 

1) DataSource 관리 :

스프링은 데이터베이스 연결에 사용되는 DataSource를 관리하고 제공 한다.

DataSource는 데이터베이스와의 연결 풀링을 관리하고,

스프링에서는 이것을 추상화 해서,

다양한 데이터베이스 연결 설정을 쉽게 관리할 수 있도록 도와준다.

 

2) JdbcTemplate :

스프링은 JDBC 작업을 더 편리하게 처리하기 위한,

JdbcTemplate 클래스를 제공 한다.

JdbcTemplate를 사용하면, 데이터베이스 연결, 쿼리 실행, 결과 매핑, 예외 처리 등을,

처리 하기 위한 다양한 메서드를 제공 한다.

 

3) NamedParameterJdbcTemplate

4) ORM 통합

5) 트랜잭션 관리

 

6) 예외 처리 :

JDBC 작업 중 발생하는 SQLException 등의 예외를,

스프링이 더 간편하게 처리 할 수 있도록 도와준다.

예외 처리를 미리 정의하여 런타임 예외로 변환하거나, 트랜잭션 롤백 등을 처리 할 수 있다.

 

3.

JdbcTemplate 클래스를 사용 하는 방법.

 

1)

.jar 파일이 필요 하다.

pom.xml 설정 파일에 라이브러리 2개를 추가 한다.

 

	<!-- Spring 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>

 

 

 

pom.xml 파일에 의존 주입을 하면,

Maven Dependencies 에 해당하는 .jar 파일들이 생성 되어 있는 모습을 확인 할 수 있다.

 

2)

★ DBCP ★

 

"DB 커넥션 풀" 이라 한다.

 

connection (conn) 들을 대신 관리 해주는 객체 (주체) 이다.

 

즉, connection (conn) 들을 내가 관리 하는게 아니라, DBCP가 대신 관리 한다.

 

 

DB 연결은 connection을 DB 으로부터 확보하는것부터 시작 하는데,

위와 같이, 그동안 DB 연결에 필수적으로 필요한 connection을 직접 했었다.

 

이제 위의 connection을 DBCP가 대신 해준다.

 

3)

그래서 대신 관리해줄 DBCP를 사용하려면, 객체화를 해야 하는데,

JdbcTemplate 클래스는 DataSource 객체를 통해서 connection 을 확보 하고 관리 한다.

 

즉, DataSource 객체를 생성 해야 한다.

 

4)

따라서, JdbcTemplate 클래스를 객체화 하고,

DataSource 객체를 <property> 태그를 사용해서,

setter 방식으로 의존 주입을 한다.

 

어디에 ?

Spring 컨테이너가 생성 하도록 설정 하기 ~

 

어떻게 ?

<bean> 태그를 사용 해서 설정 파일에 객체화 설정을 작업 한다.

 

	<!-- DataSource 객체 생성 -->
	<bean class = "org.apache.commons.dbcp.BasicDataSource" id = "dataSource" destroy-method = "close">
		<property name = "driverClassName" value = "oracle.jdbc.driver.OracleDriver" />
		<property name = "url" value = "jdbc:oracle:thin:@localhost:1521:orcl" />
		<property name = "username" value = "HWAN" />
		<property name = "password" value = "1234" />
	</bean>

	<!-- JdbcTemplate 에 DI할 객체 생성 -->
	<bean class = "org.springframework.jdbc.core.JdbcTemplate" id = "jdbcTemplate">
		<property name = "dataSource" ref = "dataSource" />
	</bean>

 

 

destroy-method = "close"

>>

연결이 끝나면 연결을 종료 하는 설정 이다.

과도한 연결로 인해서,

톰캣이 강제 종료 되거나, 기타 등등의 에러를 방지 하기 위해 설정 하면 좋다.

 

5)

이제 .xml 파일에서 설정이 끝났으니,

JdbcTemplate 를 사용할 DAO 클래스 내부에, 멤버변수로 선언 한다.

 

 

MemberDAO2 에서 JdbcTemplate jdbcTemplate가 멤버변수로 선언 되어 있기 때문에,

둘은 의존 관계 이고,

따라서, @Autowired 어노테이션을 작성 해주면 된다.

 

6)

MemberDAO2 클래스는 JdbcTemplate를 적용 하기 위해서 새로 만든 클래스 이므로,

해당 클래스를 사용하는 Service 레이어에 해당하는,

MemberServiceImpl 클래스 내부에 작성된,

의존 관계인 MemberDAO 를 MemberDAO2 로만 변경 해주면 된다.

 

 

이렇게 서비스 레이어를 사용함으로써, DAO가 변경 되더라도,

유지 보수가 용이해서 쉽게 코드 수정이 가능한것을 확인 할 수 있다.

 

4.

예시를 통해 확인 하기.

 

기존의 JDBC 인터페이스가 작성 되어 있는 BoardDAO 클래스의 코드.

 

 

등등, 위와 같이, CRUD 메서드가 작성 되어 있다.

 

모든 코드들은 유사한 형태로 작성 되어 있어서,

 

이것을 JdbcTemplate 클래스의 기능을 사용 해서, 쉽고 간단하게 코드를 작성해 보려 한다.

 

 

반복되고 유사한 코드가 작성되어 있는 각 CRUD 메서드들 내부의 코드가 훨씬 간결해진 모습을 확인 할 수 있다.

 

5.

코드 해석 하기.

 

우선,

C == insert

U == update

D == delete

 

메서드 들은,

jdbcTemplate 을 주체로 해서, update() 메서드를 사용 한다.

 

이는,

기존에 preparedStatement pstmt 를 사용 했을 때,

pstmt.executeUpdate() 메서드와 비슷한 느낌의 메서드 라고 보면 될듯 하다.

 

R == selectOne(), selectAll() 

메서드 들은,

jdbcTemplate 을 주체로 해서,

각각,

queryForObject()

메서드와,

query()

메서드를 사용 한다.

 

기존에,

pstmt.executeQuery() 메서드와 비슷한 느낌의 메서드 라고 보면 될듯 하다.

 

6.

jdbcTemplate.update() 메서드 확인 하기.

 

 

많은 함수들이 오버로딩 되어 있는것을 확인 할 수 있다.

 

 

insert 메서드의 쿼리문으로 set 되어야 할 물음표가 4개 인데,

 

이것을 update() 메서드 내부의 인자로 차례대로 작성 한다.

 

 

update 메서드와, delete 메서드도 동일한 구조의 문법을 확인 할 수 있다.

 

 

위와 같이,

물음표에 필요한 값을 배열을 선언 해서 update() 메서드의 인자로 넣는식으로 작성해도 된다.

 

위에서 말했듯이,

update() 메서드가 여러가지로 오버로딩 되어 있기 때문에,

이런식으로도 가능한 모습을 확인 할 수 있다.

 

7.

jdbcTemplate.queryForObject() 메서드 확인 하기.

 

 

Read 에 해당하는 메서드인 selectOne() 은 queryForObject() 라는 메서드를 사용 한다.

 

queryForObject() 메서드는,

RowMapper 인터페이스를 구현한,

MemberRowMapper 클래스의,

mapRow() 메서드를 호출하는 작업을 내부적으로 수행 한다.

 

이 메서드는 단일 결과를 반환하는 쿼리를 실행할 때 사용 된다. (selectOne)

 

이 메서드는 rs에 오직, 한가지 결과에 대한 행 데이터를 저장 하고,

여러 개의 결과 행 데이터가 있거나, 결과가 아예 없는 경우에는 예외가 발생 한다.

 

그래서, queryForObject() 메서드는 결과값으로 특정 자료형의 객체 한개를 반환 한다.

 

따라서, return 값으로 바로 작성 되어 있는 모습을 확인 할 수 있다.

 

queryForObject() 메서드도 여러가지 메서드들이 존재 하고, 오버로딩 되어 있는데,

 

위에서 사용된 메서드 시그니쳐는 다음과 같다.

 

queryForObject(쿼리문, INPUT, OUTPUT) 이다.

 

SQL을 실행 할 때, 필요한 INPUT은 배열의 형태 이고,

이때, 인자로 오는 데이터가 어떤 데이터 타입인지를 알 수 없으니까,

Object 자료형으로 배열을 선언 했다.

 

SQL을 실행 한 이후에, 나오는 결과값으로 나오는 OUTPUT은 특정 자료형의 "객체" 이다.

 

 

[ 참고 ]

<T> 는 제네릭 타입을 선언할 때 사용되는 표현 이다.

제네릭은 클래스나 메서드가 다양한 데이터 타입에 대해 동작 할 수 있도록,

일반화된 타입을 사용하는 개념 이다.

<T> 는 타입 매개변수를 나타내며,

클래스나 메서드에서 사용되는 실제 타입은,

이 타입 매개변수에 의해 결정 된다.

 

그리고,

위의 C, U, D 와 다르게, selectOne() 과 selectAll() 은,

 

RowMapper 라는 인터페이스를 메서드의 인자로 사용 해야 한다.

 

===================================================================

RowMapper 란 ?

 

스프링에서 제공하는 인터페이스 이다.

 

어떤 ResultSet을 특정한 자바 객체 (POJO) 와 매핑 해야 하는지를 "강제" 해주는 역할.

 

이때, DB와 매핑 되는 자바 객체가 "VO" 이다.

 

"VO" 를 알아야 하기 때문에, 자료형을 알아야 하고, 따라서 제네릭 < > 설정이 필수.

===================================================================

 

new RowMapper 을 해주기 위해서는,

RowMapper 인터페이스를 구현한 클래스가 존재 해야만 한다.

 

 

위와 같이,

RowMapper 인터페이스를 구현한 MemberRowMapper 라는 클래스를 확인 할 수 있다.

 

클래스 내부에 작성된 메서드는, RowMapper 인터페이스를 구현 했기 때문에,

강제되는 메서드 이며,

해당 메서드 내부에 필요한 코드를 작성 하면 된다.

 

[ 참고 ]

DB를 치고 나온 결과값에 대한 칼럼값들을,

객체에 set 할 때,

다양하게 set을 하고 싶다면,

MemberRowMapper 클래스의 이름을 조금씩 다르게 해서,

적절하게 클래스를 생성 하여 사용 하면 된다.

클래스를 여러개 만들더라도 new 를 하는게 아니기 때문에,

메모리를 더 사용 하고 그러한 개념이 전혀 아니다.

 

8.

jdbcTemplate.query() 메서드 확인 하기.

 

Read 에 해당하는 메서드인 selectAll() 은 query() 라는 메서드를 사용 한다.

 

query() 메서드는,

RowMapper 인터페이스를 구현한,

MemberRowMapper 클래스의,

mapRow() 메서드를 호출하는 작업을 내부적으로 수행 한다.

 

query() 메서드는 결과값으로 특정 자료형의 다수의 객체를 담은 List 를 반환 한다.

 

 

그래서, query() 메서드의 반환값을 mdatas 로 받는 모습을 확인 할 수 있다.

 

 

query() 메서드도 다양하게 오버로딩 되어 있는데, 

쿼리문에 물음표에 있는 값을 부여 해야 한다면,

보통 세번째의 query() 메서드를 사용 한다.

아래의 예시를 보자.

 

 

물음표의 값으로,

Object 타입으로 배열을 선언하고, 필요한 값을 작성 한다.

 

 

이런식으로 작성해도 되긴 하지만,

보통 저런식으로 인자를 넣는 경우는,

C, U, D 비즈니스 메서드에서 jdbcTemplate.update() 메서드를 사용 할 때,

위와 같이 작성 하는게 일반적 이다.

보편적인 방법을 사용 하도록 하자.

 

그런데,

selectAll() 의 경우, query() 메서드를 사용 한다고 했었는데,

이 query() 메서드는 어떤식의 흐름으로 작동하는지를,

로그를 찍어 확인해 보려고 한다.

 

 

콘솔창을 확인해 보자.

 

 

BoardDAO2 : selectAll() 로그가 찍히고,

 

 

11111111111111111111 로그가 찍히고,

 

 

rowNum은 0이 찍혀 있는 모습을 확인 할 수 있다.

 

query() 메서드가 호출 되면,

ResultSet 객체 rs에는 쿼리문의 결과, 

즉, selectAll() 쿼리 결과에 해당하는, 모든 행의 데이터가 저장 된다.

 

그리고,

mapRow() 메서드가,

첫번째 행의 데이터를 읽어와서,

해당 데이터를 가지고 특정한 자료형,

그러니까, 위에서는 BoardVO 타입이 되겠다.

BoardVO 타입의 자바 객체를 생성하고, 객체에 데이터를 set 하고, 리스트에 저장 한다.

이 작업을 rs에 저장된 쿼리문의 결과 행이 끝날 때 까지 반복 한다.

 

결과값인 모든 행의 각 행을 인덱스로 표현한게 rowNum 이다.

 

 

이런식으로, 로그가 찍히게 되고,

 

rs에 저장된 쿼리문의 결과 행이 끝나면,

 

 

22222222222 로그가 찍히는 것을 확인 할 수 있다.

 

 

현재, 게시판을 selectAll() 하는 경우를 예시로 들었는데,

위의 이미지 처럼, 게시글이 총 11개 이므로,

 

 

rowNum이 10 으로 나오는것을 확인 할 수 있다. (인덱스가 0번 부터 시작하니까)

반응형