1) Synchrozied ํค์๋๋ฅผ ์ด์ฉํ ๋์์ฑ ์ด์ ํด๊ฒฐ
(1) ๊ฐ์
- ์น ์ ํ๋ฆฌ์ผ์ด์ ์๋ฒ (WAS) ์์๋ HTTP ์์ฒญ ๋น User Thread๋ฅผ ๋ง๋ค๊ธฐ ๋๋ฌธ์ 1๋์ WAS์์ ๋จ์ผ DB ๋ก์ ์ธ์ ์ ์์ฒญ ๊ฐ์๋งํผ ์ฌ์ฉํ๋ค.
- ๋ฐ๋ผ์, ์ฌ๋ฌ DB ์ธ์ ์์ ํน์ ๋ฐ์ดํฐ๋ฅผ ๋ณ๊ฒฝํ๋ Race Condition ์ด ๋ฐ์ํ ์ ์๋ค.
- ์ด์ฒ๋ผ Race Condition ์ ํด๊ฒฐํ๊ธฐ ์ํด Java์์ ์ ๊ณตํ๋ Synchronized ํค์๋๋ฅผ ์ด์ฉํด ํด๊ฒฐํด๋ณด๊ณ ์ ํ๋ค.
(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) ์์ ์ฝ๋
- ๋น๋๊ธฐ ์์ ์ฒ๋ฆฌ๋ฅผ ์ํด ์ค๋ ๋ ํ (ExecutorService)์ ์ฌ์ฉํ์๋ค.
- ์๋์ ์์ ์ฝ๋๋ ์ฌ๋ฌ ์ค๋ ๋์์ DB์ ์๋ ํน์ Member ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์์ Point๋ฅผ +1 ์ฆ๊ฐํ ๋ค์ DB์ ์ ๋ฐ์ดํธ ํ๋ ๋ด์ฉ์ด๋ค.
- ํน์ Member ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๋ getMember() ๋ฉ์๋, ํน์ Member์ Point๋ฅผ +1 ์ฆ๊ฐํ ๋ค์ DB์ ์ ๋ฐ์ดํธํ๋ updateMember() ๋ฉ์๋๊ฐ ์๋ค.
- Synchronized ํค์๋ ์์ด ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๋์์ ๊ณต์ ์์์ ์ ๊ทผํ์ ๋, ๋ฐ์ดํฐ ์ผ๊ด์ฑ์ด ์ง์ผ์ง๋์ง ํ ๋ฒ ํ์ธํด๋ณด์!
@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์ด ๋์ด์๋ ๊ฒ์ ํ์ธํ ์ ์๋ค.
→ ์ฌ๋ฌ ์ค๋ ๋์์ ๊ณต์ ์์์ธ (Point)์ ๋์์ ์ ๊ทผํ์ฌ Race Condition์ด ๋ฐ์ํ์๊ธฐ ๋๋ฌธ์ด๋ค.
→ ์ด๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด Synchronized ํค์๋๋ฅผ ์ด์ฉํด๋ณด์!
(5) Synchronized ํค์๋ ์ ์ฉ
@Getter
@Setter
public class JdbcTest {
...
...
...
public static void main(String[] args) throws SQLException {
...
...
...
for (int i=1; i<=count; i++) {
// Count ์ซ์๋งํผ ๋น๋๊ธฐ๋ก ์์
์ฒ๋ฆฌ
es.execute(() -> {
try {
// Connection ๊ฐ์ฒด ์์ฑ + sql ์์ฑ
Connection conn = dataSource.getConnection();
conn.setAutoCommit(false);
synchronized (JdbcTest.class) {
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;
}
}
}
}
- Synchronized ๋ธ๋ก์ ์ด์ฉํด์ Member ๋ฐ์ดํฐ๋ฅผ ๊ฐ์ ธ์ค๊ณ ์ ๋ฐ์ดํธ ํ๋ ๋ถ๋ถ์ Lock ์ฒ๋ฆฌํ์ฌ ์ฌ๋ฌ ์ค๋ ๋๊ฐ ๋์์ ๊ณต์ ์์์ ์ ๊ทผํ์ง ๋ชปํ๋๋ก ํ์๋ค.
→ ์ํธ ๋ฐฐ์ ๋๊ธฐํ!!
(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)
- ๋์์ฑ ์ด์ ํด๊ฒฐ!
- ์ฌ์ค, ์์ ์ฝ๋๋ ์ค์ ์น ์๋ฒ๋ฅผ ํตํ ์์ฒญ์ผ๋ก ํ ๊ฒ์ด ์๋๋ผ ๊ฐ๋จํ๊ฒ JDBC์ ์ค๋ ๋ํ์ ์ด์ฉํ์ฌ ํ ์คํธํ์๊ธฐ ๋๋ฌธ์ ์ง๊ด์ ์ผ๋ก ์๋ฟ์ง ์์ ์ ์๋ค.
- ๋์ค์ ํ๋ก์ ํธํ ๋, ๋ถ์ฐ ์์คํ ํ๊ฒฝ์์ ํ ์คํธ ํด๋ณด๋ ๊ฒ์ด ์ข์ ๊ฒ ๊ฐ๋ค b
๋ฐ์ํ
'๐จโ๐ป Back End > JDBC' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
๋น๊ด์ ๋ฝ (Pessimistic Lock)์ ์ด์ฉํ ๋์์ฑ ์ด์ ํด๊ฒฐ (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 |