-
Java Springμμ “reflection(리νλ μ )“μ Javaμ λ°νμ μμ μ ν΄λμ€, λ©μλ, νλ λ±μ μ κ·Όνκ³ μ‘°μν μ μλ κΈ°λ₯μ μλ―Έν©λλ€. μΌλ°μ μΈ μ»΄νμΌ μμ μ΄ μλ, μ€ν μ€μΈ μ ν리μΌμ΄μ μ ꡬ쑰λ₯Ό λμ μΌλ‘ λΆμνκ±°λ λ³κ²½νλ λ° νμ©λ©λλ€.
π‘ 리νλ μ μ΄λ?
Java 리νλ μ APIλ
java.lang.reflectν¨ν€μ§λ₯Ό ν΅ν΄ μ 곡λλ©°, λ€μκ³Ό κ°μ κΈ°λ₯μ μ 곡ν©λλ€:- ν΄λμ€ μ΄λ¦μΌλ‘
Classκ°μ²΄λ₯Ό μ»κΈ° - ν΄λμ€μ μμ±μ, λ©μλ, νλ μ 보 μ‘°ν
- λ©μλ νΈμΆ, νλ κ° μ½κΈ°/μ°κΈ° λ± λμ μ‘°μ
- μ κ·Ό μ νμ(private λ±) 무μνκ³ μ κ·Ό (
setAccessible(true))
π Springμμ 리νλ μ μ΄ μ°μ΄λ μ£Όμ μ
Spring νλ μμν¬λ λ΄λΆμ μΌλ‘ 리νλ μ μ λ§€μ° κ΄λ²μνκ² μ¬μ©ν©λλ€:
CreatedTue, 29 Jul 2025 14:07:45 +0900 - ν΄λμ€ μ΄λ¦μΌλ‘
-
Spring Boot 3 + JPA + MySQL νκ²½μμ νΉμ ν μ΄λΈμ λν΄
findλ°saveμμ μ row-level lockλ§ μ¬μ©νκ³ table-level lockμ νΌνκ³ μΆμ κ²½μ°, λ€μκ³Ό κ°μ μ λ΅μ μ¬μ©ν μ μμ΅λλ€.
β ν΅μ¬ κ°λ μμ½
-
MySQL InnoDB μ€ν λ¦¬μ§ μμ§μ κΈ°λ³Έμ μΌλ‘ row-level lockμ μ§μνλ©°, JPAμ κΈ°λ³Έ λμμ row-level lockμ μ¬μ©ν©λλ€.
-
κ·Έλ¬λ λ€μ 쑰건μ λ§μ‘±νμ§ μμΌλ©΄ innoDBλΌλ table lockμ΄ λ°μν μ μμ΅λλ€:
- WHERE μ μ μΈλ±μ€λ₯Ό μ μ¬μ©νμ§ λͺ»νκ±°λ
- λ½ λͺ¨λκ° λͺ μλμ§ μμκ±°λ
UPDATEλSELECT ... FOR UPDATEμμ λ²μ κ²μμ μ λλ‘ κ΅¬μ±νμ§ μμκ±°λ
β μ μ 쑰건: ν μ΄λΈμ΄ InnoDB μμ§μΈμ§ νμΈ
SHOW TABLE STATUS WHERE Name = 'your_table_name';Engine컬λΌμ΄InnoDBμ¬μΌ row-level lockμ΄ μ μ©λ©λλ€.- λ§μ½
MyISAMμ΄λ©΄ row-level lockμ΄ λΆκ°λ₯νλ―λ‘ λ°λμ InnoDBλ‘ λ³κ²½ν΄μΌ ν©λλ€:
ALTER TABLE your_table_name ENGINE = InnoDB;
β JPAμμ row-level lockingλ§ μ μ©νκΈ°
1.
@Lock(LockModeType.PESSIMISTIC_WRITE)λλ@Lock(LockModeType.PESSIMISTIC_READ)@Repository public interface OrderRepository extends JpaRepository<Order, Long> { @Lock(LockModeType.PESSIMISTIC_WRITE) @Query("SELECT o FROM Order o WHERE o.id = :id") Optional<Order> findByIdForUpdate(@Param("id") Long id); }PESSIMISTIC_WRITEλSELECT ... FOR UPDATEλ₯Ό μ€ννμ¬ row-level exclusive lockμ κ²λλ€.- μ΄ μΏΌλ¦¬λ ν΄λΉ rowλ₯Ό λ€λ₯Έ νΈλμμ μμ λ³κ²½νμ§ λͺ»νκ² λ§μ΅λλ€.
PESSIMISTIC_READλ 곡μ λ½μ κ±Έλ©° μ½κΈ°λ νμ©νλ μ°κΈ°λ λ§μ΅λλ€.
β οΈ μ£Όμ: μ΄ λ°©λ²μ λ°λμ νΈλμμ (@Transactional) μμμ μ¬μ©λμ΄μΌ μ€μ λ‘ λ½μ΄ μ μ©λ©λλ€.
CreatedThu, 24 Jul 2025 10:07:45 +0900 -
-
μλμ Java Pathfinder (JPF) μ¬μ©νλ λ°©λ²μ μμΈν μλ΄λ립λλ€. Java Pathfinderλ μλ° νλ‘κ·Έλ¨μ λͺ¨λΈ 체νΉ(model checking) λꡬλ‘, νλ‘κ·Έλ¨μ μν 곡κ°μ νμνμ¬ λ²κ·Έλ λ°λλ½ λ±μ μ°Ύλ λ° νμ©λ©λλ€.
μλ λ΄μ©μ λ¨κ³λ³λ‘ μ€λͺ λ릴κ²μ.
π§ 1. Java Pathfinder(JPF)λ?
**Java Pathfinder (JPF)**λ μλ° νλ‘κ·Έλ¨μ μ€ννλ©° μν곡κ°μ 체κ³μ μΌλ‘ νμνλ ν΄μ λλ€.
μ£Όμ κΈ°λ₯
- λ©ν°μ€λ λ© λ²κ·Έ νμ§
- Assertion μλ° κ²μΆ
- Deadlock νμ§
- Non-deterministic λμ κ²μ¦
- Symbolic execution (jpf-symbc) μ§μ
βοΈ 2. μ€μΉ
JPFλ GitHub μ μ₯μμμ μ§μ λΉλνλ κ²μ΄ μΌλ°μ μ λλ€.
CreatedThu, 03 Jul 2025 10:49:24 +0900 -
μλμ Javaμμ PIT (Pitest) Mutation Testingμ μ¬μ©νλ λ°©λ²μ λ¨κ³λ³λ‘ μ λ¦¬ν΄ λ립λλ€. PITλ μλ° μ½λμ ν μ€νΈ 컀λ²λ¦¬μ§λ₯Ό μ€μ λ‘ κ²μ¦νλ κ°λ ₯ν λꡬλ‘, μλͺ»λ(λ³μ΄λ) μ½λλ₯Ό ν μ€νΈκ° μΌλ§λ μ μ‘μλ΄λμ§λ₯Ό μΈ‘μ ν©λλ€.
π 1. PIT(Pitest) μκ°
-
Mutation Testingμ΄λ?
- κΈ°μ‘΄ μ½λλ₯Ό μΌλΆλ¬ βλ³μ΄(mutant)β μμΌμ ν μ€νΈλ₯Ό λλ €λ³΄λ κΈ°λ²μ λλ€.
- ν μ€νΈκ° μ΄ λ³μ΄λ₯Ό κ²μΆνμ§ λͺ»νλ©΄, ν μ€νΈμ νμ μ΄ μλ€λ λ»μ λλ€.
- λ¨μν λΌμΈ 컀λ²λ¦¬μ§λ³΄λ€ λ μ λ’°μ± μλ ν μ€νΈ νμ§ μ§νλ₯Ό μ 곡ν©λλ€.
-
PITμ νΉμ§
- JUnit κΈ°λ° ν μ€νΈμ μ ν΅ν©λ©λλ€.
- Maven, Gradle, Antμμ μ½κ² μ¬μ© κ°λ₯ν©λλ€.
- HTML λ° XML 리ν¬νΈλ₯Ό μμ±ν©λλ€.
βοΈ 2. Maven νλ‘μ νΈμ PIT μ μ©
2.1 Maven νλ¬κ·ΈμΈ μΆκ°
pom.xmlμ λ€μ νλ¬κ·ΈμΈμ μ€μ ν©λλ€:CreatedThu, 03 Jul 2025 10:44:24 +0900 -
-
Spring Boot 3μμ SSH ν°λμ ν΅ν PostgreSQL μ°κ²°μ μν μ€μ λ°©λ²μ μλ €λλ¦¬κ² μ΅λλ€.
1. application.yml μ€μ
spring: datasource: url: jdbc:postgresql://localhost:5433/your_database_name username: your_db_username password: your_db_password driver-class-name: org.postgresql.Driver jpa: hibernate: ddl-auto: validate show-sql: true properties: hibernate: dialect: org.hibernate.dialect.PostgreSQLDialect format_sql: true # SSH ν°λ μ€μ (컀μ€ν νλ‘νΌν°) app: ssh: tunnel: host: your-ssh-server.com port: 22 username: ssh_username private-key-path: ~/.ssh/id_rsa local-port: 5433 remote-host: localhost # SSH μλ²μμ λ³Έ DB νΈμ€νΈ remote-port: 5432 # μ€μ PostgreSQL ν¬νΈ2. SSH ν°λ μλ μ°κ²°μ μν Configuration ν΄λμ€## 3. νμν μμ‘΄μ± μΆκ° (build.gradle)
dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.postgresql:postgresql' implementation 'com.github.mwiede:jsch:0.2.17' // κΈ°ν νμν μμ‘΄μ±λ€... }3. ssh key λ§λ€κΈ° μμ
ssh-keygen -t rsa -b 4096 -m PEM -f ~/.ssh/id_rsa -C "ceo@pink-spider.io"- DBκ° μ€μΉλ μλ²μ ~/.ssh λ±μ authorized_keys (κΆν 644) νμΌμ id_rsa.pub λ΄μ© μΆκ°ν΄μΌ λ©λλ€.
- rsaκ° μλ OpenSShλ‘ keygen νμ κ²½μ° com.github.mwiede:jsch κ° μλ λ€λ₯Έ λΌμ΄λΈλ¬λ¦¬λ₯Ό μ¨μΌ λ©λλ€.
SshTunnelConfig
@Configuration @Slf4j @Profile({"local", "dev", "stage", "prod"}) public class SshTunnelConfig { @Value("${app.ssh.tunnel.host}") private String sshHost; @Value("${app.ssh.tunnel.port}") private int sshPort; @Value("${app.ssh.tunnel.username}") private String sshUsername; @Value("${app.ssh.tunnel.private-key-path}") private String privateKeyPath; @Value("${app.ssh.tunnel.local-port}") private int localPort; @Value("${app.ssh.tunnel.remote-host}") private String remoteHost; @Value("${app.ssh.tunnel.remote-port}") private int remotePort; @Value("${app.ssh.tunnel.passphrase:}") private String passphrase; private Session session; @PostConstruct public void createSshTunnel() { try { log.info("JSch SSH ν°λ μ°κ²° μμ..."); // λ‘컬 ν¬νΈκ° μ¬μ© κ°λ₯νμ§ νμΈ if (!isPortAvailable(localPort)) { throw new RuntimeException("λ‘컬 ν¬νΈ " + localPort + "κ° μ΄λ―Έ μ¬μ© μ€μ λλ€."); } JSch jsch = new JSch(); // κ°μΈν€ νμΌ κ²½λ‘ μ²λ¦¬ String keyPath = privateKeyPath; if (keyPath.startsWith("~/")) { keyPath = System.getProperty("user.home") + keyPath.substring(1); } // νμΌ μ‘΄μ¬ λ° μ½κΈ° κΆν νμΈ File keyFile = new File(keyPath); if (!keyFile.exists()) { throw new RuntimeException("SSH κ°μΈν€ νμΌμ μ°Ύμ μ μμ΅λλ€: " + keyPath); } if (!keyFile.canRead()) { throw new RuntimeException("SSH κ°μΈν€ νμΌμ μ½μ μ μμ΅λλ€. νμΌ κΆνμ νμΈνμΈμ: " + keyPath); } log.info("SSH κ°μΈν€ νμΌ: {}", keyPath); // ν€ νμ νμΈ (RSA νμμΈμ§ 체ν¬) try (java.io.BufferedReader reader = new java.io.BufferedReader(new java.io.FileReader(keyFile))) { String firstLine = reader.readLine(); log.info("ν€ νμΌ νμ: {}", firstLine); if (firstLine != null && !firstLine.contains("BEGIN RSA PRIVATE KEY")) { log.warn("RSA νμμ΄ μλ ν€κ° κ°μ§λμμ΅λλ€: {}", firstLine); log.warn("RSA νμ ν€λ₯Ό μμ±νλ €λ©΄: ssh-keygen -t rsa -b 4096 -m PEM -f {}", keyPath); } } // κ°μΈν€ μΆκ° if (passphrase != null && !passphrase.trim().isEmpty()) { jsch.addIdentity(keyPath, passphrase); log.info("ν¨μ€νλ μ΄μ¦μ ν¨κ» κ°μΈν€ λ‘λ μλ£"); } else { jsch.addIdentity(keyPath); log.info("κ°μΈν€ λ‘λ μλ£"); } // SSH μΈμ μμ± session = jsch.getSession(sshUsername, sshHost, sshPort); session.setConfig("StrictHostKeyChecking", "no"); session.setConfig("PreferredAuthentications", "publickey"); session.setTimeout(30000); log.info("SSH μλ² μ°κ²° μλ: {}@{}:{}", sshUsername, sshHost, sshPort); session.connect(); log.info("SSH μ°κ²° μ±κ³΅"); // ν¬νΈ ν¬μλ© μ€μ session.setPortForwardingL(localPort, remoteHost, remotePort); // ν¬νΈκ° μ΄λ¦΄ λκΉμ§ λκΈ° waitForPortToOpen(localPort, 10000); log.info("SSH ν°λμ΄ μ±κ³΅μ μΌλ‘ μμ±λμμ΅λλ€: localhost:{} -> {}:{}:{}", localPort, sshHost, remoteHost, remotePort); } catch (JSchException e) { log.error("SSH ν°λ μμ± μ€ν¨: {}", e.getMessage(), e); // ꡬ체μ μΈ μλ¬ λ©μμ§ μ 곡 if (e.getMessage().contains("invalid privatekey")) { log.error("κ°μΈν€ νμμ΄ μλͺ»λμμ΅λλ€. RSA νμ ν€λ₯Ό μ¬μ©νμΈμ."); log.error("μ RSA ν€ μμ±: ssh-keygen -t rsa -b 4096 -m PEM -f ~/.ssh/level_up_rsa"); } else if (e.getMessage().contains("Auth fail")) { log.error("μΈμ¦μ μ€ν¨νμ΅λλ€. 곡κ°ν€κ° μλ²μ λ±λ‘λμ΄ μλμ§ νμΈνμΈμ."); log.error("곡κ°ν€ λ±λ‘: ssh-copy-id -i {} {}@{}", privateKeyPath + ".pub", sshUsername, sshHost); } else if (e.getMessage().contains("Connection refused")) { log.error("μλ² μ°κ²°μ΄ κ±°λΆλμμ΅λλ€. νΈμ€νΈμ ν¬νΈλ₯Ό νμΈνμΈμ."); } cleanupResources(); throw new RuntimeException("SSH ν°λ μ°κ²°μ μ€ν¨νμ΅λλ€: " + e.getMessage(), e); } catch (Exception e) { log.error("μμμΉ λͺ»ν μ€λ₯: {}", e.getMessage(), e); cleanupResources(); throw new RuntimeException("SSH ν°λ μ€μ μ€ μ€λ₯κ° λ°μνμ΅λλ€: " + e.getMessage(), e); } } @PreDestroy @EventListener(ContextClosedEvent.class) public void closeSshTunnel() { log.info("SSH ν°λ μ’ λ£ μμ..."); cleanupResources(); log.info("SSH ν°λ μ’ λ£ μλ£"); } private void cleanupResources() { if (session != null && session.isConnected()) { try { session.disconnect(); log.info("SSH μΈμ μ΄ μ’ λ£λμμ΅λλ€."); } catch (Exception e) { log.warn("SSH μΈμ μ’ λ£ μ€ μ€λ₯: {}", e.getMessage()); } } } private boolean isPortAvailable(int port) { try (ServerSocket serverSocket = new ServerSocket(port)) { return true; } catch (Exception e) { return false; } } private void waitForPortToOpen(int port, long timeoutMillis) throws InterruptedException { long startTime = System.currentTimeMillis(); while (System.currentTimeMillis() - startTime < timeoutMillis) { try (java.net.Socket socket = new java.net.Socket()) { socket.connect(new java.net.InetSocketAddress("localhost", port), 1000); log.info("ν¬νΈ {}κ° μ±κ³΅μ μΌλ‘ μ΄λ Έμ΅λλ€.", port); return; } catch (Exception e) { // ν¬νΈκ° μμ§ μ΄λ¦¬μ§ μμ, μ¬μλ Thread.sleep(500); } } log.warn("ν¬νΈ {}κ° {}ms λ΄μ μ΄λ¦¬μ§ μμμ΅λλ€.", port, timeoutMillis); } }CreatedThu, 03 Jul 2025 08:44:24 +0900 -
**Property-Based Testing(PBT)**μ μ λ ₯κ³Ό κ²°κ³Ό κ°μ **μΌλ°μ μΈ μμ±(Property)**μ μ μνκ³ , ν μ€νΈ νλ μμν¬κ° κ·Έ μμ±μ μΆ©μ‘±νλμ§λ₯Ό 무μμλ‘ μμ±λ λ€μν μ λ ₯κ°μ λν΄ νμΈνλ λ°©μμ ν μ€νΈμ λλ€. λ¨μ ν μ€νΈμ²λΌ νΉμ μ λ ₯κ°λ§ νμΈνλ κ²μ΄ μλλΌ, μμ² κ°μ μ λ ₯ μ‘°ν©μ μλμΌλ‘ κ²μ¬ν μ μλ€λ μ μμ λ§€μ° κ°λ ₯ν©λλ€.
π Property-Based Testingμ΄λ?
β ν΅μ¬ κ°λ
- μμ±(Property): ν μ€νΈ λμ ν¨μλ λ‘μ§μ΄ νμ λ§μ‘±ν΄μΌ νλ μΌλ°μ μΈ λ²μΉ λλ κ·μΉ.
- μ λ ₯κ° μλ μμ±: PBT νλ μμν¬κ° λ€μν μ λ ₯κ°μ 무μμλ‘ μμ±.
- Fail case μΆμ(Shrinking): μ€ν¨ν μ λ ₯κ°μ΄ λ°μνμ λ, μ€ν¨λ₯Ό μ¬νν μ μλ μ΅μ μ λ ₯κ°μΌλ‘ μ€μ¬μ λλ²κΉ μ μ½κ² ν¨.
π λ¨μ ν μ€νΈμμ λΉκ΅
νλͺ© λ¨μ ν μ€νΈ (Example-based) μμ± κΈ°λ° ν μ€νΈ (Property-based) μ λ ₯κ° κ³ μ λ μ¬λ‘ μλ μ§μ μλ μμ±λ λ€μν κ° ν μ€νΈ λ²μ μ νμ λ§€μ° κ΄λ²μ μ μ§λ³΄μ λ‘μ§ λ³κ²½ μ ν μ€νΈ μΆκ° νμ Propertyλ§ μ μ μνλ©΄ μλ μ μ© μ€ν¨ μ λλ²κΉ λͺ νν μ λ ₯κ° μΆμ κ³Όμ νμ
π§ͺ Spring Bootμμ Property-Based Test μ¬μ©νλ λ°©λ²
Spring Boot μ체λ JUnitμ κΈ°λ°μΌλ‘ ν λ¨μ ν μ€νΈκ° κΈ°λ³Έμ΄λ©°, PBTλ μΈλΆ λΌμ΄λΈλ¬λ¦¬λ₯Ό μ¬μ©ν΄μ ν΅ν©νλ κ²μ΄ μΌλ°μ μ λλ€. κ°μ₯ λ리 μ°μ΄λ Java κΈ°λ° λΌμ΄λΈλ¬λ¦¬λ λ€μκ³Ό κ°μ΅λλ€:
CreatedFri, 27 Jun 2025 10:18:03 +0900