Spring JDBC
`JDBC(Java Database Connectivity)`는 Java 언어를 통해 데이터베이스에 접속하고 SQL 쿼리를 실행하는 데 사용되는 API입니다. `Spring JDBC`는 Spring Framework에서 제공하는 JDBC를 좀 더 편리하게 사용할 수 있도록 기능을 제공하는 모듈입니다. `Spring JDBC`는 추상화된 인터페이스와 유틸리티 클래스, 예외 처리 기능 등을 제공하여 개발자가 간단하게 데이터베이스 액세스를 구현할 수 있도록 도와줍니다.
JDBC보다 편리해진 Spring JDBC
- 연결 관리 및 자원 해제
Spring JDBC는 데이터베이스 연결(Connection)과 자원 해제를 자동으로 처리합니다. JDBC에서는 데이터베이스 연결을 수동으로 열고 닫아야 했지만, Spring JDBC에서는 DataSource를 사용하여 연결 관리를 추상화하고, 자원의 생성과 해제를 Spring이 처리합니다. 이로써 개발자는 연결 관리와 자원 해제(Connection)에 대한 번거로움을 줄일 수 있습니다.
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mydb" />
<property name="username" value="root" />
<property name="password" value="password" />
</bean>
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
- SQL 쿼리 실행과 결과 매핑
Spring JDBC는 `JdbcTemplate`을 통해 간편한 SQL 쿼리 실행을 지원합니다. `JdbcTemplate`은 `PreparedStatement`를 사용하여 SQL 쿼리를 실행하고, ResultSet을 처리하여 결과를 반환합니다. 또한, Spring은 결과를 객체로 매핑하기 위한 `RowMapper` 인터페이스를 제공합니다. 개발자는` RowMapper`를 구현하여 SQL 결과를 원하는 객체로 매핑할 수 있습니다. - 트랜잭션 관리
Spring JDBC는 트랜잭션 관리를 위한 기능을 제공합니다. `PlatformTransactionManager`를 사용하여 데이터베이스 트랜잭션을 시작하고 커밋 또는 롤백할 수 있습니다. 이를 통해 개발자는 트랜잭션 관리를 보다 간편하게 처리할 수 있습니다. - 예외 처리
Spring JDBC는 JDBC에서 발생하는 일반적인 예외를 Spring의 일관된 예외 계층 구조로 변환합니다. 예를 들어 JdbcTemplate는 `DataAccessException`을 발생시킵니다. 이를 통해 개발자는 JDBC의 다양한 예외 처리 코드를 대신하여 Spring의 예외 처리 기능을 활용할 수 있습니다. 예외 처리를 통해 코드의 가독성을 높이고 예외에 대한 일관된 처리를 구현할 수 있습니다. - 유연한 데이터베이스 지원
Spring JDBC는 다양한 데이터베이스에 대한 지원을 제공합니다. 여러 데이터베이스 벤더의 드라이버를 사용하여 연결하고 SQL 쿼리를 실행할 수 있습니다. Spring의 DataSource 추상화를 통해 코드 변경 없이 다른 데이터베이스로 전환할 수 있습니다.
JdbcTemplate
JdbcTemplate 생성
public class MemberDao{
JdbcTemplate JdbcTemplate;
public void setDataSource(DataSource dataSource){
this.JdbcTemplate = new JdbcTemplate(dataSource);
}
어노테이션 방식을 이용한 경우
@Autowired
public void init(DataSource dataSource){
this.JdbcTemplate = new JdbcTemplate(dataSource);
}
SQL 쿼리에 동적인 값 전달
JdbcTemplate에 작업을 요청할 때 문자열로 된 SQL을 제공해줘야 한다.
💡위치 치환자 SQL
INSERT INTO MEMBER(ID, NAME, POINT) VALUES(?, ?, ?);
💡네임드 파라미터 SQL
INSRT INTO MEMBER(ID, NAME, POINT) VALUES(:id, :name, :point);
위치 치환자 SQL은 실제로 파라미터를 지정할 때는 바인딩할 순서가 틀리지 않도록 주의가 필요하다. 또한 바인딩할 파라미터 개수가 많아질수록 가독성도 떨어진다. 이러한 단점을 보안하는 파라미터 설정 방법이 있는데, 바로 네임드 파라미터이다.
하지만 네임드 파라미터를 쓰기 위해서는 JdbcTemplate만으로는 안된다. 이를 확장한 NamedParameterJdbcTemplate 클래스를 사용해야 한다.
@Component
public class JdbcTestDao{
@Autowired
NamedParameterJdbc Template namedParameterJdbcTemplate;
public String findNameById(String Id){
String sql = "SELECT name FROM member WHERE id = :id";
...
💡Map / MapSqlParameterSource
map 오브젝트는 이름 치환자를 가진 SQL과 함께 SimpleJdbcTemplate에 전달돼서 바인딩 파라미터로 사용될 수 있다. 맵의 각 키 값과 일치하는 치환자에 맵의 값이 자동으로 삽입된다.
코드를 이용해 맵에 정보를 직접 넣어야 한다면 Map 대신 스프링 JDBC의 MapSourceParameterSource를 사용하는 것이 편하다.
MapSqlParameterSource params = new MapSqlParameterSource()
.addValue("id", 1)
.addValue("name", "dutmdcjf")
.addValue("point", 5);
💡BeanPropertySqlParameterSource
BeanPropertySqlParameterSource는 도메인 오브젝트나 DTO를 사용하게 해준다.
Member member = new Member(1, "dutmdcjf", 5);
BeanPropertySqlParameterSource params = new BeanPropertySqlParameterSource(member);
[ JdbcTemplate 주요 메소드 ]
- queryForObject
하나의 결과 레코드 중에서 하나의 컬럼 값을 가져올 때 사용한다.
RowMapper와 함께 사용하면 하나의 레코드 정보를 객체에 매핑할 수 있다. - queryForMap
하나의 결과 레코드 정보를 Map 형태로 매핑할 수 있다. - queryForList
여러 개의 결과 레코드를 다룰 수 있다.
List의 한 요소가 한 레코드에 해당한다.
한 레코드의 정보는 queryForObject나 queryForMap을 사용할 때와 같다. - query
ResultSetExtractor, RowCallbackHandler와 함께 조회할 때 사용한다. - update
데이터를 변경하는 SQL (INSERT, DELETE, UPDATE)을 실행할 때 사용한다.
[ SQL 실행 메소드: update() 메소드 ]
INSERT, UPDATE, DELETE은 JdbcTemplate의 update() 메소드를 사용한다.
update() 메소드를 호출할 때는 SQL과 함께 바인딩할 파라미터를 다음 방식으로 전달하면 된다.
💡varargs
바인딩할 파라미터를 순서대로 전달하면 된다.
@Autowired
JdbcTemplate jdbcTemplate;
//예시1 - insert
public int insertMember(Member member){
return jdbcTemplate.update("INSERT INTO member(id, name, password, args)
VALUES(?, ?, ?)", 1, "dutmdcjf", 5);
}
//예시2 - update
public int updateById(Member member){
return jdbcTemplate.update("UPDATE member SET name=?, password=?"
+ " WHERE id = ?",
member.getName(), member.getPassword(), member.getId());
}
//예시3 - delete
public int deleteById(String id){
return jdbcTemplate.update("delete from member WHERE id=?", id);
}
💡Map
파라미터를 Map으로 전달할 수 있다.
jdbcTemplate.update(
"INSERT INTO MEMBER(ID, NAME, POINT, args) VALUES(:id, :name, :point)", map);
💡SqlParameterSource
도메인 오브젝트나 DTO를 이름 치환자에 직접 바인딩할 시 BeanPropertySqlParameterSource를 사용해 update()를 호출할 수 있다.
jdbcTemplate.update(
"INSERT INTO MEMBER(ID, NAME, POINT, args) VALUES(:id, :name, :point)",
new BeanPropertySqlParameterSource(member));
이름 치환자를 위한 파라미터를 메소드 호출 시에 직접 지정하려면 MapSqlParameterSource의 addValue() 메소드를 이용할 수 있다.
jdbcTemplate.update(
"INSERT INTO MEMBER(ID, NAME, POINT, args) VALUES(:id, :name, :point)",
new MapSqlParameterSource()
.addValue("id",1).addValue("name","dutmdcjf").addValue("point",5));
이때 jdbcTempate의 update() 메소드는 SQL 실행으로 영향을 받은 레코드의 개수를 리턴해준다.
[ SQL 조회 메소드: SELECT ]
💡int queryForInt(String sql, [SQL 파라미터])
queryForInt()의 SQL 실행 결과는 한 개의 로우에 한 개의 숫자 컬럼만 갖고 있어야 한다.
컬럼 개수가 두 개 이상이거나 로우의 개수가 여러 개이면 예외가 발생한다.
//예시1
int count = jdbcTemplate.queryForInt("select count(*) from member");
//예시2
jdbcTemplate.queryForInt("select count(*) from member where point > :min",
new MapSqlParameterSource("min", min));
💡long queryForLong(String sql, [SQL 파라미터])
하나의 long 타입 값을 조회할 때 사용한다.
위 queryForInt()와 사용법이 동일하다.
💡<T> T queryForObject(String sql, Class<T> requiredType, [SQL 파라미터])
- 쿼리를 실행해서 하나의 값을 가져올 때 사용한다.
- 결과 타입을 직접 지정할 수 있다.
- SQL 실행 결과는 하나의 컬럼을 가진 하나의 로우여야 한다.
- 검색된 로우가 없으면 EmptyResultDataAccessException 예외가 발생한다.
try{
return String name = jdbcTemplate.queryForObject("SELECT name FROM MEMBER
WHERE id=?",String.class, id);
}
catch(EmptyResultDataAccessException) {
return null;
}
💡<T> T queryForObject(String sql, RowMapper<T> rm, [SQL 파라미터])
- 위 queryForObject(...)와 다른점은 단일 컬럼이 아니라 다중 컬럼을 가지 쿼리에 사용할 수 있다는 것이다.
- SQL 실행 결과는 하나의 컬럼을 가진 하나의 로우여야 한다.
- 검색된 로우가 없으면 EmptyResultDataAccessException 예외가 발생한다.
1) RowMapper를 활용
private RowMapper<Member> memRowMapper =
new RowMapper<Member>() {
@Override
public Member mapRow(ResultSet rs, int rowNum) throws SQLException{
Member member = new Member();
member.setId(rs.getString("ID");
member.setName(rs.getString("NAME");
member.setPassword(rs.getInteger("PASSWORD");
return member;
}
};
public List<Member> getAllMember(){
List<Member> results = jdbcTemplate.query("SELECT * FROM member",
memRowMapper);
return results;
}
위 예제에서는 queryForObject() 메소드가 아닌 query()메소드로 코드를 만들었긴 했지만 원리는 같다.
2)BeanPropertyRowMapper를 활용
위처럼 RowMapper를 구현하는 방법 외에도 미리 만들어져 있는 BeanPropertyRowMapper<T> 클래스를 활용하는 방법이 있다. 이는 미리 약속된 매핑 규칙에 따라 자동 매핑하는 것이기 때문에 약간의 성능을 희생해야 한다. 때문에 상황에 맞춰 사용하는 것이 좋다.
BeanPropertyRowMapper 생성자에 매핑할 클래스를 넣어서 오브젝트를 생성하면 주어진 클래스의 프로퍼티 이름과 SQL 결과를 자동 매핑해주는 RowMapper 콜백 오브젝트로 사용할 수 있다.
RowMapper<Member> rowMapper = new BeanPropertyRowMapper<Member>(Member.class);
Member m = jdbcTemplate.queryForObject("SELECT * FROM member WHERE id = ?",
rowMapper, id);
💡<T> List<T> query(String sql, RowMapper<T> rm, [SQL 파라미터])
queryForObject()가 결과의 로우가 하나라면 query()는 여러 개의 로우를 처리할 수 있다.
List<Member> members = jdbcTemplate.query("SELECT * FROM member
WHERE point > ?",
new BeanPropertyRowMapper<Member>(Member.class), point);
💡Map<String, Object> queryForMap(String sql, [SQL 파라미터])
public Member getMemberById(String Id){
Map<String, Object> result = jdbcTemplate.queryForMap("SELECT * FROM member
WHERE id = ?" , id);
Member member = new Member();
member.setId((String) result.get("id"));
member.setName((String) result.get("name"));
member.setPassword((Integer) result.get("password"));
return member;
}
queryForObject() 와 마찬가지로 SQL 실행 결과는 하나의 로우를 돌려주는 것이 보장돼야 한다.
💡List<Map<String, Object>> queryForList<String sql, [SQL 파라미터])
queryForMap()의 다중 로우 버전이다. 각 로우의 내용을 Map에 넣고 이를 다시 리스트로 만들어 돌려준다.
public List<Member> getAllMemeber(){
List<Map<String, Object>> resultList = jdbcTemplate.queryForList("SELECT * FROM member");
List<Member> memberList = new ArrayList<Member>();
for(Map<String, Object> result : resultList){
Member member = new Member();
member.setId((String) result.get("id"));
member.setName((String) result.get("name"));
member.setPassword((Integer) result.get("password"));
return member;
}
}
'Spring Framework' 카테고리의 다른 글
Spring 다국어 적용하는 방법들 (0) | 2022.08.31 |
---|---|
Spring 트랜잭션 (0) | 2022.08.29 |
Spring 프로퍼티 파일 ( .properties, .yaml), 프로파일(profiles) (0) | 2022.08.25 |
Spring @ComponentScan, @Import, @Enable (0) | 2022.08.24 |
Spring 빈 생명주기( Bean Life Cycle ) (0) | 2022.08.24 |