computer_study

[Spring] 08. DB연동 본문

스터디/스프링5 프로그래밍 입문

[Spring] 08. DB연동

knowable 2022. 4. 24. 03:39

목차

1. JDBC 프로그래밍의 단점을 보완하는 스프링

2. 프로젝트 준비

3. DataSource 설정

4. JdbcTemplate을 이용한 쿼리 실행

5. MemberDao 테스트하기

6. 스프링의 익셉션 변환 처리

7. 트랜잭션 처리

8. 전체 기능 연동한 코드 실행

1. JDBC 프로그래밍의 단점을 보완하는 스프링

JDBC

  • 관계형 데이터베이스에 저장된 데이터를 접근 및 조작할 수 있게 하는 자바 API.
  • 데이터베이스 사용 시, 데이트베이스 종류에 상관없이 JDBC API를 사용하여 데이터베이스에 접근 가능

JDBC 프로그래밍의 단점

  • 반복되는 코드가 존재한다

스프링을 사용 시

  • 스프링은 템플릿 메서드 패턴과 전략 패턴을 함께 사용하는 클래스를 제공한다. (JdbcTemplate 클래스)
  • 트랜잭션 관리가 쉽다 (트랜잭션을 적용하고 싶은 메서드에 @Transactional 애노테이션을 붙이기만 하면 된다.)

 

2. 프로젝트 준비

3. DataSource 설정

DataSource란?

  • DB와 관계된 커넥션 정보를 담고있으며 빈으로 등록하여 인자로 넘겨준다
  • datasource는 생성자나 설정 메서드를 이용해서 주입받는다
  • Tomcat JDBC 모듈은 DataSource클래스를 제공한다.
    • Tomcat JDBC 주요 프로퍼티
      • setInitialSize(int)
      • setMaxASctive(int)
      • setMaxIdle(int)
      • setMinIdle(int)
      • setMaxWait(int)
      • setMaxAge(logn)
      • setValidationQuery(String)
      • setValidationQueryTimeout(int)
      • setTestOnBorrow(boolean)
      • setTestOnRetrun(boolean)
      • setTestWhileIdle(boolean)
      • setMionEvictableIdleTimeMillis(int)
      • setTimeBewtweenEvictionRunsMillis(int)
  • connection 과정
    • 커넥션 풀은 커넥션을 생성하고 유지
    • 커넥션 풀에 커넥션을 요청하면 커넥션이 active상태가 됨
    • 커넥션을 다시 반환하면 커넥션은 idle상태가 됨 (close 시)
    • 성능을 위해 커넥션 풀을 사용한다.(미리 생성했다 필요할 때 마다 꺼내서 사용할 수 있음)
    • DBMS설정에 딸라 일정 시간 내에 쿼리를 실행하지 않으면 연결이 끊기기도 한다

 

4. JdbcTemplate을 이용한 쿼리 실행

스프링 사용 시 DataSource, Connection, Statement, ResultSet 직접 사용 없이 JdbcTemplate을 사용하여 쿼리를 실행할 수 있다

 

객체 생성

package spring;

import java.sql.DataSource;

import org.springframework.jdbc.core.JdbcTemplate;

public class MemberDao {
    private JdbcTemplate jdbcTemplate;
    
    public MemberDao(DataSource datasource){
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }
}

///////// 이후 Bean으로 등록하여 사용 ////////

@Bean
public MemberDao memberDao(){
    return new MemberDao(datasource());
}

 

JdbcTemplate를 이용한 조회 쿼리

  • List<T> query(String sql, RowMapper<T> rowMapper)
  • List<T> query(String sql, Object[] args, RowMapper<T> rowMapper)
  • List<T> query(String sql, RowMapper<T> rowMapper, Object... args)
    • RowMapper
      • ResultSet결과를 자바 객체로 변환해준다
      • 자주 사용된다면, 인터페이스를 구현한 클래스를 만들어서 코드 중복을 막을 수 있다.
    • args
      • 인덱스 기반 파라미터를 가진 쿼리면 args를 이용해서 파라미터를 지정한다.
//RowMapper를 구현한 클래스 작성
public class MemberRowMapper implements RowMapper<Member>{
    @Override
    public Member mapRow(ResultSet rs, int row Num) throws SQLException{
        Member member = new Member(
            rs.getString("EMAIL"),
            rs.getString("PASSWORD"),
            rs.getString("NAME"),
            rs.getString("REGDATE").toLocalDateTime());
        member.setId(rs.getLong("ID"));
        return member;
    }
}

// MemberRowMapper 객체 생성 후 사용
public Member selectByEmail(String email){
    List<Member> results = jdbcTemplate.query(
        "select * from MEMBER where EMAIL = ?",
        new MemberRowMapper(),
        email);
    
    return results.isEmplty() ? null : results.get(0);
}

public Collection<Member> selectAll(){
    List<Member> results = jdbcTemplate.query(
        "select * from MEMBER",
        new MemberRowMapper());
    return results;
}

 

queryForObject()

  • 결과가 1행인 경우 사용
  • 결과가 1행이 아니면 Exception이 발생하기에 확실하게 한 개가 아니라면 query()메서드를 사용하는 것이 핸들링 하기 좋다.
  • List로 받고 후처리해주기 싫을 때, List를 반환하지 않는 함수의 return값으로 줄 때 등에 사용한다.(참고자료)
/// 예시1
Integer count = jdbcTemplate.queryForObject(
    "select count(*) from MEMBER", Integer.class);


/// 예시2 (double 타입)
double avg = queryForObject(
    "select avg(height) from FURNITURE where TYPE=? and STATUS=?",
    Double.class,
    100, "S");

 

변경 쿼리 실행

  • INSERT, UPDATE, DELETE 시 사용
  • update() 메서드를 이용한다
  • update()메서드는 변경된 행의 개수를 리턴한다.
jdbc.Template.update(
    "update MEMBER set NAME = ?, PASSWORD = ? where EMAIL = ?",
    member.getName(), member.getPasword(), member.getEmail());

 

PreparedStatementCreator

jdbcTemplate.update(
  new PreparedStatementCreator() {
    public PreparedStatement createPreparedStatement(Connection connection) throws SQLException {
      PreparedStatement ps = connection.prepareStatement(INSERT_SQL, new String[] {"id"});
      ps.setString(1, name);
      return ps;
    }
  },
  keyHolder);

 

 

INSERT시 KeyHolder를 이용하여 자동 생성 키값 구하기

  • AUTO_INCREMENT 등으로 자동 생성된 키값을 쿼리 실행 후 구할 수 있다
keyHolder keyHolder = new GenerteKeyHelder();
jectTemplate.update(new PreparedStatementCreator(){...}, keyHolder);
// PreparedStatement 실행 후 자동생선된 키값을 keyHolder에 보관한다

Number keyValue = keyHolder.getKey(); // 키값 가져오기
member.setId(KeyValue.longValue()); // 키값 long타입으로 변환 (다른 타입도 가능)

 

5. MemberDao 테스트하기

6. 스프링의 익셉션 변환 처리

  • 스프링은 JDBC, JPA, 하이버네이트 등과 연동 시, 서로 다른 익셉션 처리 코드를 하나로 변환해준다.
  • 개발자는 구현 기술에 상관 없이 동일한 코드로 익셉션을 처리할 수 있게된다.

 

7. 트랜잭션 처리

  • 두 개 이상의 쿼리를 한 작업으로 실행해야될 때 사용한다.(여러 쿼리를 논리적으로 하나로 묶어서)
  • 직접 commit(), rollback()하는 것 보다 스프링의 트랜잭션 기능을 사용하는 것이 간단하다.

 @Transactional

  • 트랜잭션 범위에서 실행하고 싶은 메서드에 @Transactional 애노테이션만 붙이면 된다.
  • 플랫폼 트랜잭션 매니저 빈을 설정해야 한다.
  • @Transactional 애노테이션 활성화 설정을 해야한다 (@EnableTransactionManagement 애노테이션)
    • 활성화하면 등록된 platformTransactionManager 빈을 사용해서 트랜잭션을 적용한다
@Transactional
public void changePassword(String email, String oldPwd, String newPwd){
    Member member = memberDao.selectByEmail(email);
    if(member == null)
        throw new MemberNotFoundException();
    member.changePassword(oldPwd, newPwd);
    
    memberDao.update(member);
}

 

 @Transactional과 프록시

  • @Transactional 애노테이션을 적용하기 위해 @EnableTransaction Management 태그를 사용하면, 스프링은 @Transactional 애노테이션이 적용된 빈 객체를 찾아서 알맞은 프록시 객체를 생성한다.

 @Transactional 적용 메서드의 롤백 처리

  • 롤백 처리도 프록시가 진행한다.
  • 별도 설정이 없다면 RuntimeException 발생 시 롤백을 진행한다.
    • DataAccessException은 RuntimeException을 상속받고있기에, 익셉션 발생 시 롤백한다
    • SQLException은 상속하지 않고있기에 롤백하지 않는다
    • 롤백을 원한다면 @Transactional의 rollbackFor속성을 사용할 수 있다.
@Transactional(rollbackFor = SQLException.clas)
public void someMethod(){
   ...
}

// 원하지 않는다면 noRoolbackFor 속성을 사용 할 수도 있다.

 

@Transactional의 주요 속성

그냥 이런게 있다정도만 알아도 된다

속성 타입 설명
value String 트랜잭션을 관리할 때 사용할 PlatformTransaction Manager빈의 이름을 지정한다.
default : " "
propagation Propagation 트랜잭션 전파 타입을 지정한다.
default : Propagation.REQUIRED
isolation Isolation 트랜잭션 격리 레벨을 지정한다.
default : Isolation.DEFAULT
timeout int 트랜잭션 제한 시간을 지정
default : -1 (데이터베이스의 타임아웃 시간을 사용)

 

@EnableTransactionManagement 주요 속성

속성 설명
proxyTargetClass 클래스를 이용해서 프록시를 생성할지 여부를 지정
deafult : false (인터페이스를 이용해 프록시 생성)
order AOP 적용 순서를 지정
default : int 최대값 (가장 낮은 우선순위)

 

트랜잭션 전파

A class : @Transactional 애노테이션 적용

B class : @Transactional 애노테이션 적용

C class : @Transactional 애노테이션 적용 (Propagation.REQUIRES_NEW)

D class : @Transactional 애노테이션 적용안함

  • A안에서 B 호출 시 -> A,B 호출하는 과정까지 묶어서 하나의 트랜잭션으로 초리
  • A안에서 C 호출 시 -> C가 REQUIRES_NEW이므로 항상 새로운 트랜잭션 시작
  • A안에서 D 호출 시 -> D는 애노테이션 적용이 안되어있지만, 같은 트랜잭션으로 묶여 처리된다

8. 전체 기능 연동한 코드 실행

 

 

 

Comments