본문 바로가기
카테고리 없음

[스터디] 스프링 DB연동

by foreverever 2022. 8. 7.
반응형

1) gradle 설정 추가

//MySql 연결에 필요한 JDBC 드라이버 제공
implementation group: 'mysql', name: 'mysql-connector-java', version: '8.0.15'

//JdbcTemplate 등 JDBC 연동에 필요한 기능 제공
implementation group: 'org.springframework', name: 'spring-jdbc', version: '5.3.20'

//DB 커넥션 풀 기능 제공
implementation group: 'org.apache.tomcat', name: 'tomcat-jdbc', version: '9.0.2'

 

- 커넥션 풀이란

출처: https://code-lab1.tistory.com/209

자바 프로그램에서 DBMS로 커넥션을 생성하는 시간은 매우 길다. 이에 매 request마다 DB커넥션을 맺는 것은

서비스 성능에 큰 영향을 끼칠 수 있다.

이를 해결하고자, 일정 개수의 DB 커넥션을 미리 만들어두는 기법인 커넥션 풀을 사용한다.

DB커넥션 풀 기능을 제공하는 모듈로는  Tomcat JDBC, HikariCP, DBCP 등이 존재한다.

 

아무리 WAS서버를 스케일 업/아웃해서 서버 성능을 향상시킨다고 하더라도, DB서버의 성능 이슈가 존재하는한 서비스 성능 향상에는 한계가 있다. 적절한 커넥션 풀 및 DB 성능관리가 서비스 품질에서 중요한 요소일 수 밖에 없다

 

 

2) DB 생성

// spring5 계정 생성
create user 'spring5'@'localhost' identified by 'spring5';
(create user '사용자'@'localhost' identified by '비밀번호';)

// spring5fs DB생성
create database spring5fs character set=utf8;

// spring5fs DB에 spring5 계정이 접근할 수 있도록 권한 부여
grant all privileges on spring5fs.* to 'spring5'@'localhost';

// member 테이블 생성
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;

 

3) DataSource 설정

JDBC API는 DataSource를 이용해 DB연결을 구한다.

DataSoruce를 스프링 빈으로 등록하고 DB 연동 기능을 구현한 빈 객체는 DataSource를 주입받아 사용한다.

@Bean(destroyMethod = "close")
public DataSource dataSource() {
   DataSource ds = new DataSource();
   //db 정보 세팅
   ds.setDriverClassName("com.mysql.jdbc.Driver");	//jdbc 드라이버 클래스 지정(MySQL드라이버 사용)
   ds.setUrl("jdbc:mysql://localhost/spring5-practice?characterEncoding=utf8");
   ds.setUsername("spring5");
   ds.setPassword("spring5");
   ds.setInitialSize(2);
   ds.setMaxActive(10);
   ds.setTestWhileIdle(true);
   ds.setMinEvictableIdleTimeMillis(60000 * 3);
   ds.setTimeBetweenEvictionRunsMillis(10 * 1000);
   return ds;
}

 

@Bean의 destroyMethod

이 inferred는 추론이란 뜻으로 close, shutdown 같은 이름을 추론하는 것이다.

종료할 메서드의 이름을 close, shutdown 으로 만들고, 굳이 destroyMethod 옵션을 주지 않아도 알아서 추론하여 실행해준다. 추론 기능을 사용하기 싫다면 초기화 값을 명시적으로 destroyMethod="" 이렇게 하면 된다.

 

3-2) DataSource 커넥션 사용

public class DbQuery {
	private DataSource dataSource;

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

	public int count() {
		Connection conn = null;
		try {
			conn = dataSource.getConnection();	//커넥션 풀에서 가용 커넥션 구함
			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) {
				}
		}
	}

}

3-3) DataSource 주요 프로퍼티

 - ds.setInitialSize(int) : 초기 커넥션 개수
 - ds.setMaxActive(int) : 커넥션 풀에서 동시에 가져올 수 있는 최대 커넥션 개수 
 - ds.setTestWhileIdle(boolean) : 유휴 커넥션 검사 (true면 검사 진행)
 - ds.setMinEvictableIdleTimeMillis(int) : 커넥션 풀 안에 유휴 상태로 유지할 최소시간 지정 (유휴시간이 지나면 제거 / 밀리초 단위)
 - ds.setTimeBetweenEvictionRunsMillis(int) : 커넥션 풀의 유휴 커넥션 검사 주기 설정 (밀리초 단위)

 

*유휴상태(idle) : 어떠한 프로그램에 의해서도 사용되지 않는 상태

 

 

 

 

4) JdbcTemplate을 이용한 쿼리 실행

스프링을 사용하면 JdbcTemplate을 이용해서 편리하게 쿼리를 수행할 수 있다.

 

4-1) JdbcTemplate 생성하기

    private JdbcTemplate jdbcTemplate;

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

dataSource를 생성자에 전달하여 JdbcTemplate 객체를 생성한다.

 

4-2) 조회

JdbcTemplate은 query() 메서드를 통해 SELECT 쿼리를 수행한다.

query()메서드의 인자인 RowMapper 인터페이스를 통해 쿼리 수행 결과를 자바 객체로 변환 

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

 

4-3) queryForObject()

쿼리 결과를 List가 아닌 Integer와 같은 정수 타입으로 한행의 결과로 받기위한 메서드

즉, 쿼리 수행 결과 행이 한 개인 경우에 사용할 수 있는 메서드

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

 

4-4) 변경

update() 메서드를 사용

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

4-5) PreparedStatementCreator

인터페이스로 createPreparedStatement()메서드가 정의되어 있음

해당 메서드는 템플릿으로부터 Connection을 제공받아 PreparedStatement를 만들어서 돌려준다.

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

 

 

 

4-6) Insert 수행 시 KeyHolder를 이용한 자동 생성 키값 구하기

자동으로 생성된 키값을 구하고 싶을 때 KeyHolder를 사용

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

 

 

 

 

 

-참고-

<MySQL 8버전 db연동 시, 다음과 같은 에러를 뱉을 경우>

client does not support authentication protocol requested by server consider upgrading mysql client

 

mysql installer > server 옆에 reconfigure > Auth 설정가서 legacy 선택 진행하면 잘됨

 

<super 권한 부여>

grant SUPER on *.* TO '사용자'@'localhost';

 

반응형