오늘은 ORM에 대해 정리해본다.
ORM(Object Relational Mapping)이란?
데이터들이 프로그램이 종료되어도 사라지지 않고 어떤 곳에 저장되는 개념을 영속성(Persistence)이라고 한다.
Java에서는 데이터의 영속성을 위한 JDBC를 지원해주는데, 이는 매핑 작업을 개발자가 일일히 수행해야 하는 번거로움이 있다.
SQL Mapper와 ORM은 JDBC 프로그래밍의 복잡함이나 번거로움 없이 간단한 작업만으로 데이터베이스와 연동되는 시스템을 빠르게 개발할 수 있도록 기능을 제공해주는 Persistence Framework 종류이다.
SQL Mapper
SQL Mapper는 Object와 SQL의 필드를 매핑하여 데이터를 객체화 하는 기술이다.
객체와 테이블 간의 관계를 매핑하는 것이 아니라, SQL문을 직접 작성하고 쿼리 수행 결과를 어떠한 객체에 매핑할지 바인딩 하는 방법이다.
그렇기 때문에 DBMS에 종속적인 문제점을 갖고 있다.
대표적인 SQL Mapper에는 JdbcTemplate, MyBatis가 있다.
ORM
ORM이란 객체(Object)와 DB의 테이블을 자동으로 연결(Mapping)시켜 RDB 테이블을 객체 지향적으로 사용하게 해주는 기술이다.
앞서 말한 영속성 개념으로 설명하자면 어플리케이션의 객체를 RDB 테이블에 자동으로 영속화 해주는 것이라고 보면 된다.
객체 지향 프로그래밍은 클래스를 사용하고, 관계형 데이터베이스는 테이블을 사용한다.
객체 모델과 관계형 모델 간에는 불일치가 존재하는데, ORM을 통해 객체 간의 관계를 바탕으로 SQL을 자동으로 생성하여 불일치를 해결한다.
객체(Object) 필드 ← Mapping → 관계형 데이터베이스 데이터
=> 이렇게 객체를 통해 간접적으로 데이터베이스의 데이터를 다룬다.
Java에서 사용하는 대표적인 ORM으로는 JPA(Java Persistent API)와 JPA 표준 명세를 구현한 구현체 Hiberante가 있다.
정리하자면 MyBatis과 같은 SQL Mapper 방법을 사용하면 Java 클래스 코드와 직접 작성한 SQL 코드를 Mapping 시켜주어야 했다.
반면 JPA와 같은 ORM 기술은 객체가 DB에 연결되기 때문에, SQL을 직접 작성하지 않고 표준 인터페이스 기반으로 처리한다.
JPA(Java Persistent API)가 등장하기 이전에는 MyBatis라는 SQL Mapper 기술을 이용했다.
물론 지금도 많은 곳에서 MyBatis를 사용하지만, 전세계적으로는 Hibernate를 많이 사용하는 추세이다.
ORM의 장점/단점
ORM 장점
1. 직관적이고 가독성 좋은 코드
- Query와 같이 필요한 선언문, 할당 등의 부수적인 코드가 줄어들고, 각종 객체에 대한 코드를 별도로 작성하여 코드의 가독성이 높아진다.
2. 객체 지향적 접근으로 생산성 증가
- SQL문이 아닌 클래스의 메서드를 통해 데이터베이스를 조작하므로 개발자가 객체 모델만 이용해서 프로그래밍을 하는 데 집중할 수 있다.
- SQL의 절차적이고 순차적인 접근이 아닌 객체지향적인 접근으로 인해 생산성을 높여준다.
3. 재사용 및 유지보수의 용이성 증가
- ORM은 독립적으로 작성되어있고, 해당 객체들을 재활용할 수 있다.
- 매핑 정보가 명확하여, ERD를 보는 것에 대한 의존도를 낮출 수 있다.
4. DBMS에 대한 종속성 감소
- 객체 간의 관계를 바탕으로 SQL을 자동으로 생성하기 때문에 RDBMS의 데이터 구조와 Java의 객체지향 모델 사이의 간격을 좁힐 수 있다.
- ORM은 특정 DB에 종속적이지 않기 때문에 DBMS를 교체하는 큰 작업에도 비교적 적은 리스크와 시간이 소요된다.
ORM 단점
1. 잘못 구현된 경우 속도 저하가 발생할 수 있다.
2. 복잡한 Query는 QueryDSL 등을 사용해 직접 처리해야 한다.
JPA(Java Persistence API)
Java에서 ORM 기술 표준으로 사용하는 인터페이스의 모음이다.
풀어서 말하자면 Java 어플리케이션에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스라고 할 수 있다.
인터페이스이기 때문에 Hibernate, OpenJPA 등이 JPA를 구현하고 있다.
대부분은 Hibernate 구현체를 사용할 것이다.
왜 JPA를 사용해야 할까?
왜 JPA를 사용해야하는지에 대한 이유는 JPA의 장점과 동일하다.
그래도 가장 중요한 이유 두 가지를 다시 정리하면 다음과 같다.
1. JPA는 반복적인 CRUD SQL을 처리해준다.
JPA는 매핑된 관계를 이용해 SQL을 생성하고 실행한다.
MyBatis를 이용한다면 간단한 CRUD 쿼리들도 모두 작성해주어야 하는 것에 반해 JPA를 이용하면 생산성을 높일 수 있다.
2. 객체 중심으로 개발할 수 있다.
JPA를 사용하면 SQL아닌 객체 중심으로 개발할 수 있다.
테이블에 매핑되는 클래스를 좀 더 객체 지향적으로 개발할 수 있다는 의미이다.
이는 객체 지향 언어인 Java에 더 적합하며, 생산성과 유지보수에도 수월하다.
직접 쿼리를 작성하는 것이 아니라 애플리케이션이 DB에 종속적이지 않다는 점도 연결되는 부분이다.
ORM 예제
ORM 사용하지 않은 코드 : JDBC 사용
아래는 회원 가입(회원 데이터를 추가) 코드이다.
Java Object와 RDB가 매핑되지 않기 때문에 각각의 쿼리 파라미터에 데이터를 직접 set을 통해 넣어준 다음 DB에 저장한다.
public void insertUser(User user){
String query = " INSERT INTO user (email, name, password) VALUES (?, ?, ?)";
PreparedStatement preparedStmt = conn.prepareStatement(query);
preparedStmt.setString (1, user.getEmail());
preparedStmt.setString (2, user.getName());
preparedStmt.setString (3, user.getPassword());
// execute the preparedstatement
preparedStmt.execute();
}
MyBatis 사용 예제
아래 예제는 Spring Boot에서 MyBatis 프레임워크를 이용한 것이다.
MyBatis는 기본적으로 DB 처리를 위한 Mapper 인터페이스와 Mapper.xml 파일을 사용한다.
Mapper 인터페이스에서 메서드 이름은 xml 파일에서 해당 쿼리의 id와 동일하며 이 id를 기준으로 쿼리를 실행한다.
// 의존성 추가
dependencies {
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:3.0.0'
}
@Mapper
public interface UserMapper {
// 회원 가입
int signUp(User user);
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.aaa.bbb.mapper.UserMapper">
<insert id="signUp" parameterType="com.aaa.bbb.model.User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user(email, name, password)
VALUES(#{email}, #{name}, #{password})
</insert>
</mapper>
JPA 예제
아래 예제 또한 Spring Boot의 Data JPA 프레임워크를 이용한 것이다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
}
JPA는 Java 객체와 테이블을 매핑시키는 ORM 기술이므로 다음과 같이 Java 클래스에 매핑 정보를 넣어주어야 한다.
@Entity
@Table(name = "USER")
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "IDX")
private Long id;
@Column(name = "EMAIL")
private String email;
@Column(name = "NAME")
private String name;
@Column(name = "PASSWORD")
private String password;
@Builder
public User(String email, String name, String password) {
this.email = email;
this.name = name;
this.password = password;
}
// 회원가입 메서드 //
public static User signUp(String email, String name, String password) {
User user = User.builder()
.email(email)
.name(name)
.password(password)
.build();
return user;
}
}
그러면 다음과 같이 Repository를 구현할 수 있다.
public interface UserRepository extends JpaRepository <User, Long> {
Optional<User> findByEmail(String email);
}
JpaRepository는 기본적인 CRUD를 제공하고 있으므로 우리는 다음과 같이 UserRepository의 save만 호출해주면 데이터가 저장된다.
그리고 findById를 이용하면 해당 객체를 꺼낼 수 있다.
@Service
@RequiredArgsConstructor
@Transactional
public class UserService {
private final UserRepository userRepository;
@Transactional
public void signUp() {
User user = User.signUp(email, name, password);
userRepository.save(member);
}
@Transactional
public User updateUser(Long id) {
User user = userRepository.findById(id);
user.setName("변경된 이름");
}
}
위의 예제에는 id 값으로 사용자를 조회하고 업데이트를 해주는데, 별도의 Update 쿼리를 해주고 있지 않다.
이러한 이유는 JPA라는 ORM 기술에 의해 DB에서 조회한 데이터들이 객체로 연결되어 있고, 객체의 값을 수정하면 DB의 값도 수정되기 때문이다.
해당 메소드가 종료될 때 JPA의 값이 변경 유무를 감지하는 Dirty Checking이 이루어지고 값이 변경되었다면 Update 쿼리가 이후에 나가게 된다.
이를 통해 JPA는 MyBatis와 달리 쿼리를 직접 작성하지 않는다는 것을 확인할 수 있다.
'Java' 카테고리의 다른 글
[Java/MySQL] Timestamp, LocalDateTime 시간 데이터의 반올림 이슈(feat. Fractional Seconds, LocalTime.MAX) (1) | 2023.11.30 |
---|---|
[Java] Integer ArrayList을 int 배열로 변환하는 방법 (1) | 2023.09.24 |
[Java] JVM(Java Virtual Machine)이란? (0) | 2023.08.22 |
[Java] 코드 특정 구간의 실행 시간 구하기 (0) | 2023.06.14 |
[Java] HashMap에 특정 key가 존재하는지 확인하기 (0) | 2023.04.17 |