JdbcTemplate, 트랜잭션

예지성준·2024년 7월 10일

스프링프레임워크

목록 보기
4/14
post-thumbnail

JdbcTemplate

1. 설치 및 설정

의존성 추가!

1) spring-jdbc

implementation 'org.springframework:spring-jdbc:6.1.10'

*JDBC API (SQL응용) 참고
2) tomcat-jdbc

  • 커넥션 풀 (연결 객체 저장소)

    • 미리 연결 객체를 여러개 생성해서 필요할때마다 빌려주고, 회수하는 방식
    • 반응성, 성능 향상의 효과
    • 데이터베이스 커넥션을 미리 생성해 두고, 필요할 때마다 이를 재사용할 수 있도록 함
      • 커넥션(Database Connection)은 애플리케이션과 데이터베이스 관리 시스템(DBMS) 간의 통신 채널을 의미
  • HikariCp

  • javax.sql.DataSource

    • DataSource인터페이스에 커넥션 풀을 제공하는 라이브러리 구현체가 있다.

+ spring-context
+ lombok
+ spring-test

+ ojdbc11
->runtimeOnly로

  • 버전을 동일하게 맞추기 위해 전역 변수 등록
ext{
    springVersion = '6.1.10'
}

...
dependencies {

    implementation "org.springframework:spring-context:$springVersion"
...
    implementation "org.springframework:spring-jdbc:$springVersion"
...
    testImplementation "org.springframework:spring-test:$springVersion"
...
}

2. DataSource 설정

  • javax.sql.DataSource

  • 연결 유효성 체크 관련 메서드 - Tomcat JDBC

Tomcat JDBC의 주요 프로퍼티

  • Tomcat JDBC 모듈의 org.apache.tomcat.jdbc.pool.DataSource클래스는 커넥션 풀 기능을 제공하는 DataSource 구현 클래스이다.
  • DataSource 클래스는 커넥션을 몇 개 만들지 지정할 수 있는 메서드를 제공한다.

스키마 만들고 시작해야쥐이..~

docker exec -it oracle-xe /bin/bash
bash-4.4$ sqlplus system/oracle

...

SQL>CREATE USER SPRING IDENTIFIED BY oracle QUOTA UNLIMITED ON USERS;

SQL> GRANT CONNECT, RESOURCE TO SPRING;

-- 회원테이블 추가
CREATE TABLE MEMBER(
	SEQ NUMBER(11) PRIMARY KEY,
	EMAIL VARCHAR2(60) NOT NULL UNIQUE,
	PASSWORD VARCHAR2(65) NOT NULL,
	USER_NAME VARCHAR2(40) NOT NULL,
	REG_DT DATE DEFAULT SYSDATE
);

--시퀀스
CREATE SEQUENCE SEQ_MEMBER;

Datasource

@Configuration
public class AppCtx {

    @Bean(destroyMethod = "close")
    //스프링 컨테이너가 소멸될때 자원도 같이 해제 된다.
    public DataSource dataSource(){
        DataSource ds = new DataSource();
        /* DB 연결 설정 S*/
        //데이터베이스 드라이브 설정
        ds.setDriverClassName("oracle.jdbc.driver.OracleDriver");
        ds.setUrl("jdbc:oracle:thin:@localhost:1521:XE");
        //스키마 설정
        ds.setUsername("SPRING");
        ds.setPassword("oracle");
        /* DB 연결 설정 E*/

        /* 커넥션 풀 설정 S*/
        ds.setTestWhileIdle(true);//유휴 객체 유효성 체크
        ds.setInitialSize(2);
        ds.setMaxActive(10);//최대 활성화 개수
        ds.setTimeBetweenEvictionRunsMillis(10 * 1000);
        //10초에 한번씩 연결객체가 연결 되어있는지 체크

        ds.setMinEvictableIdleTimeMillis(36 * 1000); //유휴 객체가 살아있는 시간 설정(1분)
        //1분후에 소멸되고 다시 만들어진다.

        /* 커넥션 풀 설정 E*/
        return ds;
    }
}

DB연결설정 테스트

  • 스프링 테스트 모듈을 사용할것임 컨테이너 직접 생성하지 XX
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = AppCtx.class) //설정 클래스가 뭔지 체크하고 의존성을 주입할 수 있게 컨테이너가 생성된다.
public class DBConnectionTest {

    //datasource 주입
    @Autowired
    private DataSource dataSource;

    @Test
    void test1() throws Exception {
        //연결객체
        Connection con = dataSource.getConnection();
        System.out.println(con);
    }
}

  • JdbcTemplate의 생성자에 DataSource를 매개변수로 넣어주는 이유는 JdbcTemplate이 데이터베이스와의 통신을 위해 데이터 소스에서 커넥션을 얻기 때문임

⚡ 1. 데이터베이스 커넥션 획득: JdbcTemplate은 쿼리를 실행하기 위해 데이터베이스 커넥션이 필요하다. DataSource를 통해 JdbcTemplate은 커넥션 풀에서 커넥션을 가져올 수 있다.

⚡ 2. 커넥션 풀 관리: DataSource는 커넥션 풀을 관리한다. 커넥션 풀은 여러 커넥션을 미리 생성해 두고, 애플리케이션이 필요할 때마다 커넥션을 할당한다. 사용이 끝난 커넥션은 다시 풀로 반환되어 재사용된다.

⚡ 3. 안전한 자원 관리: JdbcTemplate은 데이터베이스 작업 후 커넥션을 안전하게 반환한다. JdbcTemplate을 통해 쿼리를 실행할 때, 커넥션을 직접 관리하지 않아도 된다.

3. JdbcTemplate을 이용한 쿼리실행

1) query(): SELECT문

  • List query(String sql, RowMapper rowMapper)
@Test
    void test2(){
        List<Member> members = jdbcTemplate.query("select * from MEMBER", new RowMapper<Member>() {//slq문으로 조회된 결과를 넘겨주면 Member쪽으로 값을 바꿔준다.
            @Override
            public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
                return Member.builder()
                        .seq(rs.getInt("SEQ"))
                        .email(rs.getString("EMAIL"))
                        .password(rs.getString("PASSWORD"))
                        .userName(rs.getString("USER_NAME"))
                        .regDt(rs.getTimestamp("REG_DT").toLocalDateTime())
                        .build();
            }
        });
        members.forEach(System.out::println);
    }

람다식 형태로🔽🔽🔽

 @Test
    void test2(){
        List<Member> members = jdbcTemplate.query("select * from MEMBER", (rs, num) -> Member.builder()
                .seq(rs.getInt("SEQ"))
                .email(rs.getString("EMAIL"))
                .password(rs.getString("PASSWORD"))
                .userName(rs.getString("USER_NAME"))
                .regDt(rs.getTimestamp("REG_DT").toLocalDateTime())
                .build());
        
        members.forEach(System.out::println);
    }

test2실행_ 조회

  • List query(String sql, Object[] args, RowMapper rowMapper)

  • List query(String sql, RowMapper rowMapper, Object... args)

2) queryForObject()

  • 단일 데이터 조회
    - 조회된 데이터가 반드시 1개여야 함, 그렇지 않으면 예외가 발생

반복되는 builder코드 따로 정의해서 쓰자

 private Member mapper(ResultSet rs, int num) throws SQLException{
        return Member.builder()
                .seq(rs.getInt("SEQ"))
                .email(rs.getString("EMAIL"))
                .password(rs.getString("PASSWORD"))
                .userName(rs.getString("USER_NAME"))
                .regDt(rs.getTimestamp("REG_DT").toLocalDateTime())
                .build();
    }

예외처리 넣어주는게 좋음

    @Test
    void test3(){
        String email = "user08@test.org";
        try{
            Member member = jdbcTemplate.queryForObject("SELECT * FROM MEMBER WHERE EMAIL = ?", this::mapper, email);

            System.out.println(member);
        }catch (Exception e){
            System.out.println("없음");
        }
    }

🔽쿼리 개수 출력

3) update()

데이터 변경을 가하는 SQL
-> INSERT, DELETE, UPDATE 반환값 ->
반영된 레코드 수

  • int update(String sql)

  • int update(String sql, Object... args)

    • PreparedStatement 방식으로 쿼리 작성
      값 바인딩 ?, ?
@ExtendWith(SpringExtension.class)
@ContextConfiguration(classes = AppCtx.class)
public class Ex01 {
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Test
    void test() {
        String sql = "INSERT INTO MEMBER (SEQ, EMAIL, PASSWORD, USER_NAME) VALUES (SEQ_MEMBER.NEXTVAL, ?, ?, ?)";
    }

}

db에 데이터도 잘 들어와있다.

로거
slf4j-api
logback classic 구현체
implementation 'org.slf4j:slf4j-api:2.0.13'
testImplementation 'ch.qos.logback:logback-classic:1.5.6'

#src/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>

더 자세히 보려면 level속성 바꾸면 된다.

4. PreparedStatementCreator를 이용한 쿼리 실행

  • Connection 객체 매개변수로 정의되어 있는 메서드를 통해서 사용 가능

현재 Member테이블에 3명의 사용자가 저장되어있는 상태이다.

PreparedStatementCreator로 사용자 추가

👩‍🏫람다식을 사용해서 짧게쓰기!

  • 증감번호 형태의 PK를 조회

java.sql.Connection 인터페이스

preparedStatement -> 자동 생성된 키값을 가져오려고 쓴다

🔼Number클래스🔼

5. INSERT 쿼리 실행 시 KeyHolder를 이용해서 자동 생성 키값 구하기

증감번호 형태의 PK조회 코드

@Test
    void test1() {
    
        KeyHolder keyHolder = new GeneratedKeyHolder();
        
        int result = jdbcTemplate.update(con -> {
            String sql = "INSERT INTO MEMBER (SEQ, EMAIL, PASSWORD, USER_NAME)" + " VALUES (SEQ_MEMBER.NEXTVAL,?,?,?)";

            PreparedStatement pstmt = con.prepareStatement(sql, new String[] {"SEQ"});
            //Column name
            pstmt.setString(1, "user05@test.org");
            pstmt.setString(2, "12345678");
            pstmt.setString(3, "사용자05");
            return pstmt;
        },keyHolder); //세번째 매개변수 key holeder에 발생한 자동 증감번호를 넣어준다
        System.out.println(result);
       Number key =  keyHolder.getKey();//증감번호 가져오기 , 반환값이 Number인 이유? -> Number에는 변환 메서드가 있다 int, long 어떤걸 쓸지는 개발자만 아니까.. 숫자 관련 상위 추상 클래스

        long seq = key.longValue(); //자동 증감된 키값을 가져올 수 있게되었다.
        System.out.println(seq);
    }

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

  • 각 연동 기술에 따라 발생하는 익셉션을 스프링이 제공하는 익셉션으로 변환함으로써 다음과 같이 구현 기술에 상관없이 동일한 코드로 익셉션을 처리할 수 있게 된다.

SQLExcpetion, HibernateException, PersistenceException -> DataAccessException(RuntimeException)

7. 트랜잭션 처리 - 수동관리

  • @Transactional

    • 클래스명 위, 메서드명 위 선언 가능
    • 위 애노테이션만 넣으면 자동으로 관리를 할 수 있다.
    • @Transactional //테스트할땐 자동으로 롤백해줌 - DB에 저장 X

    세팅 - Appctx
    자동화 -> @EnableTransactionManagement

수동 관리할때는 autoCommit을 false로 하고 마지막에 commit을 해줘야한다.

Connection
	setAutoCommit(false) //공통기능

/*핵심기능*/
SQL문1... 
SQL문2...
SQL문3...
/*핵심기능*/

Connection
	commit(); //공통기능
profile
꽁꽁 얼어붙은 한강 위로 😺

0개의 댓글