신규프로젝트 회의 중에 팀장님이 이렇게 말씀하셨다고 가정해보자.
JPA를 사용하면 비즈니스 로직이 복잡해져서 사용하지 않을거예요
그럼 이때 JPA에 대한 개념을 모른다면 그 회의는 저 단어를 시작으로 멍 때리게 될것이다...
JPA와 JDBC에 들어가기에 앞서....
Spring은 DB에 접근하기 위해 자바의 API를 사용한다. 웹 서비스에 필요한 기능들이 추상화돼서 Spring이 만들어졌듯이, DB에 접근하는 기술들도 일종의 추상화 과정을 거치며 진화해 나갔다. 이 부분을 처음 공부하는 사람 입장에서는, 비슷해 보이면서 다른 단어들 때문에 개념이 굉장히 헷갈린다. JPA를 검색했는데 뜬금없이 Hibernate에 관한 포스팅만 쏟아지기도 한다. 이 참에 한번 정리를 해보자!!!
(각 개념에 그림과 코드를 첨부했다. 그림에서 초록색 부분은 개발자가 코드 상에서 직접 다뤄야하는 부분이다)
JPA(Java Persistent API)는 왜 등장하게 되었을까?
여기서 Persistent(영속성)이란 데이터를 생성한 프로그램의 실행이 종료되더라도 사라지지않는 데이터의 특성이다.
기술은 발전된 순서로 공부하는 것이 이해하기 편하다.
( 이것도 어느 정도 깊이까지 들어가야 편하다...끝도 없이 욕심내면 지친다ㅠㅠ)
왜냐하면 이전 기술의 불편한 점을 보완하여 새로운 기술인 나왔기 때문이다. 그래서 불편한 점이 무엇이었고 그것을 어떻게 해결했는지가 두 기술의 차이점이 된다.
그럼 JPA전에는 뭐가 있었을까?
바로 JDBC이다.
JDBC는 DB연결할 때 쓰는 거아냐?
JDBC(Java Database Connectivity)는 그 이름에 답이 있다.
- JDBC는 DB에 접근(Connectivity)하고, SQL을 날릴 수 있게 해주는 자바의 표준 API이다.
이때 JDBC DriverManager가 DB제품에 따른 드라이버를 생성하여 JDBC API에 맞게 동작할 수 있게 처리해준다.
따라서 우리는 JDBC API 변경없이 JDBC 드라이버만 바꿔주면 어떤 제품의 DB든 연결할 수 있다.
위의 구조에서 각 DB와의 접점은 JDBC 드라이버로 하고 있었다.
그렇다면 JDBC에는 이런 드라이버들을 관리해주는 매니저 역할이 필요할 것이다.
그리고 DB 서버와 연결해야 하니, 당연히 연결 정보(Port, ID, 비밀번호 등)도 필요하다.
연결이 되었으면 쿼리를 날리기 위해 쿼리문이 필요할 것이고, 실패했든 성공했든 결과값을 받아올 객체도 필요할 것이다.
이 흐름을 그림으로 정리하면 다음과 같다.
위 사진처럼 쿼리를 실행하기 전과 후에 연결 생성, 명령문, ResultSet 닫기등과 같은 많은 코드를 작성해야한다.
또 connection 관리, 예외처리등에 불편함이 있어 나온게 Spring JDBC이다.
- 코드 예제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
|
// JDBC를 사용한 Java Application(DAO)
public class CarDao {
public Connection getConnection() {
Connection con = null;
String server = "localhost:3306"; // MySQL 서버 주소
String database = "DB_NAME"; // MySQL DATABASE 이름
String option = "?useSSL=false&serverTimezone=UTC";
String userName = "USER_ID"; // MySQL 서버 아이디
String password = "USER_PASSWORD"; // MySQL 서버 비밀번호
// 드라이버 로딩
try {
Class.forName("com.mysql.cj.jdbc.Driver");
} catch (ClassNotFoundException e) {
System.err.println(" !! JDBC Driver load 오류: " + e.getMessage());
}
// 드라이버 연결
try {
con = DriverManager.getConnection("jdbc:mysql://" + server + "/" +
database + option, userName, password);
System.out.println("정상적으로 연결되었습니다.");
} catch (SQLException e) {
System.err.println("연결 오류:" + e.getMessage());
}
return con;
}
// 드라이버 연결해제
public void closeConnection(Connection con) {
try {
if (con != null)
con.close();
} catch (SQLException e) {
System.err.println("con 오류:" + e.getMessage());
}
}
// CRUD
public void addCar(Car car) throws SQLException {
String query = "INSERT INTO car (car_id, car_brand, car_created) VALUES (?, ?, ?)";
PreparedStatement pstmt = getConnection().prepareStatement(query);
pstmt.setInt(1, car.getId());
pstmt.setString(2, car.getBrand());
pstmt.setString(3, car.getCreated());
pstmt.executeUpdate();
}
}
|
cs |
Spring JDBC란?
- 핵심 : JdbcTemplate
Spring JDBC는 JDBC에서 DriveManager가 하는 일들을 JdbcTemplate에게 맡긴다. 따라서 개발자는 메서드에 쿼리를 직접 매핑한다.
쿼리작성이 줄어들고(여전히 쿼리를 사용하지만…) 불편한 점들을 추상화시켜 놓았다.
SQL Query를 직접 사용하여 데이터 조작하므로 JdbcTemplate은 SQL Mapper 중 하나이다
위 표를 살펴보면 자바 JDBC에 비해 개발자가 해야할 일이 확연하게 줄어든 것을 확인할 수 있다.
- 코드 예제
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
public class CarDao {
private String driver = "com.mysql.cj.jdbc.Driver";
private String url = "localhost:3306";
private String userid = "USER_ID";
private String userpw = "USER_PASSWORD";
private DriverManagerDataSource dataSource;
private JdbcTemplate template;
public CarDao() {
dataSource = new DriverManagerDataSource();
dataSource.setDriverClass(driver);
dataSource.setJdbcUrl(url);
dataSource.setUser(userid);
dataSource.setPassword(userpw);
template = new JdbcTemplate();
template.setDataSource(dataSource);
}
// CRUD
public int carInsert(Car car) {
String query = "INSERT INTO car (car_id, car_brand, car_created) VALUES (?, ?, ?)";
int result = template.update(query, car.getId(), car.getBrand(), car.getCreated());
return result;
}
}
|
cs |
SQL Mapper는 ?
SQL Mapper는 SQL 문장으로 직접 데이터베이스 데이터를 다룬다.
그럼 직접 데이터를 다루지 않는 것도 있을까?
직접 다루지 않는 것을 ORM(Object-Relational Mapping)이라고 부른다.
🆚SQL Mapper vs ORM
ORM에 대한 자세한 내용은 해당 글 참고
https://s-y-130.tistory.com/84
MyBatis는?
MyBatis도 SQL Mapper 중 하나이다.
MyBatis는 Plain JDBC의 문제점을 Spring JDBC와 다르게 보았다.
MyBatis는 자바코드에서 SQL을 쓰는 것을 문제라고 생각했고 SQL을 분리하고자 했다. 따라서 SQL쿼리를 Java에서 XML로 옮겨서 작성하였다.
- 코드예제
EMemberVO.java 생성한 뒤 xml파일에서 MyBatis사용한 코드이다
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
<?xml version="1.0" encoding="UTF-8"?>
<!-- DTD지정 -->
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- SQL 구문 작성후 사용 -->
<mapper namespace="com.example.mappers.memberMapper">
<select id="getTime" resultType="string">
select now()
</select>
</mapper>
<!-- DAO패키지(com.itwillbs.persistence)와 연결 -->
<context:component-scan base-package="com.example.persistence" />
|
cs |
Mybatis는 쿼리가 수정되어 데이터 정보가 바뀌면 그에 사용되고있던 DTO와 함께 수정해주어야하는 불편함이 생긴다. 즉, 물리적으로 분리시켜놨지만 논리적으로는 서로 강한 의존관계가 있다.
이 불편함을 해결하기 위해 나온 것이 ORM이다. ORM은 객체만 바꾸어주면 된다. 즉, 객체 중심으로 개발 가능해진다.
ORM이란??
ORM(Object-Relational-Mapping)의 이름 그대로 객체와 관계형데이터베이스의 데이터를 자동으로 매핑(연결)해준다.
ORM의 구조는 아래와 같다.
ORM기준 표준 인터페이스인 JPA(Java Persistence API)가 있고, 그 구현체로는 제일 유명한 Hibernate, EclipseLink, DataNucleus등이 있다.
ORM의 핵심은 엔티티매니저이다.
드디어 JPA!!!!
직접적인 SQL 문을 사용하지 않고 자바 코드를 사용해서 DB에 접근, 조작할 수 있는 기술이다. JPA 역시 내부적으로 JDBC를 사용한다.
- 자바 ORM 기술에 대한 API 표준 명세로, Java에서 제공하는 API이다.
- 장점
- SQL문을 직접 java application내에서 적을 경우가 적어짐
- 기본적인 CRUD 쿼리를 반복적으로 작성하지 않아도 됨
- 수정사항이 발생하였을 때 수정해야 할 코드가 적음
- SQL구조를 java application내에서 적용하지 않아도 됨. 어노테이션을 사용 Ex) @Id, @ManyToOne …등
- SQL 의존성 줄어듬. 객체지향적으로 데이터 관리 가능
- SQL문을 직접 java application내에서 적을 경우가 적어짐
- 단점
- 메서드 호출로 쿼리 실행은 직접 SQL을 호출하는 것보다 성능이 떨어질 수 있음
- 복잡한 통계 분석 쿼리를 메서드 호출로 처리하는 것은 어려움
- 로직이 복잡하거나 불필요한 쿼리가 발생할 수 있음
- 러닝커브가 높음
JPA를 쓰는데 EntityManager를 쓴 적 없다면 그것은 Spring Data JPA를 사용했기 때문일 것이다.
JPA는 자바 ORM의 API 표준 명세다. 즉, JPA는 이러이러한 기능이 존재해야 한다라고 정의한 규약일 뿐이다.
Hibernate가 JPA 규약에 따라 구현된 구현체의 대표적인 예시다.
Hibernate
Hibernate는 JPA를 구현한 프레임워크이다. 앞서 JPA는 자바 ORM의 API 표준 명세라는 설명이 있었다. 즉, JPA는 이러이러한 기능이 존재해야 한다라고 정의한 규약이고, 실제로 개발자가 사용하는 것은 그 구현체들이다. 이 구현체 중 하나가 Hibernate다. 그리고 Spring은 기본 JPA vendor로 Hibernate를 사용하고 있다. 이 때문에 Spring JPA 코드 구현에 관한 많은 질문들에서 JPA와 Hibernate란 단어가 혼용된다.
Spring Data JPA는?
Spring Data JPA는 JPA를 쓰기 편하게 만들어놓은 모듈이다.
Spring Data JPA의 핵심은 Repository이다.
이 JpaRepository를 뜯어보면 안에 EntityManager가 있다는 것을 알 수 있다.
- 코드예제
코드 출처: JPA - CRUD RestController 만들기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
|
@RestController()
public class BookController {
@Autowired
private BookRepository bookRepository;
@PostMapping("/book")
public BookEntity createBook(@RequestBody BookEntity bookEntity) {
BookEntity created = bookRepository.save(bookEntity);
return created;
}
@GetMapping("/book")
public List<BookEntity> listAllBooks() {
List<BookEntity> list = new ArrayList<>();
Iterable<BookEntity> iterable = bookRepository.findAll();
for (BookEntity bookEntity : iterable) {
list.add(bookEntity);
}
return list;
}
}
|
cs |
Spring Data JDBC
Spring data는 Spring 진영에서 DB를 쉽게 다루기 위해 시작한 프로젝트이다. 그 중 하나인 Spring Data JDBC는 기본적인 드라이버 설정 기능부터 CRUD 기능을 제공한다. 공식 문서를 보면 Spring Data JDBC를 간단하고 선택적인 ORM이라고 소개하고 있다. 선택적 ORM이라는 표현을 사용한 이유는, ORM이 제공하는 기본적인 기능을 제공할 뿐만 아니라, 사용자가 직접 SQL문을 질의하는 기능 역시 제공하기 때문이다(@Query 어노테이션을 사용하면 된다). Data source에 대한 설정은 application.properties 파일에서 가능하다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
|
// application.properties
spring.datasource.url=jdbc:mysql://localhost:3306
spring.datasource.username=USER_ID
spring.datasource.password=USER_NAME
spring.datasource.driver-class-name=com.mysql.jdbc.Driver
// CarRepository.java
public interface CarRepository extends CrudRepository<Car, Long> {
@Query("SELECT COUNT(*) FROM car WHERE brand = :brand")
int countByBrand(@Param("brand") String brand);
}
// CarService.java
@Service
public CarService {
@Autowired
private CarRepository carRepository;
// CRUD
public int addCar(Car car){
return carRepository.save(car);
}
// Custom SQL
public int countByBrand(String brand) {
return carRepository.countByBrand(brand);
}
}
|
cs |
📝분류
JDBC | SQL Mapper | ORM |
JDBC API | Spring JDBC | JPA |
MyBatis | Hibernate | |
Spring Data JDBC / JPA |
Hibernate와 Spring Data JPA의 차이점
Hibernate는 JPA 구현체이고, Spring Data JPA는 JPA에 대한 데이터 접근의 추상화라고 말할 수 있다.
Spring Data JPA는 GenericDao라는 커스텀 구현체를 제공한다. 이것의 메소드의 명칭으로 JPA 쿼리들을 생성할 수 있다.
Spring Data를 사용하면 Hibernate, Eclipse Link 등의 JPA 구현체를 사용할 수 있다.
또 한가지는 @Transaction 어노테이션을 통해 트랜잭션 영역을 선언하여 관리할 수 있다.
Hibernate는 낮은 결합도의 이점을 살린 ORM 프레임워크로써 API 레퍼런스를 제공한다.
여기서 반드시 기억해야할 점은 Spring Data JPA는 항상 Hibernate와 같은 JPA 구현체가 필요합니다.
요약 - 셋을 혼동하지 말고 사용하자
아래 사진은 위의 내용을 요약하여 JPA, Hibernate, 그리고 Spring Data JPA의 전반적인 개념을 그림으로 표현한 것이다.
특히 JPA와 Spring Data JPA는 똑같이 JPA가 들어가서 처음 접하는 사람은 상당히 헷갈릴 수 있다. 이 세 개념의 차이점을 정확히 인지하고 숙지하고 있으면 개발이 한층 편해질 것이다.
정리
SQL Mapper와 ORM 중에 어느 것을 사용해야될지 궁금증이 생길 수도 있다. ORM은 SQL 문을 배우지 않고도 사용할 수 있다는 장점이 있다. 하지만 ORM이 생성해주는 SQL 구문을 보고 성능을 예측할 수 없다면, 나중에 끔찍한 일이 벌어질 수도 있을 것이다. 이 부분은 나중에 다른 글로 다뤄보면 좋을 것 같다.
JPA를 쓰면 왜 비즈니스 로직이 복잡해질까?
비즈니스 로직이란 업무에 필요한 데이터 처리를 수행하는 것을 의미한다.
예를 들어 DB에서 휴대전화 데이터를 가져와 웹에서 마스킹한 형태로 출력할 때 그 가공 과정을 비즈니스 로직이라고 한다.
JPA를 사용하면 제공되는 메소드로 복잡한 SQL를 수행하려할 때 비즈니스 로직이 길어질 수 밖에 없다.
JPA는 통계 쿼리처럼 복잡한 SQL을 수행하기 힘들기 때문에, 비즈니스에 따라 Mybatis를 사용할 지 Hibernate를 사용할 지 상황에 맞는 선택이 중요할 것입니다.
처음에 살펴본 구글 트렌드를 볼 때 우리나라는 대부분 Mybatis를 사용하고 있는데, 그 이유는 우리나라 시장 대부분이 SI, 금융 시장이기 때문입니다.
비즈니스가 매우 복잡하고, 안정성을 중요시 하는 서비스일 경우에는 JPA보다 SQL을 작성하는 것이 더 좋다는 의도일 것입니다.
이미 SQL을 사용하여 개발된 애플리케이션이라면 JPA로 바꾸는 일도 쉽지 않기 때문에, 우리나라에서는 JPA가 많이 사용되지 못하는 것 같습니다.
출처: https://victorydntmd.tistory.com/195
JPA 에 대한 다른 시각에서 살펴본 글을 다음을 참고
https://victorydntmd.tistory.com/195
참고 및 인용
https://sowon-dev.github.io/2021/03/22/210323jpaVSjdbc/
https://skyblue300a.tistory.com/7
https://ict-nroo.tistory.com/130
https://suhwan.dev/2019/02/24/jpa-vs-hibernate-vs-spring-data-jpa/