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 |
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 |
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 |
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 |
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 |
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 |