[boostcourse] 3.8. Spring JDBC - BE

1. Spring JDBC란?

- JDBC 프로그래밍을 보면 반복되는 개발 요소가 있음

(드라이버 로드하고 Connection, Statement, ResultSet 객체 생성 등)

- 이러한 JDBC의 모든 저수준 세부사항을 Spring이 처리해줌

개발자가 해야 할 일

Spring JDBC 패키지

org.springframework.jdbc.core

- JdbcTemplate 및 관련 Helper 객체 제공

org.springframework.jdbc.datasource

- DataSource를 쉽게 접근하기 위한 유틸 클래스, 트랜잭션매니저 및 다양한 DataSource 구현을 제공

org.springframework.jdbc.object

- RDBMS 조회, 갱신, 저장 및 안전하고 재사용 가능한 객체 제공

org.springframework.jdbc.support

- jdbc,core 및 jdbc.object를 사용하는 JDBC 프레임워크를 지원

 

JdbcTemplate

- org.springframework.jdbc.core에서 가장 중요한 클래스

- 리소스 생성, 해지를 처리해서 연결을 닫는 것을 잊어 발생하는 문제 등을 피할 수 있도록 함

- Statement의 생성과 실행을 처리

- SQL조회, 업데이트, 저장 프로시저 호출, ResultSet 반복 호출 등을 실행함

/* Select 예제 */
// column 수 구하기
int rowCount = this.jdbcTemplate.queryForInt("select count(*) form t_actor");

// 변수 바인딩 사용하기
int countOfActorsNamedJoe = this.jdbcTemplate.queryForInt("select count(*) from t_actor where first_name = ?", "Joe");

// String 값으로 결과 받기
String lastName = this.jdbcTemplate.queryForObject("select last_name from t_actor where id = ?", new Object[] {1212L}, String.class);

/* Insert 예제 */
this.jdbcTemplate.update("insert into t_actor (first_name, last_name) values (?, ?)", "Leonor". "Watling");

/* Update 예제 */
this.jdbcTemplate.update("update t_actor set = ? where id = ?", "Banjo", 5276L);

/* Delete 예제 */
this.jdbcTemplate.update("delete from t_actor where id = ?", Long.valueOf(actorId));

 

2. Spring JDBC 실습 - 용어 정리

DTO란?

- Data Transfer Object

- 계층간 데이터 교환을 위한 Java Beans

- 일반적으로 DTO는 로직을 갖고 있지 않은 순수한 데이터 객체임

 

DAO란?

- Data Access Object

- 데이터를 조회하거나 조작하는 기능을 전담하도록 만든 객체

- 보통 데이터베이스 조작하는 기능을 전담하는 목적으로 만들어짐

 

CoonectionPool이란?

- DB 연결은 비용이 많이 듦

- 커넥션 풀은 미리 커넥션을 여러 개 맺어둠

- 커넥션이 필요하면, 커넥션 풀에게 빌려서 사용한 후 반납함

 

DataSource

- 커넥션 풀은 경우에 따라 여러 개가 생성될 수 있음

- 그러한 커넥션 풀을 관리하는 목적으로 사용되는 객체

- DataSource를 이용해 커넥션을 얻어오고 반납하는 등의 작업을 수행함

 

3. Spring JDBC 실습 - DB 접속하기

 

# 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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.1.1</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.example</groupId>
	<artifactId>daoexam</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<packaging>war</packaging>
	<name>daoexam</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jdbc</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>com.mysql</groupId>
			<artifactId>mysql-connector-j</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>

		<dependency>
			<groupId>commons-dbcp</groupId>
			<artifactId>commons-dbcp</artifactId>
			<version>1.4</version>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>

 

# ApplicationConfig.java

package com.example.daoexam.config;

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

@Configuration
@Import({DBConfig.class})       // 하나의 클래스에 모든 설정 정보를 갖고 있는 게 아니라, DB는 DB 관련 등 따로 나눠서 작성할 거임
public class ApplicationConfig {
}

- @Configuration: 설정 정보를 담은 클래스인 것을 알려주기

- @Import: 다른 설정 정보도 import 할 거임

 

# DBConfig.class

package com.example.daoexam.config;

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

import javax.sql.DataSource;

@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 = "connectuser";

    // DataSource 객체를 등록하기. 이미 작성되어 있는 DataSource를 사용하는 것
    @Bean
    public DataSource dataSource() {
        // DataSource가 생성될 때 알아야 할 정보를 설정해줌
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);

        return dataSource;
    }
}

- @Configuration: 이것도 설정 정보를 담고 있음

- DB 정보 담기

- DataSource를 통해 DB에 대한 정보를 담아주기

- @Bean: 객체니까

 

# DataSourceTest.java

package com.example.daoexam.main;

import com.example.daoexam.config.ApplicationConfig;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import javax.sql.DataSource;
import java.sql.Connection;

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

- DB에 잘 접속할 수 있는지 테스트하기 위해 main 함수 실행해보기

- ApplicationContext를 통해 Spring 컨테이너 생성하기

- 이때 ApplicationConfig.class에서 설정 정보 읽어오도록 하기

- DataSource 객체 얻어오기

- Connection 만들어서 접속 성공 여부 확인하기

 

4. Spring JDBC 실습 - SELECT ALL

# Role.java

package com.example.daoexam.dto;

public class Role {

    private int role_id;
    private String description;

    public int getRole_id() {
        return role_id;
    }

    public void setRole_id(int role_id) {
        this.role_id = role_id;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    @Override
    public String toString() {
        return "Role{" +
                "role_id=" + role_id +
                ", description='" + description + '\'' +
                '}';
    }
}

- DTO 생성

 

# RoleDaoSqls.java

package com.example.daoexam.dao;

public class RoleDaoSqls {

    public static final String SELECT_ALL = "SELECT role_id, description FROM role ORDER BY role_id";
}

- sql문 정의

 

# RoleDao.java

package com.example.daoexam.dao;

import com.example.daoexam.dto.Role;
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 javax.sql.DataSource;
import java.util.Collections;
import java.util.List;

import static com.example.daoexam.dao.RoleDaoSqls.*;

@Repository     // Bean으로 등록하기. Dao는 repository 애노테이션을 붙여줌.
public class RoleDao {

    private NamedParameterJdbcTemplate jdbc;        // ?를 사용해서 바인딩하거나 결과값을 가져옴
    private RowMapper<Role> rowMapper = BeanPropertyRowMapper.newInstance(Role.class);      // DBMS와 JAVA의 이름 규칙을 맞춰줌

    public RoleDao(DataSource dataSource) {
        this.jdbc = new NamedParameterJdbcTemplate(dataSource);
    }

    public List<Role> selectAll() {
        return jdbc.query(SELECT_ALL, Collections.emptyMap(), rowMapper);       // sql문의 바인딩할 값을 전달하기 위해 Collections.emptyMap();
    }
}

selectAll()의 return jdbc.query(SELECT_ALL, Collections.emptyMap(), rowMapper); 

 

첫번째 파라미터

static으로 정의한 select문을 import해줌

 

두번째 파라미터

sql문에 바인딩할 값이 있다면, 그 값을 전달할 목적으로 사용됨

 

세번째 파라미터

select 한 건 한 건의 결과를 저장. column의 값을 자동으로 dto에 담아줌. 생성한 dto를 리스트에 담아 반환해줌

BeanPropertyRowMapepr는 DBMS와 JAVA의 이름의 규칙을 맞춰주는 역할을 함

ex) role_id 와 roleId를 매칭

 

# ApplicationConfig.java

package com.example.daoexam.config;

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

@Configuration
@ComponentScan(basePackages = {"com.example.daoexam.dao"})
@Import({DBConfig.class})       // 하나의 클래스에 모든 설정 정보를 갖고 있는 게 아니라, DB는 DB 관련 등 따로 나눠서 작성할 거임
public class ApplicationConfig {
}

@ComponentScan으로 @Repository를 붙인 RoleDao 정보를 읽어오도록 함

 

# SelectAllTest.java

package com.example.daoexam.main;

import com.example.daoexam.config.ApplicationConfig;
import com.example.daoexam.dao.RoleDao;
import com.example.daoexam.dto.Role;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import java.util.List;

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

    }
}

 

5. Spring JDBC 실습 - INSERT 및 UPDATE

INSERT

# RoleDao.java

package com.example.daoexam.dao;

import com.example.daoexam.dto.Role;
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 javax.sql.DataSource;
import java.util.Collections;
import java.util.List;

import static com.example.daoexam.dao.RoleDaoSqls.*;

@Repository     // Bean으로 등록하기. Dao는 repository 애노테이션을 붙여줌.
public class RoleDao {

    private NamedParameterJdbcTemplate jdbc;        // ?를 사용해서 바인딩하거나 결과값을 가져옴
    private SimpleJdbcInsert insertAction;
    private RowMapper<Role> rowMapper = BeanPropertyRowMapper.newInstance(Role.class);      // DBMS와 JAVA의 이름 규칙을 맞춰줌

    // 생성자에서 jdbc, insertAction이 만들어지도록 함
    public RoleDao(DataSource dataSource) {
        this.jdbc = new NamedParameterJdbcTemplate(dataSource);
        this.insertAction = new SimpleJdbcInsert(dataSource).withTableName("role");     // 어떤 테이블에 insert할 지 알려줌
    }

    public List<Role> selectAll() {
        return jdbc.query(SELECT_ALL, Collections.emptyMap(), rowMapper);       // sql문의 바인딩할 값을 전달하기 위해 Collections.emptyMap();
    }

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

}

 

-SimpleJdbcInsert 객체

- Insert의 경우에는 primary key를 자동으로 생성해야 하는 경우가 생김 (해당 예제는 직접 넣어줌)

- withTableName()으로 어떤 테이블에 넣어줄지 명시

 

# JDBCTest.java

package com.example.daoexam.main;

import com.example.daoexam.config.ApplicationConfig;
import com.example.daoexam.dao.RoleDao;
import com.example.daoexam.dto.Role;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;

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(300);
        role.setDescription("CEO");

        int count = roleDao.insert(role);
        System.out.println(count + " 입력했습니다.");
    }
}

 

UPDATE

# RoleDaoSqls.java

package com.example.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";
}

# RoleDao.java

    public int update(Role role) {
        SqlParameterSource params = new BeanPropertySqlParameterSource(role);       // Map으로 바꿔줌. 이름 값 매칭
        return jdbc.update(UPDATE, params);
    }

# JDBCTest.java

        role.setRoleId(500);
        role.setDescription("Programmer");
        count = roleDao.update(role);
        System.out.println(count + " 수정했습니다.");

 

5. Spring JDBC 실습 - 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

package com.example.daoexam.dao;

import com.example.daoexam.dto.Role;
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 javax.sql.DataSource;
import java.util.Collections;
import java.util.List;
import java.util.Map;

import static com.example.daoexam.dao.RoleDaoSqls.*;

@Repository     // Bean으로 등록하기. Dao는 repository 애노테이션을 붙여줌.
public class RoleDao {

    private NamedParameterJdbcTemplate jdbc;        // ?를 사용해서 바인딩하거나 결과값을 가져옴
    private SimpleJdbcInsert insertAction;
    private RowMapper<Role> rowMapper = BeanPropertyRowMapper.newInstance(Role.class);      // DBMS와 JAVA의 이름 규칙을 맞춰줌

    // 생성자에서 jdbc, insertAction이 만들어지도록 함
    public RoleDao(DataSource dataSource) {
        this.jdbc = new NamedParameterJdbcTemplate(dataSource);
        this.insertAction = new SimpleJdbcInsert(dataSource).withTableName("role");     // 어떤 테이블에 insert할 지 알려줌
    }

    public List<Role> selectAll() {
        return jdbc.query(SELECT_ALL, Collections.emptyMap(), rowMapper);       // sql문의 바인딩할 값을 전달하기 위해 Collections.emptyMap();
    }

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

    public int update(Role role) {
        SqlParameterSource params = new BeanPropertySqlParameterSource(role);       // Map으로 바꿔줌. 이름 값 매칭
        return jdbc.update(UPDATE, 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;
        }
    }

    public int deleteById(Integer id) {
        Map<String, ?> params = Collections.singletonMap("roleId", id);
        return jdbc.update(DELETE_BY_ROLE_ID, params);
    }
}

- selectedById, deletedById 추가

SELECT

- 한 건 select이기에 queryForObject 사용

- 두번째 파라미터에 SqlParameterSource 사용해도 되지만 id만 필요하기에 직접 Map 형식의 params를 만들어서 넣어줌

- singletonMap은 값을 딱 한 건만 넣어서 쓸 때 사용

- 조건에 맞는 레코드가 없어 select할 수 없는 경우를 고려하여 예외처리

DELETE

- select와 동일