[Spring Data] Spring Data JDBC를 이용한 DB 연동 (기본편)

반응형

Spring Data JDBC는 우리가 일반적으로 알고 있는 Spring JDBC와는 조금 차이가 있습니다. 

 

JDBC 템플릿은 쿼리를 직접 입력하고 우리가 이들의 함수 형태를 직접 인터페이스로 구현해야 하기 때문에 손이 많이 갑니다. 따라서 이들을 Spring Data Commons를 바탕으로 재구성한 것이 바로 Spring Data JDBC 입니다.

 

 

 

Background of Spring Data JDBC

우리는 이전 글에서 Spring Data Commons가 무엇인지를 알아봤습니다. 스프링에서 데이터를 다루기 위해 기본적으로 구현되어 있는 CRUD의 추상화 레이어를 만든 것이 바로 Spring Data Commons 인데요. 이를 바탕으로 JPA를 넣은 것이 바로 Spring Data JPA입니다.

 

여태까지 Spring Data JPA를 잘 사용하신 분들이라면 Spring Data JDBC가 왜 등장했는지 의아해 하실 수도 있을 것입니다. JPA의 lazy loading과 영속성 컨텍스트로 만들어진 캐시 기능, Dirty checking은 그야말로 훌륭합니다. 하지만 이들 기능이 필요 없거나 너무 무리하게 사용하면 그만큼 단점이 따르기 마련입니다.

 

lazy loading이 단점이 되는 부분은 어떤 것일까요? 우리가 단순한 쿼리를 사용한다면 lazy loading을 사용하는 경우는 흔치 않을 것입니다. 이를테면 users 테이블에 있는 모든 레코드를 가져와주세요. 라는 쿼리를 사용했을 때 이와 연결된 테이블이 아무것도 없다면 그저 거기에 영속되어 있는 레코드만 가져오면 되므로 lazy loading이 필요하지 않습니다.

 

하지만 users 테이블에 team과 같은 다른 테이블의 데이터와 종속이 되어 있는 경우는 어떨까요? 만약 이들을 단순 쿼리를 이용해서 불러온다면 그와 연결된 데이터들은 보여지지 않을 것입니다. 따라서 join 등을 이용해 서브 쿼리를 작성하고, 그 쿼리를 같이 불러야만 그들의 데이터와 함꼐 나오게 됩니다. 

 

여기서 발생하는 문제점에는 서브 쿼리. 즉, team 테이블에 있는 데이터와 users 테이블에 있는 데이터를 가져올 때 그 시점이 명확하지 못하다는 것입니다. users 테이블에 있는 데이터를 먼저 가져올 수도 있고, team 테이블에 있는 데이터를 먼저 가져올 수도 있습니다. 따라서 이런 경우는 ORM 즉, JPA 내지 Hibernate에서 쓰는 경우가 많은데, ORM의 경우는 세션 혹은 저장소에 그 데이터를 유지한다는 점에 있어 그 의미가 있습니다. 왜냐하면 미리 데이터를 불러놓고 그곳에 보관해뒀다가 다시 호출이 되면 그 데이터를 다시 불러오는 것으로 유효성이 있다는 것이죠. 하지만 경우에 따라서는 데이터에 오류가 발생할 수도 있기 때문에 이들에 대한 예외 처리를 수동으로 해줘야하는 등 손이 많이 가는 것도 단점이 될 수 있습니다.

 

Lazy loading의 단점을 짧게 요약한다면 아래와 같습니다.

 

  • lazy loading이 발생되는 시점을 정확히 알 수 없어 원하지 않는 순간에 성능적인 영향을 초래함.
  • 객체 초기화시 발생되는 프록시 객체에 오류 처리를 별도로 해줘야 함.

 

이러한 번거로운 점이 있는 것을 보았을 때 우리가 반드시 데이터베이스에 영속하는 애플리케이션을 개발하기 위해 반드시 Spring Data JPA를 쓴다는 것은 올바른 일이 아닐 것입니다. 물론 iBatis나 MyBatis에도 lazy loading이 존재하지만 위에서 말한 단점대로 그 시점을 정확하게 단정 지을 수 없음으로 인한 단점 때문에 오히려 오버 엔지니어링이 된다는 것이죠. 

 

JPA가 가진 지연 로딩, 더티 체킹, 캐시 등의 장점을 가진 기술들은 프로젝트에 따라 단점으로 이어질 수 있는데, 이러한 사용하기 어려운 요소를 제거하고 가능한 CRUD 기본(단순한 작업)에 충실한 모듈입니다.

 

 

 

 

Architecture of Spring Data JDBC 

Spring Data JDBC는 그 구조가 굉장히 심플합니다. JPA는 JDBC 인터페이스를 거쳐 바로 데이터베이스에 영속되는 구조가 아닌 영속성 컨텍스트에서 수많은 작업을 통해 영속하고 데이터를 로드하는 방식인데요.

 

Spring Data JDBC는 기본적인 JDBC 영속 방식을 따릅니다. 

 

위 이미지는 일반 Java application으로 DB를 연동했을 때의 순서입니다. Connection을 생성한 후 Statement 객체를 이용해 쿼리를 실행하는 순서로 연동하는 것인데, Spring Data JDBC는 Spring Data Commons를 백그라운드로 하여 위와 같은 구조로 동작합니다.

 

또한 Spring Data Commons를 바탕으로 만들어졌기 때문에 Spring Data JPA를 쓰신 분들이라면 코드에는 큰 차이가 없이 사용하실 수 있습니다.

 

 

 

How to use

Spring Initializr를 이용하여 아래의 디펜던시를 추가해 새로운 프로젝트를 생성합니다.

 

데이터베이스는 원하는 것을 골라 사용하셔도 무방합니다. 저는 PostgreSQL을 사용해보겠습니다. 여기에 DB 스키마 생성을 위해 flyway를 붙이도록 하겠습니다.

 

 

Memo라는 테이블을 만들고, 고유값으로 id를 주고, 제목과 내용을 적을 수 있는 컬럼 두개를 만들도록 하겠습니다. 

 

 

프로젝트의 구조를 위와 같은 구조로 만들고, DB에 잘 영속되는지 확인하기 위해 Test 코드를 작성합니다.

 

flyway에 entity를 초기화하는 SQL 쿼리를 작성합니다.

 

도메인 객체를 SQL 쿼리와 똑같이 만들어주고, Primary key 지정을 위해 Id 어노테이션을 사용합니다.

 

Spring Data의 추상화 repository인 CrudRepository를 상속받아 사용합니다. 이 부분은 Spring Data JPA와 동일합니다.

 

application.yml 파일에 사용할 데이터베이스 정보를 입력합니다. 그리고, flyway 설정에 버전을 관리할 데이터베이스도 같이 설정합니다. 여기서 locations는 db 버전별로 마이그레이션 할 쿼리를 작성해 놓은 경로를 설정하는 곳이고 vendor는 각 DB 벤더 별로 스크립트를 짤 수 있습니다.

 

schema는 버전을 관리할 스키마, table은 버전별로 마이그레이션을 진행한 기록의 테이블 이름을 넣는 곳입니다.

 

E2E 테스트 코드를 작성합니다. 제목을 Practice memo, 내용도 Practice memo로 들어갈 수 있도록 테스트 코드를 작성한 뒤 테스트 코드를 실행합니다.

 

정상적으로 잘 동작하는 것을 볼 수 있습니다.

 

 

 

마치며...

여기까지 Spring Data JDBC의 배경과 구조를 알아보고, 간단한 사용법에 대해 적어봤습니다. 사용법이나 특징 등에 대해서는 기존의 방법과 크게 다르지 않습니다. 다만 이전에 iBatis, MyBatis 등 쿼리를 직접 사용한 개발자들이라면 앞으로는 Spring Data JDBC로 대체될 것이기 때문에 미리 익혀두는 것도 좋을 수 있습니다.

 

JPA의 장점인 캐시, 지연 로딩 등을 모두 제거했기 때문에 자동으로 테이블을 생성하는 DDL 등도 사용할 수 없기 때문에 데이터베이스의 버전 관리 기술 등을 이용하거나 수동으로 데이터베이스 쿼리를 서버에 질의하여 DDL 코드를 미리 실행시켜야 하는 불편함도 포함되어 있습니다.

 

하지만 JPA를 사용하면서 데이터베이스의 버전 관리를 진행하신 분들이라면 오히려 이런 방법이 반가울 수도 있습니다. JPA가 영속성 컨텍스트를 지니고 있어 로딩이나 변경 체크에는 유리하지만 실제로 버전 관리 등에는 약한 모습을 보입니다. 따라서 이런 방법을 주로 사용하신 분들이라면 JPA의 복잡한 기능을 잠시 버려두고 Spring Data의 함수를 그대로 사용하면서 이점을 누릴 수도 있습니다.

 

다음 글에서는 서브 쿼리 등을 사용하는 테이블에서 두 데이터를 가져오는 방법 등을 알아보는 응용편을 작성하도록 하겠습니다.

반응형