✏️ 오늘의 학습
Flow of JDBC Operation
드라이버 매니저로부터 커넥션을 받아와서 커넥션으로부터 상태를 만든다.
상태를 가지고 문제가 있으면 문제를 핸들링하고, 문제가 없으면 실제 상태를 실행시킨다. 실행시킨후 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. 멘토님이 오늘 공유해주신 내용이다. 나도 이런 개발 관련 글 많이 공유할 수 있도록 노력해야지!
'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 |