Spring/JDBC

[JDBC] JDBC로 DB 직접 연결

푸라멘 2024. 4. 10. 00:54

JDBC(Java Database Connectivity)란?

JDBC는 Java 기반 애플리케이션의 데이터를 데이터베이스에 저장 및 업데이트하거나, 데이터베이스에 저장된 데이터를 Java에서 사용할 수 있도록 하는 Java API이다.

 

JDBC가 없던 시절에는 

  • Connection 연결
  • SQL 전
  • 결과 응답

위 3가지 과정을 거쳤는데 만약 사용하는 DB를 변경하 애플리케이션의 코드 변경 및 그 DB를 사용하기위한 Connection 연결, SQL 전달, 결과 응답 받는 방법을 다시 익혀서 사용해야했다.

 

 

JDBC 표준 인터페이

 

JDBC는 3가지 기능을 표준 인터페이스로 정의하여 제공한다.

  • java.sql.Connection - 연결
  • java.sql.Statement - SQL을 담은 내용
  • java.sql.ResultSet - SQL 요청 응답

각 DB는 JDBC 표준 인터페이스를 구현한 드라이버를 제공하기 때문에  JDBC를 사용하면 각 DB의 사용법을 따로 익힐 필요 없이 사용 할 수 있게 된다.

 

JDBC 동작의 흐름

 

 

JDBC API의 구성 요소들의 동작 흐름은 다음과 같다.

 

 

  1. JDBC 드라이버 로딩 : 사용하고자 하는 JDBC 드라이버를 로딩한다. JDBC 드라이버는 DriverManager 클래스를 통해 로딩된다.
  1. Connection 객체 생성 : JDBC 드라이버가 정상적으로 로딩되면 DriverManager를 통해 데이터베이스와 연결되는 세션(Session)인 Connection 객체를 생성한다.
Connection conn = DriverManager.getConnection("jdbc:h2:tcp://localhost/~/test", "sa", "");

//application.properties에
//spring.datasource.url=jdbc:h2:tcp://localhost/~/test
//spring.datasource.driver-class-name=org.h2.Driver
//spring.datasource.username=sa
//spring.datasource.password=
// 설정을 추가해 다음과 같이 연결할 수도 있다.
//내부 코드를 보면 결국 DriveManager를 이용한다.

// dataSource에서 직접get하는 것은 새로운 connection 객체를 가져온다.
// 동일한 transaction이 아니다.
conn = dataSource.getConnection();
// or
// DataSourceUtils는 현재 스레드에 활성 트랜잭션이 있다면 그 connection을 가져온다.
// 즉 같은 작업으로 인식하기에 rollback시 같이 rollback 된다.
conn = DataSourceUtils.getConnection(dataSource);
  1. Statement 객체 생성 : Statement 객체는 작성된 SQL 쿼리문을 실행하기 위한 객체로 정적 SQL 쿼리 문자열을 입력으로 가진다.
// 쿼리생성
PreparedStatement pstmt = conn.prepareStatement("select * from member where id = ?");
// ? 를 id로 치환
pstmt.setLong(1, id);
  1. Query 실행 : 생성된 Statement 객체를 이용하여 입력한 SQL 쿼리를 실행한다.
  2. ResultSet 객체로부터 데이터 조회 : 실행된 SQL 쿼리문에 대한 결과 데이터 셋이다.
//db의 실제 쿼리가 이때 날라가간다.
ResultSet rs = pstmt.executeQuery();

//쿼리 실행 결과가 rs에 담긴다
if (rs.next()) {
	Member member = new Member();
    member.setName(rs.getString("name"));
    member.setId(rs.getLong("id"));
}
  1. ResultSet, Statement, Connection 객체들의 Close : JDBC API를 통해 사용된 객체들은 생성된 객체들을 사용한 순서의 역순으로 Close 한다.
//close하지않으면 요청 시마다 db connection 수가 늘어 
//db를 이용할 수 없게된다.

//resultSet close
try {
	if (rs != null) {
		rs.close();
	}
} catch (SQLException e) {
	e.printStackTrace();
}
//prepareState close
try {
	if (pstmt != null) {
		pstmt.close();
	}
} catch (SQLException e) {
	e.printStackTrace();
}
//connection close
try {
	if (conn != null) {
		conn.close();
	}
} catch (SQLException e) {
	e.printStackTrace();
}

 

 

 

 

JDBCTemplate

JDBC를 직접 이용하는 방식은 매번 Connection을 맺고 끊어줘야해서 코드가 길어진다. 

JDBC Template을 이용하면 이러한 반복적인 방식을 줄여 좀 더 간편하게 코드를 짤 수 있다.

 	@Override
    public Member save(Member member) {
        SimpleJdbcInsert jdbcInsert = new SimpleJdbcInsert(jdbcTemplate);
        jdbcInsert.withTableName("member").usingGeneratedKeyColumns("id");
        Map<String, Object> parameters = new HashMap<>();
        parameters.put("name", member.getName());

        Number key = jdbcInsert.executeAndReturnKey(new MapSqlParameterSource(parameters));
        member.setId(key.longValue());

        return member;
    }

    @Override
    public Optional<Member> findById(Long id) {
        List<Member> result = jdbcTemplate.query("select * from member where id = ?", memberRowMapper(), id);
        return result.stream().findAny();
    }

    @Override
    public Optional<Member> findByName(String name) {
        List<Member> result = jdbcTemplate.query("select * from member where name = ?", memberRowMapper(), name);
        return result.stream().findAny();
    }

    @Override
    public List<Member> findByAll() {
        List<Member> result = jdbcTemplate.query("select * from member", memberRowMapper());
        return result;
    }

    private RowMapper<Member> memberRowMapper() {
        return new RowMapper<Member>() {
            @Override
            public Member mapRow(ResultSet rs, int rowNum) throws SQLException {
                Member member = new Member();
                member.setId(rs.getLong("id"));
                member.setName(rs.getString("name"));
                return member;
            }
        };
    }

 

 

 

Connection Pool

데이터베이스와 연결하는 Connection 객체를 생성하는 작업은 비용이 많이드는 작업이다.

 

Connection 객체 생성 과정

  • 애플리케이션에서 DB 드라이버를 통해 커넥션을 조회한다.
  • DB 드라이버는 DB와 TCP/IP 커넥션을 연결한다. ( ex)  way handshake)
  • DB 드라이버는 TCP/IP 커넥션이 연결되면 아이디와 패스워드, 기타 부가 정보를 DB에 전달한다.
  • DB는 아이디, 패스워드를 통해 내부 인증을 거친 후 내부에 DB를 생성한다.'
  • DB 커넥션이 생성 완료되었다는 응답을 보낸다.
  • DB 드라이버는 커넥션 객체를 생성해 클라이언트에 반화한다.

Connectino객체 생성과정이 복잡하기에 DB작업을 하기 위해 매번 Connection을 만들고 지우는것은 비용이많이들고 비효율 적이다.

 

이러한 문제를 해결하기 위해 애플리케이션 로딩시점에 Connection 객체를 미리 생성하고, 애플리케이션에서  데이터베이스에 연결이 필요할 경우 미리 준비된 Connecetion 객체를 사용하여 애플리케이션의 성능을 향상시킨다.

 

Connection 객체를 미리 생성하여 보관하고 필요할 때 할 당해주는 역할을 하는 것이 Connection Pool 이다.

 

 

 

 

 

 

 

참고 문헌