8월 30, 2023

[Spring] JDBC를 통한 데이터 접근, RowMapper 인터페이스, NamedParameterJdbcTemplate

Java Spring에서 관계형 데이터베이스를 통해 쿼리를 실행하고 데이터를 변경하고 싶다면 어떤 식으로 실행해야 할까? 이것을 구현하기 위해 알아야 하는 개념으로 RowMapper, NamedParameterJdbcTemplate 등이 있다. 

 

1. RowMapper

RowMapper란 JDBC 기본 인터페이스 ResultSet에서 원하는 객체로 타입을 변경해주는 역할을 한다. 

import org.springframework.jdbc.core.RowMapper;
import java.sql.ResultSet;

위와 같이 import를 해주면 RowMapper와 ResultSet을 사용할 수 있다. 

 

예를 들어, 

public class Person {
 private String firstName;
 private String lastName;
}

과 같은 class가 존재한다고 가정해보자. 

 

그리고

@Component
public class PersonRowMapper implements RowMapper<Person> {
	public Person mapRow(ResultSet resultSet, int rowNum) throws SQLException {
     Person person = new Person();
     person.setFirstName (resultSet.getString(1));
     person.setLastName (resultSet.getString(2));
     return person;
    }
}

위와 같이 PersonRowMapper class가 RowMapper 인터페이스를 구현했다고 가정하면, 아래와 같은 코드를 쓰는 것이 가능해진다. 

 

@Service
public class PersonServiceImpl implements PersonService {
  private static final String SEARCH_ALL =
  	"select first_name, last_name";
  
  @Inject private NamedParameterJdbcOperations jdbcTemplate;
  @Inject private PersonRowMapper personRowMapper;
  
  
public List<Person> getPeople() {
  return jdbcTemplate.query{
  	SEARCH_ALL, new HashMap<String, Object>(), personRowMapper;
  }
}
   
}

static final로 실행하고자 하는 select sql을 하나 만들었고 NamedParameterJdcbOperations를 통해 해당 sql를 실행한다. (jdbcTemplate.query 부분) 

 

해당 select sql을 실행하게 되면 select로 여러 값들이 반환될 텐데, 그 값들을 RowMapper를 사용하여 원하는 객체 형태인 Person 형태들로 변환이 되게 되는 것이다. 결국 getPeople 메서드에서는 personRowMapper를 통해 sql 실행한 값들을 원하는 Person 객체들로 받아 List<Person>으로 반환할 수 있게 되는 것이다. 


2. NamedParameterJdbcTemplate

NamedParameterJdbcTemplate 에서는 변수 매핑을 ? 로 하던 기존 JDBC와는 달리 이름을 통해서 변수 매핑을 가능토록 만들어준다. 변수 이름 앞에  " : " 이것을 써서 가능하게 만들어준다. 

 

위 NamedParameterJdbcTemplate를 사용하여 Person 객체 하나를 더 만드는 메서드 하나와 기존 존재하는 Person 객체를 update하는 메서드 하나를 만들어보겠다. 

 

class는 위에서 사용하던 PersonServiceImpl과 동일한 class이고 위에서 사용한 코드에 추가하여 코드를 완성해보도록 하겠다. 

 

@Service
public class PersonServiceImpl implements PersonService {
  private static final String SEARCH_ALL =
  	"select first_name, last_name";
  
  //추가, createPerson 메서드에서 활용
  private static final String CREATE =
    "insert into person (first_name, last_name) " +
    "values (:firstName, :lastName)";
  //추가, updatePerson 메서드에서 활용
  private static final String UPDATE =
  	"update person set first_name = :firstName, " +
    				"where last_name = :lastName"; 
  @Inject private NamedParameterJdbcOperations jdbcTemplate;
  @Inject private PersonRowMapper personRowMapper;
  
  
public List<Person> getPeople() {
  return jdbcTemplate.query{
  	SEARCH_ALL, new HashMap<String, Object>(), personRowMapper;
  }
}
 // Create sql
 public void createPerson (Person person) {
  SqlParameterSource params= new MapSqlParameterSource()
  	.addValue("firstName", person.getFirstName())
    .addValue("lastName", person.getLastName());
  KeyHolder keyHolder= new GeneratedKeyHolder(); //자동생성되는 key 값을 위해
  jdbcTemplate.update(CREATE, params, keyHolder);
  
  //update
  public void updatePerson(Person person){
  	SqlParameterSource params= new MapSqlParameterSource()
  	.addValue("firstName", person.getFirstName())
    .addValue("lastName", person.getLastName());
    jdbcTemplate.update(UPDATE,params);
  }
 }
}

 

코드를 조금 더 자세히 살펴보면 createPerson 메서드에서는 데이터를 insert 하고 있는데, insert 명령문에서는 자동 생성되는 key 값을 내포하는 의미가 있으므로 GeneratedKeyHolder도 같이 생성하게 된다. 

 

여기서 살펴보아야 할 점은 NamedParameterJdbcTemplate를 사용하니 :콜론을 사용하여 자동으로 객체변환을 시켜주었다는 점이다. 

 

즉 데이터베이스 테이블 상 first_name, last_name (snake_case) 이었던 칼럼과 firstName, lastName (camelCase)로 자동매핑 되었다. 이와 같이 각각의 매개 변수 이름 앞에 콜론 기호를 접두사로 사용하여 자동매핑을 시켜주고 정확하게 의미를 구분하고 있다.