1) ๋น๊ด์ ๋ฝ (Pessimistic Lock)์ ์ด์ฉํ ๋์์ฑ ์ด์ ํด๊ฒฐ
(1) ๊ฐ์
- ์ด์ ๊ฒ์๊ธ์์ Synchronized ํค์๋๋ฅผ ์ด์ฉํ์ฌ ๋์์ฑ ์ด์๋ฅผ ํด๊ฒฐํ์์ผ๋ ์ค๋ฌด ํ๊ฒฝ์์๋ 1๋์ WAS๋ง์ ์ด์ฉํ์ง ์๊ณ ์ฌ๋ฌ ๋์ WAS ์๋ฒ๋ฅผ ์ด์ฉํ๊ธฐ ๋๋ฌธ์ Synchronized๋ฅผ ์ฌ์ฉํ์ง ์๋๋ค.
- ์๋ํ๋ฉด Synchronized ํค์๋๋ ํ ๋์ WAS ์๋ฒ์์๋ง ๋๊ธฐํ ํ ์ ์๊ธฐ ๋๋ฌธ์ด๋ค.
- ๋ฐ๋ผ์, ์ฌ๋ฌ ๋์ WAS ์๋ฒ์์ DB์ ํน์ ๋ฐ์ดํฐ๋ฅผ ๋ณ๊ฒฝํ๋ ๊ฒฝ์ฐ, Race Condition ์ด ๋ฐ์ํ ์ ์๋ค.
- ์ด์ฒ๋ผ Race Condition์ ํด๊ฒฐํ๊ธฐ ์ํด ์ด๋ฒ์๋ MySQL์์ ์ ๊ณตํ๋ Lock์ ์ด์ฉํด ๋น๊ด์ ๋ฝ์ผ๋ก ํด๊ฒฐํด๋ณด๊ณ ์ ํ๋ค.
- ์ด์ ๊ฒ์๊ธ์ "(2) ์ด๊ธฐ ์ค์ " ๋ถ๋ถ๋ถํฐ "(4) ๊ฒฐ๊ณผ [๋์์ฑ ์ด์ ๋ฐ์]" ๋ถ๋ถ๊น์ง๋ ๋์ผํ๋ฏ๋ก ์ด์ ๊ฒ์๊ธ์ ์ฝ์๋ค๋ฉด (5) ๋ถ๋ถ ๋ถํฐ ์ฝ์ด๋ณด๋๋ก ํ์!
(2) ์ด๊ธฐ ์ค์
์ค์ต ํ๊ฒฝ
- MySQL 8.0
- JDK 11 (IntelliJ)
- Spring boot 2.7
- Spring JDBC 5.3.29
application.properties ์ค์
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.url=jdbc:mysql://localhost:3306/example?serverTimezone=UTC&characterEncoding=UTF-8
spring.datasource.username=root
spring.datasource.password=root
์ค์ต ํ ์ด๋ธ ์์ฑ
// ํ
์ด๋ธ ์์ฑ
CREATE TABLE MEMBER (
ID INT not null auto_increment,
NAME VARCHAR(100) not null,
JOB VARCHAR(30) not null,
POINT int not null,
primary key (ID));
// ์ด๊ธฐ ๋ฐ์ดํฐ ์์ฑ
INSERT INTO MEMBER VALUES (1, "userA", 'Student', 0);
INSERT INTO MEMBER VALUES (2, "userB", 'Student', 10);
INSERT INTO MEMBER VALUES (3, "userC", 'Student', 20);
MemberDTO ์์ฑ
@Getter
@Setter
public class Member {
private int id;
private String name;
private String job;
private int point;
}
(3) ์์ ์ฝ๋
- ์ค์ ํ๊ฒฝ์ฒ๋ผ ์น ์๋ฒ๋ฅผ ์ฌ๋ฌ ๋ ๊ตฌ์ฑํด์ ์งํํด์ผ ํ๋ ๋จ์ํ ํ ์คํธ ์ฉ์ด๋ฏ๋ก ์ค๋ ๋ ํ์ ์ด์ฉํ์๊ณ ์ฌ๋ฌ ๋์ ์น ์๋ฒ์์ ์ ๊ทผํ๋ ๊ฒ์ฒ๋ผ ๊ฐ์ ํ๋ค!!!!
- ์๋์ ์์ ์ฝ๋๋ ์ฌ๋ฌ ์ค๋ ๋์์ DB์ ์๋ ํน์ Member ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์์ Point๋ฅผ +1 ์ฆ๊ฐํ ๋ค์ DB์ ์ ๋ฐ์ดํธ ํ๋ ๋ด์ฉ์ด๋ค.
- ํน์ Member ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ getMember() ๋ฉ์๋, ํน์ Member์ Point๋ฅผ +1 ์ฆ๊ฐํ ๋ค์ DB์ ์ ๋ฐ์ดํธํ๋ updateMember() ๋ฉ์๋๊ฐ ์๋ค.
- ๋น๊ด์ ๋ฝ ์ฒ๋ฆฌ ์์ด ์ฌ๋ฌ ์น ์๋ฒ์์ ๋์์ ๊ณต์ ์์์ ์ ๊ทผํ์ ๋, ๋ฐ์ดํฐ ์ผ๊ด์ฑ์ด ์ง์ผ์ง๋์ง ํ ๋ฒ ํ์ธํด๋ณด์!
@Getter
@Setter
public class JdbcTest {
public static HikariDataSource dataSource = new HikariDataSource();
static {
// ์ ์ ์ ๋ณด
String url = "jdbc:mysql://localhost:3306/sesac?serverTimezone=Asia/Seoul&characterEncoding=UTF-8";
String id = "root";
String pwd = "root";
// ์ค๋ ๋ํ ์ค์
dataSource.setJdbcUrl(url);
dataSource.setUsername(id);
dataSource.setPassword(pwd);
dataSource.setMaximumPoolSize(100);
dataSource.setPoolName("test");
}
// Member ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๋ ๋ฉ์๋
public static Member getMember(Connection conn, String sql, String name) {
PreparedStatement pstmt = null;
ResultSet rs = null;
Member member = new Member();
try {
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, name);
rs = pstmt.executeQuery();
while(rs.next()) {
member.setId(rs.getInt("id"));
member.setName(rs.getString("name"));
member.setJob(rs.getString("job"));
member.setPoint(rs.getInt("point"));
}
} catch (SQLException e) {
throw new RuntimeException("SQL Error ๋ฐ์!!");
} finally {
// ResultSet ๋ฐ PreparedStatement ๊ฐ์ฒด Close()
try {
rs.close();
pstmt.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
return member;
}
// Member ๋ฐ์ดํฐ ์
๋ฐ์ดํธ ํ๋ ๋ฉ์๋
public static int updateMember(Connection conn, String sql, Member member) {
PreparedStatement pstmt = null;
int result = 0;
try {
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, member.getPoint());
pstmt.setString(2, member.getName());
result = pstmt.executeUpdate();
} catch (SQLException e) {
throw new RuntimeException(e);
} finally {
// PreparedStatement ๊ฐ์ฒด Close()
try {
pstmt.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
return result;
}
public static void main(String[] args) throws SQLException {
// ์ค๋ ๋ํ ์์ฑ
ExecutorService es = Executors.newCachedThreadPool();
int count = 30;
for (int i=1; i<=count; i++) {
// Count ์ซ์๋งํผ ๋น๋๊ธฐ๋ก ์์
์ฒ๋ฆฌ
es.execute(() -> {
try {
// Connection ๊ฐ์ฒด ์์ฑ + sql ์์ฑ
Connection conn = dataSource.getConnection();
conn.setAutoCommit(false);
String sql = "Select * from member where name = ?";
String name = "userA";
// Member ๊ฐ์ฒด ๊ฐ์ ธ์ค๊ธฐ
Member member = getMember(conn, sql, name);
// Point + 1 ์ฆ๊ฐ
member.setPoint(member.getPoint() + 1);
// Member ์
๋ฐ์ดํธ
sql = "Update member set point = ? where name = ?";
int result = updateMember(conn, sql, member);
// transaction ์ฒ๋ฆฌ
if (result >= 1) {
conn.commit();
} else {
conn.rollback();
}
} catch (SQLException e) {
throw new RuntimeException(e);
}
});
// ์ค๋ ๋ ํ ์์
ํ์ ๋ชจ๋ ๋ค์ด๊ฐ๋ฉด Shutdown()
if (i == count) {
es.shutdown();
break;
}
}
}
}
(4) ๊ฒฐ๊ณผ [๋์์ฑ ์ด์ ๋ฐ์]
[์คํ ์ ]
MySQL [sesac]> select * from member;
+----+-------+---------+-------+
| ID | NAME | JOB | POINT |
+----+-------+---------+-------+
| 1 | userA | Student | 0 |
| 2 | userB | Student | 20 |
| 3 | userC | Student | 30 |
+----+-------+---------+-------+
3 rows in set (0.001 sec)
[์คํ ํ]
MySQL [sesac]> select * from member;
+----+-------+---------+-------+
| ID | NAME | JOB | POINT |
+----+-------+---------+-------+
| 1 | userA | Student | 28 |
| 2 | userB | Student | 20 |
| 3 | userC | Student | 30 |
+----+-------+---------+-------+
3 rows in set (0.001 sec)
→ Point +1์ฉ 30๋ฒ ํ์ผ๋ฏ๋ก ์ด 30์ด ๋์ด์ผ ํ๋๋ฐ 28์ด ๋์ด์๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
→ ์ฌ๋ฌ ๋์ WAS ์๋ฒ์์ ๊ณต์ ์์์ธ (Point)์ ๋์์ ์ ๊ทผํ์ฌ Race Condition์ด ๋ฐ์ํ์๊ธฐ ๋๋ฌธ์ด๋ค.
→ ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด ๋น๊ด์ ๋ฝ์ ์ด์ฉํด๋ณด์!
(5) ๋น๊ด์ ๋ฝ ์ ์ฉ
@Getter
@Setter
public class JdbcTest2 {
...
...
...
// Member ๋ฐ์ดํฐ ๊ฐ์ ธ์ค๋ ๋ฉ์๋
public static Member getMember(Connection conn, String sql, String name) {
PreparedStatement pstmt = null;
ResultSet rs = null;
Member member = new Member();
try {
// ๋น๊ด์ ๋ฝ ์ค์ !!!!!
pstmt = conn.prepareStatement(sql + " for update");
pstmt.setString(1, name);
rs = pstmt.executeQuery();
while(rs.next()) {
member.setId(rs.getInt("id"));
member.setName(rs.getString("name"));
member.setJob(rs.getString("job"));
member.setPoint(rs.getInt("point"));
}
} catch (SQLException e) {
throw new RuntimeException("SQL Error ๋ฐ์!!");
} finally {
// ResultSet ๋ฐ PreparedStatement ๊ฐ์ฒด Close()
try {
rs.close();
pstmt.close();
} catch (SQLException e) {
throw new RuntimeException(e);
}
}
return member;
}
...
...
...
- DB์ ์ ๊ทผํ์ฌ Member ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ ์ ํด๋น ๋ ์ฝ๋์ ๋ฒ ํ ๋ฝ์ ์ค์ ํ๋ค.
- ๋ค๋ฅธ WAS ์๋ฒ์์ ํด๋น ๋ ์ฝ๋๋ฅผ ๊ฐ์ ธ์ค๊ธฐ ์ ์ ๋ฒ ํ ๋ฝ์ ์ค์ ํด์ผ ํ๋๋ฐ ์ด๋ฏธ ๋ค๋ฅธ WAS ์๋ฒ์์ ๋ฒ ํ ๋ฝ์ด ๊ฑธ๋ ค์์ผ๋ฏ๋ก ํด๋น ๋ฝ์ด ํ๋ฆฌ๊ธฐ ์ ๊น์ง๋ ๋ฐ์ดํฐ๋ฅผ ์กฐํํ ์ ์๋ค.
- ํด๋น ๋ฐ์ดํฐ์ ๋ฒ ํ ๋ฝ์ ์ฒ์ ์ค์ ํ ์๋ฒ์์ Member ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ณ ์ ๋ฐ์ดํธ ํ ๋ค์ Commit ํ๊ธฐ ์ ๊น์ง๋ ๋ฒ ํ ๋ฝ์ด ํด์ ๋์ง ์์ผ๋ฏ๋ก ๋์์ฑ ์ด์๋ฅผ ํด๊ฒฐํ ์ ์๋ค.
(6) ๊ฒฐ๊ณผ [๋์์ฑ ์ด์ ํด๊ฒฐ]
[์คํ ์ ]
MySQL [sesac]> select * from member;
+----+-------+---------+-------+
| ID | NAME | JOB | POINT |
+----+-------+---------+-------+
| 1 | userA | Student | 0 |
| 2 | userB | Student | 20 |
| 3 | userC | Student | 30 |
+----+-------+---------+-------+
3 rows in set (0.001 sec)
[์คํ ํ]
MySQL [sesac]> select * from member;
+----+-------+---------+-------+
| ID | NAME | JOB | POINT |
+----+-------+---------+-------+
| 1 | userA | Student | 30 |
| 2 | userB | Student | 20 |
| 3 | userC | Student | 30 |
+----+-------+---------+-------+
3 rows in set (0.001 sec)
- ๋!
- ๋ค์์๋ ๋๊ด์ ๋ฝ์ ์ด์ฉํด ๋์์ฑ ์ด์๋ฅผ ํด๊ฒฐํด๋ณด์!
[์ฐธ๊ณ ] ๋น๊ด์ ๋ฝ (Pessimistic Lock) ์ด๋
- ๋น๊ด์ ๋ฝ์ ํ์ฌ ํธ๋์ญ์ ์์ ๋ณ๊ฒฝํ๊ณ ์ ํ๋ ๋ ์ฝ๋์ ๋ํด ์ ๊ธ์ ๋จผ์ ํ๋ํ๊ณ ๋ณ๊ฒฝ ์์ ์ ์ฒ๋ฆฌํ๋ ๋ฐฉ์์ ๋งํ๋ค.
- ์ด ์ฒ๋ฆฌ ๋ฐฉ์์ ์ด๋ฆ์์๋ ๋๋ ์ ์๋ฏ์ด, “ํ์ฌ ๋ณ๊ฒฝํ๊ณ ์ ํ๋ ๋ ์ฝ๋๋ฅผ ๋ค๋ฅธ ํธ๋์ญ์ ์์๋ ๋ณ๊ฒฝํ ์ ์๋ค” ๋ผ๋ ๋น๊ด์ ์ธ ๊ฐ์ ์ ํ๊ธฐ ๋๋ฌธ์ ๋จผ์ ์ ๊ธ์ ํ๋ํ๋ค.
๋ฐ์ํ
'๐จโ๐ป Back End > JDBC' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
Synchrozied ํค์๋๋ฅผ ์ด์ฉํ ๋์์ฑ ์ด์ ํด๊ฒฐ (0) | 2023.08.28 |
---|---|
์ปค๋ฅ์ ํ์ด๋ (Connection Pool) (0) | 2023.08.28 |
Statement๋ณด๋ค PreparedStatement๋ฅผ ์ฌ์ฉํด์ผ ํ๋ ์ด์ (2) | 2023.08.27 |
JDBC์์ ์์ฃผ ์ฌ์ฉ๋๋ ๋ฉ์๋ ์ ๋ฆฌ (0) | 2023.08.26 |
JDBC๋ฅผ ์ด์ฉํ CRUD ์ค์ต (0) | 2023.08.26 |