[Spring MVC] Spring Jdbc 정리 (JdbcTemplate 예제)
JdbcTemplate 란?
JDBC의 모든 기능을 최대한 활용할 수 있는 유연성을 제공하는 클래스이다.
제공하는 기능은 실행, 조회, 배치 이렇게 세 가지가 있다.
실행 : insert, update, delete
조회 : select
배치 : 여러 개의 쿼리를 한번에 수행하는 작업
JdbcTemplate은 DataSource를 파라미터로 받아서 다음과 같이 생성이 가능하다.
dataSource는 Bean으로 등록해서 사용한다.
JdbcTemplate template = new JdbcTemplate(dataSource);
사용할 Dto, Dao 클래스와 SQL문을 관리할 클래스(StudentSql.class)
DTO : Student.class
간단하게 name과 age를 성분으로 하는 Student클래스를 만들었다.
package kr.or.jdbcexam.dto;
public class Student {
private String name;
private int age;
public Student(int age, String name) {
this.name = name;
this.age = age;
}
public Student() {
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Student [name=" + name + ", age=" + age + "]";
}
}
DAO : StudentDao
StudentDao의 생성자에서 dataSource를 주입시켜주기 위해 @Autowired 애노테이션을 사용해야 하지만
생성자가 하나인 경우 생략해도 상관없다.
@Repository
public class StudentDao {
private JdbcTemplate jdbcTemplate;
public StudentDao(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
}
StudentSql.class
사용할 sql문을 static으로 선언하여 다른 클래스에서 참조할 수 있게 하였다.
package kr.or.jdbcexam.dao;
public class StudentSql {
public static String SELECT_STUDENTS = "SELECT * FROM student";
public static String SELECT_STUDENT = "SELECT * FROM student where name=?";
public static String SELECT_COUNT = "SELECT COUNT(*) FROM student";
public static String SEARCH_AGE_BY_NAME = "SELECT age FROM student WHERE name = ?";
public static String SEARCH_NAME_BY_AGE = "SELECT name FROM student WHERE age = ?";
}
이렇게 DTO와 미완성인 DAO 그리고 sql문을 간단하게 작성해봤다.
이제 데이터 처리를 위해서 DAO클래스에 메서드를 추가해보자
JdbcTemplate의 여러 가지 메서드
1. queryForObject 메서드
한 개의 레코드를 반환
template.queryForObject(인자1, 인자2, [인자3, ...] )
인자1 : sql문
인자2 : 반환받을 유형 클래스
인자3 : sql문에서 ?를 사용해서 인자를 받는데 그 ?에 들어갈 값, 필요 없을 시 생략 가능.
예제로 알아보기
getNumver()는 레코드의 수를 반환하는 메서드이다.
그래서 sql문과 반환받을 유형인 Integer.class를 인자로 넣어줬다.
// SELECT_COUNT = "SELECT COUNT(*) FROM student";
public int getNumber() {
int num = jdbcTemplate.queryForObject(SELECT_COUNT, Integer.class);
return num;
}
searchAge(String name)은 특정 이름을 입력받아 거기에 해당하는 사람의 나이를 알려주는 메서드이다.
sql문과 반환받을 유형(나이 이므로) Integer.class 그리고 sql에 파라미터 값으로 name을 넘겼다.
// SEARCH_AGE_BY_NAME = "SELECT age FROM student WHERE name = ?";
public int searchAge(String name) {
int age = jdbcTemplate.queryForObject(SEARCH_AGE_BY_NAME, Integer.class, name);
return age;
}
searchName(int age)은 특정 나이를 입력받아 거기에 해당하는 사람의 이름을 알려주는 메서드이다.
위 메서드와 유사하다.
// SEARCH_NAME_BY_AGE = "SELECT name FROM student WHERE age = ?";
public String searchName(int age) {
String name = jdbcTemplate.queryForObject(SEARCH_NAME_BY_AGE, String.class, age);
return name;
}
getStudent(String name)은 이름을 입력받아 Student객체로 값을 받는 방법이다.
첫 번째와 세 번째 인자는 위 방식과 같지만 두 번쨰 인자의 값으로
RowMapper를 사용하여 Student객체를 반환받았다.
RowMapper는 아래 그림처럼 object를 원하는 class형태로 변경해준다.
아래 코드에서는 sql문의 결과를 Student객체로 변경하여 반환하였다.
// SELECT_STUDENT = "SELECT * FROM student where name=?";
public Student getStudent(String name) {
return jdbcTemplate.queryForObject
(SELECT_STUDENT,
new RowMapper<Student>() {
public Student mapRow(ResultSet rs, int rowNum) throws SQLException{
Student student = new Student();
student.setName(rs.getString("name"));
student.setAge(rs.getInt("age"));
return student;
}
}
,name);
}
위 방식은 코드가 너무 길다.
두 번째 인자인 RowMapper 부분을 람다식을 사용하여 간략하게 나타낼 수 있다.
너무 간단해져서 코드가 깔끔해보인다.
public Student getStudent_ramda(String name) {
return jdbcTemplate.queryForObject(SELECT_STUDENT,
(rs, rowNum) -> new Student(rs.getInt("age"), rs.getString("name"))
, name);
}
람다식을 이용해서 하나 더 만들어 봤다. 이번에는 name과 age를 파라미터로하는 메서드이다.
public Student getStudentByTwoParam(String name, int age) {
return jdbcTemplate.queryForObject("select * from student where name = ? and age = ?",
(rs,count)->new Student(rs.getInt("age"), rs.getString("name"))
,name, age);
}
+ 추가사항
queryForObject메서드는 다양한 파라미터 형식을 지원한다.
위에서는 queryForObject(String sql, Object[] args, RowMapper<T> rowMapper) 방식을 사용하지 않았다.
이 부분에 대해 추가적인 글을 작성했다. 위에서 작성했던 방식과 다른 파라미터 순서를 갖는다.
(sql, Object형 sql파라미터, 반환형식) 이 순서가 된다.
이 방법은 JdbcTemplate의 query문에서도 사용할 수 있다.
이와 관련해서 추가적인 코드를 첨부해본다. sql파라미터와 반환형인 RowMapper와 순서가 바꼈다. 대신 Object[]{}를 통해 좀 더 많은 파라미터를 넣을 수 있다. 예를 들면 new Object(name, age, ...) 와 같이 말이다.
public Student getStudent_new_ramda(String name) {
return jdbcTemplate.queryForObject(SELECT_STUDENT
, new Object[] {name}
,(rs, rowNum) -> {
Student student = new Student();
student.setName(rs.getString("name"));
student.setAge(rs.getInt("age"));
return student;
}
);
}
2. query 메서드
queryForObject와 유사하다. 차이점은 query는 여러 개의 레코드를 반환한다.
예제로 알아보기
// SELECT_STUDENTS = "SELECT * FROM student";
public List<Student> getStudents(){
return jdbcTemplate.query(SELECT_STUDENTS, new RowMapper<Student>() {
public Student mapRow(ResultSet rs, int rowNum) throws SQLException {
Student student = new Student();
student.setName(rs.getString("name"));
student.setAge(rs.getInt("age"));
return student;
}
});
}
이 코드 역시 람다식으로 표현이 가능하다.
public List<Student> getStudents_ramda(){
return jdbcTemplate.query(SELECT_STUDENTS,
(rs, count)-> new Student(rs.getInt("age"), rs.getString("name")));
}
갑자기 드는 생각은 람다식에서 Student객체를 만들 때 생성자에 age와 name을 바로 입력하는 방식 말고
기본생성자를 만든 후 setter를 통해서 입력하는 방법은 없을까? 라는 생각이 들었다.
방법이 있었다. 아래 코드와 같이 해주면 된다...
public Student getStudent_ramda(String name) {
return jdbcTemplate.queryForObject(SELECT_STUDENT,
(rs, rowNum) -> {
Student student = new Student();
student.setName(rs.getString("name"));
student.setAge(rs.getInt("age"));
return student;
}
, name);
}
코드 실행 결과
작성한 코드를 테스트하기 위해서 main메서드를 작성하였다.
package kr.or.jdbcexam.dao;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import kr.or.jdbcexam.config.ApplicationConfig;
import kr.or.jdbcexam.config.DBConfig;
public class DBTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
StudentDao dao = context.getBean(StudentDao.class);
System.out.println("이름이 kim1인 학생의 모든 정보를 출력");
System.out.println(dao.getStudent("kim1"));
System.out.println("이름이 kim1인 학생의 모든 정보를 람다식으로 출력");
System.out.println(dao.getStudent_ramda("kim1"));
System.out.println("모든 학생의 수 출력");
System.out.println(dao.getNumber() + "명");
System.out.println("이름이 kim1인 학생의 나이는?");
System.out.println(dao.searchAge("kim1"));
System.out.println("나이가 20살인 학생의 이름은?");
System.out.println(dao.searchName(20) + "입니다");
System.out.println("두개의 인자로 데이터 받아오기");
System.out.println(dao.getStudentByTwoParam("kim1",26));
System.out.println("모든 학생 정보 출력");
System.out.println(dao.getStudents());
System.out.println("모든 학생 정보 출력");
System.out.println(dao.getStudents_ramda());
}
}
실행결과는 아래 이미지로 첨부하였다.
3. update 메서드
insert, update, delete와 같은 sql을 실행할 때 이 메서드를 사용한다.
sql실행으로 영향을 받은 레코드의 수(int)를 반환한다.
template.update(String sql, parameter) -> parameter는 가변인자(한개 or 두개..)
(1) insert
반환 값이 정수이므로 메서드의 반환type을 int로 했다.
public int insertStudent(Student student) {
return jdbcTemplate.update("insert into student values(?,?)"
,student.getName()
,student.getAge());
}
그리고 테스트해봤다. 코드와 결과 이미지이다.
...
public class DBTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
StudentDao dao = context.getBean(StudentDao.class);
System.out.println("25살인 Lee3 등록");
Student student = new Student();
student.setAge(25);
student.setName("Lee3");
int result = dao.insertStudent(student);
System.out.println(result + "건이 등록 되었습니다.");
}
}
db에서 보면 (Lee3, 25)의 데이터가 정상적으로 들어왔다.
(2) update
이름과 나이를 입력받아, 이름이 name인 사람의 나이를 age로 수정하였다.
public int updateStudent(String name, int age) {
return jdbcTemplate.update("update student set age = ? where name = ?"
, age
,name);
}
테스트 코드와 결과 이미지이다.
...
public class DBTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
StudentDao dao = context.getBean(StudentDao.class);
System.out.println("이름이 Lee3인 사람의 나이를 30으로 변경해라");
int update_result = dao.updateStudent("Lee3", 30);
System.out.println(update_result+"건 수정이 되었습니다. ");
}
}
db에서 보면 이름이 Lee3인 학생의 나이가 30으로 정상적으로 수정되었다.
(3) delete
name을 입력받아 해당하는 데이터를 삭제하는 코드이다.
public int deleteStudent(String name) {
return jdbcTemplate.update("delete from student where name = ?", name);
}
테스트 코드와 결과 이미지이다.
...
public class DBTest {
public static void main(String[] args) {
ApplicationContext context = new AnnotationConfigApplicationContext(ApplicationConfig.class);
StudentDao dao = context.getBean(StudentDao.class);
System.out.println("이름이 Lee3인 학생을 삭제해라");
int delete_result = dao.deleteStudent("Lee3");
System.out.println(delete_result + "건 삭제되었습니다. ");
}
}
db에서 보면 이름이 Lee3인 학생이 정상적으로 삭제되었다.
다음에는 NamedParameterJdbcTemplate사용법에 대해 알아보자