반응형
해당 글에서는 Spring Boot 환경에서 Spring Boot Data JPA를 기반으로 초기 환경설정을 하고 JPA 인터페이스를 활용하여 데이터 액세스 기능을 확인해 봅니다.
💡 [참고] JPA 관련해서 구성 내용에 대해 궁금하시면 아래의 글을 참고하시면 도움이 됩니다.
분류 | 링크 |
Spring Boot Data JPA -1: ORM, JPA, Hibernate, QueryDSL 이론 | https://adjh54.tistory.com/421 |
Spring Boot Data JPA -2: 초기 환경 구성 + JpaRepository 활용 방법 | https://adjh54.tistory.com/422 |
Spring Boot Data JPA -3: 상세 JpaRepository 활용 방법 | https://adjh54.tistory.com/481 |
Spring Boot Data JPA 엔티티 어노테이션 -1 : 테이블 컬럼 단위 | https://adjh54.tistory.com/466 |
Spring Boot Data JPA 엔티티 어노테이션 -2 : 엔티티(테이블)간의 관계 | https://adjh54.tistory.com/477 |
Spring Boot Data JPA FetchType 이해하기 : 즉시/지연로딩 | https://adjh54.tistory.com/476 |
Spring Boot Data JPA : JPQL 활용 방법 | https://adjh54.tistory.com/479 |
Spring Boot Data JPA : Criteria API 활용 방법 | https://adjh54.tistory.com/483 |
Spring Boot Data JPA : QueryDSL 활용 방법-1 : 정의 및 구성요소 | https://adjh54.tistory.com/484 |
Spring Boot Data JPA : QueryDSL 활용 방법-2 : 초기 환경설정 및 활용예시 | https://adjh54.tistory.com/485 |
1) Spring Boot Data JPA(Java Persistence API)
💡 Spring Boot JPA(Java Persistence API)
- 데이터베이스를 쉽게 다루기 위한 ‘데이터 액세스 기술’로 ORM(Object-Relational Mapping) 기법을 사용하여 자바 애플리케이션에서 사용하는 객체와 관계형 데이터베이스 사이의 매핑을 관리하는 ORM 기술에 대한 API 표준 명세서(인터페이스) 의미합니다.
- 이 API를 사용하여 개발자가 직접적인 SQL을 작성하지 않고도 데이터베이스에서 데이터를 저장, 업데이트, 삭제, 조회하는 등의 작업을 수행할 수 있게 해 줍니다.
- JPA는 표준화된 API를 제공함으로써, 다양한 ORM 프레임워크(예: Hibernate, EclipseLink, OpenJPA 등)와의 호환성을 보장합니다. 이로 인해 개발자는 특정 ORM 프레임워크에 종속되지 않고 필요에 따라 다른 프레임워크로 쉽게 전환할 수 있습니다.
1. JPA 환경설정 속성
💡 JPA 환경설정 속성
- 다양한 속성이 있지만 일부 속성만 참고하였습니다.
속성-1 | 속성-2 | 설명 |
database | JPA가 사용할 데이터베이스를 지정하는 속성입니다. | |
open-in-view | 'Open EntityManager in View' 패턴 사용 여부를 지정하는 속성입니다. | |
mapping-resources | JPA 매핑 파일의 위치를 지정하는 속성입니다. | |
generate-ddl | DDL 생성을 활성화하는 속성입니다. | |
defer-datasource-initialization | 데이터소스 초기화를 지연시킬지 여부를 지정하는 속성입니다. | |
database-platform | 사용할 데이터베이스 플랫폼을 지정하는 속성입니다. | |
show-sql | SQL 쿼리를 로그로 출력할지 여부를 지정하는 속성입니다. | |
hibernate | 하이버네이트 관련 설정을 위한 속성입니다. | |
ddl-auto | DDL 자동 생성전략을 설정하는 속성입니다. | |
use-new-id-generator-mappings | 새로운 ID 생성전략을 사용할지 결정하는 속성입니다. | |
naming.physical-strategy | 물리적 네이밍 전략을 설정하는 속성입니다. | |
naming.implicit-strategy | 암시적 네이밍 전략을 설정하는 속성입니다. | |
properties | hibernate.default_schema | 기본 스키마를 설정하는 속성입니다. |
hibernate.show_sql | SQL 쿼리를 콘솔에 출력할지 결정하는 속성입니다. | |
hibernate.use_sql_comments | SQL 주석을 사용할지 결정하는 속성입니다. | |
hibernate.format_sql | SQL 문을 포맷팅할지 결정하는 속성입니다. | |
hibernate.dialect | 사용할 SQL 방언을 설정하는 속성입니다. |
2) JPA 환경 구성 하기 : 초기 환경 설정
💡 환경구성하기
- 아래와 같은 개발 환경에서 진행하였습니다.
개발 환경 | 버전 |
Java | 11 |
Gradle | 7.5.1 |
Spring Boot | 2.7.18 |
spring-boot-starter-data-jpa | 2.7.18 |
spring-boot-starter-web | 2.7.18 |
spring-boot-data-jdbc | 2.7.18 |
org.postgresql:postgresql | 42.7.1 |
Lombok | - |
💡 [참고] 데이터베이스는 PostgreSQL이 아니더라도 RDBMS로 설치하시면 동일하게 환경 구성이 가능합니다.
- PostgreSQL 로컬 설치를 원하시면 아래의 링크를 참고하시면 도움이 됩니다.
1. 라이브러리 추가
💡 위에 환경 구성 부분에서 언급했던 라이브러리를 설치하였습니다.
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-data-jpa:2.7.18' // Spring Data JPA
implementation 'org.springframework.boot:spring-boot-starter-web:2.7.18' // Spring Boot Web
implementation 'org.springframework.boot:spring-boot-starter-data-jdbc:2.7.18' // Spring Boot JDBC
compileOnly 'org.projectlombok:lombok'
runtimeOnly 'org.postgresql:postgresql'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
2. 환경 구성 파일
💡 환경 구성 파일
- 환경 구성 파일은 properties 기반의 yml 확장자를 참조하여 구성할 예정입니다.
- 이와 관련되어. yml 파일을 관리하기 위해 아래의 글을 참고하시면 도움이 됩니다.
💡 아래와 같이 application.properties 파일 내에서 아래의 속성을 입력하여 application-multiflex-jpa-local.yml 파일을 불러오도록 구성하였습니다.
# local 환경(multiflex-jpa-local)
spring.profiles.active=multiflex-jpa-local
💡 아래와 같이 참조한 ‘multiflex-jpa-local’ 파일이 호출됨을 확인합니다.
3. 환경 파일 구성
💡 환경 파일 구성
- 구성한 yml 파일 내에 아래의 환경 구성을 수행합니다.
속성-1 | 속성-2 | 속성-3 | 설명 |
datasource | driver-class-name | 데이터베이스 드라이버 클래스를 지정합니다. | |
url | 데이터베이스의 연결 URL을 지정합니다. | ||
username | 데이터베이스 연결을 위한 사용자 이름을 지정합니다. | ||
password | 데이터베이스 연결을 위한 비밀번호를 지정합니다. | ||
jpa | database | 사용할 JPA 데이터베이스를 지정합니다. | |
open-in-view | 'Open EntityManager in View' 패턴 사용 여부를 지정하는 속성입니다. | ||
hibernate | Hibernate 설정을 위한 부분입니다. | ||
ddl-auto | Hibernate의 DDL 자동 생성 전략을 설정합니다.(하단의 ddl-auto 속성 참고) | ||
properties | Hibernate의 추가 속성을 설정하는 부분입니다. | ||
hibernate | Hibernate 설정을 위한 부분입니다. | ||
hibernate.show_sql | SQL문을 콘솔에 출력할지 여부를 설정합니다. | ||
hibernate.format_sql | SQL 문을 포맷팅(가독성 향상)할지 여부를 설정합니다. | ||
hibernate.use_sql_comments | SQL 주석을 사용할지 여부를 설정합니다. |
spring:
datasource:
driver-class-name: org.postgresql.Driver
url: jdbc:postgresql://localhost:5432/multiflexjpa
username: localmaster
password: 1234
jpa:
database: postgresql
open-in-view: false
hibernate:
ddl-auto: create # or update, create-drop, validate
properties:
hibernate:
show_sql: true
format_sql: true
use_sql_comments: true
💡 [참고] ddl-auto 속성
- 데이터베이스 스키마 생성 관련 동작을 제어합니다. 이 속성을 사용하면 Hibernate가 데이터베이스의 DDL(Data Definition Language)을 자동으로 생성하고 실행할 수 있습니다.
- 해당 속성은 개발 중에는 매우 편리하지만, 프로덕션 환경에서는 데이터 손실 위험이 있기에 ‘none’ 속성을 사용해야 합니다.
- 또한 ‘create’ 속성을 사용하게 되면 테이블을 우선 DROP 하고 새로 생성하기에 적재한 데이터까지 사라질 수 있으니 주의해야 합니다.
옵션 | 설명 |
create | 세션 팩토리가 시작될 때, 데이터베이스 드롭을 실행한 후 새로 생성합니다. |
update | 세션 팩토리가 시작될 때, 데이터베이스를 변경합니다. |
create-drop | 세션 팩토리가 시작될 때 데이터베이스를 드롭한 후 새로 생성하고, 세션 팩토리가 종료될 때 드롭합니다. |
validate | 세션 팩토리가 시작될 때 엔티티와 테이블이 매핑되어 있는지 검증합니다. |
none | 아무 작업도 실행하지 않습니다. |
4. DDL-AUTO 속성을 테스트를 위해 Entity 생성
💡 DDL-AUTO 속성을 테스트를 위해 Entity 생성
- DDL-AUTO 속성 값으로 create를 사용했습니다. 그렇기에 객체(Object) 내에 지정한 값을 통해 데이터베이스 테이블이 자동으로 생성될 예정입니다. 추후 해당 내용은 아래에서 설명하겠습니다.
package com.adjh.multiflex.jpa.entity;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.NoArgsConstructor;
import lombok.ToString;
import javax.persistence.*;
import java.io.Serializable;
/**
* tb_user 테이블과 매핑 객체
*
* @author : jonghoon
* @fileName : UserEntity
* @since : 2/8/24
*/
@ToString
@Entity
@Table(name = "tb_user")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class UserEntity implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "user_sq")
private int userSq;
@Column(name = "user_id")
private String userId;
// 사용자 패스워드
@Column(name = "user_pw")
private String userPw;
// 사용자 이름
@Column(name = "user_nm")
private String userNm;
// 사용자 상태
@Column(name = "user_st")
private String userSt;
@Column(name = "user_email")
private String userEmail;
@Builder(toBuilder = true)
public UserEntity(int userSq, String userId, String userPw, String userNm, String userSt, String userEmail) {
this.userSq = userSq;
this.userId = userId;
this.userPw = userPw;
this.userNm = userNm;
this.userSt = userSt;
this.userEmail = userEmail;
}
}
5. 테스트 확인
💡 테스트 확인
- 테스트 확인을 위해 로컬 서버를 실행하면 생성이 됩니다.
💡 하단에는 데이터베이스 스키마만 구성된 상태입니다.
💡 구성한 서버를 실행하면 아래와 같이 DDL 시퀀스와 테이블이 생성되었습니다.
💡 스키마를 확인해 보았을 때 역시 테이블과 시퀀스가 생성됨을 확인되었습니다.
3) JpaRepository
💡 JpaReposiory
- Spring Data JPA에서 제공하는 기능 중 하나로 개발자가 데이터베이스와의 CRUD(Create, Read, Update, Delete) 연산을 더욱 쉽게 처리할 수 있도록 도와주는 인터페이스입니다.
- JpaRepository 인터페이스를 상속받은 인터페이스를 만들면, Spring Data JPA가 자동으로 해당 인터페이스의 구현체를 만들어서 Bean으로 등록해 줍니다.
- 이를 통해 개발자는 SQL 쿼리를 직접 작성하지 않고도 메서드 이름만으로 데이터베이스 연산을 수행할 수 있게 됩니다.
4) JPA 구성 테스트 : JpaRepository 구성하기
💡 JPA 구성 테스트 : JpaRepository 구성하기
- 해당 환경은 4-Tier Architecture 기반의 환경에서 Repository는 JpaRepository를 상속받아서 재 구성합니다.
[ 더 알아보기 ]
💡 JpaRepository랑 CRUDRepository가 있는데 왜 JpaRepository를 사용하는 걸까?
- JpaRepository를 사용하면 상속받고 있는 PagingAndSortingRepository, CRUDRepository, Repository 인터페이스를 사용할 수 있습니다.
1. Entity 구성
💡 Entity 구성
- 해당 Entity는 데이터베이스 컬럼과 매핑되는 객체를 구성한 공간입니다. 해당 명세한 객체를 기반으로 데이터베이스 테이블 및 컬럼이 생성이 됩니다.
💡 [참고] 해당 Entity 구성에 사용된 JPA 어노테이션
어노테이션 | 설명 |
@Entity | JPA가 관리하는 Entity 클래스임을 지정하며 이 어노테이션이 붙은 클래스는 JPA가 관리하며, 테이블과 매핑됩니다. |
@Table | Entity 클래스가 매핑될 테이블을 지정합니다. |
@Column | 필드가 매핑될 테이블의 컬럼을 지정합니다. |
@Id | 해당 필드가 테이블의 주키(primary key) 역할을 하는 필드임을 지정합니다. |
@GeneratedValue | 주키의 생성 전략을 지정합니다. |
package com.adjh.multiflex.jpa.entity;
import lombok.*;
import org.hibernate.annotations.CreationTimestamp;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import javax.persistence.*;
import java.io.Serializable;
/**
* tb_user 테이블과 매핑 객체
*
* @author : jonghoon
* @fileName : UserEntity
* @since : 2/8/24
*/
@ToString
@Entity
@Getter
@Table(name = "tb_user")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class UserEntity implements Serializable {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "user_sq", unique = true)
private long userSq;
@Column(name = "user_id", nullable = false, length = 100)
private String userId;
// 사용자 패스워드
@LastModifiedDate
@Column(name = "user_pw", length = 100)
private String userPw;
// 사용자 이름
@Column(name = "user_nm", length = 255)
private String userNm;
// 사용자 상태
@Column(name = "user_st", nullable = false, length = 10)
private String userSt;
@Column(name = "user_email", length = 255)
private String userEmail;
@Builder(toBuilder = true)
public UserEntity(long userSq, String userId, String userPw, String userNm, String userSt, String userEmail) {
this.userSq = userSq;
this.userId = userId;
this.userPw = userPw;
this.userNm = userNm;
this.userSt = userSt;
this.userEmail = userEmail;
}
}
💡 Entity 구성 이후 서버를 수행하였을 시 구성한 객체를 기반으로 테이블이 생성되었습니다.
2. 디렉터리 구조
디렉터리 | 설명 |
controller | 사용자의 요청을 처리하는 레이어로, HTTP 요청을 받아 서비스 레이어에 작업을 위임하고 결과를 클라이언트에게 전달합니다. |
entity | 데이터베이스 테이블에 해당하는 클래스를 담는 레이어로, 이 클래스는 데이터베이스의 테이블과 매핑됩니다. |
repository | 데이터베이스와 관련된 연산을 처리하는 레이어로, 데이터베이스 CRUD(Create, Read, Update, Delete) 연산이 이 레이어에서 수행됩니다. |
service | 비즈니스 로직을 담당하는 레이어로, 컨트롤러로부터 요청을 받아 필요한 데이터를 리포지토리에서 가져와 처리하고 결과를 컨트롤러에게 반환합니다. |
service.Impl | 서비스 인터페이스를 구현하는 클래스를 담는 레이어로, 이 레이어에는 실제 비즈니스 로직이 구현되어 있습니다. |
3. UserJpaRepository
💡 UserJpaRepository
- Spring Data JPA에서 제공하는 JpaRepository 인터페이스를 상속받는 UserJpaRepository 인터페이스를 정의합니다
- JpaRepository 인터페이스는 JPA를 좀 더 쉽게 사용할 수 있도록 도와주며, CRUD(Create, Read, Update, Delete) 기능을 기본적으로 제공합니다.
package com.adjh.multiflex.jpa.repository;
import com.adjh.multiflex.jpa.entity.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
/**
* JpaRepository를 기본적으로 활용하여 Repository 구성
*
* @author : jonghoon
* @fileName : UserCRUDRepository
* @since : 2/8/24
*/
@Repository
public interface UserJpaRepository extends JpaRepository<UserEntity, Long> {
}
4. UserSerivce
💡 UserSerivce
- 사용자 서비스에 대한 인터페이스를 정의합니다.
1. userList() : 사용자 리스트를 조회하는 메서드입니다.
2. saveUser(UserEntity userEntity) : 입력받은 UserEntity 객체를 이용하여 새로운 사용자를 등록하는 메서드입니다.
3. updateUser(UserEntity userEntity) : 입력받은 UserEntity 객체를 이용하여 기존 사용자의 정보를 업데이트하는 메서드입니다.
4. deleteUserByUserSq(long userSq) : 입력받은 사용자 번호(userSq)를 이용하여 해당 사용자를 삭제하는 메서드입니다.
package com.adjh.multiflex.jpa.service;
import com.adjh.multiflex.jpa.entity.UserEntity;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* 사용자 서비스 인터페이스입니다.
*
* @author : jonghoon
* @fileName : UserService
* @since : 2/8/24
*/
@Service
public interface UserService {
List<UserEntity> userList(); // 사용자 리스트 조회
UserEntity saveUser(UserEntity userEntity); // 사용자 등록
UserEntity updateUser(UserEntity userEntity); // 사용자 수정
void deleteUserByUserSq(long userSq); // 사용자 삭제
}
5. UserServiceImpl
💡 UserServiceImpl
- 해당 코드는 UserService 인터페이스를 기반으로 구현체를 구현합니다.
1. userList()
- 이 메서드는 데이터베이스에서 모든 사용자의 목록을 검색합니다.
- findAll()은 UserJpaRepository에서 제공하는 인터페이스입니다.
2. saveUser(UserEntity userEntity)
- 이 메서드는 새로운 사용자를 데이터베이스에 저장합니다.
- save()는 UserJpaRepository에서 제공하는 인터페이스입니다.
3. updateUser(UserEntity userEntity):
- 이 메서드는 데이터베이스에서 기존 사용자의 세부 사항을 업데이트합니다.
- findById()를 통해서 사용자 정보를 조회한 뒤 save()를 통해 데이터를 수정합니다.
4. deleteUserByUserSq(long userSq):
- 이 메소드는 ID를 기반으로 데이터베이스에서 사용자를 삭제합니다.
- deleteById()는 UserJpaRepository에서 제공하는 인터페이스입니다.
💡 UserJpaRepository는 CRUDRepository <T, ID> 인터페이스를 상속받기에 해당 기능을 모두 사용할 수 있습니다.
리턴 타입 | 메서드 | 설명 |
<S extends T>S | save(S entity) | 주어진 엔티티를 저장합니다. |
Optional<T> | findById(ID id) | id로 엔티티를 검색합니다. |
void | deleteById(ID id) | 주어진 id의 엔티티를 삭제합니다. |
package com.adjh.multiflex.jpa.service.impl;
import com.adjh.multiflex.jpa.entity.UserEntity;
import com.adjh.multiflex.jpa.repository.UserJpaRepository;
import com.adjh.multiflex.jpa.service.UserService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Objects;
/**
* 사용자 인터페이스의 구현체입니다.
*
* @author : jonghoon
* @fileName : UserServiceImpl
* @since : 2/8/24
*/
@Service
public class UserServiceImpl implements UserService {
private final UserJpaRepository userJpaRepository;
public UserServiceImpl(UserJpaRepository userJpaRepository) {
this.userJpaRepository = userJpaRepository;
}
/**
* 사용자 리스트 조회
*
* @return
*/
@Override
@Transactional(readOnly = true)
public List<UserEntity> userList() {
return (List<UserEntity>) userJpaRepository.findAll();
}
/**
* 사용자 등록
*
* @param userEntity
* @return
*/
@Override
@Transactional
public UserEntity saveUser(UserEntity userEntity) {
return userJpaRepository.save(userEntity);
}
/**
* 사용자 수정
*
* @param userEntity
* @return
*/
@Override
@Transactional
public UserEntity updateUser(UserEntity userEntity) {
UserEntity user = userJpaRepository.findById(userEntity.getUserSq()).get();
UserEntity resultEntity = UserEntity.builder().build();
if (Objects.nonNull(user.getUserNm()) && !"".equalsIgnoreCase(user.getUserNm())) {
resultEntity = userEntity.toBuilder().userNm(user.getUserNm()).build();
}
if (Objects.nonNull(user.getUserEmail()) && !"".equalsIgnoreCase(user.getUserEmail())) {
resultEntity = userEntity.toBuilder().userEmail(user.getUserEmail()).build();
}
if (Objects.nonNull(user.getUserSt()) && !"".equalsIgnoreCase(user.getUserSt())) {
resultEntity = userEntity.toBuilder().userEmail(user.getUserSt()).build();
}
return userJpaRepository.save(resultEntity);
}
/**
* 사용자 삭제
*
* @param userSq
*/
@Override
@Transactional
public void deleteUserByUserSq(long userSq) {
userJpaRepository.deleteById(userSq);
}
}
6. UserController
💡 UserController
HTTP Method | 엔드포인트 | 메서드 | 설명 |
POST | /api/v1/user/users | selectUserList | UserJpaRepository 인터페이스를 활용하여 사용자를 리스트를 조회합니다 |
POST | /api/v1/user/user | userSave | UserJpaRepository 인터페이스를 활용하여 사용자를 등록합니다 |
PUT | /api/v1/user/user | updateUser | UserJpaRepository 인터페이스를 활용하여 사용자를 수정합니다 |
DELETE | /api/v1/user/user | deleteUser | UserJpaRepository 인터페이스를 활용하여 사용자를 삭제합니다. |
package com.adjh.multiflex.jpa.controller;
import com.adjh.multiflex.jpa.entity.UserEntity;
import com.adjh.multiflex.jpa.service.UserService;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.List;
/**
* Please explain the class!!
*
* @author : jonghoon
* @fileName : UserController
* @since : 2/8/24
*/
@RequestMapping("/api/v1/user")
@RestController
public class UserController {
private final UserService userService;
public UserController(UserService userService) {
this.userService = userService;
}
/**
* @return
*/
@PostMapping("/users")
public ResponseEntity<Object> selectUserList() {
List<UserEntity> userEntityList = userService.userList();
return new ResponseEntity<>(userEntityList, HttpStatus.OK);
}
/**
* 사용자 등록
*
* @param userEntity
* @return
*/
@PostMapping("/user")
public ResponseEntity<Object> userSave(@RequestBody UserEntity userEntity) {
UserEntity result = userService.saveUser(userEntity);
return new ResponseEntity<>(result, HttpStatus.OK);
}
/**
* 사용자 수정
*
* @param userEntity
* @return
*/
@PutMapping("/user")
public ResponseEntity<Object> updateUser(@RequestBody UserEntity userEntity) {
UserEntity result = userService.updateUser(userEntity);
return new ResponseEntity<>(result, HttpStatus.OK);
}
/**
* 사용자 삭제
*
* @param userSq
* @return
*/
@DeleteMapping("/user")
public ResponseEntity<Object> deleteUser(@RequestParam long userSq) {
userService.deleteUserByUserSq(userSq);
return new ResponseEntity<>(HttpStatus.OK);
}
}
7. 구성 테스트
💡 사용 예시
- 사용자 등록
💡 사용 예시
- 사용자 리스트 조회
💡 사용 예시
- 사용자 수정
💡 사용 예시
- 사용자 삭제
오늘도 감사합니다. 😀
반응형