1. JDBC 프로그래밍을 보완하는 Spring

JDBC API를 이용하면 DB 연동에 필요한 Connection 요청 및 쿼리 실행을 위한 준비 작업에 관한 코드를 작성해야 한다. 그리고 쿼리 종료 시 요청했던 Connection을 종료하는 등의 후처리 작업에 관한 코드를 finally 블록에 추가적으로 작성해야 한다.
또한 JDBC API로 트랜잭션을 처리할 경우, Connection의 AutoCommit 설정을 비활성화한 후 트랜잭션의 Commit/Rollback 작업을 수행하는 코드를 일일이 작성해야 한다.
이처럼 JDBC 프로그래밍은 그 구조적 특성상 실질적인 데이터 처리와는 관련없는 부가적인 코드를 반드시 작성해야 한다는 번거로움이 존재한다.

Java Spring에서는 JDBC 프로그래밍에서 코드 작성 시 나타나는 구조적인 반복을 줄이기 위해 Template Method 패턴과 전략 패턴을 모두 사용하는 JdbcTemplate 클래스를 제공한다. 또한 트랜잭션 관리에서도 @Transactional을 사용하는 것만으로도 Spring이 자동으로 Commit과 Rollback을 수행하기 때문에 사용자는 핵심 코드의 작성에만 집중할 수 있다.


2. 프로젝트 준비

Maven 프로젝트 생성

<?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>org.example</groupId>
  <artifactId>chapter8</artifactId>
  <version>1.0-SNAPSHOT</version>

  <dependencies>
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-context</artifactId>
      <version>6.1.3</version>
    </dependency>

    <!-- spring-jdbc : JdbcTemplate 등 JDBC 연동에 필요한 기능 제공 -->
    <dependency>
      <groupId>org.springframework</groupId>
      <artifactId>spring-jdbc</artifactId>
      <version>6.1.3</version>
    </dependency>

    <!-- tomcat-jdbc : DB Connection Pool 기능 제공 -->
    <dependency>
      <groupId>org.apache.tomcat</groupId>
      <artifactId>tomcat-jdbc</artifactId>
      <version>8.5.27</version>
    </dependency>

    <!-- mysql-connector-java : MySQL 연결에 필요한 JDBC 드라이버 제공 -->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.33</version>
    </dependency>

    <!-- logback : 트랜잭션 처리 시 로그 메시지 출력 기능 제공 모듈 -->
    <dependency>
      <groupId>ch.qos.logback</groupId>
      <artifactId>logback-classic</artifactId>
      <version>1.5.3</version>
    </dependency>
  </dependencies>

  <properties>
    <maven.compiler.source>17</maven.compiler.source>
    <maven.compiler.target>17</maven.compiler.target>
    <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
  </properties>

</project>

cf) Logback 사용 설정
위와 같이 Maven의 pom.xml 파일에 Logback 모듈을 추가하고, 프로젝트 파일의 src/main/resource 폴더 내부에 다음과 같이 logback.xml 파일을 작성한다. (비교적 최근 버전에서는 slf4j-api 모듈 없이 logback-classic 모듈만 추가해도 상관없는 것 같다.)

src/main/resource/logback.xml

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

<configuration>
  <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
    <encoder>
      <pattern> %d [%5p] %c{2} - %m%n</pattern>
    </encoder>
  </appender>
  <root level="INFO">
    <appender-ref ref="STDOUT" />
  </root>

  <logger name="org.springframework.jdbc" level="DEBUG"/>
</configuration>

DB 테이블 생성

create user 'spring5'@'localhost' identified by 'spring5';

create database spring5fs character set=utf8;

grant all privileges on spring5fs.* to 'spring5'@'localhost';

create table spring5fs.MEMBER (
  ID int auto_increment primary key,
  EMAIL varchar(255),
  PASSWORD varchar(100),
  NAME varchar(100),
  REGDATE datetime,
  unique key (EMAIL)
) engine = InnoDB character set = utf8;

위의 코드를 입력하여 DB 사용자, 데이터베이스, 테이블을 생성한 다음 INSERT문을 입력하면 아래와 같은 테이블 스키마를 생성한 다음 INSERT문으로 데이터를 테이블에 추가했다.


3. DataSource 설정

Spring에서는 DB Connection Pool 기법이 적용된 DataSource를 통해 DB 연동 기능을 제공한다. 이를 위해 DB 연동에 사용할 DataSource를 Spring Bean으로 등록하고 DB 연동 기능을 구현한 Bean 객체에 DataSource를 주입받아 사용한다.

커넥션 풀(Connection Pool)

Connection Pool은 일정 개수의 DB 커넥션을 미리 만들어둠으로써 실제 서비스 운영 환경에서 Java 프로그램을 DBMS에 최초로 연결할 때 발생하는 응답 속도 저하 문제를 해결하고, 동시 접속자 수의 증가할 때 DBMS에 가해지는 부하를 줄이는 기법이다. 이 기법에서는 DB 커넥션을 요청한 프로그램이 커넥션 풀로부터 커넥션을 가져와 사용한 후 커넥션을 다시 반납한다.

이처럼 Connection Pool은 항상 일정 개수의 커넥션을 미리 생성해 두므로 커넥션 사용 시점에서는 커넥션 생성 시간을 절약할 수 있다. 또한 커넥션 생성으로 인한 부하를 줄이면서도 DBMS에 가해지는 부하를 안정적으로 유지할 수 있어 동시 접속자 수가 많은 상황에도 대처할 수 있다.
DB 커넥션 풀을 제공하는 모듈로는 Tomcat JDBC, HikariCP, DBCP, c3p0 등이 있다.

package config;

import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class DbConfig {

    /* close() : DataSource 타입 Bean 객체 소멸 시 호출, 커넥션 풀에 보관된 커넥션 종료 */
    @Bean(destroyMethod = "close")
    public DataSource dataSource() {
        DataSource ds = new DataSource();

        /* 1. MySQL 드라이버 클래스를 JDBC 드라이버 클래스로 지정 */
        ds.setDriverClassName("com.mysql.jdbc.Driver");

        /* 2. JDBC URL(MySQL주소 + DB명) & MySQL 연동 시 사용할 캐릭터셋 지정(utf8) */
        ds.setUrl("jdbc:mysql://localhost/spring5fs?characterEncoding=utf8");

        /* 3. DB 연동 시 사용할 사용자 계정과 암호 지정 */
        ds.setUsername("spring5");
        ds.setPassword("spring5");

        /* 4. DB Connection Pool의 초기 커넥션 개수 & 활성/유휴 상태 최대 커넥션 개수 설정 */
        ds.setInitialSize(2);
        ds.setMaxActive(10);
        ds.setMaxIdle(10);
        
        /* 유휴 상태 커넥션 검사 옵션 & 유휴 상태 유지 가능 시간 설정 */
        ds.setTestWhileIdle(true);  // 유휴 상태인 커넥션 검사
        ds.setMinEvictableIdleTimeMillis(1000*60*3);    // 최소 유휴 시간: 3분
        ds.setTimeBetweenEvictionRunsMillis(1000*10);   // 유휴 커넥션 검사 주기: 10초
        return ds;
    }
}

Tomcat JDBC의 주요 프로퍼티

Tomcat JDBC 모듈은 javax.sql.DataSource를 구현한 org.apache.tomcat.jdbc.pool.DataSource 클래스를 제공하므로 이 클래스를 Spring Bean으로 등록해서 DataSource로 사용할 수 있다. DataSource 클래스에서는 생성할 커넥션의 개수를 지정하기 위한 다음의 설정 메서드를 지원한다.

이러한 설정 메서드는 다음과 같은 Connection의 상태와 크게 연관되어 있다.

활성 상태(Active)

  • 커넥션 풀로 커넥션에 관한 요청이 들어와 해당 커넥션을 사용 중인 상태
  • 주요 메서드
    : setInitialSize(int), setMaxActive(int), setMaxWait(int),
    setTestOnBorrow(boolean), setTestOnReturn(boolean)

유휴 상태(Idle)

  • 커넥션 풀에 저장된 커넥션에 관한 요청이 없는 상태
  • 주요 메서드
    : setMaxIdle(int), setMinIdle(int),
    setTestWhileIdle(boolean), setTestWhileIdle(boolean),
    setMinEvictableIdleTimeMillis(int), setTimeBetweenEvictionRunsMillis(int)

쿼리 실행 시 커넥션의 상태 변화 양상

package dbquery;

import java.sql.Connection;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import org.apache.tomcat.jdbc.pool.DataSource;

public class DbQuery {

    private final DataSource dataSource;

    public DbQuery(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public int count() {
        Connection conn = null;
        try {
            /* 1. 커넥션 풀에 커넥션 요청 시 해당 커넥션은 활성 상태 */
            conn = dataSource.getConnection();

            /* 2. 쿼리 실행 */
            try (Statement stmt = conn.createStatement();
                    ResultSet rs = stmt.executeQuery("select count(*) from MEMBER")) {
                rs.next();
                return rs.getInt(1);
            }
        } catch (SQLException e) { 		
            throw new RuntimeException(e);	// 유효한 sql문이 아니면 Exception 발생
        } finally {
            /* 3. 쿼리 종료 이후 커넥션은 유휴 상태로 전환, 
               커넥션의 유휴 상태 유지 가능 시간 초과 시 연결이 해제되어 Exception 발생 */
            if (conn != null) {
                try { conn.close(); } catch (SQLException e) { }
            }
        }
    }
}

4. JdbcTemplate을 이용한 CRUD

1) JdbcTemplate 생성하기

MemberDao 클래스

package spring;

import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;

public class MemberDao {

    private final JdbcTemplate jdbcTemplate;

	/* JdbcTemplate 객체 생성 및 dataSource 주입 */
    public MemberDao(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }
    
    ...
}

AppCtx 클래스

@Configuration
@EnableTransactionManagement
public class AppCtx {

	@Bean(destroyMethod = "close")
	public DataSource dataSource() {
		DataSource ds = new DataSource();

		/* 1. JDBC 드라이버 클래스로 MySQL 드라이버 클래스를 사용 */
		ds.setDriverClassName("com.mysql.cj.jdbc.Driver");

		/* 2. JDBC URL을 지정하고, MySQL과 연동할 때 사용할 케릭터셋을 DB & 테이블과 통일(utf8) */
		ds.setUrl("jdbc:mysql://localhost/spring5fs?characterEncoding=utf8");

		/* 3. DB 연동 시 사용할 사용자 계정과 암호 지정 */
		ds.setUsername("spring5");
		ds.setPassword("spring5");

		/* 4. 커넥션 풀의 초기 커넥션 개수 & 대여 가능한 최대 커넥션 개수 설정 */
		ds.setInitialSize(2);
		ds.setMaxActive(10);
		ds.setMaxIdle(10);

		/* 5. 유휴 상태 커넥션 검사 옵션 설정 */
		ds.setTestWhileIdle(true);                      // 유휴 상태인 커넥션 검사
		ds.setMinEvictableIdleTimeMillis(1000*60*3);    // 최소 유휴 시간: 3분
		ds.setTimeBetweenEvictionRunsMillis(1000*10);   // 유휴 커넥션 검사 주기: 10초
		return ds;
	}

	@Bean
	public MemberDao memberDao() {
		return new MemberDao(dataSource());
	}
    
    ... 생략
}

2) JdbcTemplate를 이용한 조회 쿼리

JdbcTemplate의 조회 - select

JdbcTemplate.query()JdbcTemplate.queryForObject()는 sql 쿼리를 통해 데이터를 조회할 때 사용하는 메서드이다. 두 메서드 모두 작성한 조회 쿼리를 통해 유효한 데이터를 찾을 수 없는 경우 DataAccessException과 그 하위 클래스에 해당하는 Exception을 유발한다.

JdbcTemplate.query() 메서드

List<T> query(String sql, RowMapper<T> rowMapper)
List<T> query(String sql, Object[] args, RowMapper<T> rowMapper)
List<T> query(String sql, RowMapper<T> rowMapper, Object... args)
  • String sql : select 쿼리를 수행하기 위한 sql문
  • Object[] args : sql문으로 조회하고자 하는 테이블의 속성(들)
  • RowMapper<T> rowmapper
    조회 결과를 지정된 객체 타입에 관한 List로 변환하며, 이 List가 JdbcTemplate.query()의 리턴값이 된다.
  • Object... args
    where절의 조건 설정 시 사용할 속성값 (JdbcTemplate.query()를 호출한 메서드의 파라미터)

JdbcTemplate.queryForObject() 메서드
리턴값의 타입은 Class<T> requiredType의 타입 또는 RowMapper<T> rowMapper로 구한 List에 저장된 객체 타입이 된다.

T queryForObject(String sql, Class<T> requiredType) throws DataAccessException;
T queryForObject(String sql, Class<T> requiredType, Object... args)
T queryForObject(String sql, RowMapper<T> rowMapper)
T queryForObject(String sql, RowMapper<T> rowMapper, Object... args)
  • String sql : select 쿼리를 수행하기 위한 sql문
  • Class<T> requiredType
    속성값을 읽어올 때 사용할 타입이자 쿼리를 통해 구한 결과값의 타입
  • RowMapper<T> rowmapper : (where절의 조건에 맞는) 조회 결과를 지정된 객체 타입에 관한 List로 변환한다.
  • Object... args
    where절의 조건에 사용할 속성값 (JdbcTemplate.query()를 호출한 메서드의 파라미터)

JdbcTemplate.queryForObject() 메서드는 쿼리 수행 결과가 정확히 1개 행일 때만 사용 가능한 메서드이다. 만약 쿼리 실행 결과 행이 없거나 여러 개인 경우 IncorrectResultSizeDataAccessException이 발생하고, 행 개수가 0이면 하위 클래스인 EmptyResultSizeDataAccessException이 발생한다.

MemberDao 클래스

.. 코드 일부 생략

import java.util.List;
import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;

public class MemberDao {

    ... 생략

    /* JdbcTemplate을 이용한 특정 대상 조회 쿼리 실행 
       (MemberRowMapper 클래스는 RowMapper<Mapper> 인터페이스의 구현체) */
    public Member selectByEmail(String email) {
        List<Member> results = jdbcTemplate.query(
                "select * from MEMBER where EMAIL = ?", // sql 조회 쿼리
                new MemberRowMapper()                   // 조회 쿼리 결과를 리스트로 저장
                , email);                               // 조회 조건(파라미터와 동일)
        return results.isEmpty() ? null : results.get(0);
    }
    
    /* JdbcTemplate을 이용한 전체 조회 쿼리 실행 */
    public List<Member> selectAll() {
        return jdbcTemplate.query("select * from MEMBER", new MemberRowMapper());
    }

    /* jdbcTemplate과 queryForObject를 활용한 전체 인원수 구하기 */
    public Integer count() {
        return jdbcTemplate.queryForObject("select count(*) from MEMBER", Integer.class);
    }
    
    ... 생략
}

RowMapper

package org.springframework.jdbc.core;

import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.lang.Nullable;

@FunctionalInterface
public interface RowMapper<T> {

    @Nullable
	T mapRow(ResultSet rs, int rowNum) throws SQLException;
}

mapRow(ResultSet, int) 메서드를 통해 쿼리 수행의 결과가 저장된 ResultSet에서 데이터를 읽어와 지정된 타입 파라미터(아래 코드에서는 Member)로 변환하는 기능을 제공한다.

MemberRowMapper 클래스

package spring;

.. 코드 일부 생략

import java.sql.ResultSet;
import java.sql.SQLException;
import org.springframework.jdbc.core.RowMapper;

public class MemberRowMapper implements RowMapper<Member> {

    @Override
    public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
        /* 조건을 충족하는 tuple의 속성값을 Member 객체의 각 필드에 매핑하여 저장 */
        Member member = new Member(
                rs.getString("EMAIL"),
                rs.getString("PASSWORD"),
                rs.getString("NAME"),
                rs.getTimestamp("REGDATE").toLocalDateTime());
        member.setId(rs.getLong("ID"));
        return member;
    }
}

3) JdbcTemplate를 이용한 변경 쿼리

JdbcTemplate.update()

이 메서드는 DB 테이블에 저장된 데이터를 변경하는 기능을 수행하며, insert, update, delete 쿼리를 수행할 때 사용된다. 이 메서드의 리턴값은 쿼리 실행 결과로 변경된 행의 개수이다.

int update(String sql);
int update(String sql, Object... arg);
  • String sql : sql문으로 작성한 delete, update, insert 쿼리
  • Object... arg
    적용할 변경사항 또는 where절의 조건에 해당되는 속성값
    (JdbcTemplate.update()가 사용된 메서드의 파라미터와 동일, sql문 내 '?'와의 순서 일치 필수)

MemberDao 클래스

package spring;

import java.sql.PreparedStatement;
import java.sql.Timestamp;
import java.util.List;
import java.util.Objects;

import javax.sql.DataSource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.support.GeneratedKeyHolder;
import org.springframework.jdbc.support.KeyHolder;

public class MemberDao {

    private final JdbcTemplate jdbcTemplate;

	public MemberDao(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }
    
    .. 생략
    
    /* 1. jdbcTemplate을 이용한 변경 쿼리 */
    public void update(Member member) {
//        jdbcTemplate.update(
//                "update MEMBER set NAME = ?, PASSWORD = ? where EMAIL = ?",
//                member.getName(), member.getPassword(), member.getEmail());

        jdbcTemplate.update(con -> {
            PreparedStatement pstmt = con.prepareStatement(
                    // sql문의 괄호 안 속성은 인덱스 파라미터
                    "update MEMBER set NAME = ?, PASSWORD = ? where EMAIL = ?");

            /* 인덱스 파라미터(sql문의 '?' 부분)에 부여할 값 설정 */
            pstmt.setString(1, member.getName());
            pstmt.setString(2, member.getPassword());
            pstmt.setString(3, member.getEmail());

            /* 생성한 PreparedStatement 객체 리턴 */
            return pstmt;
        });
    }
    
    /* 2. jdbcTemplate과 PreparedStatement를 이용한 insert 쿼리 */
    public void insert(Member member) {
        /* keyHolder: 쿼리 실행 후 생성된 key값을 저장 */
        KeyHolder keyholder = new GeneratedKeyHolder();

        /* PreparedMemberCreatorForInsert : PreparedStatementCreator 인터페이스 구현체 */
        jdbcTemplate.update(
                con -> new PreparedMemberCreatorForInsert(member)
                        .createPreparedStatement(con),
                keyholder);
        Number keyValue = keyholder.getKey();
        member.setId(Objects.requireNonNull(keyValue).longValue());
    }
    
    /* 3. jdbcTemplate을 이용한 delete 쿼리 */
    public void deleteByEmail(String email) {
        jdbcTemplate.update("delete from MEMBER where EMAIL != ?", email);
    }
}

PreparedStatementCreator 인터페이스를 이용한 쿼리 수행

PreparedStatementCreator 인터페이스

package org.springframework.jdbc.core;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException

@FunctionalInterface
public interface PreparedStatementCreator {
	PreparedStatement createPreparedStatement(Connection con) throws SQLException;
}

MemberPreparedStatementCreatorForInsert 클래스

package spring;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.sql.Timestamp;
import org.springframework.jdbc.core.PreparedStatementCreator;

public class MemberPreparedStatementCreatorForInsert implements PreparedStatementCreator {

    private final Member member;

    public MemberPreparedStatementCreatorForInsert(Member member) {
        this.member = member;
    }

    @Override
    public PreparedStatement createPreparedStatement(Connection con) throws SQLException {
        /* 1. 전달받은 Connection으로 PreparedStatement 생성 */
        PreparedStatement pstmt = con.prepareStatement(
                // sql문의 '?'에 순서대로 대응되는 속성이 인덱스 파라미터
                "insert into MEMBER (EMAIL, PASSWORD, NAME, REGDATE) values (?, ?, ?, ?)",
                new String[]{"ID"});

        /* 인덱스 파라미터 값 결정 (아래 update() 메서드와 동일) */
        pstmt.setString(1, member.getEmail());
        pstmt.setString(2, member.getPassword());
        pstmt.setString(3, member.getName());
        pstmt.setTimestamp(4, Timestamp.valueOf(member.getRegisterDateTime()));

        /* 생성한 PreparedStatement 객체 리턴 */
        return pstmt;
    }
}

PreparedStatementCreator를 구현한 클래스는 createPreparedStatement() 메서드의 파라미터로 전달받는 Connection을 이용해서 PreparedStatement 객체를 생성한다.
이렇게 생성된 PreparedStatement 객체는 직접 쿼리의 인덱스 파라미터의 값을 설정하는 경우에 사용된다. 특히 동일한 쿼리를 특정 값만 바꾸어서 여러번 실행해야 하거나, 쿼리 수행 시 사용하는 속성의 개수가 많은 경우 유용하게 활용된다.

JdbcTemplate 쿼리 실행 메서드에서의 사용 예시

List<T> query(PreparedStatementCreator psc, RowMapper<T> rowMapper);
int update(PreparedStatementCreator psc);
int update(PreparedStatementCreator psc, Keyholder generatedKeyHolder);

INSERT 쿼리에서 KeyHolder를 이용한 자동 생성 Key값 구하기

org.springframework.jdbc.support.KeyHolder 인터페이스
KeyHolder 인터페이스는 쿼리 실행 후 생성된 key값을 구할 때 사용되며, 이 인터페이스의 구현 클래스인 GeneratedKeyHolder를 통해 key값을 구할 수 있다.
특히 테이블의 key 속성값에AUTO_INCREMENT와 같은 자동 증가 옵션이 적용된 상태에서 insert 쿼리를 수행할 때, 객체의 필드에 별도로 key 속성값을 부여해야 하는 상황에서 key 속성값을 구하기 위해 사용된다.

KeyHolder 사용 예시

	... 코드 생략
	/* 2. jdbcTemplate과 PreparedStatement를 이용한 insert 쿼리 */
    public void insert(Member member) {
        /* keyHolder: 쿼리 실행 후 생성된 key값을 저장 */
        KeyHolder keyholder = new GeneratedKeyHolder();

        /* PreparedMemberCreatorForInsert : PreparedStatementCreator 인터페이스 구현체 */
        jdbcTemplate.update(
                con -> new PreparedMemberCreatorForInsert(member)
                        .createPreparedStatement(con),
                keyholder);
        Number keyValue = keyholder.getKey();
        member.setId(Objects.requireNonNull(keyValue).longValue());
    }
    ... 코드 생략

5. MemberDao 테스트

MainForMemberDao 클래스

package main;

import config.AppCtx;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.List;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import spring.Member;
import spring.MemberDao;

public class MainForMemberDao {
    private static MemberDao memberDao;

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(AppCtx.class);
        memberDao = ctx.getBean(MemberDao.class);
        selectAll();
        updateMember();
        insertMember();
        // deleteMember();
        ctx.close();
    }

	/* 전체 데이터 조회 쿼리 실행 */
    private static void selectAll() {
        System.out.println("----- selectAll");
        int total = memberDao.count();
        System.out.println("전체 데이터: " + total);
        List<Member> members = memberDao.selectAll();
        for (Member m : members) {
            System.out.println(m.getId() + ":" + m.getEmail() + ":" + m.getName());
        }
    }

	/* 특정 이메일의 암호 변경 쿼리 실행 */
    private static void updateMember() {
        System.out.println("----- updateMember");
        Member member = memberDao.selectByEmail("madvirus@madvirus.net");
        String oldPw = member.getPassword();
        String newPw = Double.toHexString(Math.random());
        member.changePassword(oldPw, newPw);

        memberDao.update(member);
        System.out.println("암호 변경: " + oldPw + " > " + newPw);
    }

    private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("MMddHHmmss");

	/* 데이터 추가 쿼리 실행 */
    private static void insertMember() {
        System.out.println("----- insertMember");
        String prefix = formatter.format(LocalDateTime.now());
        Member member = new Member(prefix+"@test.com", prefix, prefix, LocalDateTime.now());
        memberDao.insert(member);
        System.out.println(member.getId() + " 데이터 추가");
    }

	/* 데이터 삭제 쿼리 실행 */
    private static void deleteMember() {
        System.out.println("---- deleteMember");
        memberDao.deleteByEmail("madvirus@madvirus.net");
        System.out.println("데이터 삭제 완료! ");
    }
}

실행 결과


Reference

0개의 댓글