JDBC 프로그래밍 단점 보안하는 스프링
//반복영역
Member member;
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
ResultSet rs = null;
try
{
conn = DriverManager.getConnection("jdbc:mysql://localhost/spring5fs","DBMS 아이디","DBMS 비밀번호");
/*--------------------------------------------------------------------------------------------------------------------------*/
//핵심영역
pstmt = conn.prepareStatement("select * from MEMBER where Email = ?");
pstmt.setString(1,email);
rs = pstmt.executeQuery();
if(rs.next())
{
member = new Member(rs.getString("EMAIL")),
rs.getString("PASSWORD"),
rs.getString("NAME"),
rs.getTimestamp("REGDATE");
member.setId(rs.getLong("ID"));
return member;
}else{
return null;
}
/*--------------------------------------------------------------------------------------------------------------------------*/
//반복영역
}catch(SQLException e){
e.printStackTrace();
throw e;
}finally{
if(rs != null)
try{rs.close();} catch (SQLException e2)
if(pstmt != null)
try{pstmt.close();} catch (SQLException e1)
if(conn != null)
try{conn.close();} catch (SQLException e)
}
JDBC API는 DB연동에 필요한 Connection을 구한 후 쿼리 실행을 위한 PreparedStatement를 생성한다. finally에선 Resultset, PreparedStatement, Connection을 닫는다.
점선 위아래 코드는 데이터 처리에는 상관없지만 JDBC 프로그래밍을 할 때 구조적으로 반복된다.
구조적 반복을 줄이기 위해 템플릿 메서드 패턴과 전략 패턴을 함께 사용한다. (Around 방식과 유사하다.)
스프링에서 위 두 패턴을 엮은 JdbcTemplate 클래스를 제공한다. 이 클래스를 사용하면 다음과 같이 변경 할 수 있다.
List<Member> results = jdbcTemplate.query(
"select * from MEMBER where EMAIL = ?"
new RowMapper<Member>(){
@Override
public Member mapRow(ResultSet rs, int rowNum) throws SQLException{
Member member = new Member(rs.getString("EMAIL"),
rs.getString("PASSWORD"),
rs.getString("NAME"),
rs.getTimestamp("REGDATE"));
member.setId(rs.getLong("ID"));
return member;
}
},
email);
return results.isEmpty() ? null : results.get(0);
스프링으로 처리시 트랜잭션(일괄처리) 관리가 쉽다. Connection의 setAutoCommit(false)을 이용하여 자동커밋을 비활성화 하고, commit()과 rollback() 메서드를 이용해서 트랜잭션 커밋 또는 롤백한다.
스프링에서 트랜젝션을 적용하는 방법은 적용할 메서드에 @Transactional 어노테이션을 붙인다.
@Transactional
public void insert(Member member)
{
...
}
커밋과 롤백은 스프링이 처리하므로 핵심코드만 집중하여 작성하면 된다.
커넥션풀 : 일정 개수 DB커넥션을 미리 만들어 두어 DBMS 부하를 줄인다. DB 커넥션이 필요한 프로그램은 커넥션 풀에서 커넥션을 사용한 후 반납(close) 한다.
DB 커넥션 제공 모듈은 Tomcat JDBC, HikariCP, DBCP, c3p0 등이 존재한다.
스프링이 제공하는 DB 연동 기능은 Datasource를 사용하여 DB Connection을 구한다. DB 연동에 사용할 DataSource를 스프링 빈으로 등록하고 DB 연동 기능을 구현할 빈 객체는 DataSource를 주입받아 사용한다.
public interface RowMapper<T>
{
T mapRow(ResultSet rs, int rowNum) throws SQLException;// <T> : 특정 데이터 타입 지정하지 않음
}
RowMapper의 mapRow() 메서드는 SQL 실행 결과로 구한 ResultSet에서 한 행의 데이터를 읽어와 자바 객체로 변환하는 메퍼 기능을 구현한다.
RowMapper는 ResultSet 에서 데이터를 읽어와 Member 객체를 변환해주는 기능을 제공한다.
동일한 RowMapper 구현하여 여러 곳에서 사용시 구현한 클래스를 생성하여 코드 중복을 막는다.
결과가 1행인 경우 사용할 수 있는 queryForObject() 메서드
public int count() {
Integer count = jdbcTemplate.queryForObject(
"select count(*) from MEMBER", Integer.class);//Integer 객체 반환
return count;
}
count (*) 쿼리는 결과가 한 행 뿐이라 Integer와 같은 정수타입으로 받으면 편리하다.
queryForObject() 메서드를 사용하려면 쿼리 실행 결과가 반드시 한 행이여야 한다.
INSERT 쿼리 실행 시 KeyHolder를 이용해서 자동 생성 키값 구하기
update() 메서드는 변경된 행의 개수를 리턴할 뿐 생성된 키값을 리턴하지 않는다.
JdbcTemplate은 자동으로 생성된 키값을 구하는 KeyHolder를 사용하는 방법을 제공한다. String 배열 {"ID"}와 같이 생성하고 자동 생성 되는 키 칼럼 목록을 지정할 때 사용한다.
DB 연동 예외처리
Access denied for user '유저아이디'@'localhost' ~~~~
- DB 연결 정보가 올바르지 않다. (아이디나 비밀번호가 틀리거나 DB접속 권한이 없다)
CannotGetJdbcConnectionException ~~~ Communications link failure
- 방화벽에 막혀 있어서 DB 연결 자체가 불가능하다.
- DBMS를 잘못 설치했거나, 버전이 맞지 않는다.
BadSqlGrammarException ~~~ MySQLSyntaxErrorException
- 잘못된 쿼리를 사용했다. (휴먼에러)
스프링의 예외 변환 처리
JdbcTemplate은 예외처리를 DataAccessException을 상속받은 BadSqlGrammarException으로 변환한다. DataAccessException은 스프링이 제공한다.
스프링의 연동 기능을 사용하면 동일한 방식으로 예외처리 할 수 있다.
DuplicateKeyException(기본키 중복 - 유일성 위배), QueryTimeException(처리시간지연) 등 DataAccessException을 상속한 다양한 예외처리 클래스를 제공한다.
트랜잭션 처리
트랜잭션은 두 개 이상의 쿼리를 한 작업으로 실행해야 될 때 사용한다. 즉, 여러 쿼리를 논리적으로 하나의 작업으로 묶어준다.
롤백(rollback)은 쿼리 실행 결과를 취소하고 DB를 기존 상태로 되돌리는 것이다.
커밋(commit)은 트랜잭션으로 묶인 모든 쿼리가 성공해서 쿼리 결과를 DB에 반영하는 것이다.
JDBC는 Connection의 setAutoCommit(false)를 통해 트랜잭션을 시작하고 커밋과 롤백을 이용하여 트랜잭션을 관리한다.
스프링이 제공하는 @Transactional 애노테이션을 트랜잭션 범위에서 실행하고 싶은 메서드에 붙이면 트랜잭션 범위를 지정할 수 있다. 메서드 안의 2개 이상의 쿼리는 한 트랜젝션으로 묶인다.
@Transactional 애노테이션이 동작하기 위해 두개 내용을 스프링에 추가한다.
- 플랫폼 트랜젝션 메니저 빈 설정
- @Transaction 애노테이션 활성화 설정
@Configuration
@EnableTransactionManagement
public class AppCtx {
@Bean(destroyMethod = "close")
public DataSource dataSource() {
DataSource ds = new DataSource();//PlatformTransactionManager에서 사용할 DataSource 생성
ds.setDriverClassName("com.mysql.jdbc.Driver");
.
.
.
return ds;
}
@Bean
public PlatformTransactionManager transactionManager() {
DataSourceTransactionManager tm = new DataSourceTransactionManager();
tm.setDataSource(dataSource());//PlatformTransactionManager에서 생성된 dataSource 사용
return tm;
}
PlatformTransactionManager는 스프링이 제공하는 트랜젝션 매니저 인터페이스이다.
dataSource 프로퍼티를 이용해서 트랜잭션 연동에 사용할 DataSource를 지정한다.
Logback을 이용하면 트랜잭션 관련 로그 메시지를 출력할 수 있다.
다음 두 의존을 추가한다.
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-api</artifactId>
<version>1.7.25</version>
</dependency>
<dependency>
<groupId>ch.qos.logback</groupId>
<artifactId>logback-classic</artifactId>
<version>1.2.3</version>
</dependency>
프로젝트를 업데이트하고 main/resources/logback.xml 에 다음과 같이 설정한다.
<?xml version="1.0" encoding="UTF-8"?>
<configuration>
<appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d %5p %c{2} - %m%n</pattern>
</encoder>
</appender>
<root level="INFO">
<appender-ref ref="stdout" />
</root>
<logger name="org.springframework.jdbc" level="DEBUG" />
</configuration>
디버그 레벨을 상세하게 출력 할 수 있다.
여러개의 빈 객체에 공통으로 적용되는 기능을 구현하는 방법인 AOP의 기능 중 트랜잭션이 공통기능 중 하나로포함된다. 스프링은 @Transactional 애노테이션을 이용하여 트랜잭션을 처리하기 위한 내부 AOP를 사용한다. @EnableTransactionManagement 태그 사용 시 @Transactional 애노테이션 적용 빈 객체를 찾아 프록시 객체를 생성한다.
@Transaction의 rollbackFor 속성을 사용하면 트랜잭션을 롤백 할 수 있다.
@Transactional(rollbackFor = SQLExeeption.class)
public void someMethod()
{
...
}
@Transactional 애노테이션의 주요속성은 다음과 같다.
Propagation 열거 타입의 대표적인 예시 3가지는 다음과 같다.
Isolation 열거 타입은 다음과 같다. (초급 개발자는 설정하지 않는 것을 추천)
@EnableTransactionManagement 애노테이션 주요속성
proxyTargetClass
- 클래스를 이용하여 프로시 생성여부 지정. 기본값은 false로 인터페이스를 이용하여 프록시를 생성한다.
order
- aop 적용순서 지정. 기본값은 가장 낮은 우선순위에 해당하는 int 최댓값이다.
트랜잭션 전파
자바코드와 스프링 설정으로 트랙잭션 전파를 설명하겠다.
//java
public class SomeService{
private AnyService anyService;
@Transactional
public void some()
{
anyService.any();
}
public void setAnyService(AnyService as)
{
this.anyService = as;
}
public class AnyService
{
@Transactional
public void any()
{
...
}
}
}
//spring
@Configuration
@EnableTransactionManagement
public class Config{
@Bean
public SomeService some()
{
SomeService some = new SomeService();
some.setAnyService(any());
return some;
}
@Bean
public AnyService any()
{
return new AnyService();
}
//DataSourceTransactionManager 빈 설정
//DataSource 설정
}
SomeService, AnyService 클래스 둘 다 @Transactional 애노테이션을 적용한다. 두 클래스에 대해 프록시가 생성된다. 즉 두 클래스 모두 트랜잭션이 시작되고, some() 메서드 내부에서 다시 any() 메서드를 호출한다. 이때 some() 메서드와 any() 메서드를 한 트랜젝션으로 묶어서 실행한다.
'Java > JavaSpring' 카테고리의 다른 글
Chap 10. 스프링 MVC 프레임워크 동작 방식 (0) | 2022.07.06 |
---|---|
Chap 9. 스프링 MVC 시작하기 (0) | 2022.07.01 |
Chap 7. AOP 프로그래밍 (0) | 2022.06.27 |
Chap 6. 빈 라이프사이클과 범위 (0) | 2022.06.24 |
Chap 5. 컴포넌트 스캔 (0) | 2022.06.20 |