BoostCource/Back-end

#03. BE - Spring JDBC 기초와 실습

칸타탓 2018. 8. 2. 21:45

<3. Spring JDBC>

http://www.edwith.org/boostcourse-web





* Spring JDBC


JDBC 프로그래밍을 보면 반복되는 개발 요소가 있다. (드라이버를 로딩하고, statement 객체를 얻어내고, ResultSet객체로 결과를 받아낸 후 객체들을 일일이 닫아주는 등...) 이러한 반복적인 요소는 생산성을 떨어뜨리는 주요인이다.

Spring JDBC를 사용하면 개발하기 지루한 JDBC의 모든 저수준 세부사항을 스프링 프레임워크가 처리해준다.

=> 개발자는 필요한 부분만 개발하면 되는 것!



- Spring JDBC에서 개발자가 해야 할 일은?

연결 파라미터 정의에서는 어떤 데이터베이스에 접속할 것인지, SQL문, 파라미터가 어떤 값을 가지고 쿼리문을 수행할 것인지는 프레임워크가 알 수 없기 떄문에 이 부분은 개발자가 지정해준다.



- Spring JDBC 패키지


  • org.springframework.jdbc.core
  • org.springframework.jdbc.datasource
  • org.springframework.jdbc.object
  • org.springframework.jdbc.support



- JDBC Template


  • org.springframework.jdbc.core에서 가장 중요한 클래스이다.
  • 리소스 생성, 해지를 처리해서 연결을 닫는 것을 잊어 발생하는 문제 등을 피할 있도록 한다.
  • 스테이먼트(Statement) 생성과 실행을 처리한다.
  • SQL 조회, 업데이트, 저장 프로시저 호출, ResultSet 반복호출 등을 실행한다.
  • JDBC 예외가 발생할 경우 org.springframework.dao 패키지에 정의되어 있는 일반적인 예외로 변환시킨다.

 



Jdbcemplate 실습


JdbcTemplate select 예제1

열의 수 구하기

실제 테이블에 몇 건의 데이터가 들어있는지를 출력한다.

int rowCount = this.jdbcTemplate.queryForInt("select count(*) from t_actor");


JdbcTemplate select 예제2

변수 바인딩 사용하기

두 번째 파라미터를 이용해서 ?에 값을 채워서 쿼리를 실행한다.

int countOfActorsNamedJoe = this.jdbcTemplate.queryForInt("select count(*) from t_actor where first_name = ?", "Joe"); 


JdbcTemplate select 예제3

String값으로 결과 받기

세 번째 파라미터르 받아들인 값에 리턴받을 타입을 적어주면 원하는 타입으로 리턴을 받을 수 있다.

String lastName = this.jdbcTemplate.queryForObject("select last_name from t_actor where id = ?", new Object[]{1212L}, String.class); 


JdbcTemplate select 예제4

한 건 조회하기

해당하는 컬럼을 원하는 객체에 매핑을 해줄 때 사용하는 RowMapper 객체를 이용한다.

Actor actor = this.jdbcTemplate.queryForObject(

  "select first_name, last_name from t_actor where id = ?",

  new Object[]{1212L},

  new RowMapper<Actor>() {

    public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {

      Actor actor = new Actor();

      actor.setFirstName(rs.getString("first_name"));

      actor.setLastName(rs.getString("last_name"));

      return actor;

    }

  });


JdbcTemplate select 예제5

여러 건 조회하기

한 건을 조회할 때와는 다르게 query 메서드를 이용한다.

List<Actor> actors = this.jdbcTemplate.query(

  "select first_name, last_name from t_actor",

  new RowMapper<Actor>() {

    public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {

      Actor actor = new Actor();

      actor.setFirstName(rs.getString("first_name"));

      actor.setLastName(rs.getString("last_name"));

      return actor;

    }

  });


JdbcTemplate select 예제6

중복 코드 제거 (1건 구하기와 여러 건 구하기가 같은 코드에 있을 경우)

public List<Actor> findAllActors() {

  return this.jdbcTemplate.query( "select first_name, last_name from t_actor", new ActorMapper());

}

private static final class ActorMapper implements RowMapper<Actor> {

  public Actor mapRow(ResultSet rs, int rowNum) throws SQLException {

    Actor actor = new Actor();

    actor.setFirstName(rs.getString("first_name"));

    actor.setLastName(rs.getString("last_name"));

    return actor;

  }

}


JdbcTemplate insert 예제

INSERT 하기

모두 update 메서드를 이용해서 실행한다.

this.jdbcTemplate.update("insert into t_actor (first_name, last_name) values (?, ?)",  "Leonor", "Watling");


JdbcTemplate update 예제

UPDATE 하기

this.jdbcTemplate.update("update t_actor set = ? where id = ?",  "Banjo", 5276L);


JdbcTemplate delete 예제

DELETE 하기

this.jdbcTemplate.update("delete from actor where id = ?", Long.valueOf(actorId));


- JdbcTemplate 외의 접근방법

  • NamedParameterJdbcTemplate: ? 대신에 이름을 사용해서 바인딩 할 수 있도록 해준다.

  • SimpleJdbcTemplate: JdbcTemplate, NamedParameterJdbcTemplate에서 가장 빈번하게 사용되는 작업을 합쳐놓은 객체

  • SimpleJdbcInsert: 조금 더 간단하게 insert 작업을 수행할 수 있다.


 



* DTO란?


  • DTO란 Data Transfer Object의 약자

  • 계층 간 데이터 교환을 위한 자바 빈즈이다.

  • 여기서의 계층이란 컨트롤러, 뷰, 비지니스 계층, 퍼시스턴스 계층을 의미한다.

  • 일반적으로 DTO는 로직을 가지고 있지 않으며 순수한 데이터 객체이다.

  • 필드와 getter, setter를 가진다.

  • 추가적으로 toString(), equals(), hashCode()등의 Object 메소드를 오버라이딩 할 수 있다.

  • 데이터를 한꺼번에 가지고 다닐 수 있는 용도로 사용된다고 할 수 있다.

 


- DTO의 예

public class ActorDTO {
    private Long id;
    private String firstName;
    private String lastName;
    public String getFirstName() {
        return this.firstName;
    }
    public String getLastName() {
        return this.lastName;
    }
    public Long getId() {
        return this.id;
    }
    // ......
}






* DAO란?


  • DAO란 Data Access Object의 약자로 데이터를 조회하거나 조작하는 기능을 전담하도록 만든 객체이다.

  • 보통 데이터베이스를 조작하는 기능을 전담하는 목적으로 만들어진다.






* ConnectionPool 이란?


커넥션 풀은 미리 커넥션을 여러 개 맺어 두고 커넥션이 필요하면 커넥션 풀에게 빌려서 사용한 후 반납한다.

사용 후 반납을 꼭 해야한다. 사용 가능한 커넥션이 없어서 장애를 발생시킬 수 있기 때문이다.





* DataSource란?

  • DataSource는 커넥션 풀을 관리하는 목적으로 사용되는 객체
  • DataSource를 이용해 커넥션을 얻어오고 반납하는 등의 작업을 수행한다.





Spring JDBC를 이용한 DAO작성 실습


* 실습 프로그램의 구조

  • applicationContext는 설정파일로 ApplicationConfig라는 파일을 읽어들인다. (Spring이 제공해주는 클래스, IoC/DI 컨테이너라고 할 수 있다.)
  • ApplicationConfig는 componentScan 어노테이션이 DAO를 찾을 수 있도록 설정한다.
  • 찾은 모든 DAO 클래스는 스프링 컨테이너가 관리한다.
  • applicationContext는 DBConfig를 import 한다.
  • DBConfig 클래스에서는 Data Source와 TransactionManager 객체를 생성한다.
  • DAO는 필드로 NamedParameterJdbcTemplate, SimpleJdbcInsert(Spring 제공해주는 객체)를 가지며 두 개의 이 객체는 모두 DataSource를 필요로 한다.
  • SQL은 RoldDao에 상수로 정의되어 있으며, RoleDaosqls에서 SQL을 작성하여 수정이 쉽도록 한다. 
  • 한 건의 Role 정보를 저장하고 전달하기 위한 목적으로 DTO를 사용한다.




데이터베이스 접속 실습


설정과 라이브러리 추가를 위해 아래와 같이 pom.xml 에 추가하기 -> 추가한 후 Maven 업데이트 꼭 해주기.

DataSource는 아파치에서 제공하는 commons-dbcp2를 사용한다.

<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>
	<packaging>jar</packaging>

	<name>daoexam</name>
	<url>http://maven.apache.org</url>

	<properties>
		<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<spring.version>4.3.5.RELEASE</spring.version>
	</properties>

	<dependencies>
		<!-- Spring -->
		<dependency>
			<groupId>org.springframework</groupId>
			<artifactId>spring-context</artifactId>
			<version>${spring.version}</version>
		</dependency>
		<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>

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>3.8.1</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
	<build>
		<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>
		</plugins>
	</build>

</project>

 

ApplicationConfig.java

설정에 관련된 부분으로 @Configuration 어노테이션을 붙여 설정에 대한 정보들을 읽어들일 수 있도록 한다.

DBConfig를 Import하여 데이터베이스에 관련된 설정을 따로 빼서 관리한다. (하나에 클래스가 모든 정보를 가지고 있으면 유지보수가 힘들어지기 때문이다.)

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
@Import({DBConfig.class})
public class ApplicationConfig {

}

 

DBConfig.java

Database 설정만 따로 가지고 있다. 드라이버 설정, 사용하는 데이터베이스, 유저 정보를 추가한다.

@Bean 어노테이션을 추가하여 DataSource를 생성하는 클래스를 추가한다. 여기서 만들어진 DataSource 객체는 커넥션을 관리할 때 쓰인다.

DBConfig도 Configuration이므로 @Configuration 어노테이션이 필요하다. 또한 트랜잭션을 위한 어노테이션도 추가해준다.

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
@EnableTransactionManagement
public class DBConfig {
	private String driverClassName = "com.mysql.jdbc.Driver";
    private String url = "jdbc:mysql://localhost:3306/connectdb?useUnicode=true&characterEncoding=utf8";

    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;

    }
}

 

DataSourceTest.java

데이터베이스에 접속이 잘 되는지 확인하기 위한 클래스

ApplicationContext로어떤 클래스에서 정보를 읽어올 것인지를 설정한다. (ApplicationConfig.class)

그리고 getBean을 이용하여 DataSource 객체를 얻어온다. (DataSource.class)

그 후 커넥션 객체를 사용하여 연결이 되었는지 확인한다.

package kr.or.connect.daoexam.main;

import java.sql.Connection;

import javax.sql.DataSource;

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

import kr.or.connect.daoexam.config.ApplicationConfig;

public class DataSourceTest {

	public static void main(String[] args) {
		ApplicationContext ac = new AnnotationConfigApplicationContext(ApplicationConfig.class);
		DataSource ds = ac.getBean(DataSource.class);
		Connection conn = null;
		try {
			conn = ds.getConnection();
			if(conn != null)
				System.out.println("접속 성공^^");
		}catch (Exception e) {
			e.printStackTrace();
		}finally {
			if(conn != null) {
				try {
					conn.close();
				}catch (Exception e) {
					e.printStackTrace();
				}
			}
		}
	}

}






Spring JDBC 작성 실습 (Select)


Role.java

select 해오기 위해서는 데이터의 이동이 필요하므로 DTO를 생성한다.

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 + "]";
	}
	
}

 

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";
}

 

RoleDao.java

컴포넌트, 서비스, 레파지토리, 컨트롤러 어노테이션 중에 @Repository를 이용하여 저장소의 역할을 한다고 표시한다.

Named~(바인딩 할 때 ? 말고 문자열을 사용하기 위해), Simple~ 객체를 사용한다.

생성자 부분에서는 DataSource를 파라미터로 받아들이고 있다. @Bean으로 등록했던 datasource가 파라미터로 전달된다.

RoleDaoSqls의 쿼리문을 사용하기 위해서는 import static을 해주어야 한다.

BeanPropertyRowMapper 객체를 이용해서 컬럼의 값을 자동으로 DTO에 담아주게 된다.


* 자바와 데이터베이스는 이름 규칙이 다른데, BeanPropertyRowMapper는 자동으로 다른 이름 규칙을 맞추어준다.

package kr.or.connect.daoexam.dao;

import static kr.or.connect.daoexam.dao.RoleDaoSqls.*;

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;
@Repository
public class RoleDao {
	private NamedParameterJdbcTemplate jdbc;
	private SimpleJdbcInsert insertAction;
	private RowMapper<Role> rowMapper = BeanPropertyRowMapper.newInstance(Role.class);

	public RoleDao(DataSource dataSource) {
		this.jdbc = new NamedParameterJdbcTemplate(dataSource);
		this.insertAction = new SimpleJdbcInsert(dataSource)
                .withTableName("role");
	}
	
	public List<Role> selectAll(){
		return jdbc.query(SELECT_ALL, Collections.emptyMap(), rowMapper);
	}

}

 

ApplicationConfig.java 에 추가하기

ComponentScan으로 읽어낼 것이라는 설정을 해주어야 한다. 그래야 설정파일을 읽어낼 때 약속 된 어노테이션이 붙어있는 객체들을 찾아서 작업을 진행하기 때문이다.

때문에 자동으로 RoleDao에 @Repository 어노테이션이 붙어있는 클래스를 빈으로 등록해준다.

@ComponentScan(basePackages = { "kr.or.connect.daoexam.dao" })

 

SelectAllTest.java

Select를 테스트 할 수 있는 클래스

package kr.or.connect.daoexam.main;

import java.util.List;

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 SelectAllTest {

	public static void main(String[] args) {
		ApplicationContext ac = new AnnotationConfigApplicationContext(ApplicationConfig.class); 
		
		RoleDao roleDao =ac.getBean(RoleDao.class);

		List<Role> list = roleDao.selectAll();
		
		for(Role role: list) {
			System.out.println(role);
		}

	}

}





Spring JDBC 작성 실습 (Insert, Update)


RoleDaoSqls.java에 쿼리문 추가하기

public static final String UPDATE = "UPDATE role SET description = :description WHERE ROLE_ID = :roleId";


RoleDao.java

insert문에서는 SimpleJdbcInsert가 필요하므로 이 객체를 생성하는 코드를 추가한다.

withTableName은 어떤 테이블에 넣을 것인지를 설정하는 것이다.

jdbc.update 할 때 첫번째 파라미터는 sql, 두번째 파라미터는 맵 객체이다.

package kr.or.connect.daoexam.dao;

import static kr.or.connect.daoexam.dao.RoleDaoSqls.*;

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;
@Repository
public class RoleDao {
	private NamedParameterJdbcTemplate jdbc;
	private SimpleJdbcInsert insertAction;
	private RowMapper<Role> rowMapper = BeanPropertyRowMapper.newInstance(Role.class);

	public RoleDao(DataSource dataSource) {
		this.jdbc = new NamedParameterJdbcTemplate(dataSource);
		this.insertAction = new SimpleJdbcInsert(dataSource)
                .withTableName("role");

	}
	
	public int insert(Role role) {
		SqlParameterSource params = new BeanPropertySqlParameterSource(role);
		return insertAction.execute(params);
	}

	public int update(Role role) {
		SqlParameterSource params = new BeanPropertySqlParameterSource(role);
		return jdbc.update(UPDATE, params);
	}
	

}

 

JDBCTest.java

insert 테스트

insert 하기 전에 DTO 객체인 Role에 값이 들어있어야 하므로 set 해준다.

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();
		role.setRoleId(201);
		role.setDescription("PROGRAMMER");
		
		int count = roleDao.insert(role);
		System.out.println(count + "건 입력하였습니다.");
			
		int count = roleDao.update(role);
		System.out.println(count +  " 건 수정하였습니다.");
	}

}





Spring JDBC 작성 실습 (1건 Select, Delete)


RoleDaoSqls.java에 추가하기

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";


RoleDao.java에 추가하기

SqlParameterSource를 이용해서 객체를 이용해 map으로 바꿔도 되지만, delete는 값이 하나만 들어올 것이므로 Collections.singletonMap을 이용해보자.

해당 조건에 맞는 조건이 없을 때를 고려하여 예외처리를 해주어야 한다.

package kr.or.connect.daoexam.dao;

import static kr.or.connect.daoexam.dao.RoleDaoSqls.*;

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;
@Repository
public class RoleDao {
	private NamedParameterJdbcTemplate jdbc;
	private SimpleJdbcInsert insertAction;
	private RowMapper<Role> rowMapper = BeanPropertyRowMapper.newInstance(Role.class);

	public RoleDao(DataSource dataSource) {
		this.jdbc = new NamedParameterJdbcTemplate(dataSource);
		this.insertAction = new SimpleJdbcInsert(dataSource)
                .withTableName("role");
	}
	
	public int deleteById(Integer id) {
		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);
			return jdbc.queryForObject(SELECT_BY_ROLE_ID, params, rowMapper);		
		}catch(EmptyResultDataAccessException e) {
			return null;
		}
	}

}

 

JDBCTest.java에 추가하기

Role resultRole = roleDao.selectById(201);
System.out.println(resultRole);
		
int deleteCount = roleDao.deleteById(500);
System.out.println(deleteCount + "건 삭제하였습니다.");
	
Role resultRole2 = roleDao.selectById(500);
System.out.println(resultRole2);