본문 바로가기
Spring & Boot/소?설

[두 개의 룬, 새로운 시대의 서막] 5화

by 한휴르 2025. 9. 8.

이 소설은 gemini-2.5-pro를 사용하여 작성되었습니다.

5화: JPA, 영혼을 기록하는 고대의 마법

로드 스프링은 이제 이안의 방식이 가진 구조적 우아함을 인정하지 않을 수 없었다. 역할의 분리는 명확했고, 의존성 주입은 유연했다. 하지만 그의 경험 많은 눈은 시스템의 가장 치명적인 약점을 간파하고 있었다.

"모든 것이 그럴듯하지만, 결국은 모래 위에 지은 성이구나." 로드 스프링이 말했다. "네가 만든 포션, 네가 기록한 레시피는 마법이 끝나면(애플리케이션이 종료되면) 모두 사라지는 허상이다. 진정한 마법 시스템은 영속성(Persistence), 즉 데이터를 보존하는 능력을 갖춰야 한다."

그는 과거의 방식을 회상했다. 'JDBC(Java Database Conjurings)'라 불리는 원시적인 데이터 마법. 그것은 데이터베이스라는 거대한 정보의 광산에 직접 연결해 원석(데이터)을 캐내는 것과 같았다. 마법사는 '커넥션'이라는 통로를 직접 열고, 'SQL'이라는 고대의 언어로 된 정교한 채굴 주문을 외워야 했다. 겨우 얻어낸 원석은 다시 마법 세계의 객체라는 형태로 일일이 손으로 깎아내야 했고, 가장 중요한 것은, 작업을 마친 뒤 반드시 커넥션 통로를 닫아야 한다는 점이었다. 조금이라도 실수를 하면 광산의 에너지가 유출되어 시스템 전체를 마비시킬 수 있는, 강력하지만 너무나도 위험하고 고된 작업이었다.

"물론 영속성이 필요합니다, 스승님. 하지만 이제는 광산에 직접 들어갈 필요가 없습니다. 우리에겐 'JPA(Java Persistence API)'라는 위대한 번역가가 있으니까요."

이안은 JPA를 소개했다. 그것은 마법사(개발자)와 데이터베이스(광산) 사이에 서서, 마법사의 언어(객체)를 광산의 언어(SQL)로, 또 광산의 언어를 마법사의 언어로 자동으로 번역해주는 통역사와 같았다.

이안은 먼저 Potion 클래스를 수정했다. 그리고 그 위에 @Entity 라는 룬을 새겼다.

"이 룬은 JPA 번역가에게 '이 Potion 객체는 데이터베이스 광산의 특정 기록과 일대일로 대응되는 실체입니다'라고 알려주는 표식입니다. 이제 우리는 Potion 객체를 다루는 것만으로, 보이지 않는 곳에서 데이터베이스의 기록을 함께 다루게 됩니다."

그는 이어서 포션의 고유번호 필드 위에 @Id 룬을 추가했다. 이것은 수많은 포션들 사이에서 특정 포션을 유일하게 식별할 수 있는 주민등록번호와 같은 역할을 했다.

다음으로 이안은 마법의 각종 설정을 기록하는 application.properties 양피지를 펼쳤다. 과거 로드 스프링은 DataSource라는 데이터베이스 연결 통로를 만들기 위해, 연결할 광산의 종류, 주소, 비밀번호 등을 XML에 복잡하게 설정해야 했다. 하지만 이안은 단 몇 줄의 주문을 적는 것으로 그 과정을 대체했다.

spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.username=sa
spring.datasource.password=
spring.jpa.hibernate.ddl-auto=create

"이 주문들은 우리가 어떤 광산(H2 데이터베이스)에, 어떤 신분(username, password)으로 연결할지를 알려줍니다. 특히 spring.jpa.hibernate.ddl-auto=create 라는 주문은, 마법이 시작될 때마다 JPA 번역가가 우리의 @Entity들을 살펴보고, 그에 맞는 기록 보관함(테이블)을 광산 안에 자동으로 만들어주게 하는 강력한 마법입니다."

이제 가장 놀라운 순간이 다가왔다. 이안은 지난번에 만들었던 PotionRepository 인터페이스를 수정했다.

public interface PotionRepository extends JpaRepository<Potion, Long> {
}

이게 전부였다. 클래스가 아닌 인터페이스. 그리고 그 안에는 아무런 코드도 없었다. 단지 JpaRepository라는 고대의 마법 인터페이스를 상속받았을 뿐이다.

로드 스프링은 혼란에 빠졌다. "아무런 구현이 없지 않느냐! 대체 어떻게 데이터를 저장하고, 조회하고, 삭제하겠다는 것이지?"

이안이 미소를 지으며 말했다. "이것이 JPA의 위대함입니다, 스승님. JpaRepository<Potion, Long>를 상속받는 순간, JPA 번역가는 '아, 이 사서는 Potion 객체를, Long 타입의 ID로 관리하는구나'라고 이해합니다. 그리고 save(), findById(), findAll(), delete() 같은 기본적인 데이터 관리 마법들을 자동으로 만들어냅니다. 우리는 그저 그 마법들을 호출하기만 하면 됩니다."

이안은 곧바로 PotionService를 수정하여, 임시로 만들었던 로직을 지우고 새로운 레파지토리를 사용하도록 코드를 변경했다. 이제 save 메소드를 호출하면 포션이 데이터베이스에 저장되고, findById를 호출하면 저장된 포션을 꺼내올 수 있었다.

모든 준비를 마친 이안이 마법을 실행했다. 그는 어제와 같이 POST 방식으로 '마나 포션'을 생성해달라는 요청을 보냈다. 서비스는 레파지토리의 save 메소드를 호출했고, JPA 번역가는 Potion 객체를 SQL 주문으로 번역하여 H2 데이터베이스 광산에 기록했다.

이안은 마법을 중지시켰다. 내장 톰캣 요정이 사라지고, 마법진의 빛이 꺼졌다. 시스템은 완전히 정지했다.

잠시 후, 이안은 다시 마법을 실행했다. 그리고 이번에는 GET 방식으로, 방금 만들었던 '마나 포션'의 정보를 조회하는 요청을 보냈다. 컨트롤러는 서비스에게, 서비스는 레파지토리에게 포션을 찾아달라고 요청했다. 레파지토리는 findById 메소드를 호출했다. JPA 번역가는 이 요청을 다시 SQL 주문으로 번역하여 데이터베이스에 전달했고, 광산 깊은 곳에 기록되어 있던 '마나 포션'의 정보를 찾아 다시 Potion 객체로 번역하여 돌려주었다.

이안의 화면에 '마나 포션'의 정보가 나타나는 순간, 로드 스프링은 할 말을 잃었다. 마법이 꺼졌다 켜졌음에도 데이터는 사라지지 않고 완벽하게 보존되었다. 그토록 고되고 위험했던 데이터 영속성 작업이, 몇 개의 룬과 약속, 그리고 텅 빈 인터페이스 하나로 이토록 완벽하고 안전하게 구현될 수 있다는 사실.

그것은 이미 마법이 아니라 기적의 영역에 가까웠다.

댓글