Backend/Database

백엔드 개발자를 위한 DB 기초 개념부터 JPA 까지 - 2

mopil 2022. 10. 25. 00:13
반응형

이 글은 전편과 이어집니다.

https://mopil.tistory.com/89

 

백엔드 개발자를 위한 DB 기초 개념부터 JPA 까지 - 1

데이터베이스 개념은 매우 딥하지만, 백엔드 개발을 막 시작하여 프로젝트에 투입되어야 하는데 DB에 대한 지식이 별로 없을때 도움이 될 수 있도록 작성한 글이다. 데이터베이스(DB)란? "많은 기

mopil.tistory.com

 

전 글에서 데이터베이스가 무엇인지 살펴보았다. 이번 글에서는 Java와 데이터베이스를 연동하는 방법에 대해서 알아본다.

 

DB GUI, CLI가 아닌 Java 코드에서 DB 다루기

우리는 개발자이기 때문에 DB GUI, CLI를 이용해서 데이터베이스를 직접 다루는 일은 거의 없다. (테스트를 제외 하곤)

우리가 어플리케이션을 개발할 때, 즉, Java를 사용할 때 DB와 연결해서 작업을 하고 싶은데 어떻게 하는 것일까?

다행이도 Java는 기본적으로 이를 제공한다.

 

JDBC (Java Database Connection)

앞선 예제를 다시 떠올려보자. 쇼핑몰에 회원가입 로직을 구현하고 싶다. 그러면 다음과 같은 과정을 거친다.

 

1. 회원 정보를 클라이언트로 부터 받아온다.

2. 회원 정보를 DB에 저장한다.

 

어찌저찌해서 회원정보를 자바 객체 (User)에 저장시켰다. User는 name과 age를 갖는 객체다.

이를 DB에 저장하고 싶으면 동일한 이름의 테이블을 만들고, INSERT 쿼리를 작성해야 한다.

이를 도와주는 Java API로 JDBC가 있다. JDBC는 데이터베이스를 연결, 쿼리 적용, 연결 끊기를 도와주는 일종의 라이브러리다.

 

오케이. 그런데 시간이 흘러서 회원 정보에 엄청나게 많은 정보가 추가되었다고 하자.

(name, age, gender, address, phone, email...)

 

이를 INSERT 쿼리를 작성하면 다음과 같을 것이다.

INSERT INTO User VALUES (?, ?, ?...);

 

SQL을 사용하는 개발의 문제점

JDBC는 DB와 연결을 도와주는 라이브러리이지만, 수많은 불편함과 단점이 존재한다.

1. 쿼리를 날리기 위해서 DB연결, 쿼리 작성, DB연결 종료를 모두 개발자가 해줘야 한다. (매우 수고스러운 일이다.)

2. 쿼리를 문자열로 작성하기 때문에 오류를 범하기 쉽다.

 

그래서 개발자들은 시간이 흐를수록 최대한 SQL을 사용하지 않는 쪽을 점점 지향하기 시작하는데,

 

SQL Mapper

자바 함수 save(name, age, address...); 를 호출하면 사전에 미리 정의된 INSERT 쿼리가 나가도록 설정했다.

전 보다는 편해진 것 같지만, 여전히 SQL을 작성해야 하는 번거로움이 있다.

여담으로 이렇게 SQL 과 코드를 1:1로 매핑시켜서 사용하는 라이브러리중 가장 많이 쓰이는 것이 MyBatis이다.

 

하지만 이렇게 해도 불편한 점이 있다.

User 객체의 정보를 DB에 넣기 위해서는 각 정보를 추출해서 쿼리에 전달해야 한다는 것이다.

이를 해결하기 위해서 다른 기술이 등장하는데...

 

ORM (Object Relation Mapper)

SQL과 코드를 매핑 짓지말고, DB 테이블과 자바 객체를 매핑 지으면 어떨까?라는 발상에서 출발했다.

그렇게 도와주는 기술을 ORM이라고 한다. ORM 라이브러리는 DB 테이블과 해당 언어의 객체를 1:1 매핑지어서 관리하게끔 도와준다.

앞선 예시의 User 테이블을 User 객체로 정확하게 1:1로 매핑시켜서 CRUD를 진행할 수 있다.

 

JPA (Java Persistence API)

ORM기술은 거의 모든 언어 진영에 대표하는 라이브러리들이 존재한다. (ex. 파이썬 - SQLAlchemy)

당연히 Java도 존재하는데, ORM을 아무 규격없이 만들지 않게끔 일종의 규격(인터페이스)를 정해놨는데,

그게 JPA다.

명심할 것은 JPA는 그저 인터페이스(틀)이라는 점이다!

 

Hibernate

이러한 JPA 규격을 맞춰서 구현한 구현체중, 가장 많이 사용되는 라이브러리가 이 하이버네이트다.

따라서 대부분의 사람들이 JPA == Hibernate를 말한다.

하이버네이트는 내부적으로 엔티티 메니져라는 객체를 통해서 디비와 소통하게끔 구현되어 있다.

 

Spring Data JPA

회원, 주문등 도메인이 추가될 때 마다 해당 도메인의 삽입, 수정, 삭제, 조회 (CRUD) 메소드(쿼리)를 작성하는 것은 상당히 귀찮을 것이다.

그래서 기본적인 CRUD 메소드를 알아서 만들어주는 라이브러리가 등장했는데, 그게 Spring Data JPA다.

Spring Data JPA로 개발자는 반복되는 CRUD 코드를 작성하지 않고 개발을 할 수 있게 되었다.

이렇게 인터페이스만 정의해주면
굉장히 많은 기본적인 CRUD 메소드를 제공해준다
SQL 비슷한걸 적는 기능도 지원한다 (실제론 JPQL)

근데 만약, 엄청나게 복잡한 쿼리 (서브쿼리도 포함된)를 작성해야 한다고 가정해보자.

JPA도 만능은 아니기때문에, 이를 어쩔수 없이 SQL (정확히는 JPQL)로 작성해야한다.

 

뭔가 매우긴 SQL....

 

그러면 또 다시 문제점이 돌아온다. SQL을 문자열로 작성해야 한다는 것이다!

 

QueryDSL

선배 개발자들은 이것마저 원천 차단하고 싶어서, 복잡한 쿼리를 문자열이 아닌, Java 코드로 (스트림 비슷하게, 메소드 체이닝 형식으로) 작성하게끔 도와주는 라이브러리를 만들어 냈는데, 그게 QueryDSL이다.

 

이제 개발자는 SQL 문자열을 진짜 1도 안쓰고 디비와 소통하면서 개발을 할 수 있는 지경까지 도달했다.

SQL을 메소드 체이닝 형식으로 Kotlin 코드로 쓴 예시

실제 프로젝트를 진행하면, Spring Data JPA + QueryDSL을 필수로 챙겨 간다. 실제 실무에서도 이런 조합을 많이 사용한다고들 한다.

 

 

그럼 Spring Data JPA + QueryDSL은 만능인가?

아쉽게도 모든 문제점을 이걸로 다 해결할 수는 없다.

단적인 예시로 다수의 데이터를 조작하는 작업을 batch 작업이라고 하는데, JPA는 벌크연산이라는걸 제공하긴 하지만, 그래도 기능이 부족하다.

 

가령, 10만개의 유저정보를 한번에 넣는다고 생각해보자. JPA를 사용하면 INSERT 쿼리가 10만개 발생한다. 당연히 비효율적이다.

 

DBMS는 batchInsert를 지원해서 10만개의 쿼리를 몇개씩 나눠서 보낼수 있다. (심지어 10만개를 한번에 넣을 수도 있을 것이다. 메모리가 된다면...)

 

이런 batchInsert는 JPA가 지원하지 않기 때문에, 어쩔수 없이 JdbcTemplate이라는 JDBC를 조금 편리하게끔 사용하게 한 라이브러리를 활용해야 한다.

 

결론

  • Spring Data JPA + QueryDSL 조합이면 한 프로젝트에서 디비 관리를 모두 커버할 수 있다.
  • 조금 더 깊은 기능(batchInsert) 같은 경우, JPA를 활용 못 할 수도 있다.

 

반응형