Spring/공부

[Spring MVC] Spring Jdbc 정리 (JdbcTemplate 예제)

하루인생 2021. 2. 3. 16:17

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사용법에 대해 알아보자