본문 바로가기
[SpringBoot]

SpringBoot 간단 실습 - 4 ORM과 JPA

by Hevton 2022. 12. 11.
반응형

 

ORM이란

어플리케이션의 객체와 관계형 데이터베이스의 데이터를 자동으로 매핑해주는 것

 - 객체가 테이블이 되도록 매핑 시켜주는 프레임워크이다. 

JPA, Hibernate가 이에 속한다.

name, email, organization을 갖고 있는 클래스의 객체와 DataBase의 table간 변환을 할 수 있다.

 

ORM을 이용함으로써 SQL Query가 아닌 직관적인 코드(메서드)로서 데이터를 조작할 수 있다.
ex) 기존쿼리 : SELECT * FROM MEMBER; 

이를 ORM을 사용하면 Member테이블과 매핑된 객체가 member라고 할 때, 

member.findAll()이라는 메서드 호출로 데이터 조회가 가능하다. 

 

 

ORM의 장점

SQL 쿼리가 아닌 직관적인 코드로 데이터 조작 가능

 

ORM의 단점

복잡성이 커질 경우 ORM만으로 구현이 어려움 -> 그렇기 때문에 직접 쿼리문을 하드코딩해야하는 경우가 당연히 있음.

 

Hibernate

ORM Framework중에 하나이다.

JPA의 실제 구현체 중 하나이다. 현재 JPA 구현체 중에 가장 많이 사용된다.

( JPA는 애플리케이션에서 관계형 데이터베이스를 사용하는 방식을 정의한 인터페이스이므로, Hibernate같은 구현체로 이용해야한다. )

한마디로, JPA를 사용하기 위해서 JPA를 구현한 ORM 프레임워크중 하나. 

 

Spring Data JPA

Spring Framework에서 JPA를 편리하게 사용할 수 있게 지원하는 라이브러리이다.

JPA를 실제 구현해 놓은 것 중 하나가 Hibernate이고,

이 Hibernate에서 자주 사용되는 기능을 조금 더 쉽게 사용할 수 있도록 구현한 것이 Spring Data JPA이다.

 

 


 

이제 간단한 실습을 진행해본다.

그러기 위해서 우선 Mysql이 설치되어 있어야 하는데, 설치되어 있지 않은 맥 유저는 아래 글을 통해 간단하게 설치할 수 있다.

https://hevton.tistory.com/27

 

 

build.gradle에 아래 두 dependencies를 추가해준다.

    implementation 'org.springframework.boot:spring-boot-starter-data-jpa:2.7.6'
    implementation 'mysql:mysql-connector-java:8.0.31'

 

전체 코드는 이렇게 될 것이다.

plugins {
    id 'java'
    id 'org.springframework.boot' version '2.7.6'
    id 'io.spring.dependency-management' version '1.0.15.RELEASE'

}

group 'org.example'
version '1.0-SNAPSHOT'
sourceCompatibility = '1.8'

configurations {
    compileOnly {
        extendsFrom annotationProcessor
    }
}

repositories {
    mavenCentral()
}

dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation group: 'com.google.code.gson', name: 'gson', version: '2.7'

    // 두줄 추가
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa:2.7.6'
    implementation 'mysql:mysql-connector-java:8.0.31'

    compileOnly 'org.projectlombok:lombok'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

tasks.named('test') {
    useJUnitPlatform()
}

 

그리고 아래와 같은 경로의 application.properties 파일을 수정해준다. 없다면 생성하고 작성해준다.

# MySQL 설정
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# DB Source URL
spring.datasource.url=jdbc:mysql://127.0.0.1:3306/User?useSSL=false&useUnicode=true&serverTimezone=Asia/Seoul
# jdbc:mysql://<IP>:<Port/<DB>?useSSL=false&useUnicode=true&serverTimezone=Asia/Seoul

#DB Username
spring.datasource.username=<username>

#DB Password
spring.datasource.password=<password>

#true 설정시 JPA 쿼리문 확인 가능
spring.jpa.show-sql=true

#DDL(Create, Alter, Drop) 정의시 DB의 고유 기능을 사용할 수 있다.
spring.jpa.hibernate.ddl-auto=update

# JPA의 구현체인 Hibernate가 동작하면서 발생한 SQL의 가독성을 높여준다.
spring.jpa.properties.hibernate.format_sql=true

요즘엔 application.yaml 또는 application.yml 형식으로 작성하기도 한다.

 

위에 보는 바와 같이, 스키마를 User로 지정했으므로

CREATE DATABASE User를 통해 database를 생성해준 뒤에 (mysql은 대소문자 구분 없음)

 

 

기존에 작성했었던 User class 파일을 좀 수정해준다.

package com.test.admin.entity;


import com.google.gson.Gson;
import lombok.Getter;

import javax.persistence.Entity;
import javax.persistence.Id;
import java.util.UUID;

// @Entity가 붙은 클래스는 JPA가 관리하는 클래스이고, 테이블과 매핑할 테이블에 해당 어노테이션을 붙인다.
@Getter
@Entity
public class User {

    // @ID 어노테이션은 Primary key를 뜻함.
    @Id
    String id;

    String name;

    String email;

    public User() {

        this.id = UUID.randomUUID().toString();
    }

    public User(String name, String email) {
        this();
        this.name = name;
        this.email = email;
    }

}

@Entity가 붙은 클래스는 JPA가 관리하는 클래스다. 테이블과 매핑할 테이블에 이 어노테이션을 붙인다.

즉, 이 Class 파일이 곧 테이블 정보가 된다.

 

 

뭐 대강 이런 느낌이 될 것이다. 이따가 프로그램을 실행하면 아래 sql문이 자동으로 실행됨을 볼 수 있을 것이다.

CREATE TABLE User( id VARCHAR(255) NOT NULL, name VARCHAR(255) NOT NULL, email VARCHAR(255) NOT NULL, PRIMARY KEY(id));

 

 

 

이제 UserRepository와 UserRepository를 구현하는 구현체 UserRepositoryLogic도 대거 수정해야 하는데,

일단 UserRepositoryLogic은 더 이상 쓸 일이 없다.

 

UserRepository 그 자체만을 사용해도 된다.

 

JPA에서는 단순히 Repository 인터페이스를 생성한후 JpaRepository<Entity, 기본키 타입> 을 상속받으면(extends하면) 기본적인 Create, Read, Update, Delete가 자동으로 생성된다. 
그렇기 떄문에 단순히 인터페이스를 만들고, 상속만 잘해주면 기본적인 동작 테스트가 가능하다.

package com.test.admin.repository;
import com.test.admin.entity.User;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;


// JpaRepository<엔티티명, PK 타입>
@Repository
public interface UserRepository extends JpaRepository<User, String> {

    //비워있어도 잘 작동한다. 기본적인 CRUD는 자동으로 추가된다.


    // findBy뒤에 컬럼명을 붙여주면 이를 이용한 검색이 가능하다.
    public List<User> findByName(String name);

//    PK에 대한 find는 기본적으로 추가되어 있음.
//    public List<MemberVo> findById(String id);


}

UserRepository에 @Repository 어노테이션을 추가해줬고, JpaRepository<User, String> 을 extends 하게 추가해줬다.

이렇게 되면 기본적인 CRUD는 모두 자동으로 생성된다. 추가적으로 작성하고 싶은 것이 있으면, 규칙에 맞게 작성해주면 된다.

예를들면 findBy~ 하게 되면 해당 컬럼으로 찾는 검색이 가능하다. 로직이 자동으로 구현되기 때문에, Service에서 이용만 하면 된다.

 

 

 

그리고 UserService와 UserService를 구현하는 구현체 UserServiceLogic도 수정해준다.

이렇게 interface와 구현체를 나누는 것을 loose-coupling 이라고 한다

 

 

일단 간단한 테스트로 register와 findAll 두가지만 정의해보겠다.

package com.test.admin.service;

import com.test.admin.entity.User;

import java.util.List;

public interface UserService {

    User register(User newUser);

    List<User> findAll();

}
package com.test.admin.service.logic;

import com.test.admin.entity.User;
import com.test.admin.repository.UserRepository;
import com.test.admin.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

// UserServiceLogic도 bean 객체로 등록하기 위해 @Service
@Service
@RequiredArgsConstructor
public class UserServiceLogic implements UserService {

    // DI 적용 방법 1
    // UserRepository를 구현한 구현클래스 UserRepositoryLogic을 찾아서 userRepositry 변수에 주입함.
    private final UserRepository userRepository; // @RequiredArgsConstructor와 세트


    @Override
    public User register(User newUser) {
        return this.userRepository.save(newUser);
    }


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

 

 

마지막으로 UserController 부분을 수정해준다.

package com.test.admin.controller;


import com.test.admin.entity.User;
import com.test.admin.service.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@RestController
@RequiredArgsConstructor
@RequestMapping("/users")
public class UserController {

    private final UserService userService;

    // json 데이터가 body로 넘어올텐데, User 객체로 받아오기 위해선 @RequestBody를 이용한다
    @PostMapping("")
    public User register(@RequestBody User newUser) {
        return userService.register(newUser);
    }

    @GetMapping("")
    public List<User> findAll() {
        return userService.findAll();
    }
}

 

모든 준비가 끝났다. 이제 프로그램을 실행시켜본다.

그러면 이렇게 콘솔창에 테이블이 생성되는 구문이 진행됨을 확인할 수 있다.

실제 테이블이 생성된 것을 확인해볼 수 있다.

 

 

이제 Insomnia를 이용해,

간단하게 register와 findAll에 대한 테스트를 진행해 볼 것이다.

성공적으로 생성이 되어서, 데이터가 그대로 반환된 것을 확인할 수 있고

다시 한번 데이터가 들어간 것을 cli로 확인해 볼 수도 있다.

 

findAll() 또한 제대로 동작함을 확인할 수 있다.

 

 

데이터를 하나 더 추가해보고

findAll 을 다시해봐도 마찬가지로 잘 된다.

 

이번 글에서는 정말 간단하게,

 

JpaRepository<T, ID> 중에

 

객체 저장(save)과 모두 불러오기(findAll) 두가지에 대해서만 실습을 진행했는데

이 외에도 다양한 기본적인 CRUD가 가능하다. 이에 대한 글은 이후 추가로 작성할 예정이다.

 

 

 

 

참고

JPA ORM 이론 강의 (https://youtu.be/OiAYmtq4Av8)

 

JPA 실습

(https://goddaehee.tistory.com/209)

(https://dev-coco.tistory.com/85)

(https://pizza301.tistory.com/30)

(https://velog.io/@alswl689/MySQL-JPA-SpringBoot-연동-및-테스트Gradle)

반응형