
이번주에 배운 내용(엔티티 매니저, 커넥션 풀 등)들이 생각보다 너무 어려워서 머리가 어질어질하다.
심지어 오늘 병원가느라 오후 수업도 못들어서 그 핑계로 모르는척 외면하고 싶을정도ㅎㅎ…
모르거나 이해가 안가는건 아닌데 머리속에서 정리할 시간이 부족한것 같다.
나는 개념을 하나 배우면 생각이 빨리빨리 돌아가지 않아서 개념을 배운뒤 바로 실습에 적용하는게
쉽지않다.. 이럴땐 어떻게 해야될까😢 연습밖에 답이 없나
@Test
@DisplayName("INSERT INTO TEST")
void insert_into_test() throws Exception {
//쿼리입력
String query = "INSERT INTO member (username, password) VALUES ('%s', '%s')".formatted("user1", "user1");
Connection con = ConnectionUtil.getConnection();
Statement stmt = con.createStatement();
//파괴적인 행위 -> 값을 삭제, 삽입 등
//비파괴적인 행위 -> 값 조회
int resultRows = stmt.executeUpdate(query);
log.info("Insert result: {}", resultRows);
//닫는 순서 중요
stmt.close();
con.close();
}
@Test
@DisplayName("INSERT INTO TEST")
void insert_into_test() throws Exception {
//쿼리입력
Member admin = genMember("admin", "admin");
Member member = genMember("admin", "admin");
String sql1 = genInsertQuery(admin);
String sql2 = genInsertQuery(member);
Connection con = ConnectionUtil.getConnection();
Statement stmt = con.createStatement();
//파괴적인 행위 -> 값을 삭제, 삽입 등
//비파괴적인 행위 -> 값 조회
int resultRows = stmt.executeUpdate(sql1);
log.info("Insert result: {}", resultRows);
resultRows = stmt.executeUpdate(sql2);
log.info("Insert result: {}", resultRows);
//닫는 순서 중요
stmt.close();
con.close();
}
private static String genInsertQuery(Member admin) {
return "INSERT INTO member (username, password) VALUES ('%s', '%s')".formatted(admin.getUsername(), admin.getPassword());
}
private static Member genMember(String username, String password) {
return new Member(0, username, password);
}
@BeforeEach , @AfterEach 를 통해 테스트 전 후에 반복되는 작업을 분리해서 실행시킬 수 있음Connection con = ConnectionUtil.getConnection();
Statement stmt;
@BeforeEach
void init(){
con = ConnectionUtil.getConnection();
}
@AfterEach
void close(){
//에러에 대한 핸들링이 필요
if(stmt != null){
try {
stmt.close();
} catch (SQLException e) {
log.error(e.getMessage());
}
}
if(con != null){
try {
con.close();
} catch (SQLException e) {
log.error(e.getMessage());
}
}
}
테스트는 서로 독립적으로 이루어져야 좋은 테스트
ResultSet을 통해 값을 자료구조로 가져오고, 안에 커서를 둬서 행별로 읽어올 수 있도록함Member user1 = genMember("member", "member");
String sql = genSelectQuery(user1);
stmt = con.createStatement();
//rs도 닫아줘야함
rs = stmt.executeQuery(sql);
/*String findUsername = "";
String findPassword = "";*/
Member findMember = new Member();
//커서의 처음은 컬럼명을 가리키고 있기 때문에
//다음줄이 있는지 확인해야함
if(rs.next()){
findMember.setUsername(rs.getString("username"));
findMember.setPassword(rs.getString("password"));
}
log.info("Select result: {}", findMember.getUsername());
log.info("Select result: {}", findMember.getPassword());
assertThat 으로 검증 가능❗️비밀번호를 틀리게 입력해도 로그인이 정상적으로 수행됨
password = ’’ or ‘‘ = ‘‘모두 참이기 때문에 공격이 가능함
→ SQL Injection 공격
@Test
@DisplayName("Statement Test, SQL Injection")
void statement_test() throws Exception {
Member admin = genMember("admin", "");
String query = genSelectQuery(admin);
stmt = con.createStatement();
rs = stmt.executeQuery(query);
Member findMember = new Member();
if(rs.next()){
findMember.setMemberId(rs.getInt("member_id"));
findMember.setUsername(rs.getString("username"));
findMember.setPassword(rs.getString("password"));
}
log.info("Find Member: {}", findMember);
}
PreparedStatement
매번 connection을 생성해야되는 문제가 있음
@Test
@DisplayName("Pool")
void test_1() throws Exception{
//접속정보를 풀한테 넘겨줌 -> 대신 커넥션을 따와준다.
DriverManagerDataSource dataSource = new DriverManagerDataSource(
ConnectionUtil.MysqlDbConnectionConstant.URL,
ConnectionUtil.MysqlDbConnectionConstant.USERNAME,
ConnectionUtil.MysqlDbConnectionConstant.PASSWORD
);
Connection conn1 = dataSource.getConnection();
Connection conn2 = dataSource.getConnection();
log.info("conn1 = {}", conn1);
log.info("conn2 = {}", conn2);
conn1.close();
conn2.close();
}
<configuration>
<appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
<encoder>
<pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%kvp- %msg%n</pattern>
</encoder>
</appender>
<root level="DEBUG">
<appender-ref ref="STDOUT" />
</root>
</configuration>
정보를 보기 위해선 디버그를 확인해야함
<appender> → 설정안에 기능을 묶어서 설정을 추가해줌
<encoder>→ 어떤 로그를 출력해줄지 결정
%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} -%kvp- %msg%n
[%thread] → 로그를 발생시킨 쓰레드 이름
%-5level → 로그의 레벨을 5글자로 출력
%msg → 진짜 로그에 들어있는 메세지
private final DataSource dataSource;
private Connection getConnection() throws SQLException {
return dataSource.getConnection();
}
private void closeConnection(Connection connection, Statement statement, ResultSet resultSet) {
JdbcUtils.closeResultSet(resultSet);
JdbcUtils.closeStatement(statement);
JdbcUtils.closeConnection(connection);
}
JdbcUtils는 Spring에서 제공하는 JDBC 자원 정리 유틸, null 체크 및 예외처리가 자동으로 되어 있어 편리하다.spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: user
password: pass
hikari:
maximum-pool-size: 10
minimum-idle: 5
idle-timeout: 30000
connection-timeout: 20000
pool-name: HikariPool