[Jersey] 2. JPA 및 datasource 설정하기

2022. 5. 11. 17:08Spring/Jersey

반응형
  1. dependency 추가
  2. datasource 설정 추가
  3. User 엔티티
  4. UserRepository 생성
  5. Service 생성
  6. Endpoint 생성
  7. Endpoint 컴포넌트 등록
  8. api 테스트

작업 중인 jersey 프로젝트에 DB를 연결해봅니다.

spring boot에서 DB를 연동하는 방법은 매우 다양하지만, 이 프로젝트에서는 spring data jpa를 이용해서 DB 연동을 할 것입니다.

JPA에 관련된 기본 설명은 이번 포스팅에서는 생략합니다.

※ 만일, JPA관련 기본 설정에 대해 궁금한 점이 있다면 Spring Data JPA Tutorial를 참고해주시기 바랍니다.

build.gradle 에 spring data jpa 스타터와 연동할 DB인 MariaDB에 관련된 의존성 라이브러리를 추가할 것입니다.


1. dependency 추가

build.gradle 의 dependencies에 spring-boot-starter-data-jpa 와 mariadb-java-client를 추가합니다.

dependencies {
  ...
  implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
  runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'
  annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor'
}

build.gradle을 수정한 후에는 반드시 Gradle을 reload 해야합니다.


2. datasource 설정 추가

의존성 라이브러리를 추가한 후
application.yml에 datasource 관련 설정을 추가해줍니다.

spring:
  application:
    name: jersey
  profiles:
    active: local

  datasource:
    driver-class-name: org.mariadb.jdbc.Driver
    url: "jdbc:mariadb://localhost:3306/jiniworld_demo"
    username: test
    password: test
    hikari:
      auto-commit: false
      connection-test-query: SELECT 1
      minimum-idle: 10
      maximum-pool-size: 50
      pool-name: jiniworld_demo
  jpa:
    database-platform: org.hibernate.dialect.MariaDB103Dialect
    properties:
      hibernate:
        default_batch_fetch_size: 100
        format_sql: true
        hbm2ddl.auto: update
        implicit_naming_strategy: org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
        physical_naming_strategy: org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
    open-in-view: false
    show-sql: true

  • spring.datasource
    • DataSource 자동 구성을 설정하는 곳입니다.
    • driver-class-name
      • JDBC 드라이버 이름
    • url
      • JDBC URL
    • username
      • Database 로그인 username
    • password
      • Database 로그인 password

  • spring.datasource.hikari
    • DB Connection Pool 일종
    • 그밖에 Common DBCP, Tomcat-JDBC 가 있습니다.
    • auto-commit
      • 자동커밋 여부. (default: true)
    • connection-test-query
      • connection 유효성 검사 쿼리
    • minimum-idle
      • pool에 유지할 유휴 connection 최소 개수
    • maximum-pool-size
      • pool에 유지시킬 수 있는 최대 connection 수
    • transaction-isolation
      • 트랜잭션 격리 수준
    • pool-name
      • connection pool 이름

  • spring.jpa
    • database-platform
      • 접속할 database의 SQL Dialect 설정
      • JPA를 활용하여 동적 쿼리 생성시, database에 맞는 방언 sql을 생성합니다.
    • generate-ddl
      • 앱 시작시 @Entity로 정의한 테이블의 create 문 실행
    • properties.hibernate
      • hibernate vendor 관련 설정
      • hbm2ddl.auto
        • 스키마 변동시 취할 행동
      • format_sql
        • true로 설정할 시 sql 출력문을 보기 좋게 출력해줍니다.
        • false로 설정할 경우 SQL이 한줄로 길게 출력됩니다.
    • open-in-view
      • 템플릿 view 화면의 렌더링이 끝날 때 까지 Lazy fetch 가 가능하도록 해주는 속성
      • default는 true로, 매우 유용한 기능이지만 기능적 면에서 false로 설정해주는 것이 좋습니다.

3. User 엔티티

조회 테스트를 위한 User 엔티티를 생성합니다.

※ Entity 설정에 대한 자세한 설명은 [Spring Data JPA Tutorial] 4. JPA 및 datasource 설정하기를 참고해주세요.


package xyz.applebox.jersey.domain.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import org.hibernate.annotations.ColumnDefault;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.annotations.Where;

import javax.persistence.*;
import java.time.LocalDate;
import java.time.LocalDateTime;

@NoArgsConstructor
@DynamicInsert
@DynamicUpdate
@Getter
@Setter
@Entity
@Table(indexes = {@Index(name = "UK_USER_EMAIL", columnList = "email", unique = true)})
@Where(clause = "active = true")
public class User {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(updatable = false, nullable = false)
    private Long id;

    @Column(length = 10)
    @ColumnDefault("'BASIC'")
    private String type;

    @Column(nullable = false, length = 100)
    private String email;

    @Column(nullable = false, length = 50)
    private String name;

    @Column(length = 1)
    @ColumnDefault("'M'")
    private String sex;

    private LocalDate birthDate;

    @Column(length = 20)
    private String phoneNumber;

    @JsonIgnore
    @Column(nullable = false, length = 150)
    private String password;

    @Column(nullable = false)
    @ColumnDefault("true")
    private boolean active;

    @Column(updatable = false)
    @ColumnDefault("CURRENT_TIMESTAMP()")
    private LocalDateTime createdAt;

    private LocalDateTime updatedAt;

}

 

4. UserRepository 생성

Spring Data JPA 문법을 사용하여 User 엔티티를 조회하기 위해 JpaRepository를 상속한 UserRepository 인터페이스를 생성합니다.

package xyz.applebox.jersey.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import xyz.applebox.jersey.domain.entity.User;

public interface UserRepository extends JpaRepository<User, Long> {

}

5. Service 생성

트랜젝션이 적용될 Service 클래스를 생성합니다.
아주 간단하게 id를 이용하여 특정 사용자를 조회하는 findById 메서드와, 모든 사용자를 조회하는 findAll 메서드를 정의했습니다.

findAllfindById 는 JpaRepository에서 기본적으로 선언되어있는 메서드이기 때문에 UserRepository에서 별도로 선언하지 않아도 이용할 수 있습니다.

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import xyz.applebox.jersey.domain.entity.User;
import xyz.applebox.jersey.domain.exception.InvalidRequestException;
import xyz.applebox.jersey.repository.UserRepository;

import java.util.List;


@Transactional(readOnly = true)
@RequiredArgsConstructor
@Service
public class UserService {

    private final UserRepository userRepository;

    public List<User> findAll() {
        return userRepository.findAll();
    }

    public User findById(long id) {
        return userRepository.findById(id).orElse(null);
    }

}

6. Endpoint 생성

User관련 request를 정의할 UserEndpoint 클래스를 생성합니다.

모든 user를 조회하는 request와 id를 이용하여 특정 user를 조회하는 api를 만들건데 아래와 같은 모양으로 만들것입니다.

  • GET /v1/users
  • GET /v1/users/{userId}

package xyz.applebox.jersey.endpoint.v1;

import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Component;
import xyz.applebox.jersey.domain.entity.User;
import xyz.applebox.jersey.service.UserService;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.core.MediaType;
import java.util.List;

@Produces(MediaType.APPLICATION_JSON)
@RequiredArgsConstructor
@Component
@Path("/users")
public final class UserEndpoint {

    private final UserService userService;

    @GET
    public List<User> getAll() {
        return userService.findAll();
    }

    @GET
    @Path("/{userId}")
    public User getOne(@PathParam("userId") Long userId) {
        return userService.findById(userId);
    }

}

Jersey에서 제공해주는 @PathParam 을 이용하여 path상에 정의된 {userId} 를 메서드 인자에서 매핑할 수 있습니다.

기본 media type은 /text/html 이므로 class 상단에 @Produces(MediaType.APPLICATION_JSON) 를 설정하여 클래스 전체 response의 Content-Type을 applicaion/json 으로 설정해줍니다.

만일, @Produces 를 설정하지 않았을 경우 아래와 같은 에러가 발생됩니다.

2022-05-11 15:39:51.741 ERROR 15748 --- [nio-8080-exec-1] o.g.j.m.i.WriterInterceptorExecutor
      : MessageBodyWriter not found for media type=text/html, type=class xyz.applebox.jersey.domain.entity.User,
       genericType=class xyz.applebox.jersey.domain.entity.User.

7. Endpoint 컴포넌트 등록

기존에 IndexPoint만 등록되어있던 것에서 새로 추가한 UserEndpoint도 등록해줍니다.

각각의 Endpoint클래스를 직접 추가해줘도 상관없지만, Endpoint 클래스가 추가될때마다 매번 저렇게 추가하는 것도 번거로운 일이니 packages로 등록했습니다.

package xyz.applebox.jersey.config;

import org.glassfish.jersey.server.ResourceConfig;
import org.springframework.stereotype.Component;

import javax.ws.rs.ApplicationPath;

@Component
@ApplicationPath("/v1")
public class ResourceV1Config extends ResourceConfig {

    public ResourceV1Config() {
        packages("xyz.applebox.jersey.endpoint.v1");
    }
}

8. api 테스트

테스트를 위해 사전에 user 정보를 추가한 후 api를 실행시켜봅니다.

insert into `user` (`id`, `type`, `name`, `sex`, `phone_number`, `birth_date`, `email`, `password`, `created_at`, `updated_at`) values
('1','BASIC','지니','F','01011112222','1992-02-01','jini@jiniworld.me','1','2022-04-11 16:59:07.148000',NULL),
('2','OWNER','이한','M','01088887777','1988-07-02','2han@appleboxy.xyz','1','2022-04-13 12:21:30.542000',NULL),
('3','OWNER','코코','F','01044447777','1995-07-05','coco@jiniworld.me','1','2022-04-13 16:14:01.098665','2022-04-18 11:00:14.687000');

01-6


01-7


※ GitHub에서 jersey 프로젝트(v1.0.1)를 다운받아 볼 수 있습니다.


++

  • Rest API with Jersey and Spring Data JPA
  • Rest Web Service with Jersey and JPA
728x90
반응형