티스토리 뷰

프로그래밍/Web

Spring JDBC

국윤창 2018. 7. 19. 17:11

Spring JDBC를 사용하지 않고 DB를 사용하기 위해선, Driver를 로드하고 connection을 맺고 statement 준비를 하고 query를 실행하기까지 여러 코드를 적어야했다. 이런 저수준의 작업들은 Spring JDBC를 이용하여 간단하게 처리할 수 있다.

Spring JDBC 패키지

  • org.springframework.jdbc.core
  • org.springframework.jdbc.datasource
  • org.springframework.jdbc.object
  • org.springframework.jdbc.support


위와 같은 패키지를 사용하고, 이 중 core에는 JDBC Template이라는 중요한 클래스가 있다.

JDBC Template

  • org.springframework.jdbc.core에서 가장 중요한 클래스입니다.
  • 리소스 생성, 해지를 처리해서 연결을 닫는 것을 잊어 발생하는 문제 등을 피할 수 있도록 합니다.
  • 스테이먼트(Statement)의 생성과 실행을 처리합니다.
  • SQL 조회, 업데이트, 저장 프로시저 호출, ResultSet 반복호출 등을 실행합니다.
  • JDBC 예외가 발생할 경우 org.springframework.dao패키지에 정의되어 있는 일반적인 예외로 변환시킵니다.


JDBC Template은 위와 같은 처리를 하여 DB에 쉽게 연결하여 사용할 수 있도록 해준다.

RowMapper

query 결과를 원하는 객체에 매핑하도록 도와주는 클래스이다. 

Actor actor = this.jdbcTemplate.queryForObject(
	"select first_name, last_name from t_actor where id = ?",
	new Object[]{1212L},
	new RowMapper() {
		public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {
			Actor actor = new Actor();
			actor.setFirstName(rs.getString("first_name"));
			actor.setLastName(rs.getString("last_name"));
			return actor;
		}
 	}
);

위 코드처럼 RowMapper의 mapRow 함수를 재정의하여 원하는 객체에 query 결과를 매핑하여 반환하도록 할 수 있다.

JDBC Config

이전 글에서 Bean을 등록하기 위해서 Config 클래스를 만들어서 사용했는데, 데이터베이스도 설정을 Connection Pool에 접근하는 DataSource를 설정해야 Spring JDBC를 사용할 수 있다.


이를 위해서 아래와 같이 기존 ApplicationConfig에 @Import 어노테이션을 추가한다.

@Configuration
@ComponentScan(basePackages = { "com.example.daoexam.dao" })
@Import({DBConfig.class})
public class ApplicationConfig {

}

@Import 어노테이션에 중괄호로 DBConfig 클래스를 넣어준 것을 볼 수 있다. ApplicationConfig를 로드할 때 DBConfig라는 Configuration 클래스를 같이 로드하라는 의미이다. 또한, ComponentScan이나 Import 어노테이션처럼 중괄호를 사용하면 여러개의 매개변수를 넣을 수 있게된다.


아래는 DBConfig 클래스이다. 마찬가지로 @Configuration 어노테이션을 적는다. DataSource를 Bean으로 등록하는 것을 볼 수 있는데, 나중에 이를 이용해서 DB에 접속할 것이다.

@Configuration
@EnableTransactionManagement
public class DBConfig {
	private String driverClassName = "com.mysql.jdbc.Driver";
	private String url = "jdbc:mysql://localhost:3306/connectdb?useUnicode=true&characterEncoding=utf8";
	private String username = "connectuser";
	private String password = "connect123!@#";
    
	@Bean
	public DataSource dataSource() {
		BasicDataSource dataSource = new BasicDataSource();
		dataSource.setDriverClassName(driverClassName);
		dataSource.setUrl(url);
		dataSource.setUsername(username);
		dataSource.setPassword(password);
		return dataSource;
	}
}

DAO

DAO는 @Repository라는 어노테이션을 적어 Bean으로 등록한다. Repository는 stereo type의 일종으로 Bean을 좀 더 목적에 맞도록 사용할 수 있게 해준다. @Repository를 적어놓으면 Classpath Scanning 때 Bean Container에 등록된다.

아래 코드처럼 DAO를 만들 수 있다.

@Repository
public class RoleDao {
	private NamedParameterJdbcTemplate jdbc;
	private SimpleJdbcInsert insertAction;
	private RowMapper<Role> rowMapper = BeanPropertyRowMapper.newInstance(Role.class);

	public RoleDao(DataSource dataSource) {
		this.jdbc = new NamedParameterJdbcTemplate(dataSource);
		this.insertAction = new SimpleJdbcInsert(dataSource).withTableName("role");
	}
	
	public List<Role> selectAll(){
		return jdbc.query(SELECT_ALL, Collections.emptyMap(), rowMapper);
	}
}

NamedParameterJdbcTemplate 클래스는 query에서 매핑되는 변수를 '?' 대신 이름으로 매핑될 수 있도록 도와주는 클래스이다. 

또한 SimpleJdbcInsert 클래스는 insert를 좀 더 쉽게 하도록 도와주는 클래스이다. dataSource로 DB에 접근해 role이라는 이름의 테이블에 insert를 할 것이라고 명시한다.

BeanPropertyRowMapper는 RowMapper를 직접 재정의 할 필요없이, 클래스만 집어넣으면 알아서 그에 맞는 RowMapper를 만들어준다.


그 후 selectAll 함수를 호출하면, SELECT_ALL 이라는 상수에 정의된 query에 따라 실행하고, 결과를 rowMapper에 등록된 클래스 형태로 반환해준다.

일반적인 방법

insert query를 수행할 때 SimpleJdbcInsert 클래스를 쓰는 것말고, query를 수행하는 일반적인 방법이 있다. NamedParameterJdbcTemplate 클래스의 update 함수나 query 함수를 이용하는 것이다.

update 함수는 query를 저장한 String 객체와 parameter를 저장한 SqlParameterSource 객체 또는 Map을 받는다.


아래와 같이 사용할 수 있다.

@Repository
public class RoleDao {
	public static final String SELECT_ALL = "SELECT role_id, description FROM role ORDER BY role_id";
	public static final String UPDATE = "UPDATE role set description = :description WHERE role_id = :roleId";
	public static final String SELECT_BY_ROLE_ID = "SELECT role_id, description FROM role WHERE role_id = :roleId";
	public static final String DELETE_BY_ROLE_ID = "DELETE FROM role WHERE role_id = :roleId";

	private NamedParameterJdbcTemplate jdbc;
	private SimpleJdbcInsert insertAction;
	private RowMapper<Role> rowMapper = BeanPropertyRowMapper.newInstance(Role.class);

	public RoleDao(DataSource dataSource) {
		this.jdbc = new NamedParameterJdbcTemplate(dataSource);
		this.insertAction = new SimpleJdbcInsert(dataSource).withTableName("role");
	}

	public List<Role> selectAll() {
		return jdbc.query(SELECT_ALL, Collections.emptyMap(), rowMapper);
	}

	public int insert(Role role) {
		SqlParameterSource params = new BeanPropertySqlParameterSource(role);
		return insertAction.execute(params);
	}

	public int update(Role role) {
		SqlParameterSource params = new BeanPropertySqlParameterSource(role);
		return jdbc.update(UPDATE, params);
	}
	
	public int deleteById(Integer id) {
		Map<String, ?> params = Collections.singletonMap("roleId", id);
		return jdbc.update(DELETE_BY_ROLE_ID, params);
	}
	
	public Role selectById(Integer id) {
		try {
			Map<String, ?> params = Collections.singletonMap("roleId", id);
			return jdbc.queryForObject(SELECT_BY_ROLE_ID, params, rowMapper);
		} catch(EmptyResultDataAccessException e) {
			return null;
		}
	}
}

위 DAO 클래스의 insert, update 함수를 보면 BeanPropertySqlParameterSource 클래스를 이용하여 params를 생성하고 update 함수에 매개변수로 집어 넣는다. 이렇게 되면, query에서 변수와 Role이라는 클래스의 필드 이름과 매칭하여 query에 입력되고 실행된다. query에서 변수는 콜론(':') 다음에 나오는 이름이다.

select 함수에선 query함수를 사용하는데, update와 다르게 RowMapper 클래스를 이용하여 결과값의 반환 형태를 지정할 수 있다. 또한, BeanPropertySqlParameterSource 클래스를 이용하는 대신 Map을 생성하여 매개변수를 넣는데, 매개변수의 개수가 적을때는 이런 방법도 많이 사용한다.

댓글
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/04   »
1 2 3 4 5 6
7 8 9 10 11 12 13
14 15 16 17 18 19 20
21 22 23 24 25 26 27
28 29 30
글 보관함