1. 개요
Swagger를 설정하고 나서 내가 개발하고 있는 웹페이지에 적용할 기술이 또 뭐가 있을지를 생각해봤다. 많은 구글링을 통해 ORM이라는 존재를 알게 됐고, Java를 사용하는 Spring boot에 필요한 JPA를 공부하기 시작했다. 전에는 Mybatis와 Mapper를 활용하여 데이터베이스에 접근하는 방식을 사용했는데, Spring Data JPA와 Entity로 개발을 해보니 확실이 편하다는 것을 느낄 수 있었다. 물론 각각의 장단점이 있지만, 쿼리를 객체지향적으로 조작하고 유지보수에 ORM이 강하다는 점은 확실이 알 수 있었다. 또한 데이터베이스 쿼리라면 치가 떨리던 나기에 ORM은 데이터베이스에 대한 종속성 자체가 줄어든다는 강점을 주었다. 이번에는 ORM을 적용하기 위해 기본적인 Entity를 설계하는 방법과 Repository를 사용하는 방법을 올려보고자한다.
2.본론
- ORM, JPA, Hibernate, Spring Data JPA에 대해
처음 ORM을 알고 나서 JPA, Hibernate, Spring Data JPA에 대한 개념들이 햇갈리기 시작했다. 그래서 가장 먼저 이런 용어들의 개념을 정리해보았다. 단어의 상관관계를 정리해보니 그렇게 어려운 개념도 아니라는 생각이 들었다.
- ORM
가장 큰 개념인 ORM에 대해 알아보자 ORM은 Object Relational Mapping의 줄임말로 객체 관계 매핑을 뜻한다. 쉽게 말하자면 객체와 RDB의 테이블을 자동으로 매핑하는 방법이다. 여기서 객체는 Java, C++과 같은 객체지향언어에서 사용하는 Class를 의미하고 RDB는 Relational Database를 의미한다.
클래스와 데이터베이스의 테이블은 전혀 다른 존재이다 이 둘 사이에는 어쩔 수 없는 불일치가 존재한다. ORM은 이 둘 사이의 불일치를 해결해준다고 생각하면 편할 것 같다. 그리고 우리는 이 ORM 덕분에 쿼리문이 아닌 코드로 데이터를 조작할 수 있다. 다음은 ORM에 대해 잘 설명되어있는 글의 링크이니, 참고해보면 좋을 것 같다.
ORM: https://gmlwjd9405.github.io/2019/02/01/orm.html
- JPA
JPA는 앞서 알아본 ORM을 Java에 맞게 구체화한 개념이라고 생각하면 편하겠다. Java진영에서는 JPA를 ORM 기술 표준으로 채택하였다. JPA는 Java Persistence API의 약자로 실제로 동작하는 것이 아닌 어떻게 동작해야 하는지에 대한 메커니즘을 정리한 표준명세 즉 인터페이스의 모음이다.
JPA의 메커니즘을 보면 내부적으로 JDBC를 사용하는데, 개발자가 직접 JDBC를 구현하게 하지는 않는다. 개발자가 직접 JDBC를 구현하면 SQL에 의존하게 되는데 JPA는 적절한 쿼리를 생성함으로써 이런 문제를 해결해준다.
- Hibernate
하이버네이트는 앞서말한 JPA의 구현체이다. JPA가 정의하는 인터페이스를 직접 구현한 것이 Hibernate라고 생각하면 된다. 여기서 Spring Data JPA라는 개념이 나오는데, 이는 Hibernate를 더욱 편하게 사용할 수 있도록 각각의 구현체를 모듈화 했다고 생각하면 되겠다. 다음은 Spring 공식 사이트에서 설명하는 Spring Data JPA이다.
Spring Data JPA: https://spring.io/projects/spring-data-jpa
다음은 그냥 내가 정리하려 끄적인 노트내용이다.
- 구현
Spring boot의 application.properties파일에 이미 데이터베이스 연동이 끝나있다면, 다음과 같은 내용만 추가하면 된다.
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
각 속성을 풀어 설명하다면 다음과 같다.
- spring.jpa.hibernate.ddl-auto: 하이버네이트를 활성화할 수 있는 선택사항. update는 SessionFactory가 실행될 때 객체를 검사해서 변경된 스키마를 생신하고, 기존의 데이터는 건드리지 않는다. update말고도 create, create-drop과 같은 다양한 값을 설정할 수 있고, 운영환경에서는 create, create-drop, update 보단 validate, none값을 사용한다.
- spring.jpa.show-sql: 로그에 하이버네이트가 생성한 쿼리문을 출력하는 옵션.
- spring.jpa.properties.hibernate.format_sql: 로그 포매팅에 대한 옵션. 로그를 조금 보기 좋게 출력할 수 있다.
이렇게 하면 기본적인 설정은 끝난셈이다. 엔티티와 레포지토리는 그냥 Java와 다르지 않다는 생각이 들정도로 간단하다.
- Entity 설계
다음은 Entity의 설계이다. 나같은 경우는 Spring에서 사용하는 모든 Data와 관련된 Class들은(Entity, DTO, DAO, Repository) 전부 Data package 안에 넣어 놓는다. 따라서 내 Entity의 경로는 data/entity와 같다. 다음은 내가 설계한 Entity이다.
-data/entity/BaseEntity.java
package com.kang.knu_30.data.entity;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedBy;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;
import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;
@Getter
@Setter
@ToString
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity {
@CreatedDate
@Column(updatable = false)
private LocalDateTime createdAt;
@LastModifiedDate
private LocalDateTime updatedAt;
}
- data/entity/AdminLogin.java
package com.kang.knu_30.data.entity;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import javax.persistence.*;
@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Table(name="admin_login")
public class AdminLogin extends BaseEntity{
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long number;
@Column(nullable = false)
private String name;
@Column(nullable = false)
private String id;
@Column(nullable = false)
private String passwd;
@Column(nullable = false)
private String role;
}
두개의 Entity를 생성했는데, AdminLogin엔티티에서 BaseEntity를 상속받는 것을 알 수 있다. BaseEntity는 각 테이블마다 기본으로 들어가는 정보를 미리 구현한 엔티티이다. 생성날짜, 수정날짜, 생성자와 같은 값들이 대표적인 예이다.
BaseEntity에는 @CreatedDate, @LastModifiedDate 어노테이션을 사용하는 것을 볼 수 있는데, 이는 자동으로 생성날짜와 업데이트날짜를 주입해주는 어노테이션이다. 이렇게 하면 간단한 상속으로 기본적으로 들어가는 값들의 생성을 자동화할 수 있다.
클래스 앞에 @Entity 어노테이션이 있다면, 이는 Spring Data JPA에서 이 클래스를 하나의 엔티티로 취급하여 엔티티 매니저 객체에서 해당 클래스를 관리한다. Entity는 각 데이터베이스의 Table과 매칭되며, @Table 어노테이션에서 Database에서 저장될 Table 명을 설정할 수 있다. 해당이름의 테이블이 없다면 Entity Class에 맞게 테이블이 생성되며, 만약 Table이 있다면 앞에 설정된 값(spring.jpa.hibernate.ddl-auto)에따라 테이블이 처리된다. 이렇게 해당 Entity를 설계하고 어플리케이션을 실행하면 다음과 같은 Hibernate 쿼리문을 볼 수 있다.
Entity에 맞게 Table을 생성하는 것을 확인할 수 있다.
- Repository 구현
JpaRepository는 Spring Data JPA에서 제공해주는 아키택쳐라고 생각하면 된다. JpaRepository를 통해 더 쉽게 데이터베이스에 접근할 수 있다. JpaRepository를 상속받기면 하면 다양한 기본적인 메소드를 사용할 수 있기 때문에 매우 편하다. 다음은 AdminLogin Entity를 사용하기 위해 생성한 Repository이다.
- data/repository/AdminLoginRepository.java
package com.kang.knu_30.data.repository;
import com.kang.knu_30.data.entity.AdminLogin;
import org.springframework.data.jpa.repository.JpaRepository;
public interface AdminLoginRepository extends JpaRepository<AdminLogin, Long> {
}
JpaRepository<엔티티명, ID Type>를 상속받음으로써 몇가지 메소드를 사용할 수 있다. 이러한 Repository를 이용하여 DAO Class를 만들면 된다.
- DAO 구현
- data/dao/AdminLoginDAO.java
package com.kang.knu_30.data.dao;
import com.kang.knu_30.data.entity.AdminLogin;
public interface AdminLoginDAO {
AdminLogin insertUser(AdminLogin adminLogin);
AdminLogin selectUser(Long number);
}
- data/dao/impl/AdminLoginDAOImpl.java
package com.kang.knu_30.data.dao.impl;
import com.kang.knu_30.data.dao.AdminLoginDAO;
import com.kang.knu_30.data.entity.AdminLogin;
import com.kang.knu_30.data.repository.AdminLoginRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
@Component
public class AdminLoginDAOImpl implements AdminLoginDAO {
private final AdminLoginRepository adminLoginRepository;
@Autowired
public AdminLoginDAOImpl(AdminLoginRepository adminLoginRepository){
this.adminLoginRepository = adminLoginRepository;
}
@Override
public AdminLogin insertUser(AdminLogin adminLogin) {
AdminLogin savedAdminLogin = adminLoginRepository.save(adminLogin);
return savedAdminLogin;
}
@Override
public AdminLogin selectUser(Long number) {
AdminLogin selectedAdminLogin = adminLoginRepository.getById(number);
return selectedAdminLogin;
}
}
코드를 보면 AdminLoginRepository를 사용해 Database에 접근하고 이를 Entity의 개념으로 값을 다룰 수 있다는 것을 알 수 있다. Repository의 메소드는 구글링을 하면 더 잘 나올 것이다. 이렇게 DAO를 구성하면 서비스와 컨트롤러의 설계가 더욱 쉽다. 잘 활용하길 바란다.
3. 결어
다음에는 이런 JPA를 더욱 많이 활용한 방법을 사용해봤으면 좋겠다. 객체와의 매핑을 공부하고 관련된 글을 올릴 것이다.
'기록지 > KNU_30' 카테고리의 다른 글
[KNU_30 개발일기] 낡은 웹페이지의 업데이트 시작 - Swagger를 활용한 API 개발 명세 자동화 (1) | 2022.12.12 |
---|