Spring : Spring JDBC 예제

Spring JDBC 예제

Spring JDBC를 예제를 통해 알아보겠습니다!

Spring JDBC를 이용한 DAO작성 예제를 위한 다이어그램

img

먼저 Spring 컨테이너인 ApplicationContext는 설정 파일로 ApplicationConfig라는 클래스를 읽어들입니다.

ApplicationConfigcomponentScan 어노테이션이 DAO 클래스를 찾습니다. 찾은 DAO 클래스는 스프링 컨테이너가 관리하게 됩니다.

ApplicationContextDBConfigimport하게 되고 DBConfig 클래스에서는 Data Source와 트랜잭션 매니저 객체를 생성합니다.

DAO는 필드로 NamedParameterJdbcTemplateSimpleJdbcInsert를 가지게 됩니다. 두 개의 객체 모두 SQL의 실행을 편리하게 하도록 Spring JDBC에서 제공하는 객체이기 때문에 DB연결을 위해서 내부적으로 Data Source를 필요로 합니다. 이 두개의 객체는 RoleDao 생성자에서 초기화를 하고 RoleDao의 메서드들을 구현하게 됩니다.

Spring JDBC를 사용하는 사용자는 파라미터와 SQL을 가장 많이 신경 써야 됩니다. SQLRoleDaoSQL상수로 정의를 해놓음으로써 나중에 SQL이 변경될 경우에 좀 더 편하게 수정할 수 있도록 합니다.

한 건의 Role 정보를 저장하고 전달하기 위한 목적으로 Role DTO가 사용되고 있는 것을 볼 수 있습니다.

Pom.xml

<?xml version="1.0" encoding="UTF-8"?>

<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
  xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>

  <groupId>kr.or.connect</groupId>
  <artifactId>daoexam</artifactId>
  <version>0.0.1-SNAPSHOT</version>

  <name>daoexam</name>
  <!-- FIXME change it to the project's website -->
  <url>http://www.example.com</url>

  <properties>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    <maven.compiler.source>1.8</maven.compiler.source>
    <maven.compiler.target>1.8</maven.compiler.target>
    <spring.version>4.3.5.RELEASE</spring.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.11</version>
      <scope>test</scope>
    </dependency>
    
    <!-- Spring -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-context</artifactId>
		<version>${spring.version}</version>
	</dependency>
	
	<!-- JDBC -->
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-jdbc</artifactId>
		<version>${spring.version}</version>
	</dependency>
	<dependency>
		<groupId>org.springframework</groupId>
		<artifactId>spring-tx</artifactId>
		<version>${spring.version}</version>
	</dependency>
	
	<!-- basic data source -->
	<dependency>
		<groupId>org.apache.commons</groupId>
		<artifactId>commons-dbcp2</artifactId>
		<version>2.1.1</version>
	</dependency>

	
	<dependency>
		<groupId>mysql</groupId>
		<artifactId>mysql-connector-java</artifactId>
		<version>5.1.45</version>
	</dependency>
		
  </dependencies>

  <build>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
      
      	<plugin>
			<groupId>org.apache.maven.plugins</groupId>
			<artifactId>maven-compiler-plugin</artifactId>
			<version>3.6.1</version>
			<configuration>
				<source>1.8</source>
				<target>1.8</target>
			</configuration>
		</plugin>
        <!-- clean lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#clean_Lifecycle -->
        <plugin>
          <artifactId>maven-clean-plugin</artifactId>
          <version>3.1.0</version>
        </plugin>
        <!-- default lifecycle, jar packaging: see https://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_jar_packaging -->
        <plugin>
          <artifactId>maven-resources-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-compiler-plugin</artifactId>
          <version>3.8.0</version>
        </plugin>
        <plugin>
          <artifactId>maven-surefire-plugin</artifactId>
          <version>2.22.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-jar-plugin</artifactId>
          <version>3.0.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-install-plugin</artifactId>
          <version>2.5.2</version>
        </plugin>
        <plugin>
          <artifactId>maven-deploy-plugin</artifactId>
          <version>2.8.2</version>
        </plugin>
        <!-- site lifecycle, see https://maven.apache.org/ref/current/maven-core/lifecycles.html#site_Lifecycle -->
        <plugin>
          <artifactId>maven-site-plugin</artifactId>
          <version>3.7.1</version>
        </plugin>
        <plugin>
          <artifactId>maven-project-info-reports-plugin</artifactId>
          <version>3.0.0</version>
        </plugin>
      </plugins>
    </pluginManagement>
  </build>
</project>

먼저 pom.xmlmaven 플러그인 버전, Spring, JDBC, DataSource, mysql dependency와 properties를 추가해줍니다.

Role.java

package kr.or.connect.daoexam.dto;

public class Role {
	private int roleId;
	private String description;
	
	public int getRoleId() {
		return roleId;
	}
	public void setRoleId(int roleId) {
		this.roleId = roleId;
	}

	public String getDescription() {
		return description;
	}
	public void setDescription(String description) {
		this.description = description;
	}
	
	@Override
	public String toString() {
		return "Role [roleID=" + roleId + ", description=" + description + "]";
	}
}

정보를 저장하고 전달하기 위한 목적DTO입니다.

ApplicationConfig.java

package kr.or.connect.daoexam.config;

import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;

@Configuration
@ComponentScan(basePackages = {"kr.or.connect.daoexam.dao"})
@Import({DBConfig.class}) 
public class ApplicationConfig {

}

@ComponentScan를 이용해 kr.or.connect.daoexam.dao 패키지안의 파일들을 스캔합니다.

@Import라는 어노테이션을 이용해 설정 파일을 여러개로 나눠서 작성할 수 있습니다. DBConfig 설정파일을 import 합니다.

{ }안에 넣으면 여러가지를 지정할 수 있습니다.

DBConfig.java

package kr.or.connect.daoexam.config;

import javax.sql.DataSource;

import org.apache.commons.dbcp2.BasicDataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@Configuration
public class DBConfig {
	private String driverClassName = "com.mysql.jdbc.Driver";
    private String url = "jdbc:mysql://localhost:3306/connectdb?useUnicode=true&characterEncoding=utf8&useSSL=false";

    private String username = "connectuser";
    private String password = "connect123!@#";
    
    @Bean
    public DataSource dataSource() {
    	BasicDataSource dataSource = new BasicDataSource();
    	dataSource.setDriverClassName(driverClassName);
    	dataSource.setUrl(url);
    	dataSource.setUsername(username);
    	dataSource.setPassword(password);
    	return dataSource;
    }
}

DBConfig는 DB정보들을 저장해놓고, DataSource객체를 생성하고 정보를 설정해줍니다. 이 때, DataSource는 외부 라이브러리 이므로 @Bean으로 생성해야 합니다.

RoleDaoSqls.java

package kr.or.connect.daoexam.dao;

public class RoleDaoSqls {
	public static final String SELECT_ALL = "SELECT role_id, description FROM role order by role_id";
	public static final String UPDATE = "UPDATE role SET description = :description WHERE ROLE_ID = :roleId";
	public static final String SELECT_BY_ROLE_ID = "SELECT role_id, description FROM role where role_id = :roleId";
	public static final String DELETE_BY_ROLE_ID = "DELETE FROM role WHERE role_id = :roleId";
}

sql문만 static으로 따로 관리해주는 class 입니다.

JDBC에서 전체를 출력할 때 *를 쓰지 않고 컬럼 하나하나 작성해주는게 나중에 더 알아보기 쉽습니다.

이제까지 사용했던 query문과 똑같지만 바인딩 되는 부분이 ?가 아니고 :속성명을 붙여 작성해줍니다.

RoleDao.java

package kr.or.connect.daoexam.dao;

import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import javax.sql.DataSource;

import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.BeanPropertySqlParameterSource;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.jdbc.core.namedparam.SqlParameterSource;
import org.springframework.jdbc.core.simple.SimpleJdbcInsert;
import org.springframework.stereotype.Repository;

import kr.or.connect.daoexam.dto.Role;

// static import를 사용하면 RoleDaoSqls 객체에 선언된 static 변수를 클래스 이름없이 바로 사용할 수 있습니다.
import static kr.or.connect.daoexam.dao.RoleDaoSqls.*;

// 이런 Dao객체에는 저장소의 역할을 한다는 의미로 "@Repository" 어노테이션을 붙입니다. "ComponentScan"이 스캔해줍니다.
@Repository
public class RoleDao {
	private NamedParameterJdbcTemplate jdbc;

	// DBMS에선 대소문자를 거의 구문하지 않기 때문에 '_'를 이용해 명명 규칙을 'role_id'같은 형태로 사용하고
	// Java는 camel표기법을 사용해 roleId 같은 형식으로 사용합니다.
	// "BeanPropertyRowMapper"는 DBMS와 Java의 이런 이름 규칙을 맞춰주는 기능을 가집니다.
	// 여기서 주의할 점은 "role_Id"가 "roleId"가 되는 것이기 때문에 자바에서 "roleID"같이 명명 규칙이 맞지 않는다면 값을 제대로 불러올 수 없습니다.
	// getter, setter도 마찬가지 입니다.
	private RowMapper<Role> rowMapper = BeanPropertyRowMapper.newInstance(Role.class);
	
	private SimpleJdbcInsert insertAction;
	
	// RoleDao객체를 만들 때 "dataSource"를 이용해 "NamedParameterJdbcTemplate"과 "SimpleJdbcInsert" 객체를 같이 생성해줍니다.
	public RoleDao(DataSource dataSource) {
		this.jdbc = new NamedParameterJdbcTemplate(dataSource);
        
		//  "SimpleJdbcInsert"를 생성할 때 테이블을 지정해줍니다.
		this.insertAction = new SimpleJdbcInsert(dataSource).withTableName("role");
	}
	
	public List<Role> selectAll() {
		
		// 두 번째 파라미터는 sql문(SELECT_ALL)에 바인딩 할 값이 있을 경우에 바인딩 할 값을 전달할 목적으로 "Collections.emptyMap()" 을 사용합니다.
		// 세 번째 파라미터는 "BeanPropertyRowMapper"객체를 이용해서 select 한 건, 한 건의 결과를 DTO에 저장하는 목적으로 사용하게 됩니다.
		// query() 메서드는 결과가 여러 건이었을 때 내부적으로 반복하면서 DTO를 생성하고 List에다 담아준 후 반환해줍니다.
		return jdbc.query(SELECT_ALL, Collections.emptyMap(), rowMapper);
	}
	

	public int insert(Role role) {
		// "BeanPropertySqlParameterSource"는 Role 객체를 받아, Role 객체에 있는 값을 맵으로 바꿔줍니다.
		// 이 때, 선언한 "roleId" 를 DBMS의 컬럼명 "role_id"로 알아서 매핑되는 객체를 생성해줍니다.
		SqlParameterSource params = new BeanPropertySqlParameterSource(role);
        
		// "SimpleJdbcInsert"을 이용하여 INSERT를 하게되면 따로 sql문이 필요없습니다.
		return insertAction.execute(params);
	}
	
	public int update(Role role) {
		SqlParameterSource params = new BeanPropertySqlParameterSource(role);
		return jdbc.update(UPDATE, params);
	}
	
	public int deleteById(Integer id) {
		// Map으로 바꿔주는 "SqlParameterSource"를 사용해도 되지만 값이 딱 하나만 들어오는 경우에는 "Collections.singletonMap"을 사용하는게 좋습니다.
		Map<String, ?> params = Collections.singletonMap("roleId", id);
		return jdbc.update(DELETE_BY_ROLE_ID, params);
	}
	
	public Role selectById(Integer id) {
		try {
			Map<String, ?> params = Collections.singletonMap("roleId", id);
            
			// 한 건 select할 때는 "queryForObject"를 사용합니다.
			return jdbc.queryForObject(SELECT_BY_ROLE_ID, params,rowMapper);
		
		// select 할 때 조건에 맞는 값이 없으면 "EmptyResultDataAccessException" Exception이 발생하게 됩니다.
		} catch (EmptyResultDataAccessException e) {
			return null;
		}
	}
}

스프링 JDBC의 주요 부분입니다. 코드가 길기 때문에 주석으로 바로 보는게 편할 것 같아 주석으로 달았습니다.

JDBCTest.java

package kr.or.connect.daoexam.main;

import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import kr.or.connect.daoexam.config.ApplicationConfig;
import kr.or.connect.daoexam.dao.RoleDao;
import kr.or.connect.daoexam.dto.Role;

public class JDBCTest {
	public static void main(String[] args) {
		ApplicationContext ac = new AnnotationConfigApplicationContext(ApplicationConfig.class);
		
		RoleDao roleDao = ac.getBean(RoleDao.class);
		
		Role role = new Role();
        
        // selectAll
//      List<Role> list = roleDao.selectAll();		
//		for(Role role : list) {
//			System.out.println(role);
//		}
		
		// insert
//		role.setRoleId(500);
//		role.setDescription("CEO");
//		int count = roleDao.insert(role);
//		System.out.println(count + "입력하였습니다.");
		
		// update
//		role.setRoleId(102);
//		role.setDescription("PROGRAMMER");
//		int count = roleDao.update(role);
//		System.out.println(count + "건 수정하였습니다..");
		
		// selectById
		Role resultRole = roleDao.selectById(102);
		System.out.println(resultRole);
		
		// delete
		int deletecount = roleDao.deleteById(500);
		System.out.println(deletecount + "건 삭제하였습니다.");
		
		Role resultRole2 = roleDao.selectById(500);
		System.out.println(resultRole2);
		
	}
}

RoleDao의 기능들을 테스트 해봅니다.

여기서 어디에도 roleDao 내부에서 사용되는 DataSource를 생성해주는 부분이 없습니다. 하지만 기능들이 다 실행이 되는데 왜 그럴까요? 그 이유는ApplicationContext가 어노테이션들을 찾아서 Bean Factory를 만들고, 이 때 파라미터를 갖지 않는 Bean(DataSource)을 먼저 생성하고 파라미터를 가지는 Bean(roleDao)들에게 자동으로 적합한 파라미터를 전달해주고 생성해주기 때문입니다.


참고 : https://www.boostcourse.org/web326/lecture/258531/?isDesc=false

댓글남기기