스프링 JDBC 예제

Spring JDBC는 이 튜토리얼의 주제입니다. 데이터베이스는 대부분의 엔터프라이즈 애플리케이션의 중요한 부분입니다. 그래서 Java EE 프레임워크에서는 JDBC와의 통합이 매우 중요합니다.

Spring JDBC

Spring Framework은 JDBC API와 훌륭한 통합을 제공하며 우리가 데이터베이스 작업 로직에서 Connection 열기/닫기, ResultSet, PreparedStatement 등과 같은 보일러플레이트 코드를 피할 수 있도록 JdbcTemplate 유틸리티 클래스를 제공합니다. 먼저 간단한 Spring JDBC 예제 애플리케이션을 살펴보고, 그런 다음 JdbcTemplate 클래스가 리소스가 올바르게 닫혔는지 여부를 걱정하지 않고도 우리가 모듈화된 코드를 쉽게 작성하는 데 어떻게 도움을 줄 수 있는지 살펴보겠습니다. Spring 기반 애플리케이션을 개발하는 데 매우 유용한 Spring Tool Suite를 사용하여 Spring JDBC 애플리케이션을 만들 것입니다. 최종 프로젝트 구조는 아래 이미지와 같이 보일 것입니다. STS 메뉴에서 간단한 Spring Maven 프로젝트를 생성하세요. 원하는 이름을 선택하거나 SpringJDBCExample과 같은 프로젝트 이름을 유지할 수 있습니다.

Spring JDBC 종속성

먼저 maven 프로젝트 pom.xml 파일에 Spring JDBC 및 데이터베이스 드라이버를 포함해야 합니다. 최종 pom.xml 파일은 아래와 같습니다.

<project xmlns="https://maven.apache.org/POM/4.0.0" xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="https://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<groupId>org.springframework.samples</groupId>
	<artifactId>SpringJDBCExample</artifactId>
	<version>0.0.1-SNAPSHOT</version>

	<properties>

		<!-- Generic properties -->
		<java.version>1.6</java.version>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>

		<!-- Spring -->
		<spring-framework.version>4.0.2.RELEASE</spring-framework.version>

		<!-- Logging -->
		<logback.version>1.0.13</logback.version>
		<slf4j.version>1.7.5</slf4j.version>

	</properties>

	<dependencies>
		<!-- Spring and Transactions -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring-framework.version}</version>
		</dependency>
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-tx</artifactId>
			<version>${spring-framework.version}</version>
		</dependency>

		<!-- Spring JDBC Support -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-jdbc</artifactId>
			<version>${spring-framework.version}</version>
		</dependency>
		
		<!-- MySQL Driver -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.0.5</version>
		</dependency>

		<!-- Logging with SLF4J & LogBack -->
		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-api</artifactId>
			<version>${slf4j.version}</version>
			<scope>compile</scope>
		</dependency>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-classic</artifactId>
			<version>${logback.version}</version>
			<scope>runtime</scope>
		</dependency>

	</dependencies>
</project>

대부분은 STS에 의해 자동으로 생성되었지만, Spring Framework 버전을 최신 버전으로 업데이트하여 4.0.2.RELEASE를 사용했습니다. 또한 필요한 아티팩트인 spring-jdbcmysql-connector-java를 추가했습니다. 첫 번째는 Spring JDBC 지원 클래스를 포함하고, 두 번째는 데이터베이스 드라이버입니다. 저는 테스트 목적으로 MySQL 데이터베이스를 사용하고 있으므로 MySQL JConnector JAR 종속성을 추가했습니다. 다른 RDBMS를 사용하는 경우 종속성에서 해당 변경을 수행해야 합니다.

Spring JDBC 예제 – 데이터베이스 설정

우리의 애플리케이션에서 CRUD 작업에 사용할 간단한 테이블을 만들어 봅시다.

CREATE TABLE `Employee` (
  `id` int(11) unsigned NOT NULL,
  `name` varchar(20) DEFAULT NULL,
  `role` varchar(20) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Spring JDBC 예제 – 모델 클래스

JDBC 작업에 대한 DAO 패턴을 사용할 것이므로, 우리의 직원 테이블을 모델링할 자바 빈을 만들어 봅시다.

package com.journaldev.spring.jdbc.model;

public class Employee {

	private int id;
	private String name;
	private String role;
	
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public String getRole() {
		return role;
	}
	public void setRole(String role) {
		this.role = role;
	}
	
	@Override
	public String toString(){
		return "{ID="+id+",Name="+name+",Role="+role+"}";
	}
}

Spring JDBC 예제 – DAO 인터페이스 및 구현

DAO 패턴에 대해, 먼저 구현하려는 모든 작업을 선언하는 인터페이스를 만들겠습니다.

package com.journaldev.spring.jdbc.dao;

import java.util.List;

import com.journaldev.spring.jdbc.model.Employee;

//CRUD 작업
public interface EmployeeDAO {
	
	//Create
	public void save(Employee employee);
	//Read
	public Employee getById(int id);
	//Update
	public void update(Employee employee);
	//Delete
	public void deleteById(int id);
	//모두 가져오기
	public List getAll();
}
package com.journaldev.spring.jdbc.dao;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

import javax.sql.DataSource;

import com.journaldev.spring.jdbc.model.Employee;

public class EmployeeDAOImpl implements EmployeeDAO {

	private DataSource dataSource;

	public void setDataSource(DataSource dataSource) {
		this.dataSource = dataSource;
	}

	@Override
	public void save(Employee employee) {
		String query = "insert into Employee (id, name, role) values (?,?,?)";
		Connection con = null;
		PreparedStatement ps = null;
		try{
			con = dataSource.getConnection();
			ps = con.prepareStatement(query);
			ps.setInt(1, employee.getId());
			ps.setString(2, employee.getName());
			ps.setString(3, employee.getRole());
			int out = ps.executeUpdate();
			if(out !=0){
				System.out.println("Employee saved with id="+employee.getId());
			}else System.out.println("Employee save failed with id="+employee.getId());
		}catch(SQLException e){
			e.printStackTrace();
		}finally{
			try {
				ps.close();
				con.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}

	@Override
	public Employee getById(int id) {
		String query = "select name, role from Employee where id = ?";
		Employee emp = null;
		Connection con = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		try{
			con = dataSource.getConnection();
			ps = con.prepareStatement(query);
			ps.setInt(1, id);
			rs = ps.executeQuery();
			if(rs.next()){
				emp = new Employee();
				emp.setId(id);
				emp.setName(rs.getString("name"));
				emp.setRole(rs.getString("role"));
				System.out.println("Employee Found::"+emp);
			}else{
				System.out.println("No Employee found with id="+id);
			}
		}catch(SQLException e){
			e.printStackTrace();
		}finally{
			try {
				rs.close();
				ps.close();
				con.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		return emp;
	}

	@Override
	public void update(Employee employee) {
		String query = "update Employee set name=?, role=? where id=?";
		Connection con = null;
		PreparedStatement ps = null;
		try{
			con = dataSource.getConnection();
			ps = con.prepareStatement(query);
			ps.setString(1, employee.getName());
			ps.setString(2, employee.getRole());
			ps.setInt(3, employee.getId());
			int out = ps.executeUpdate();
			if(out !=0){
				System.out.println("Employee updated with id="+employee.getId());
			}else System.out.println("No Employee found with id="+employee.getId());
		}catch(SQLException e){
			e.printStackTrace();
		}finally{
			try {
				ps.close();
				con.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}

	@Override
	public void deleteById(int id) {
		String query = "delete from Employee where id=?";
		Connection con = null;
		PreparedStatement ps = null;
		try{
			con = dataSource.getConnection();
			ps = con.prepareStatement(query);
			ps.setInt(1, id);
			int out = ps.executeUpdate();
			if(out !=0){
				System.out.println("Employee deleted with id="+id);
			}else System.out.println("No Employee found with id="+id);
		}catch(SQLException e){
			e.printStackTrace();
		}finally{
			try {
				ps.close();
				con.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
	}

	@Override
	public List<Employee> getAll() {
		String query = "select id, name, role from Employee";
		List<Employee> empList = new ArrayList<Employee>();
		Connection con = null;
		PreparedStatement ps = null;
		ResultSet rs = null;
		try{
			con = dataSource.getConnection();
			ps = con.prepareStatement(query);
			rs = ps.executeQuery();
			while(rs.next()){
				Employee emp = new Employee();
				emp.setId(rs.getInt("id"));
				emp.setName(rs.getString("name"));
				emp.setRole(rs.getString("role"));
				empList.add(emp);
			}
		}catch(SQLException e){
			e.printStackTrace();
		}finally{
			try {
				rs.close();
				ps.close();
				con.close();
			} catch (SQLException e) {
				e.printStackTrace();
			}
		}
		return empList;
	}

}

CRUD 작업의 구현은 이해하기 쉽습니다. DataSource에 대해 더 알고 싶다면 JDBC DataSource 예제를 읽어보세요.

Spring JDBC 예제 – 빈 구성

위의 모든 클래스를 살펴보면 모두 표준 JDBC API를 사용하고 Spring JDBC 프레임워크에 대한 언급이 없습니다. Spring JDBC 프레임워크 클래스는 Spring 빈 구성 파일을 생성하고 빈을 정의할 때 나타납니다. Spring 빈 구성 파일은 아래와 같습니다.

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="https://www.springframework.org/schema/beans"
	xmlns:xsi="https://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="https://www.springframework.org/schema/beans https://www.springframework.org/schema/beans/spring-beans.xsd">

	<bean id="employeeDAO" class="com.journaldev.spring.jdbc.dao.EmployeeDAOImpl">
		<property name="dataSource" ref="dataSource" />
	</bean>
	
	<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/TestDB" />
		<property name="username" value="pankaj" />
		<property name="password" value="pankaj123" />
	</bean>

</beans>

우선 우리는 DriverManagerDataSource 클래스의 DataSource 객체를 생성하고 있습니다. 이 클래스는 사용할 수 있는 DataSource의 기본 구현을 제공합니다. MySQL 데이터베이스 URL, 사용자 이름 및 암호를 DataSource 빈에 속성으로 전달하고 있습니다. 다시 말해 dataSource 빈이 EmployeeDAOImpl 빈으로 설정되고, Spring JDBC 구현이 준비되었습니다. 이 구현은 느슨하게 결합되어 있으며 다른 구현으로 전환하거나 다른 데이터베이스 서버로 이동하려면 빈 구성에서 해당 변경만 하면 됩니다. 이것은 Spring JDBC 프레임워크가 제공하는 주요 이점 중 하나입니다.

Spring JDBC 테스트 클래스

모든 것이 잘 작동하는지 확인하기 위해 간단한 테스트 클래스를 작성해 봅시다.

package com.journaldev.spring.jdbc.main;

import java.util.List;
import java.util.Random;

import org.springframework.context.support.ClassPathXmlApplicationContext;

import com.journaldev.spring.jdbc.dao.EmployeeDAO;
import com.journaldev.spring.jdbc.model.Employee;

public class SpringMain {

	public static void main(String[] args) {
		//Spring Context 가져오기
		ClassPathXmlApplicationContext ctx = new ClassPathXmlApplicationContext("spring.xml");
		
		//EmployeeDAO Bean 가져오기
		EmployeeDAO employeeDAO = ctx.getBean("employeeDAO", EmployeeDAO.class);
		
		//JDBC CRUD 작업에 대한 일부 테스트 실행
		Employee emp = new Employee();
		int rand = new Random().nextInt(1000);
		emp.setId(rand);
		emp.setName("Pankaj");
		emp.setRole("Java Developer");
		
		//Create
		employeeDAO.save(emp);
		
		//Read
		Employee emp1 = employeeDAO.getById(rand);
		System.out.println("Employee Retrieved::"+emp1);
		
		//Update
		emp.setRole("CEO");
		employeeDAO.update(emp);
		
		//모두 가져오기
		List empList = employeeDAO.getAll();
		System.out.println(empList);
		
		//Delete
		employeeDAO.deleteById(rand);
		
		//Spring Context 닫기
		ctx.close();
		
		System.out.println("DONE");
	}

}

I am using Random Class to generate random number for employee id. When we run above program, we get following output.

Mar 25, 2014 12:54:18 PM org.springframework.context.support.ClassPathXmlApplicationContext prepareRefresh
INFO: Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@4b9af9a9: startup date [Tue Mar 25 12:54:18 PDT 2014]; root of context hierarchy
Mar 25, 2014 12:54:18 PM org.springframework.beans.factory.xml.XmlBeanDefinitionReader loadBeanDefinitions
INFO: Loading XML bean definitions from class path resource [spring.xml]
Mar 25, 2014 12:54:19 PM org.springframework.jdbc.datasource.DriverManagerDataSource setDriverClassName
INFO: Loaded JDBC driver: com.mysql.jdbc.Driver
Employee saved with id=726
Employee Found::{ID=726,Name=Pankaj,Role=Java Developer}
Employee Retrieved::{ID=726,Name=Pankaj,Role=Java Developer}
Employee updated with id=726
[{ID=726,Name=Pankaj,Role=CEO}]
Employee deleted with id=726
Mar 25, 2014 12:54:19 PM org.springframework.context.support.ClassPathXmlApplicationContext doClose
INFO: Closing org.springframework.context.support.ClassPathXmlApplicationContext@4b9af9a9: startup date [Tue Mar 25 12:54:18 PDT 2014]; root of context hierarchy
DONE

Spring JdbcTemplate 예제

DAO 구현 클래스를 살펴보면 Connection, PreparedStatements 및 ResultSet을 열고 닫는 부분에 많은 보일러플레이트 코드가 있습니다. 누군가가 리소스를 제대로 닫지 않는다면 리소스 누출로 이어질 수 있습니다. 이러한 오류를 피하기 위해 org.springframework.jdbc.core.JdbcTemplate 클래스를 사용할 수 있습니다. Spring JdbcTemplate은 Spring JDBC 코어 패키지의 중심 클래스이며 쿼리를 실행하고 ResultSet을 자동으로 구문 분석하여 객체 또는 객체 목록을 가져 오기 위한 많은 메서드를 제공합니다. 우리가 해야 할 일은 인수를 Object 배열로 제공하고 PreparedStatementSetterRowMapper와 같은 콜백 인터페이스를 구현하여 인수를 매핑하거나 ResultSet 데이터를 빈 객체로 변환하는 것입니다. 다른 종류의 쿼리를 실행하기 위해 Spring JdbcTemplate 클래스를 사용하는 EmployeeDAO의 다른 구현을 살펴 보겠습니다.

package com.journaldev.spring.jdbc.dao;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;

import com.journaldev.spring.jdbc.model.Employee;

public class EmployeeDAOJDBCTemplateImpl implements EmployeeDAO {

	private DataSource dataSource;

	public void setDataSource(DataSource dataSource) {
		this.dataSource = dataSource;
	}
	
	@Override
	public void save(Employee employee) {
		String query = "insert into Employee (id, name, role) values (?,?,?)";
		
		JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
		
		Object[] args = new Object[] {employee.getId(), employee.getName(), employee.getRole()};
		
		int out = jdbcTemplate.update(query, args);
		
		if(out !=0){
			System.out.println("Employee saved with id="+employee.getId());
		}else System.out.println("Employee save failed with id="+employee.getId());
	}

	@Override
	public Employee getById(int id) {
		String query = "select id, name, role from Employee where id = ?";
		JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
		
		//RowMapper 익명 클래스를 사용하면 재사용을 위해 별도의 RowMapper를 만들 수 있습니다.
		Employee emp = jdbcTemplate.queryForObject(query, new Object[]{id}, new RowMapper(){

			@Override
			public Employee mapRow(ResultSet rs, int rowNum)
					throws SQLException {
				Employee emp = new Employee();
				emp.setId(rs.getInt("id"));
				emp.setName(rs.getString("name"));
				emp.setRole(rs.getString("role"));
				return emp;
			}});
		
		return emp;
	}

	@Override
	public void update(Employee employee) {
		String query = "update Employee set name=?, role=? where id=?";
		JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
		Object[] args = new Object[] {employee.getName(), employee.getRole(), employee.getId()};
		
		int out = jdbcTemplate.update(query, args);
		if(out !=0){
			System.out.println("Employee updated with id="+employee.getId());
		}else System.out.println("No Employee found with id="+employee.getId());
	}

	@Override
	public void deleteById(int id) {

		String query = "delete from Employee where id=?";
		JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
		
		int out = jdbcTemplate.update(query, id);
		if(out !=0){
			System.out.println("Employee deleted with id="+id);
		}else System.out.println("No Employee found with id="+id);
	}

	@Override
	public List getAll() {
		String query = "select id, name, role from Employee";
		JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
		List empList = new ArrayList();

		List> empRows = jdbcTemplate.queryForList(query);
		
		for(Map empRow : empRows){
			Employee emp = new Employee();
			emp.setId(Integer.parseInt(String.valueOf(empRow.get("id"))));
			emp.setName(String.valueOf(empRow.get("name")));
			emp.setRole(String.valueOf(empRow.get("role")));
			empList.add(emp);
		}
		return empList;
	}

}

Spring JdbcTemplate에 대한 위 코드의 중요한 포인트는 다음과 같습니다:

  • PreparedStatement 인수를 전달하기 위해 Object 배열 사용, PreparedStatementSetter 구현을 사용할 수도 있지만 Object 배열을 전달하는 것이 쉽습니다.
  • 연결, 문 또는 결과 집합을 열고 닫는 데 관련된 코드가 없습니다. 이 모든 것은 Spring JdbcTemplate 클래스에 의해 내부적으로 처리됩니다.
  • queryForObject() 메서드에서 ResultSet 데이터를 Employee 빈 객체로 매핑하기 위한 RowMapper 익명 클래스 구현.
  • queryForList() 메서드는 Map의 목록을 반환하며, Map에는 열 이름을 키로 매핑하고 데이터베이스 행과 일치하는 행 데이터가 값으로 포함됩니다.

Spring JdbcTemplate 구현을 사용하려면 Spring Bean 구성 파일에서 아래와 같이 employeeDAO 클래스를 변경하면 됩니다.

<bean id="employeeDAO" class="com.journaldev.spring.jdbc.dao.EmployeeDAOJDBCTemplateImpl">
	<property name="dataSource" ref="dataSource" />
</bean>

메인 클래스를 실행하면 Spring JdbcTemplate 구현의 출력은 일반적인 JDBC 구현과 유사한 것으로 나타날 것입니다. Spring JDBC 예제 튜토리얼은 여기까지입니다. 아래 링크에서 샘플 프로젝트를 다운로드하여 더 많이 배우고 실험해 보세요.

Spring JDBC 프로젝트 다운로드

Source:
https://www.digitalocean.com/community/tutorials/spring-jdbc-example