2022.01.18

서승원·2022년 1월 18일
0

TIL

목록 보기
56/68

Spring을 통한 sql문 실행 다양한 방법

PreparedStatementCreator
Connection을 매개변수로 받아서 PreparedStatement를 작성해서 return시킨다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public class Test570 {
    public static void main( String[] args ) {
        AbstractApplicationContext spring = 
                new ClassPathXmlApplicationContext("spring.xml");
        
        JdbcTemplate jdbcTemplate = spring.getBean("jtpl" , JdbcTemplate.class );
        
        PreparedStatementCreator psc = new PreparedStatementCreator() {
            @Override
            public PreparedStatement createPreparedStatement(Connection conn) throws SQLException {
                String sql = "INSERT INTO spring_T VALUES(?,?)";
                PreparedStatement stmt = conn.prepareStatement(sql);
                stmt.setInt( 1,  107 );
                stmt.setString( 2,  "PotatoXX" );
                return stmt;
            }             
        };
        
        int uc = jdbcTemplate.update( psc );
        System.out.println( uc );
        
        spring.close();
    }
}
cs
위와 같이 사용한다. 내부의 createPreparedStatement 함수를 오버라이딩 해야하는데, 해당 함수의 내용은 sql문을 작성하고, 입력 변수를 다르게 하기 위한 PreparedStatement를 이용해 set함수로 값을 설정해서 해당 Statement를 Return한다. 그 후 psc를 매개변수로 하는 jdbcTemplate의 update 함수를 호출하여 해당 sql 문을 실행한다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public int update(PreparedStatementCreator psc) throws Exception {
        int uc = 0 ;
        Connection conn = null;
        PreparedStatement stmt = null;
        ResultSet rs = null;
        
        try {
            conn = dataSource.getConnection();
            stmt = psc.createPreparedStatement(conn);
            if( stmt == null ) {
                throw new Exception("invalid Statement");
            }
            uc = stmt.executeUpdate();
            
        }
        catch( Exception e ) { throw e; }
        finally {
            if( rs != null ) rs.close();
            if( stmt != null ) stmt.close();
            if( conn != null ) conn.close();
        }
        return uc;
    }
cs
위의 update 함수를 가상으로 작성해보면 다음과 같다. 역시 update 내부에서 Connection을 할당받은 뒤 createPreparedStatement 를 통해 return 받은 Statement를 이용해서 executeUpdate 함수를 호출하게 된다.

Anonymous Class
위와 같은 코드들을 작성할 때 중요한 것은 어나니머스 클래스의 활용이다. 어떤 상황에 어나니머스 클래스를 사용할지 판단하는게 중요한데, 그것은 재사용의 여부에 가장 많이 달려있다.

1
2
3
4
5
6
7
8
9
10
11
List<SpringVO> lr = jdbcTemplate.query( new PreparedStatementCreator() {
            @Override
            public PreparedStatement createPreparedStatement(Connection conn) throws SQLException {
                String sql = "SELECT * FROM spring_T";
                return conn.prepareStatement( sql );
            } 
        } , new RowMapper_Spring() );
        for( SpringVO t : lr ) { 
            System.out.println( t.getNo() + "\t" + t.getData() );
        }
        spring.close();
cs
1
2
3
4
5
6
7
8
9
public class RowMapper_Spring implements RowMapper<SpringVO>{
 
    @Override
    public SpringVO mapRow(ResultSet rs, int idx) throws SQLException {
        SpringVO vo = new SpringVO();
        vo.setNo( rs.getInt("no") );
        vo.setData( rs.getString("data") );
        return vo;
    }
cs
위의 경우 psc의 경우 sql문을 그때그때 필요할 때마다 작성하여 사용할 것이고, 어떤 sql문을 작성할지, 어떤 변수를 입력할지에 따라 함수의 내용이 변하기 때문에 재사용 가능성이 낮아 어나니머스 클래스로 작성한다. RowMapper_Spring 의 경우는 SpringVo 에 대해서 SELECT 문을 실행하는 경우에는 대부분 사용하게 될 것이다. 이럴 경우는 일반 클래스로 작성하여 재사용 시 효율을 높일 수 있다.

JdbcTemplate - execute
execute 는 SELECT , INSERT , DELETE , UPDATE , PROCEDURE 모두 사용할 수 있어 다양한 응용이 가능하지만 그에 따라 JDBC에 버금가는 예외처리가 필요하게 된다.

SELECT
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
PreparedStatementCallback<Object> psc = new PreparedStatementCallback<Object>() {
            @Override
            public Object doInPreparedStatement(PreparedStatement stmt) 
                    throws SQLException, DataAccessException {
                ResultSet rs = stmt.executeQuery();
                String time = null;
                if( rs.next() ) {
                    time = rs.getString( 1 );
                }
                rs.close();
                return time;
            }
        
        };
        Object t = jdbcTemplate.execute("SELECT NOW()",psc);
        System.out.println( t );
        spring.close();
cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public <X> X execute(String sql, PreparedStatementCallback<X> psc) throws Exception{
        X t = null;
        
        Connection conn = null;
        PreparedStatement stmt = null;
        try {
            conn = dataSource.getConnection();
            stmt = conn.prepareStatement(sql);
            t = psc.doInPreparedStatement(stmt);
            
        }
        catch( Exception e ) { throw e; }
        finally {
            if( stmt != null ) stmt.close();
            if( conn != null ) conn.close();
 
        }
        return t;
    }
cs
execute 를 이해하기 위해 직접 사용해 본 뒤 그 동작에 맞게 가상으로 직접 작성해본 execute 함수다. 제너릭의 사용이 관건인데, 지정 시 클래스 , 인스턴스 , 함수 리턴 타입, 리턴 값을 모두 맞춰줘야 한다. psc 를 선언한 뒤 Object 로 제너릭을 모두 동일하게 해주고, doInPreparedStatement 함수를 오버라이딩 한다. 여기서는 해당 함수 안에서 sql 문을 작성하지 않고, execute 함수의 매개변수로 sql 문을 취하게 된다.
PROCEDURE

SQL에서 새로 정의한 함수와 비슷한 개념으로 , 전에 배웠던 개념이다. 이를 Spring을 통해 실행시키기 위해, 먼저 PuTTy에서 프로시져를 다음과 같이 정의한다.

1
2
3
4
5
6
7
8
9
10
11
delimiter $$
drop procedure if exists p_spring$$
 
create procedure p_spring( in v_no INT , in v_data VARCHAR(10) , out v_rc INT )
begin
    insert into spring_T values( v_no , v_data );
    select count(*INTO v_rc from spring_T;
end;
$$
 
delimiter ;
cs

CallableStatementCallback 과 execute를 사용해서 해당 프로시져를 동작시킬 것이다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
CallableStatementCallback<Integer> csc = new CallableStatementCallback<Integer>() {
            @Override
            public Integer doInCallableStatement(CallableStatement stmt) throws SQLException, DataAccessException 
            {
                stmt.setInt( 1,  300 );
                stmt.setString( 2,  "banana" );
                stmt.registerOutParameter( 3 , Types.INTEGER );
                stmt.execute();
                
                int out = stmt.getInt( 3 );
                return out;
            }
        };
        Integer i = jdbcTemplate.execute("{CALL p_spring(?,?,?)}", csc );
        System.out.println( i );
        spring.close();
cs

p_spring 프로시져에는 인풋 파라미터 2개와 아웃풋 파라미터 1개가 사용되는데 , 그 자료형에 맞도록 execute 의 파라미터에 입력하는 sql문을 {}을 이용해 표시하고 , ? 로 하여, doInCallableStatement 를 오버라이딩하면서 해당 파라미터를 set을 이용해 대입해준다. 아웃풋 파라미터에는 registerOutparameter를 이용해 자료형을 지정해줘야 한다. 그리고 getInt로 해당 값을 return에 쓰기 위해 out 에 대입한다.

Transaction
여러 개의 논리를 묶어서 그 중 하나라도 오류가 발생하면 일괄적으로 Rollback , 모두 통과해야 Commit 시키는 업무논리의 핵심.

먼저 TransactionTemplate(txtpl) , DataSourceTransactionManager (ds_mgr), TransactionCallback (action), DBCP 를 통해 해당 과정을 구현하기 위해서

다음과 같이, DBCP가 각 update 함수 호출을 통해 Connection을 할당하면, 두 함수가 트랜잭션을 보장받기 위해서는 같은 로그를 공유해야 하고 , 그러기 위해서는 같은 Connection을 할당받아야 한다. txtpl이 호출한 execute 함수가 매개변수로 TransactionCallback이 상속받은 클래스의 인스턴스인 action을 취하고 action에서 오버라이딩한 doInTransaction 을 통해 두 함수가 Connection을 할당받아 sql문을 실행한다. 이 때 같은 커넥션을 할당받기 위해서는 ds_mgr이 DBCP에 관여를 하게 된다. 해당하는 Statement가 같은 Connection을 통할 수 있도록 조작하여 같은 로그에 임시적으로 변경 내역을 기록하게 되고, 그를 통해서 트랜잭션을 보장받는다.

1
2
3
4
5
6
7
    <bean id="ds_mgr" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
        <property name="dataSource" ref="ds"/>
    </bean>
 
    <bean id="txtpl" class="org.springframework.transaction.support.TransactionTemplate">
        <property name="transactionManager" ref="ds_mgr"/>
    </bean>
cs
xml 파일에 위와같이 ds_mgr 과 txtpl 의 bean을 입력하고,
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
    JdbcTemplate jdbcTemplate = spring.getBean("jtpl" , JdbcTemplate.class );
        
        TransactionTemplate txtpl = spring.getBean("txtpl", TransactionTemplate.class );
        
        TransactionCallback<String> action = new TransactionCallback<String>( ){
            @Override
            public String doInTransaction(TransactionStatus status) {
                jdbcTemplate.update("INSERT INTO spring_T VALUES ( 600, 'Apple')");
                jdbcTemplate.update("INSERT INTO spring_T VALUES ( 601, 'Apple')");
                status.setRollbackOnly();
                
                return "HelloWorld";
            }
            
        };
        String t = txtpl.execute(action);
        spring.close();
        System.out.println( t );
 
cs
이렇게 함수를 오버라이딩 한다. 여기서 doIntransaction이 status라는 매개변수를 취하는데, status에 아무 조작을 가하지 않으면, 트랜잭션의 정상적인 기능을 한다. setRollbackOnly라는 함수를 호출하게 되면 해당 트랜잭션은 오류가 일어나지 않아도 일괄적으로 동작이 취소된다.
profile
2년차 백엔드 개발자, crimy

0개의 댓글