[Spring] 토비의 스프링 Vol.2 2장 데이터 액세스 기술

Shiba·2023년 10월 25일
0

🍀 스프링 정리

목록 보기
14/21
post-thumbnail

📗 데이터 액세스 기술

대부분의 엔터프라이즈 애플리케이션은 데이터베이스와 연결하여 동작
- 자바에는 JDBC가 존재하지만, SQL과 JDBC API 조합만으로는 엔터프라이즈 애플리케이션의 복잡하고 거대한 데이터를 처리하는 깔끔한 코드를 만들기엔 많은 제약과 문제점이 존재

2 장에서는 스프링의 데이터 액세스 기술에 관한 기본 개념을 다시 정리해보고 스프링이 지원하는 핵심 데이터 액세스 기술의 구체적 사용 방법과 선택할 수 있는 옵션을 알아보자.

📖 공통 개념

모든 기술에 공통적으로 적용되는 원칙과 기본개념에 대해 정리해보자.
Vol.1에서 이미 정리한 내용이므로 간단히 정리해볼 것이다.

📝 DAO 패턴

데이터 액세스 계층DAO 패턴이라 불리는 방식으로 분리하는 것이 원칙

  • DTO 또는 도메인 오브젝트만을 사용하는 인터페이스를 통해 데이터 액세스 기술을 외부에 노출하지 않도록 만드는 것
    - 구현 기술에 대한 정보를 외부에 공개해서는 안됨
    - DAO를 이용하는 서비스 계층의 코드를 기술이나 환경에 종속되지 않는 POJO로 개발 가능

◼ DAO 인터페이스와 DI

DAO는 인터페이스를 이용해 접근하고 DI 되도록 만들어야 한다
- 인터페이스를 만들 때 public 메소드를 추가하지 않도록 주의
서비스 계층 코드에서 의미 있는 메소드만 인터페이스로 공개해야 한다

◼ 예외처리

데이터 액세스 중에 발생하는 예외는 대부분 복구 불가능
- DAO 밖으로 던져질 때는 런타임 예외여야 함.
- DAO 메소드 선언부에 내부 기술을 드러내는 예외를 직접 노출해선 안 된다.
- 서비스 계층 코드는 DAO가 던지는 대부분의 예외를 직접 다뤄야 할 이유가 없음

때로는 DAO가 던지는 의미있는 예외를 잡아 비즈니스 로직에 적용하는 경우가 존재
- 낙관적인 락킹, 중복키 예외
- 예외에 대한 일관성이 없기 때문에 구현 기술에 따라 달라지는 예외를 서비스 계층에서 알고 있어야만 함.
(이 때문에 스프링은 데이터 예외 추상화를 제공하고, 각 기술과 DB에서 발생하는 예외를 스프링의 데이터 예외로 변환해주는 서비스가 자동으로 적용됨)

📝 템플릿과 API

스프링은 try/catch/finally와 판에 박힌 반복된 코드를 피하고 꼭 바뀌는 내용만을 담을 수 있도록 템플릿을 제공
- 미리 만들어진 작업 흐름을 담은 템플릿은 중복 제거, 예외 변환, 트랜잭션 동기화 기능 제공
- 템플릿이 제공하는 API를 사용해야한다는 단점

📝 DataSource

스프링에서는 DataSource를 하나의 독립된 빈으로 등록하도록 강력히 권장
- 특정 기술의 내부에 종속되어있으면 곤란함

◼ 학습 테스트와 통합 테스트를 위한 DataSource

테스트용 DataSource를 운영서버에 적용하는 실수를 하지 않기 위해 개발용, 테스트용 스프링 설정파일을 따로 만들어서 적용

SimpleDriverDataSource

스프링이 제공하는 가장 단순한 DataSource 구현 클래스
getConnection()을 호출할 때마다 매번 DB 커넥션을 새로 만들고 따로 풀을 관리하지 않음. - 따라서 실전에서는 절대 사용하면 안된다!

SingleConnectionDataSource

하나의 물리적인 DB커넥션만 만들어두고 이를 계속 사용
- 순차적으로 진행되는 통합 테스트에서 사용가능하지만, 동시에 두 개 이상의 스레드가 동작하는 경우, 하나의 커넥션을 공유하게 되므로 위험

◼ 오픈소스 또는 상용 DB커넥션 풀

스프링과 함께 많이 사용되는 대표적인 두 가지 오픈소스 DB 커넥션 풀에 대해 알아보자

아파치 Commons DBCP

가장 유명한 오픈소스 DB 커넥션 풀 라이브러리

c3p0 JDBC/DataSource Resource Pool

JDBC 3.0 스펙을 준수하는 Connection과 Statement 풀을 제공하는 라이브러리

상용 DB 커넥션 풀

일부 상용 DB는 자체적으로 커넥션 풀 라이브러리를 제공
- 스프링의 빈으로 등록 가능하고 프로퍼티로 설정이 가능하다면 무엇이든 사용가능

◼ JNDI/WAS DB 풀

대부분의 자바 서버는 자체적으로 DB 풀 서비스를 제공
- DB 풀 라이브러리를 사용해 애플리케이션 레벨의 전용 풀을 만드는 대신 서버가 제공하는 DB풀을 사용해야 하는 경우에는 JNDI를 통해 서버의 DataSource에 접근
- <jee:jndi-lookup> 태그를 이용하면 JNDI를 통해 가져온 오브젝트를 스프링의 빈으로 사용 가능

📖 JDBC

스프링 JDBC는 JDBC 개발의 장점과 단순성을 그대로 유지하면서, 기존 JDBC API 사용 방법의 단점을 템플릿/콜백 패턴을 이용해 극복할 수 있게 해주고, 가장 간결한 형태의 API의 사용법을 제공하며, JDBC API에서는 지원되지 않는 편리한 기능을 제공해주기도 함.

📝 스프링 JDBC 기술과 동작원리

스프링 3.0에서는 다섯 가지 종류의 접근 방법을 제공

◼ 스프링 JDBC 접근 방법

접근 방법 중에서도 가장 사용이 편하고 자주 이용되는 두 가지 방법에 대해 알아보자

SimpleJdbcTemplate

JdbcTemplate, NameParameterJdbcTemplate에서 가장 많이 사용되는 기능을 통합하고 자바 5 이상의 장점을 최대한 활용할 수 있게 만든 것
- 방대한 템플릿 메소드와 내장된 콜백 제공
- JDBC의 모든 기능을 최대한 활용할 수 있는 유연성을 가짐

SimpleJdbcInsert, SimpleJdbcCall

DB가 제공해주는 메타정보를 활용해서 최소한의 코드만으로 단순한 JDBC 코드를 작성하게 해줌.
- 메타정보에서 컬럼 정보와 파라미터 정보를 가져와 삽입용 SQL과 프로시저 호출 작업에 사용하여 편리함

◼ 스프링 JDBC가 해주는 작업

스프링 JDBC를 이용하면 다음과 같은 작업을 템플릿이나 스프링 JDBC가 제공하는 오브젝트에게 맡길 수 있음.

Connection 열기와 닫기

직접 Connection을 열고 닫는 작업을 할 필요가 없음
- 스프링 JDBC가 필요한 시점에서 알아서 진행
- 어떤 순서로 호출하였고, 트랜잭션 경계를 어떻게 선언해뒀고에 따라 Connection을 열고 닫는 위치가 달라질 수 있음

Statement 준비와 닫기

SQL정보가 담긴 Statement 또는 PreparedStatement를 생성하고 필요한 준비 작업을 해주는 것도 대부분 스프링 JDBC의 몫.
- 스프링 JDBC가 Statement를 준비하는 동안필요한 정보를 미리 준비해주는 건 개발자의 책임

Statement 실행

SQL이 담긴 Statement를 실행하는 것도 스프링 JDBC의 몫

ResultSet 루프

ResultSet에 담긴 쿼리 실행 결과가 한 건 이상이라면 루프를 돌면서 각각의 로우를 처리해야하는데, 루프를 만들어 반복해주는 것도 스프링 JDBC가 해주는 작업
- 각 오브젝트 내용을 어떻게 저장할지는 콜백을 만들어 템플릿에 제공해주면 됨
(스프링 JDBC가 미리 정해둔 포맷을 사용할 수도 있음)

예외처리와 반환

JDBC 작업 중 발생하는 모든 예외는 스프링 JDBC의 예외 변환기가 처리
- 체크 예외인 SQLException을 런타임 예외인 DataAccessException 타입으로 변환
- DB별 에러코드를 참고하여 일관된 의미를 가진 DataAccessException 계층구조 내의 예외로 변환

트랜잭션 처리

스프링 JDBC는 트랜잭션 동기화 기법을 이용선언적 트랜잭션 기능과 맞물려 돌아감.
- 트랜잭션 시작 후에 스프링 JDBC 작업 요청시 진행중인 트랜잭션에 참여
- 트랜잭션이 없는 채로 호출될시 새로운 트랜잭션을 만들어 사용
- 트랜잭션과 관련된 모든 작업에 대해 신경쓰지 않아도 됨!
(개발자는 데이터 액세스 로직마다 달리지는 부분만 정의해주고, DataSource를 정의해주면 된다)

📝 SimpleJdbcTemplate

스프링 JDBC를 사용한다면 가장 많이 이용하게될 JDBC용 템플릿.
- 실행, 조회, 배치의 세 가지 작업으로 구분

◼ SimpleJdbcTemplate 생성

DataSource를 파라미터로 하여 다음과 같이 생성

SimpleJdbcTemplate template = new SimpleJdbcTemplate(dataSource);

- 멀티스레드 환경에서도 안전하게 공유해서 사용 가능
- 관례적으로 DAO 코드에서 DataSource를 제공받아 SimpleJdbcTemplate 생성

◼ SQL 파라미터

SimpleJdbcTemplate에 작업을 요청할 때문자열로 된 SQL을 제공해주어야 함
- SimpleJdbcTemplate는 이름 치환자 기능 제공

INSERT INTO MEMBER(ID, NAME, POINT) VALUES(:id, :name, :point);

- 중간에 순서가 바뀌어도 파라미터 바인딩에 영향이 없음

이름 치환자는 맵이나 오브젝트에 담긴 내용(파라미터 소스)을 키 값이나 프로퍼티 이름을 이용해 바인딩 가능.

Map/MapSqlParameterSource

맵의 각 키 값과 일치하는 치환자에 맵의 값이 자동으로 삽입.

//예시용 맵
Map<String, Object> map = new HashMap<>();
map.put("id", 1);
map.put("name", "Spring");
map.put("point", 3.5);
INSERT INTO MEMBER(ID, NAME, POINT) VALUES(1, 'Spring', 3.5);

BeanPropertySqlParameterSource

맵 대신 도메인 오브젝트나 DTO를 사용하게 해줌
-오브젝트의 프로퍼티 이름과 SQL의 이름 치환자를 매핑해서 파라미터의 값을 넣어주는 방식
- 도메인 오브젝트의 파라미터와 SQL 치환자의 이름만 같게 만들어주면 매우 편리하게 사용 가능

◼ SQL 실행 메소드

INSERT, UPDATE, DELETE와 같은 SQL을 실행할 때SimpleJdbcTemplate의 update()메소드 사용
- update() 메소드를 호출할 때 SQL과 함께 바인딩할 파라미터를 다음 세 가지 방식 중 하나로 전달

varargs

위치 치환자 '?'를 사용하는 경우, 바인딩할 파라미터를 순서대로 전달

simpleJdbcTemplate.update(
"INSERT INTO MEMBER(ID, NAME, POINT, args) VALUES(?,?,?)", 1, "Spring", 1.5);

Map

이름 치환자를 사용한다면 파라미터를 Map으로 전달 가능

simpleJdbcTemplate.update(
"INSERT INTO MEMBER(ID, NAME, POINT, args) VALUES(:id, :name, :point)", map);

SqlParameterSource

도메인 오브젝트나 DTO이름 치환자에 직접 바인딩할 때 사용
- BeanPropertySqlParameterSource 사용

simpleJdbcTemplate.update(
"INSERT INTO MEMBER(ID, NAME, POINT, args) VALUES(:id, :name, :point)", 
	new BeanPropertySqlParameterSource(member));

◼ SQL 조회 메소드

SELECT를 이용하는 조회용 메소드는 단순 값이나 오브젝트를 가져오는 경우리스트를 가져오는 경우로 구분 가능

int queryForInt(String sql, [SQL 파라미터])

하나의 int 타입 값을 조회할 때 사용
- SQL파라미터로 Object타입 가변인자, 맵, 파라미터 소스를 이용할 수 있음
(단순한 쿼리라면 SQL파라미터 부분 생략도 가능)

//이름치환자
simpleJdbcTemplate.queryForInt("select count(*) from member where point > :min",
								new MapSqlParameterSource("min", min));
//파라미터 생략
int count = simpleJdbcTemplate.queryForInt("select count(*) from member");

실행 결과로 한 개의 로우에 한 개의 숫자 컬럼만 갖고 있어야 함.
- 컬럼 개수가 두 개 이상이거나 로우의 개수가 여러 개이면 예외 발생

long queryForLong(String sql, [SQL 파라미터])

queryForInt()에서 타입만 long으로 바뀐 것. 사용 방법 동일

<T> T queryForObject(String sql, Class<T> requiredType, [SQL파라미터])

쿼리를 실행해서 하나의 값을 가져올 때 사용. 결과 타입을 직접 지정 가능

//id에 해당하는 name값을 가져옴
String name = simpleJdbcTemplate.queryForObject(
								"select name from member where id =?", 
														  String.class, id);

실행결과로 하나의 컬럼을 가진 하나의 로우여야 한다.

<T> T queryForObject(String sql, RowMapper<T> rm, [SQL파라미터])

앞의 queryForObject()와 달리 다중 컬럼을 가진 쿼리에 사용 가능
(다중 컬럼을 가진 결과이므로 도메인 오브젝트나 DTO와 같은 여러 개의 프로퍼티를 오브젝트로 전환해야 함)

//id에 해당하는 Member값을 가져옴
Member m = simpleJdbcTemplate.queryForObject(
				"select name from member where id =?", 
					   new BeanPropertyRowMapper<Member>(Member.class), id);

실행결과로 하나의 컬럼을 가진 하나의 로우여야 한다.

Map<String, Object> queryForMap(String sql, [SQL 파라미터])

맵에 로우의 내용을 저장해서 돌려줌
- 맵의 키에는 컬럼 이름, 값에는 컬럼의 값이 저장됨.

Map<String, Object> map = simpleJdbcTemplate.queryForMap(
"select * from member where id = ?", id);

실행결과로 하나의 컬럼을 가진 하나의 로우여야 한다.

List<Map<String, Object>> queryForList(String sql, [SQL 파라미터])

queryForMap()의 다중 로우 버전
- 각 로우의 내용을 Map에 넣고 이를 다시 리스트로 만들어 돌려줌

<T> List<T> query(String sql, RowMapper<T> rm, [SQL파라미터])

여러 개의 컬럼을 가진 로우RowMapper 콜백을 통해 도메인 오브젝트나 DTO에 매핑 (queryForObject()와 달리 여러 개의 로우 처리 가능)
- 결과 값은 매핑한 오브젝트의 리스트로 받음

List<Member> members = simpleJdbcTemplate.query("select * from member where
							point > ?", new BeanPropertyRowMapper<Member>
                            (Member.class), point);

◼ SQL 배치 메소드

update()로 실행하는 SQL들배치 모드로 실행하게 해준다
- 내부적으로 JDBC Statement의 addBatch()executeBatch()메소드를 이용해 여러 개의 SQL한 번에 처리
- 동일한 SQL을 파라미터만 바꿔가면서 실행하는 경우 사용 가능

int[] batchUpdate(String sql, Map<String, ?>[] batchValues)

이름치환자를 가진 SQL파라미터 정보가 담긴 맵의 배열을 이용
- 배열의 개수만큼 SQL을 실행. 리턴 값은 각 SQL을 실행했을 때 영향받은 로우의 개수를 담은 배열

int[] batchUpdate(String sql, SqlParameterSource[] batchArgs)

맵 대신 SqlParameterSource 타입 오브젝트의 배열로 파라미터를 제공할 수도 있음
- 배열 개수만큼 SQL을 실행

int[] batchUpdate(String sql, List<Object[]> batchArgs)

varags로 전달했던 SQL 파라미터Object 배열에 넣고 이를 List로 만들어서 전달가능하다.
- 리스트의 크기만큼 SQL이 반복 실행

📝 SimpleJdbcInsert

SQL을 사용하는 DB 프로그래밍의 가장 귀찮은 일은 비슷한 구조의 SQL을 반복적으로 만들어야 한다는 점
- 특히 INSERT문을 작성할 때는 모든 컬럼 정보를 다 적어야 함.
=> 이를 간단하게 만들어주는 것이 SimpleJdbcInsert이다.

◼ SimpleJdbcInsert 생성

SimpleJdbcInsert는 테이블별로 만들어 사용.
- 하나의 DAO에서 여러 개의 SimpleJdbcInsert를 사용할 수 있음
- SimpleJdbcInsert를 생성하는데에는 DataSource 필요.

SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(dataSource);

SimpleJdbcInsert 오브젝트를 생성한 후적어도 어떤 테이블을 적용할지 초기화해주어야 한다.
주요 초기화 메소드는 다음과 같다

SimpleJdbcInsert withTableName(String tableName)

테이블 이름을 지정
- 지정된 이름을 가지고 DB로부터 테이블 메타정보를 읽어 INSERT 문장을 만드는데 활용
- 테이블 이름은 필수적임

//필수이기 때문에 다음과 같이 초기화와 함께 진행
SimpleJdbcInsert jdbcInsert = 
				new SimpleJdbcInsert(dataSource).withTableName("member");

SimpleJdbcInsert withSchemaName(String schemaName),
SimpleJdbcInsert withCatalogName(String catalogName)

스키마카탈로그 이름을 지정해야 할 경우 사용

SimpleJdbcInsert usingColumns(String... columnNames)

일부 컬럼만 사용해서 INSERT문 작성 가능

SimpleJdbcInsert usingGeneratedKeyColumns(String... columnNames)

DB에 의해 자동으로 생성되는 키 컬럼을 지정할 수 있다.
- 지정된 컬럼은 INSERT문장에서 제외

SimpleJdbcInsertOperations withoutTableColumnMetaDataAccess()

DB에서 테이블 메타데이터를 가져오지 않도록 만듦.
- 일부 DB는 메타정보를 제공해주지 않는 경우가 존재

◼ SimpleJdbcInsert 실행

초기화를 마친 SimpleJdbcInsert를 다음과 같은 메소드를 이용실제 INSERT문 실행 가능

int execute([이름 치환자 SQL 파라미터])

SimpleJdbcInsert가 내부적으로 생성하는 SQL은 이름 치환자를 가진 INSERT문
- 파라미터 지정 방법으로 맵과 SqlParameterSource 타입 오브젝트 사용 가능
- 파라미터만 정확히 지정해주면 SQL작성의 번거로운 과정을 모두 생략 가능

//SimpleJdbcInsert의 활용
SimpleJdbcInsert insert = 
			new SimpleJdbcInsert(dataSource).withTableName("member");
Member member = new Member(1, "Spring", 3.5);
insert.execute(new BeanPropertySqlParameterSource(member));

📌 테이블 설정이 같다면 재사용 가능

Number executeAndReturnKey([이름 치환자 SQL 파라미터])

execute()와 같은 작업을 수행, 자동생성된 키 값을 Number 타입으로 돌려줌
- 숫자로 된 자동생성 키를 갖고 있는 경우에 사용

KeyHolder executeAndReturnKeyHolder([이름 치환자 SQL 파라미터])

하나 이상의 자동생성 키 컬럼을 갖는 테이블인 경우 사용. KeyHolder타입으로 생성된 키 정보를 돌려줌

📝 SimpleJdbcCall

DB에 생성해둔 저장 프로시저(stored procedure) 또는 저장 펑션(stored function)을 호출할 때 사용

◼ SimpleJdbcCall 생성

SimpleJdbcCall도 dataSource를 이용해 생성하며, 멀티스레드 환경에 안전하게 공유 가능

SimpleJdbcCallOperations withProcedureName(String procedureName)

실행할 프로시저 이름을 지정

SimpleJdbcCallOperations withFunctionName(String functionName)

실행할 펑션의 이름을 지정

SimpleJdbcCallOperations returningResultSet(String parameterName, ParameterizedRowMapper rowMapper)

프로시저가 ResultSet을 돌려주는 경우에 이를 RowMapper를 이용해 매핑해줌.
- 하나 이상의 ResultSet이 돌아오는 경우라면 순차적으로 returningResultSet()을 지정하면 됨.

◼ SimpleJdbcCall 실행

DB의 메타정보를 이용필요한 파라미터 정보를 가져옴

<T> T executeFunction(Class<T> returnType, [SQL 파라미터])

저장 펑션을 실행해주는 메소드

create function find_name(in_id INT)
returns varchar(255)
begin
	declare out_name varchar(255);
    select name
    	into out_name
        from member
        where id = in_id;
    return out_name;
end
//위의 sql함수를 호출!
SimpleJdbcCall call = new SimpleJdbcCall(dataSource.withFunctionName("find name");
String ret = call.executeFunction(String.class, id);

<T> T executeObject(Class<T> returnType, [SQL 파라미터])

저장 프로시저를 호출할 때 사용. 사용 방법은 executeFunction()과 동일
- 프로시저의 출력 파라미터가 하나일 때만 이용가능

Map<String, Object> execute([SQL 파라미터])

하나 이상의 출력 파라미터를 가진 저장 프로시저를 호출할 때 사용
- 여러 개의 출력 파라미터 값이 맵의 형태로 리턴

📝 스프링 JDBC DAO

스프링 JDBC를 이용DAO 클래스를 설계하는 방법
- 가장 권장되는 방법은 DAO는 DataSource에만 의존하게 만들고 스프링 JDBC 오브젝트코드를 이용해 직접 생성하거나 초기화해서 DAO의 인스턴스 변수에 저장해두고 사용하는 것

public class MemberDao {
	private SimpleJdbcTemplate simpleJdbcTemplate;
    private SimpleJdbcInsert memberInsert;
    private SimpleJdbcCall memberFindCall;
    
    @Autowired
    public void init(DataSource dataSource) {
    	this.simpleJdbcTemplate = new SimpleJdbcTemplate(dataSource);
        this.memberInsert = new SimpleJdbcInsert(dataSource).withTableName("member");
        this.memberFindCall =
        	new SimpleJdbcCall(dataSource).withFunctionName("find_member");
    }
}

📖 iBatis SqlMaps

iBatis란?

  • 자바 오브젝트와 SQL문 사이의 자동매핑 기능을 지원하는 ORM 프레임워크.
    • 자바오브젝트만을 이용해 데이터 로직 작성 가능
    • SQL별도의 파일로 분리해서 관리할 수 있음
      - XML파일 안에 저장이 가능하여 자바 코드를 수정하고 컴파일하지 않아도 됨
    • 오브젝트-SQL 사이의 파라미터 매핑 작업을 자동으로 해줌
    • 익숙한 SQL을 그대로 사용할 수 있으면서 JDBC 코드 작성의 불편함을 제거해주고, 도메인 오브젝트나 DTO를 중심으로 개발이 가능

📝 SqlMapClient 생성

◼ iBatis 설정파일과 매핑파일

iBatis를 이용하려면 공통 설정을 담은 XML 설정파일매핑정보를 담은 XML 매핑파일이 필요
- 하나의 설정파일하나 이상의 매핑파일로 구성

설정파일

데이터소스, 트랜잭션 매니저, 매핑 리소스 파일 목록, 프로퍼티, 타입별칭과 핸들러, 오브젝트 팩토리와 설정 프로퍼티 값을 넣을 수 있음
- 이 중에서 데이터소스와 트랜잭션 매니저스프링의 빈으로 등록된 것을 사용하는 게 좋다.
- 매핑파일 정보는 반드시 넣어야 한다.

매핑파일

SQL-오브젝트 사이의 매핑정보는 XML 파일로 만들고, 사용할 SQL 문과 SQL 파라미터, 실행 결과를 어떻게 자바오브젝트로 변환하는지 들어있음

◼ SqlMapClient를 위한 SqlMapClientFactoryBean 등록

iBatis를 사용하기 위해서는 SqlMapClient를 구현한 오브젝트가 필요
- 스프링에서는 SqlMapClient를 빈으로 등록해주는 팩토리 빈의 도움이 필요

📝 SqlMapClientTemplate

SqlMapClient를 DI받아 직접 사용하는 대신 스프링이 제공하는 SqlMapClientTemplate를 이용하는 것이 좋음
- 스프링 데이터 액세스 기술이 제공하는 다양한 혜택을 받을 수 있음
(스프링 트랜잭션과 동기화, 예외 변환 등)

//SqlMapClientTemplate를  사용해 iBatis 만들기
public class MemberDao {
	private SqlMapClientTemplate sqlMapClientTemplate;
    
    public void setSqlMapClient(SqlMapClient sqlMapClient) {
    	sqlMapClientTemplate = new SqlMapClientTemplate(sqlMapClient);
    }
}
//SqlMapClientDaoSupport를 사용해 iBatis 만들기
public class MemberDao extends SqlMapClientDaoSupport {

	//SqlMapClientTemplate를 SqlMapClientDaoSupport가 만들어줌
	public void insert(Member member) { 
    	getSqlMapClientTemplate().insert("insertMember", member);
    }
}

◼ 등록, 수정, 삭제

insert()

SQL 아이디와 파라미터를 넣어주면 INSERT 문을 실행
- SqlMapExecutor의 insert()메소드가 사용됨

//파라미터 유무에 따라 두 가지 메소드중 하나를 사용
Object insert(String statementName)
Object insert(String statementName, Object parameterObject)

update()

SQL 아이디와 파라미터 오브젝트를 제공하면 UPDATE 문을 실행
- 파라미터로 사용할 수 있는 오브젝트 종류에 제한이 없음
- 파라미터 오브젝트의 타입은 매핑파일 안에 정의

//영향을 받은 로우의 갯수 리턴
int update(String statementName)
//영향을 받은 로우의 갯수 리턴
int update(String statementName, Object parameterObject)
// 기대했던 로우의 개수(requiredRowsAffected)가 실행 결과와 일치하지 않으면
// JdbcUpdateAffectedIncorrectNumberOfRowsException 예외 발생
void update(String statementName, Object parameterObject,
		int requiredRowsAffected) 

delete()

매핑파일에 정의된 DELETE 문을 실행. SqlMapExecutor의 delete() 메소드가 사용됨

//영향을 받은 로우의 갯수 리턴
int delete(String statementName)
//영향을 받은 로우의 갯수 리턴
int delete(String statementName, Object parameterObject)
// 기대했던 로우의 개수(requiredRowsAffected)가 실행 결과와 일치하지 않으면
// JdbcUpdateAffectedIncorrectNumberOfRowsException 예외 발생
void delete(String statementName, Object parameterObject,
		int requiredRowsAffected) 

◼ 조회

SqlMapClientTemplate의 조회용 메소드는 대부분 iBatis가 제공하는 메소드를 그대로 위임

단일 로우 조회: queryForObject()

SQL 실행 결과가 한 건인 경우 사용. 리턴 오브젝트는 매핑파일에 지정된 타입이 사용됨
- 파라미터가 있는 경우, 없는 경우, 파라미터와 함께 결과를 매핑해서 돌려줄 오브젝트를 직접 제공하는 경우 세 가지로 구분

Object queryForObject(String stateName)
Object queryForObject(String stateName, Object parameterObject)
Object queryForObject(String stateName, Object parameterObject,
					Object resultObject)

다중 로우 조회: queryForList()

SQL의 실행 결과가 한 건 이상인 경우 각 로우를 오브젝트에 담고 이를 다시 리스트로 만들어서 돌려주는 메소드

List queryForList(String stateName)
List queryForList(String stateName, Object parameterObject)
List queryForList(String stateName, int skipResults, int maxResults)
List queryForList(String stateName, Object parameterObject, 
									int skipResults, int maxResults)

다중 로우 조회: queryForMap()

스프링 JDBC의 queryForMap()과 전혀 다른 방식
- 다중로우를 가진 결과를 조회할 때 사용.
- 맵의 엔트리마다 로우 하나의 내용이 들어감

Map queryForMap(String statementName Object parameterObject,
				String keyProperty)
Map queryForMap(String statementName Object parameterObject,
				String keyProperty, String valueProperty)

다중 로우 조회: queryWithRowHandler()

스프링 JDBC의 RowMapper처럼 SQL의 결과를 루프로 돌면서 각 로우마다 콜백 오브젝트를 호출해주는 방식
- RowMapper는 반환이 목적이지만 RowHandler는 결과를 어떻게 처리할지를 스스로 결정

void queryWithRowHandler(String statementName, RowHandler rowHandler)
void queryWithRowHandler(String statementName, Object parameterObject, 
						RowHandler rowHandler)

◼ SqlMapClientCallback

콜백이 내장된 템플릿 메소드 대신 직접 iBatis의 SqlMapExecutor의 API를 사용하고 싶다면 SqlMapClientCallback 인터페이스를 사용
- 스프링이 모든 필요한 작업 흐름을 관리하면서 콜백 오브젝트를 호출
-- 콜백 인터페이스구현한 오브젝트를 생성템플릿의 메소드에 전달해주기만 하면 된다

// SqlMapClientCallback 인터페이스
public interface SqlMapClientCallback<T> {
	T doInSqlMapClient(SqlMapExecutor executor) throws SQLException;
}

📝 JPA

Java Perisistent API의 약자로 EJB 3.0과 함께 등장한 JavaSE와 JavaEE를 위한 영속성 관리와 O/R 매핑(ORM)을 위한 표준 기술

ORM이란?

  • 오브젝트와 RDB 사이에 존재하는 개념과 접근 방법, 성격의 차이 때문에 요구되는 불편한 작업을 제거해주는 역할
  • 프레임워크가 필요한 SQL을 매핑정보를 참고생성하여 DB 작업을 한 뒤, 다시 오브젝트로 돌려줌
    • ORM을 사용하는 개발자는 모든 데이터를 오브젝트 관점으로만 본다
      • RDB에 담긴 정보를 자바오브젝트를 다루는 것만으로 관리가 가능하게 됨

📝 하이버네이트

가장 크게 성공한 오픈소스 ORM 프레임워크.
- POJO로 SQL을 직접 사용하는 전통적인 방식 못지않게 강력하고 빠르면서도 편리한 ORM 방식의 개발이 가능함을 보여준 기술

📖 트랜잭션

📝 트랜잭션 추상화와 동기화

스프링의 서비스 추상화와 동기화에 대한 동작원리나 특징은 Vol.1에서 진행
여기서는 데이터 액세스 기술에 따라 사용할 수 있는 트랜잭션 추상화 클래스의 종류사용방법에 대해 알아볼 것

◼ PlatformTransactionManager

스프링 트랜잭션 추상화의 핵심 인터페이스
- 트랜잭션 경계를 지정하는 데 사용

public interface PlatformTransactionManager {
	TransactionStatus getTransaction(TransactionDefinition definition) throws
    	TransactionException;
    
    void commit(TransactionStatus status) throws TransactionException;
    
    void rollback(TransactionStatus status) throws TransactionException;
}

TransactionDefinition : 트랜잭션의 네 가지 속성을 나타내는 인터페이스
TransactionStatus : 현재 참여하고 있는 트랜잭션의 ID와 구분정보를 담음.

◼ 트랜잭션 매니저의 종류

스프링이 제공하는 PlatformTransactionManager 구현 클래스를 살펴보자.

DataSourceTransactionManager

Connection의 트랜잭션 API를 이용해서 트랜잭션을 관리해주는 트랜잭션 매니저
트랜잭션을 적용할 DataSource가 스프링의 빈으로 등록돼야 한다.

<bean id="memberDao" class="...MemberJdbcDao">
  	<property name="dataSource" ref="dataSource" />
</bean>

<bean id="dataSource" class="org...SimpleDriverDataSource">
  	...
</bean>

<bean id="transactionManager" class=
      "org.springframework.jdbc.dataSource.DataSourceTransactionManager">
  	<property name="dataSource" ref="dataSource" />
</bean>

//모두 동일한 DataSource를 DI해야 한다!

사용할 DataSourcegetConnection()이 호출될 때마다 매번 새로운 Connection을 돌려줘야 한다.

JpaTransactionManager

JPA를 이용하는 DAO에는 JpaTransactionManager를 사용
LocalContainerEntityManagerFactoryBean 타입의 빈을 프로퍼티로 등록해주어야 함

<bean id="emf" class=
      "org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
  	...
</bean>

<bean id="transactionManager"
      class="org.springframework.orm.jpa.JpaTransactionManager">
  	<property name="entityManagerFactory" ref="emf" />
</bean>

HibernateTransactionManager

하이버네이트 DAO에는 HibernateTransactionManager를 사용
SessionFactory 타입의 빈을 프로퍼티로 넣어주면 됨

JmsTransactionManager, CciTransactionManager

스프링은 JMS와 CCI를 위해서도 트랜잭션 매니저를 제공

JtaTransactionManager

하나 이상의 DB 또는 트랜잭션 리소스가 참여하는 글로벌 트랜잭션을 적용하려면 JTA를 이용해야 함.

  • JTA는 여러 개의 트랜잭션 리소스에 대한 작업을 하나의 트랜잭션을 묶을 수 있고, 여러 대의 서버에 분산되어 진행되는 작업을 트랜잭션으로 연결해주기도 한다
  • JTA 트랜잭션을 이용하려면 트랜잭션 서비스를 제공하는 WAS를 이용하거나 독립 JTA 서비스를 제공해주는 프레임워크를 사용

📝 트랜잭션 경계설정 전략

◼ 코드에 의한 트랜잭션 경계설정

스프링의 트랜잭션 매니저는 모두 PlatformTransactionManager를 구현하고 있음
- 트랜잭션 매니저의 종류와 상관없이 동일한 방식으로 트랜잭션을 제어하는 코드를 만들 수 있음
- PlatformTransactionManager의 메소드를 직접 사용하는 대신 템플릿/콜백 방식의 TransactionTemplate을 이용하면 편리

public class MemberService {
	@Autowired private MemberDao memberDao;
    private TransactionTemplate transactionTemplate;
    
    @Autowired
    public void init(PlatformTransactionManager transactionManager) {
    	this.transactionTemplate = new TransactionTemplate(transactionManager);
    }
    
    public void addMembers(final List<Member> members) {
    	this.transactionTemplate.execute(new TransactionCallback {
        	public Object doInTransaction(TransactionStatus status) {
            	for(Member m : members) {
                	memberDao.addMember(m);
                }
            		return null; //리턴되면 트랜잭션 커밋. 예외 발생하면 롤백.
        	}
     	}); 
    }
}

대개는 선언적 트랜잭션 방식으로 충분하기 때문에 잘 사용되지는 않음

◼ 선언적 트랜잭션 경계설정

코드에는 전혀 영향을 주지 않으면서 특정 메소드 전 후에 트랜잭션이 시작되고 종료되거나 기존 트랜잭션에 참여하도록 만들 수 있음

  • 이를 위해 데코레이터 패턴을 적용한 트랜잭션 프록시 빈을 사용해야 한다. (Vol.1)

📝 데이터 액세스 기술 트랜잭션의 통합

애플리케이션의 데이터 액세스 기술과 방식은 한 가지로 통일하는 게 좋다. 하지만 때로는 두가지 이상의 데이터 액세스 기술을 혼합해서 사용해야 할 경우도 존재
- 스프링은 두 개 이상의 데이터 액세스 기술로 만든 DAO를 하나의 트랜잭션으로 묶어서 사용하는 방법 제공

◼ 트랜잭션 매니저별 조합 가능 기술

DataSourceTransactionManager

JDBC와 iBatis 두 가지 기술을 함께 사용할 수 있다
- 항상 동일한 DataSource를 사용해야한다
업로드중..

JpaTransactionManager

스프링에서는 JPA의 EntityManagerFactory가 스프링의 빈으로 등록된 DataSource를 사용가능
- 이 DataSource를 JDBC DAO나 iBatis DAO에서도 사용 가능
=> JpaTransactionManager를 통해 하나의 트랜잭션으로 관리 가능
업로드중..

HibernateTransactionManager

JpaTransactionManager와 동일한 방식을 이용해서 SessionFactory와 같은 DataSource를 공유하는 JDBC, iBatis DAO와 트랜잭션을 공유
업로드중..

JtaTransactionManager

서버를 제공하는 트랜잭션 서비스를 JTA를 통해 이용하는 모든 종류의 데이터 액새스 기술의 DAO가 같은 트랜잭션 안에서 동작하게 만들 수 있음
- 다른 DB를 사용하는 DAO도 하나의 트랜잭션으로 묶어줄 수 있음
하나 이상의 DB 또는 JMS와 같은 트랜잭션이 지원되는 서비스를 통합해서 하나의 트랜잭션으로 관리하려고 할 때는 JTA가 반드시 필요

◼ ORM과 비 ORM DAO를 함께 사용할 때의 주의사항

JPA나 하이버네이트(ORM)는 새로 등록된 오브젝트를 일단 엔티티 매니저나 세션에만 저장해둠
- persist()를 실행해도 DB에는 바로 INSERT문이 전달되지 않음
- 따라서 트랜잭션 종료나 엔티티 반영 전 까지는 JDBC나 iBatis(비 ORM)는 알지 못함
=> 아직 반영되지 않은 엔티티가 존재한다면 관련 테이블을 참조하는 JDBC DAO나 iBatis DAO를 바로 이용하면 안된다.

profile
모르는 것 정리하기

0개의 댓글