본문 바로가기

공부/Spring

[Spring] Chapter 08 - DB 연동(MySQL + Gradle + JDBC)

Chapter 8 내용

  • DataSource 설정
  • JdbcTemplate을 이용한 쿼리 실행
  • DB 관련 익셉션 변환 처리
  • 트랜잭션 처리

 

 

 MYSQL과 연동(MySQL + Gradle + JDBC)


1. build.gradle에 추가(jdbc)

 

1
2
3
4
    implementation group: 'org.apache.tomcat'name'juli', version: '6.0.26'
    implementation group: 'org.apache.tomcat'name'tomcat-jdbc', version: '7.0.19'
    compile group: 'mysql'name'mysql-connector-java', version: '8.0.19'
    implementation group: 'org.springframework'name'spring-jdbc', version: '5.2.3.RELEASE'
cs

 

2. DB 테이블 생성, 데이터 삽입하기

 

member 테이블 생성 후, 몇가지 데이터를 넣어줬다.

 

 

 

 

3. DataSource 설정

  • JDBC API는 DriverManager 외에 DataSource를 이용해서 DB연결을 구한다.

@Configuration
public class DbConfig {

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


        DataSource ds = new DataSource();
        ds.setDriverClassName("com.mysql.jdbc.Driver");
        ds.setUrl("jdbc:mysql://localhost:3306/test?serverTimezone=UTC");
        ds.setUsername("root");
        ds.setPassword("비밀번호");
        ds.setInitialSize(2);
        ds.setMaxActive(10);
        ds.setTestWhileIdle(true);
        ds.setMinEvictableIdleTimeMillis(60000 * 3);
        ds.setTimeBetweenEvictionRunsMillis(10 * 1000);
        return ds;
    }
}

 

- 스프링 설정 파일에 Bean으로 db연결 정보를 등록해 준다.

- setUrl에는 mysql 주소와, DB스키마 이름을 넣으면 된다., 위의 코드에서 스키마 이름은 test이다.

- setUsername과 setPassword는 DB를 설치했을 때 설정한 계정 정보

 

 

4.  DBQuery 작성

  public class DbQuery {
      private DataSource dataSource;

      public DbQuery(DataSource dataSource) {
          this.dataSource = dataSource; //구현해 놓은 dataSource 의존성 주입받음
      }

      public int count() {
          Connection conn = null;
          try {
              conn = dataSource.getConnection(); //dbConnetcion 구하기
              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);
          } finally {
              if (conn != null)
                  try {
                      conn.close();
                  } catch (SQLException e) {
                  }
          }
      }

  }
public class MainUsingDbQuery {

    public static void main(String[] args) {
        AnnotationConfigApplicationContext ctx = new AnnotationConfigApplicationContext(DbConfig.class,
                DbQueryConfig.class);

        DbQuery dbQuery = ctx.getBean(DbQuery.class);
        int count = dbQuery.count();
        System.out.println(count);
        ctx.close();
    }
}

 

 

[결과] 2

 

JDBCTemplate을 이용한 쿼리 실행


 

spring을 사용하면 DataSource, Connection등을 사용하지 않고, jdbcTemplate을 이용해서 쿼리를 실행할 수 있다.

jdbcTemplate 객체를 생성하고, DataSource를 주입받으면 된다.

 

  public class MemberDao {

      private JdbcTemplate jdbcTemplate;

      public MemberDao(DataSource dataSource) {
          this.jdbcTemplate = new JdbcTemplate(dataSource);
      }

 

- JdbcTemplate 코드 < 더보기 >

 

더보기
public class MemberDao {

    private JdbcTemplate jdbcTemplate;

    public MemberDao(DataSource dataSource) {
        this.jdbcTemplate = new JdbcTemplate(dataSource);
    }

    public Member selectByEmail(String email) {
        List<Member> results = jdbcTemplate.query(
                "select * from MEMBER where EMAIL = ?",
                new RowMapper<Member>() {
                    @Override
                    public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
                        Member member = new Member(
                                rs.getString("EMAIL"),
                                rs.getString("PASSWORD"),
                                rs.getString("NAME"),
                                rs.getTimestamp("REGDATE").toLocalDateTime());
                        member.setId(rs.getLong("ID"));
                        return member;
                    }
                }, email);

        return results.isEmpty() ? null : results.get(0);
    }

    public void insert(Member member) {
        KeyHolder keyHolder = new GeneratedKeyHolder();
        jdbcTemplate.update(new PreparedStatementCreator() {
            @Override
            public PreparedStatement createPreparedStatement(Connection con)
                    throws SQLException {
                // 파라미터로 전달받은 Connection을 이용해서 PreparedStatement 생성
                PreparedStatement pstmt = con.prepareStatement(
                        "insert into MEMBER (EMAIL, PASSWORD, NAME, REGDATE) " +
                        "values (?, ?, ?, ?)",
                        new String[] { "ID" });
                // 인덱스 파라미터 값 설정
                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;
            }
        }, keyHolder);
        Number keyValue = keyHolder.getKey();
        member.setId(keyValue.longValue());
    }

    public void update(Member member) {
        jdbcTemplate.update(
                "update MEMBER set NAME = ?, PASSWORD = ? where EMAIL = ?",
                member.getName(), member.getPassword(), member.getEmail());
    }

    public List<Member> selectAll() {
        List<Member> results = jdbcTemplate.query("select * from MEMBER",
                (ResultSet rs, int rowNum) -> {
                    Member member = new Member(
                            rs.getString("EMAIL"),
                            rs.getString("PASSWORD"),
                            rs.getString("NAME"),
                            rs.getTimestamp("REGDATE").toLocalDateTime());
                    member.setId(rs.getLong("ID"));
                    return member;
                });
        return results;
    }

    public int count() {
        Integer count = jdbcTemplate.queryForObject(
                "select count(*) from MEMBER", Integer.class);
        return count;
    }

}

 

마찬가지로 의존성 주입을 위해서 설정 파일에 Bean으로 등록해준다.

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

 

생성한 JdbcTemplate 코드를 필요할 때 꺼내서 쓰기면 하면 된다.

 

 

INSERT 쿼리 실행 시 KeyHolder를 이용해서 자동 생성 키 값 구하기


 

MySQL에서는 AUTO_INCREMENT 칼럼을 지정해서 자동 증가 칼럼을 만들 수 있다.

 

      CREATE TABLE `member` (
        `ID` int NOT NULL AUTO_INCREMENT,
        `EMAIL` varchar(255) DEFAULT NULL,
        `PASSWORD` varchar(100) DEFAULT NULL,
        `NAME` varchar(100) DEFAULT NULL,
        `REGDATE` datetime DEFAULT NULL,
        PRIMARY KEY (`ID`),
        UNIQUE KEY `EMAIL` (`EMAIL`)
      ) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;

 

  • JdbcTemplate은 KeyHolder을 사용해서 자동으로 생성된 키값을 구할 수 있다.

KeyHolder keyHolder = new GeneratedKeyHolder();
jdbcTemplate.update(new PreparedStatementCreator(){…생략}, keyHolder);

 

 - GeneratedKeyHolder 객체 생성(KeyHolder 구현 클래스)

 - PreparedStatement 객체 생성 후, 두번째 파라미터로 자동 생성 되는 키 칼럼 목록(ID) 전달 ( new String[] {“ID”})

 - update()에는 자동 생성된 키값을 KeyHolder 에 보관한다.

 

 

트랜잭션 처리


 

- 트랜잭션은 여러 쿼리를 논리적으로 하나의 작업으로 묶어주고, 묶인 쿼리 중 하나라도 실패하면 전체 쿼리를 실패로 간주하고 실패 이전에 실행된 쿼리를 취소한다.

- (commit과 rollback)실행

-JDBC는 Connectin의 setAutoCommit(flase)를 이용해서 트랜잭션을 시작하고 commit(),rollback()을 이용한다.

단점) 개발자가 코드 누락을 하기 쉽고, 중복이 반복되는 문제가 있음.

 

  Connection conn = null;
  try{
      ...
      conn.setAutoCommit(false); // 트랜잭션 범위 시작
      ... 쿼리실행
      conn.commit(); // 트랜잭션 범위 종료: 커밋
  }
  catch(SQLException ex){
      if(conn != null)
          // 트랜잭션 범위 종료: 롤백
          try{ conn.rollback(); } catch (SQLException e){}
  }
  finally{
      if(conn!= null)
          try{ conn.close(); } catch(SQLException e){}
  }

 

 

 @Transactional


트랜잭션을 이용하려면, 트랜잭션 범위에서 실행하고 싶은 메서드에 @Transactional 애노테이션만 붙이면 된다.

    @Transactional
    public void changePassword(String email, String oldPwd, String newPwd) {
        Member member = memberDao.selectByEmail(email);
        if (member == null)
            throw new MemberNotFoundException();

        member.changePassword(oldPwd, newPwd);

        memberDao.update(member);
    }

 

- @Transactional이 붙은 chagePassword() 내에서 실행하는 쿼리는 한 트랜잭션에 묶이며, 하나라도 실패 시 롤백된다.

- @Transactional 애노테이션이 제대로 동작하려면 스프링 설정에 두가지를 설정해야한다.

 

1. PlatformTransactionManager 빈설정
2. @Transcational 애노테이션 활성화 설정( @EnableTransactionManagement)

 

      @Configuration
      @EnableTransactionManagement
      public class AppCtx {


          @Bean
          public PlatformTransactionManager transactionManager() {
              DataSourceTransactionManager tm = new DataSourceTransactionManager();
              tm.setDataSource(dataSource());
              return tm;
          }
         (코드생략)
      }

 

REFERENCE


 초보 웹 개발자를 위한 스프링 5 프로그래밍 입문(최범균)

300x250