본문 바로가기

TIL/2021

[TIL] 0824 JDBC (Java Database Connectivity)


✏️  오늘의 학습

Flow of JDBC Operation

 

https://www.javarticles.com/2015/01/spring-jdbctemplate-example.html

 

드라이버 매니저로부터 커넥션을 받아와서 커넥션으로부터 상태를 만든다.

상태를 가지고 문제가 있으면 문제를 핸들링하고, 문제가 없으면 실제 상태를 실행시킨다. 실행시킨후 resultSet을 받아서 결과를 처리한다.

여기서 주의할 점은, 요청이 다 끝나면 statement close, connection close를 해야한다. 

prepared statement

아래와 같은 코드가 있다고 하자. 디비에서 daisy 의 이름을 가진 customer 정보를 찾는 내용이다.

daisy에 해당하는 정보를 잘 가져온다. 그런데 이 코드에는 위험한 요소가 있다.

만약 List<String> names = new JdbcCustomerRepository().findNames("tester01' OR 'a'='a"); 와 같이 OR문을 주입시켜서(SQL 인젝션) 코드가 변경된다면 이 코드는 모든 고객의 정보를 불러오게 된다. 이처럼 sql문을 문자열 조합을 하게되면 sql 인젝션에 취약한 문제를 야기한다. 이를 보완하기위해 prepare statement를 사용할 수 있다.

public class JdbcCustomerRepository {

    public List<String> findNames(String name) {
        String SELECT_SQL = "select * from customers WHERE name = '%s'".formatted(name);
        List<String> names = new ArrayList<>();

        try (Connection connection = DriverManager.getConnection("db 정보");
             Statement statement = connection.createStatement();
             ResultSet resultSet = statement.executeQuery(SELECT_SQL);) {
            //개별 row에 대한 cursor가 있어서 next로 하나씩 가져온다.
            while(resultSet.next()) {
                String customerName = resultSet.getString("name");
                UUID customerId = UUID.nameUUIDFromBytes(resultSet.getBytes("customer_id"));
                LocalDateTime createdAt = resultSet.getTimestamp("created_at").toLocalDateTime();

                names.add(customerName);
            }
        } catch (SQLException throwables) {
            logger.error("Got error while closing connection", throwables);
            throwables.printStackTrace();
        }

        return names;
    }

    public static void main(String[] args) {
        List<String> names = new JdbcCustomerRepository().findNames("daisy");
        names.forEach(v->logger.info("Found name : {}", v));
    }
}

 

일반적으로 statement를 사용하면 매번 쿼리를 실행할때마다 쿼리 문장을 분석 / 컴파일 / 실행 단계를 거치게되는 반면

prepare statement는 처음 쿼리 실행할 때 쿼리 문장을 분석 / 컴파일 / 실행하고 그 이후에는 캐쉬에 담아서 재사용하게 된다. 

또한 컴파일이 되기때문에 처음 실행한 쿼리 문장이 변경될 일이 없어서, statement 인젝션 으로 인해 발생하는 문제를 보완할 수 있게된다.

 

public class JdbcCustomerRepository {

    private static final Logger logger = LoggerFactory.getLogger(JdbcCustomerRepository.class);

    public List<String> findNames(String name) {
        String SELECT_SQL = "select * from customers WHERE name = ?";
        List<String> names = new ArrayList<>();

        try (Connection connection = DriverManager.getConnection("db 정보");
             PreparedStatement statement = connection.prepareStatement(SELECT_SQL);) {
            statement.setString(1, name); //SELECT_SQL 의 ? 에 매칭되는 값 = name
            //개별 row에 대한 cursor가 있어서 next로 하나씩 가져온다.
            try(ResultSet resultSet = statement.executeQuery()) {
                while(resultSet.next()) {
                    String customerName = resultSet.getString("name");
                    UUID customerId = UUID.nameUUIDFromBytes(resultSet.getBytes("customer_id"));
                    LocalDateTime createdAt = resultSet.getTimestamp("created_at").toLocalDateTime();
                    logger.info("customer name -> {}, customerId -> {}, createdAt -> {}", name, customerId, createdAt);

                    names.add(customerName);
                }
            }
        } catch (SQLException throwables) {
            logger.error("Got error while closing connection", throwables);
            throwables.printStackTrace();
        }

        return names;
    }

    public static void main(String[] args) {
        //List<String> names = new JdbcCustomerRepository().findNames("daisy' OR 'a'='a"); -> prepareStatement상태에서 동작하지 않음.
        List<String> names = new JdbcCustomerRepository().findNames("daisy");
        names.forEach(v->logger.info("Found name : {}", v));
    }
}

 

findNames("tester01' OR 'a'='a") 로 statement 사용할 경우 

String SELECT_SQL = "select * from customers WHERE name = '%s'".formatted(name);
Statement statement = connection.createStatement();
ResultSet resultSet = statement.executeQuery(SELECT_SQL);
logger.info("statement -> {}", statement); // statement -> com.mysql.cj.jdbc.StatementImpl@387a8303

findNames("tester01' OR 'a'='a") 로 prestatement 사용할 경우

String SELECT_SQL = "select * from customers WHERE name = ?";
PreparedStatement statement = connection.prepareStatement(SELECT_SQL);
statement.setString(1, name);
logger.info("statement -> {}", statement); // statement -> com.mysql.cj.jdbc.ClientPreparedStatement: select * from customers WHERE name = 'daisy'' OR ''a''=''a'

 

1일 1알고리즘을 다시 해보자..

https://leetcode.com/problems/complex-number-multiplication/


🎊 오늘의 느낀점

1. 약 3주간 하루 순 공부시간이 10~12시간은 되는 것 같다. 주말도 빠짐없이 공부를 했는데,

3주를 미친듯이(?) 달렸더니 몸이 신호를 보낸다. 오늘은 많이 피곤했고 집중력이 흐려지는 순간이 꽤나 많았다. 컨디션 조절 잘 해야겠다.

 

2. 오늘 팀원분의 제안으로 타일러팀의 학습 루틴을 바꿔보았다.

더 나은 학습 루틴을 만들어가기위해 아이디어를 제안하고, 개선점을 제안하는 팀원들에게 많은걸 배운다.

멘토님이 이 슬랙글을 보고 하산하라고 댓글을 남겨주셨다. https://blog.daum.net/-hi1004-/46 

너무 빡세서 학습 루틴에서 하산하라는 말인가? 생각했는데, 그런의미가 아니였다. 약간 기분 좋았다. 🥺

멘토님이 하산하라 하셨다

3. 멘토님이 오늘 공유해주신 내용이다. 나도 이런 개발 관련 글 많이 공유할 수 있도록 노력해야지! 

https://stackoverflow.com/questions/9579403/intellij-idea-automatically-add-final-keyword-to-the-generated-variables

 

'TIL > 2021' 카테고리의 다른 글

[TIL] 0823 테스팅에 대해 알아보자  (0) 2021.08.23
[TIL] 0822 Logger  (0) 2021.08.22
[TIL] 0820 우선순위를 명확하게  (0) 2021.08.21
[TIL] 0818 Spring 컴포넌트  (0) 2021.08.18
[TIL] 0813 Database 이것저것5  (0) 2021.08.13