Web/Java+Spring

Spring JDBC - DTO/DAO

WakaraNai 2021. 6. 2. 22:30
728x90
반응형

DTO

  • Data Transfer Object의 약자
  • 계층간 데이터 교환을 위한 자바빈즈
  • 여기서의 계층이란 컨트롤러 뷰, 비지니스 계층, 퍼시스턴스 계층을 의미
  • 일반적으로 DTO는 로직을 가지고 있지 않고, 순수한 데이터 객체
    • 데이터를 한꺼번에 들고 다닐 수 있도록 만들어짐
  • 필드와 getter, setter를 가진다. 추가적으로 toString(), equals(), hashCode()등의 Object 메소드를 오버라이딩 할 수 있습니다.
    • 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

  • DB연결은 비용이 많이 듭니다.
    • 프로그램이 DBMS에 접속하는 시간이 조금 오래 걸리거나, 자원소모가 심한 경우
  • 커넥션 풀은 미리 커넥션을 여러 개 맺어 둡니다.
  • 커넥션이 필요하면 커넥션 풀에게 빌려서 사용한 후 반납합니다.
    • Client 1, Client 2가 ConnectionPool로부터 Connection 객체를 얻어서 사용
    • 그 다음엔 Client 1, Client 3이 다시 Connection을 얻어서 사용
    • Client 1이 Connection을 종료하면 사용한 Connection을 반납
    • 마지막으로 Client 3이 Connection 종료하여 반납됨
    • 이렇게 Connection을 되도록 빨리 사용하고 반납해야 함
  • 커넥션을 반납하지 않으면 어떻게 될까요?
    • ConnectionPool에서 사용가능한 Connection이 없어서
    • 프로그램이 늦어지거나, 심할 경우 장애를 발생시킴

 

 

DataSource

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

 

 

 

Spring JDBC로 DAO 작성해보기

 

DB 접속

  1. ApplicationConfig 클래스
    1. @Import
  2. DBConfig 클래스
    1. @EnableTransactionManagement
    2. DataSource
  3. DataSourceTest 실행 클래스
    1. ApplicationContext - AnnotationConfigApplicationContext
    2. DataSource(프로그램 속 Component들), Connection
  4. DTO 클래스 
    1. row 한 건에 대한 정보, column들을 필드와 getter, setter로 표현
  5. DaoSqls 클래스
    1. 필요한 SQL문을 상수 형태로 담는 곳
  6. DAO 클래스
    1. @Repository
    2. NamedParameterJdbcTemplate(dataSource) 객체
    3. DaoSqls 클래스 속 SQL문을 각각 하나의 메소드로 생성
  7. ApplicationConfig.java 수정
    1. @ComponentScan(basePackages = {"패키지명"}) 추가
  8. SelectAllTest.java로 테스트
  9. INSERT
    1. RoleSqls에 insert 문 추가
    2. new SimpleJdbcInsert(dataSource).withTableName("");
    3. insert용 메소드를 DAO 클래스에 작성
  10. UPDATE, (DELETE도 비슷)
    1. RoleSqls에 update 문 추가
    2. SqlParameterSource params = new BeanPropertySqlParameterSource(role);
    3. return jdbc.update(UPDATE, params);
  11. DELETE
    1. RoleSqls에 delete 문 추가
    2. Map<String, Integer> params = Collections.singletonMap("roleId", id); return jdbc.update(DELETE_BY_ROLE_ID, params);
  12. SELECT 한 건
    1. RoleSqls에 delete 문 추가
    2. Map<String, ?> params = Collections.singletonMap("roleId", id); return jdbc.queryForObject(SELECT_BY_ROLE_ID, params, rowMapper);

 

 

 

1. Maven Project 생성

archtype  = maven - quickstart

artifact id = daoexam

 

 

2. 라이브러리 가져오기

pom.xml에 추가할 내용

  <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 -->
    	<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
		<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>
		
		<!--  Spring 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>
		
		
		<!--  mysql -->
		<dependency>
			<groupId>mysql</groupId>
			<artifactId>mysql-connector-java</artifactId>
			<version>5.1.46</version>
		</dependency>
  </dependencies>

  <build>
    <pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
      <plugins>
      
      <!-- 1.8버전으로 지정 -->
			<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>

 

 

 

3. ApplicationConfig 클래스 파일 생성

- 먼저 config 파일만 담을 패키지 생성

경로 : daoexam/src/main/java 

패키지 이름 : kr.or.connect.daoexam.config

해당패키지 안에 

ApplicationConfig라는 이름으로 클래스파일 생성

 

@Import

  • 설정 파일을 여러 개로 나눠서 작성할 수 있음
  • 이 설정 파일 하나에 모든 설정을 넣지 않고
  • DB 연결과 관련된 설정만 따로 저장 => 그래야 유지보수하기 좋음
package kr.or.connect.daoexam.config;

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

@Configuration // 여기에 작성한 설정들을 읽어들일 수 있도록 선언
@Import({DBConfig.class}) 
public class ApplicationConfig {

}

 

 

4. DBConfig 클래스 생성

위의 파일과 같은 경로에, 이름은 DBConfig

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!@#";
    
    //pom.xml에서 지정한 DataSource 객체 라이브러리를 이용하여
    //DataSource를 생성하지 않고 가져오기 위한 설정하기 위해 Bean 등록
    //등록할 때 메소드의 이름은 id로 지정한 이름과 일치하게 해줌
    @Bean
    public DataSource dataSource() {
    	BasicDataSource dataSource = new BasicDataSource();
    	
    	// dataSource 객체는 커넥션을 관리하기 때문에
    	// JDBC 드라이버, url, username, password 설정
    	dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    	
    }
}

 

 

4. DataSourceTest 실행 파일로 DB 접속 테스트

- main 클래스를 담을 패키지 생성

경로 : daoexam/src/main/java

패키지 이름 : kr.or.connect.daoexam.main

해당패키지 안에 

DataSourceTest라는 이름으로 클래스 파일 생성

 

!! DataSource는 DBConfig 안에 있지만, DBConfig를 ApplicationConfig가 import하기 때문에

AnnotationConfigApplicationContext으로 ApplicationConfig 클래스를 넣어줌

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) {
		// Spring 컨테이너가 Bean들을 생성하고 관리
		// 그 일을 담당해줄 공장을 불러옴 
		ApplicationContext ac = new AnnotationConfigApplicationContext(ApplicationConfig.class);
		// 어노테이션을 정보를 읽어서 만들어줄 공장 생성
		// Application Context는 IoC/DI 컨테이너
		
		
		// DataSource라는 클래스 요청
		DataSource ds = ac.getBean(DataSource.class); 
		Connection conn = null;
		
		try {
			conn = (Connection) 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로 DAO/DTO 객체 생성하기

SELECT_ALL

1. DTO 클래스 생성

- dto 클래스를 담을 패키지 생성

경로 : daoexam/src/main/java/kr.or.connect.daoexam

패키지 이름 : kr.or.connect.daoexam.dto

해당패키지 안에 

Role이라는 이름으로 클래스 파일 생성

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

 

 

2. 쿼리문을 담을 객체를 생성할 클래스 만들기

- 쿼리문을 담을 객체를 생성하는 클래스를 담을 패키지 생성

경로 : daoexam/src/main/java/kr.or.connect.daoexam

패키지 이름 : kr.or.connect.daoexam.dao

해당패키지 안에 

RoleDaoSqls이라는 이름으로 클래스 파일 생성

 

여기에 쿼리문을 상수 형태로 넣어줌 -> final

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

 

 

3. Role 객체(데이터)에 접근할 객체를 생성하는 DAO 클래스 만들기

방금 전 클래스와 같은 경로에 RoleDao 클래스 생성

 

@Repository

Spring 컨테이너가 이 클래스를 읽어서 사용하기 위해 Bean 등록을 해야함.

DAO 객체는 저장소의 역할을 한다는 것을 밝히기 위해 @Repository를 붙임

 

package kr.or.connect.daoexam.dao;

import javax.sql.DataSource;

import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
import org.springframework.stereotype.Repository;

import kr.or.connect.daoexam.dto.Role;
// SQL문을 가져오기 위한 import
// static으로 가져오면 그 객체 속 변수를 클래스 이름 없이 바로 사용 가능
import static kr.or.connect.daoexam.dao.RoleDaoSqls.*;

import java.util.Collections;
import java.util.List;
@Repository
public class RoleDao {
	private NamedParameterJdbcTemplate jdbc;
	private RowMapper<Role> rowMapeer = BeanPropertyRowMapper.newInstance(Role.class);
	// BeanPropertyRowMapper 객체 : column 값을 자동으로 DTO에 넘겨줌
	// 또한 java와 DBMS의 변수명 규칙인 camleCase와 under_bar를 자동으로 맞춰줌
	
	public RoleDao(DataSource dataSource) {
		this.jdbc = new NamedParameterJdbcTemplate(dataSource);
	}
	
	// 직접 SQL 관련 메소드를 생성
	public List<Role> selectAll(){
		// 1. RoleDaoSqls에서 적은 SQL인 SELECT_ALL
		// 2. 비어있는 맵 객체
		// 3. 이전에 만들어둔 rowMapper => select한 결과를 DTO에 저장하는 목적으로 사용
		//      query()의 결과가 여러 건이라면 내부적으로 반복하며 DTO를 생성하여 List에 담아줌
		return jdbc.query(SELECT_ALL, Collections.emptyMap(), rowMapeer);
		// sql문에 바인딩할 값이 있다면 바인딩하기 위한 내용
	}
}

 

  • select해올 때 NamedParameterJdbcTemplate의 메소드를 이용
    • 이를 이용해 ?에 값을 바인딩하거나, 결과값을 가져옴
  • 생성자에서 DataSource를 받아옴
    • Spring 4.3부터 ComponentScan으로 객체를 찾았을 때 기본 생성자가 없다면 자동으로 객체를 주입해줌
      • DBConfig에서 Bean으로 등록된 DataSource가 파라미터로 전달됨
      • 이를 이용해 NamedParameterJdbcTemplate 객체 생성

 

 

4. ApplicationConfig에 컴포넌트를 읽어들일 방법 설정하기 -> @ComponentScan

자동으로 RoleDao에 Repository가 붙어있는 클래스를,

Bean으로 등록해준 것처럼 대함

 

ApplicationConfig.java 파일에 아래 코드 추가

// basePackages를 이용해 살펴 볼 패키지를 정확히 지정해주기
// basePackages를 쓰면 패키지를 여러 개 명시할 수 있음
@ComponentScan(basePackages = {"kr.or.connect.daoexam.dao"})

 

5. 동작 확인하기

 kr.or.connect.daoexam.main 패키지에

SelectAllTest.java 클래스 만들어서 실행

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

}

 

 

 

INSERT

1. INSERT문을 실행하기 위해 SimpleJdbcInsert 추가하기

private SimpleJdbcInsert insertAction;
//~~

public RoleDao(DataSource dataSource) {
		//~~
        this.insertAction = new SimpleJdbcInsert(dataSource).withTableName("role");
		// DB 속 접근할 테이블 이름 명시
	}

 

RoleDao.java

@Repository
public class RoleDao {
	private NamedParameterJdbcTemplate jdbc;
	// 추가
	private SimpleJdbcInsert insertAction;
	private RowMapper<Role> rowMapeer = BeanPropertyRowMapper.newInstance(Role.class);
	
	public RoleDao(DataSource dataSource) {
		this.jdbc = new NamedParameterJdbcTemplate(dataSource);
		//추가
		this.insertAction = new SimpleJdbcInsert(dataSource).withTableName("role");
		// DB 속 접근할 테이블 이름 명시
	}

 

 

2. 추가할 값 만들기

Way 1) 개발자가 직접 primary key를 세세히 지정하는 방법

아래 코드를 RoleDao.java에 추가

// primary key 값도 함께 직접 넣어줌
	public int insert(Role role) {
		SqlParameterSource params = new BeanPropertySqlParameterSource(role);
		//role 객체를 받아서 맵으로 바꿔줌
		return insertAction.execute(params); // execute 메소드만 쓰면 값이 알아서 저장됨
	}

 

main 패키지에 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();
		role.setRoleId(500);
		role.setDescription("CEO");
		
		int count = roleDao.insert(role);
		System.out.println(count+"건 입력하였습니다.");
	}
}

 

그 다음에 SelectAllTest.java를 실행해보면

방금 넣은 값도 함께 출력됨

 

 

 

 

UPDATE

Way 2) 자동으로 primary key 생성하도록 만들기

 

RoleDaoSqls.java에 추가

:description과 :roleId가 값으로 바인딩될 부분

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

 

RoleDao.java에 추가

	public int update(Role role) {
		SqlParameterSource params = new BeanPropertySqlParameterSource(role);
		// 1. sql문 - static으로 import했기에 클래스 이름 명시 안해도 됨
		// 2. 인자로 받아온 DTO 속 값을 맵으로 변환시킬 사용할 객체
		return jdbc.update(UPDATE, params);
	}

 

JdbcTest.java에 수정 후 실행

		Role role = new Role();
		role.setRoleId(500);
		role.setDescription("PROGRAMMER");
		
		//int count = roleDao.insert(role);
		//System.out.println(count+"건 입력하였습니다.");
		 
		int count = roleDao.update(role);
		System.out.println(count + "건 수정하였습니다.");

 

그 다음에 SelectAllTest.java를 실행해보면

방금 수정한 값도 함께 출력됨

 

 

DELETE

 

RoleDaoSqls.java에 추가

public static final String DELETE_BY_ROLE_ID = "DELETE FROM role WHERE role_id = :roleId";

RoleDao.java에 추가

	public int deleteById(Integer id) {
		// Delete 경우 값이 딱 하나만 들어오기 때문에 간단하게 객체 하나만 받아줄 Map을 사용
		Map<String, Integer> params = Collections.singletonMap("roleId", id);
		return jdbc.update(DELETE_BY_ROLE_ID, params);
	}

 

 

SELECT - 한 개

RoleDaoSqls.java에 추가

public static final String SELECT_BY_ROLE_ID 
	= "SELECT role_id, description FROM role where role_id = :roleId";

RoleDao.java에 추가

	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에 추가 후 실행

101을 조회

500을 삭제하고 조회하면 null 값이 출력

		Role resultRole = roleDao.selectById(101);
		System.out.println(resultRole);
		
		int deleteCount = roleDao.deleteById(500);
		System.out.println(deleteCount + "건 삭제했습니다.");
		
		Role resultRole500 = roleDao.selectById(500);
		System.out.println(resultRole500);
728x90
반응형

'Web > Java+Spring' 카테고리의 다른 글

Spring MVC - Controller 작성 실습 1  (0) 2021.06.07
Spring MVC  (0) 2021.06.04
Spring JDBC  (0) 2021.06.02
JAVA Config  (0) 2021.06.02
Spring IoC/DI 컨테이너  (0) 2021.06.01