Proejct Code : 링크
MySQL
, JDBC
, junit
등의 의존성을 추가해주었습니다. (편의를 위해서 롬복 추가!)~~
dependencies {
compileOnly("org.projectlombok:lombok:1.18.36")
annotationProcessor("org.projectlombok:lombok:1.18.36")
testImplementation platform('org.junit:junit-bom:5.10.0')
testImplementation 'org.junit.jupiter:junit-jupiter'
runtimeOnly("com.mysql:mysql-connector-j:9.1.0")
testImplementation("org.assertj:assertj-core:3.26.3")
implementation("com.fasterxml.jackson.core:jackson-databind:2.18.2")
implementation("com.fasterxml.jackson.datatype:jackson-datatype-jsr310:2.18.2")
}
~~
SimpleDB
, Sql
SimpleDB
Sql
private void makeArticleTestData() {
IntStream.rangeClosed(1, 6).forEach(no -> {
boolean isBlind = no > 3;
String title = "제목%d".formatted(no);
String body = "내용%d".formatted(no);
simpleDb.run("""
INSERT INTO article
SET createdDate = NOW(),
modifiedDate = NOW(),
title = ?,
`body` = ?,
isBlind = ?
""", title, body, isBlind);
});
}
SQL Injection 공격을 방어할 필요가 있습니다.
PreparedStatement
를 사용해서, 안전하게 파라미터를 바인딩 해주었습니다. @Override
public void execute(String inputSql, Object... params) {
try {
PreparedStatement preparedStatement = connection.prepareStatement(inputSql);
for (int i = 0; i < params.length; i++) {
preparedStatement.setObject(i + 1, params[i]);
}
preparedStatement.execute();
printRowSql(preparedStatement.toString());
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
if(autoCloseConnection) {
simpleDb.returnConnection(connection);
}
}
}
SimpleDb
에서 Connection pool 을 관리하도록 하였습니다.Lazy Initialization
을 채택하였습니다.concurrent
관련된 클래스를 적극 활용하였습니다. (ConcurrentLinkedQueue
, AtomicInteger
)getConnection()
메서드를 사용하게 하고, 이 메서드는 Queue를 사용해 컨트롤 하도록 구성하였습니다. SimpleDb.java
~~~
private Connection getConnection() throws SQLException {
if(!pool.isEmpty()) {
return pool.poll();
} else if(currentConnections.get() < maxConnections) {
currentConnections.incrementAndGet();
return newConnection();
} else {
throw new SQLException("Now connection is Max, maxConnections:" + this.maxConnections);
}
}
~~~
Sql.java
~~
@Override
public void execute(String inputSql) {
try {
Statement statement = connection.createStatement();
statement.execute(inputSql);
printRowSql(inputSql);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
if(autoCloseConnection) {
simpleDb.returnConnection(connection);
}
}
}
~~
returnConnection()
을 사용하여 Queue에 반납해서 재활용하도록 구성하였습니다. @Test
@DisplayName("rollback")
public void t018() {
// SimpleDB에서 SQL 객체를 생성합니다.
long oldCount = simpleDb.genSql()
.append("SELECT COUNT(*)")
.append("FROM article")
.selectLong();
// 트랜잭션을 시작합니다.
Sql sql = simpleDb.genSql();
simpleDb.startTransaction(sql.getConnection());
sql
.append("INSERT INTO article ")
.append("(createdDate, modifiedDate, title, body)")
.appendIn("VALUES (NOW(), NOW(), ?)", "새 제목", "새 내용")
.insert();
simpleDb.rollback(sql.getConnection());
long newCount = simpleDb.genSql()
.append("SELECT COUNT(*)")
.append("FROM article")
.selectLong();
assertThat(newCount).isEqualTo(oldCount);
}
autoCommit
기능을 끄고, 별도의 flag를 둬서, 반납또한 진행하지 않도록 구성하였습니다.connection.rollback()
을 통해 롤백 시켜줍니다. public void startTransaction(Connection connection) {
try {
connection.setAutoCommit(false);
setAutoCloseConnection(false);
} catch (SQLException e) {
setAutoCloseConnection(true);
throw new RuntimeException(e);
}
}
public void rollback(Connection connection) {
try {
connection.rollback();
connection.setAutoCommit(true);
setAutoCloseConnection(true);
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
setAutoCloseConnection(true);
}
}