[Spring] LOMBOK과 JPA

LOMBOK이란

20210327_013619

20210327_013746

자바 코딩할 때 힘든건 변수 선언하고 생성자라던지 각 변수마다 get,set이나 서브제너레이션 했는데 생성자에 대해서는 인텔리제이가 생성 안해주는데 그걸 한번에 해결해주는 게 롬복.

20210327_013759

어노테이션 붙여주면 기본 생성자부터 get,set메서드 까지 한 번에 해결.

public SearchParam(){

   }

   public SearchParam(String account) {
       this.account = account;
   }

   public SearchParam(String account, String email, int page) {
       this.account = account;
       this.email = email;
       this.page = page;
   }

이런식으로 오버라이딩 하는 경우 많은데 굉장히 생산성이 안 좋다.

일단 롬복을 깔아보자.

File에 plugin을 검색하고 롬복을 설치

20210327_014628

20210327_014904

팁으로 저기 아래 하단에 Structure를 클릭하면 현재 클래스가 뭔지 뭐가 있는지 나온다.

그리고 우린 롬복을 설치 했기 때문에 다 지우자.

20210327_015036

그리고 바로 롬복 어노테이션을 사용하려 하면 사용이 안되는데 그건 플러그인을 설치했지만 실제 라이브러리를 gradle에 추가하지 않았기 떄문

20210327_015302

근데 위는 옛날버전인거 같아 검색해서

plugins {
    id 'org.springframework.boot' version '2.4.4'
    id 'io.spring.dependency-management' version '1.0.11.RELEASE'
    id 'java'
}

group = 'com.example'
version = '0.0.1-SNAPSHOT'
sourceCompatibility = '1.8'

repositories {
    mavenCentral()
}

dependencies {
    compile('org.projectlombok:lombok')
    testCompile('org.projectlombok:lombok')
    annotationProcessor('org.projectlombok:lombok')
    testAnnotationProcessor('org.projectlombok:lombok')
    implementation 'org.springframework.boot:spring-boot-starter-web'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}

test {
    useJUnitPlatform()
}

로 넣어줬다.

20210327_022630

그리고 다 지웠는데 롬복인 @Data를 넣으면 롬복이 임포트 되면서 게터세터와 생성자가 자동 생성된다.

그리고 annotation 가서 저 Enable을 체크 해주고 실행해야 스프링이 제대로 실행된다.

20210327_022915


JPA

Lombok & JPA

ORM ( Object Relational Mapping) 으로, RDB 데이터 베이스의 정보를 객체지향으로 손쉽게 활용할 수 있도록 도와주는 도구 이다.
  • Object(자바객체)와 Relation(관계형 데이터베이스) 둘간의 맵핑을 통해서 보다 손쉽게 적용할 수 있는 기술을 제공해준다.
  • 또한 쿼리에 집중 하기 보다는 객체에 집중 함으로써, 조금 더 프로그래밍 적으로 많이 활용 할 수 있다.

20210327_025217

JPA 를 시험해보기 위해 mysql workbench로 가서 스키마를 하나 만들어 준다.

20210327_025639

생성시 utf-8, utf-8 bin(ary)로 한글 설정 해주고 만들자. 그리고 apply하기전 저 부분은 워크벤치에서 자동으로 쿼리문을 만들어서 우리는 버튼만 누르면 실행되게 해준거다. 20210327_031426

그리고 creat table을 위와같이 만들어 두자.

20210327_031509

보면 테이블이 생성 된 걸 확인 가능하다.

compile('org.springframework.boot:spring-boot-starter-data-jpa')
compile('mysql:mysql-connector-java')
compile('org.projectlombok:lombok')

그리고 위 코드들을 build.gradle에 추가해주자.

20210327_031702

그리고 추가했으면 저기 resource 밑에 application.properties 부분에 가는데 여긴 스프링 부트에 추가한 라이브러리들에 대해 설정을 관리해 주는 곳이다.

저기에

# properties
spring.datasource.url=jdbc:mysql://localhost:3306/study?useSSL=false&useUnicode=true&serverTimezone=Asia/Seoul

# db response name
spring.datasource.username=root

# db response password
spring.datasource.password=root

을 넣어주자.

20210327_033256

jpa와 톰캣포트 8080이 제대로 올라갔다면 정상으로 실행 되었다.


Entity

  • Camel Case : 단어를 표기할 때 첫 문자는 소문자로 시작하며 띄어쓰기 대신 ( 대문자 )로 단어를 구분
  • Java의 변수를 선언할 때 camelCase 로 선언한다. ex) phoneNumber , createdAt, updatedAt
  • Snake Case : 단어를 표기할 때 모두 소문자로 표기하며, 띄어쓰기 대신 ( _ ) 로 표기
  • DB 컬럼에 사용 ex) phone_number , created_at , updated_at
  • API를 정의하기에 따라 다르지만, 주로 API통신 규격에는 구간에서는 Snake Case를 많이 사용 합니다.
Entity : JPA에서는 테이블을 자동으로 생성해주는 기능 존재.
DB Table == JPA Entity

20210327_035106

package com.example.study.model.entity;



import lombok.AllArgsConstructor;
import lombok.Data;

import javax.persistence.*;
import java.time.LocalDate;
@Data
@AllArgsConstructor
@Entity //엔티티라는 거 정의
//@Table(name = "user")   //table을 유저라는 테이블을 가진 곳에 매핑시킬거다 선언
//근데 클래스의 이름이 동일하면 굳이 table 어노테이션을 설정 안해줘도 된다.
public class User { //이 클래스 이름은 디비의 이름과 동일하게(여기선 카멜 케이스에 맞게 선언)

    @Id//식별자에 대해선 Id를 붙이고![20210327_042455](/assets/20210327_042455.png)
    @GeneratedValue(strategy = GenerationType.IDENTITY) //어떤식으로 관리할지 전략 설정
    private Long id;
//    @Column(name = "account")   이거도 마찬가지로 이름이 동일하면 안 써줘도 된다.
    private String account;
    private String email;
    private String phoneNumber;
    private LocalDate createdAt;
    private String createdBy;
    private LocalDate updatedAt;
    private String updatedBy;

    public User() { //이 부분은 에러나서 넣어줬다. Allargs넣으니까 에러나는데 ㅇㅅㅇ..

    }
}

//기본적으로 이 위까지가 mysql과 테이블 어떻게 할지 설정 완료

20210327_042455


Repository

따로 쿼리문 을 작성하지 않아도 기본적인 CREATE : 생성 READ : 읽기 (SELECT) UPDATE : 업데이트 DELETE : 삭제

이미 개발 되있는 JPA 레파지토리를 상속받아줌.

20210327_042630


20210327_111228

여기서 main은 실제 서비스 하는 내용들이고 test는 실제 작동에는 영향을 안 미치고 테스트 하게 만들어둔 폴더이다.

package com.example.study.repository;


import com.example.study.StudyApplicationTests;
import com.example.study.model.entity.User;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;

import java.time.LocalDate;

public class UserRepositoryTest extends StudyApplicationTests {

    @Autowired//DI로 Dependency Inㅓection으로 우선순위 주입을 뜻한다. 직접 객체를 만들지 않고(new) 스프링이 직접 관리하겠다는 뜻.
    private UserRepository userRepository;

    @Test
    public void create(){
        //String sql = insert into user(%s,%s,%d) value (account, email, age);
        User user = new User();//싱글톤으로 User는 매번 다른 값이 들어갈 수 있어 매번 생성하고 사용해야
        user.setAccount("TestUser01");
        user.setEmail("TestUser01@gmail.com");
        user.setPhoneNumber("010-1111-1111");
        user.setCreatedAt(LocalDate.now());
        user.setCreatedBy("admin");
        //데이터 들어갈 타입 맞춰서 컬럼으로 생성

        User  newUser = userRepository.save(user);
        System.out.println("newUser:"+newUser);
    }

    public void read(){

    }
    public void update(){

    }
    public void delete(){

    }
}

참고로 위 코드를 실행하기 전 setting에 가서

20210327_121252

저 Run Test using 부분을 intelj로 바꿔줘야 정상 실행되고

20210327_121329

이처럼 넣은 값이 잘 나오는게 보인다.

롬복에서 자동으로 toString형태로 재정의 해줬기 떄문에 깔끔하게 어떤 값들이 들어있는지 볼 수 있다.

20210327_121456

이걸 누르고 테이블 가보면

20210327_121522

자동으로 쿼리문 생성되었고 안에 들어간 내용도 확인 할 수 있다.

그리고 build.gradle 부분에


#이 부분을 추가해주자. jpa가 실행한 옵션에 대해 보겠다는 뜻.

spring.jpa.show-sql=true

20210327_121927

실행하면 Hibernate가 뜨고 옵션에 대해 보이게 된다.(sql이 어떻게 동작하는 지 알 수 있다.)


@Test
  public void read(){
      Optional<User> user= userRepository.findById(2L);
      //2L는 lonlong이고 옵셔널은 제너릭 타입으로 받게 됨.

      user.ifPresent(selectUser->{       //있을때만 실행에 대한 결과를 받겠다.
          //seleectUser가 있으면 그 값을 꺼내 달라는 뜻.
          System.out.println("user:"+selectUser);
      });
  }

이번엔 read부분에 @Test를 넣고 mysql workbench에서 id2컬럼 선택후 read를 테스트 해보면

20210327_125918

정보가 잘 나온다.

@Test
    public void update(){
        Optional<User> user= userRepository.findById(2L);   //2번 셀렉트
        //2L는 lonlong이고 옵셔널은 제너릭 타입으로 받게 됨.

        user.ifPresent(selectUser->{
           selectUser.setAccount("PPPP");
           selectUser.setUpdatedAt(LocalDate.now());
           selectUser.setUpdatedBy("update Method");
           //값은 이거만 바꿨지만  jpa에서는 셀렉트유저값에 들어있는 특정 아이디값을 검색하고 한번더 꺼낸 다음
            //한번 더 업데이트 쳐줌.

           userRepository.save(selectUser);
           //쿼리문 통해 특정 유저 셀렉트 해주고 아이디를 한번 더 셀렉트 값 변경되서 값 찾고 그 값에 대해 업데이트 시킴.
        });
    }

업데이트 부분.

20210327_131149

실행결과 값이 변경된게 확인 된다.


삭제부분

@Test
   public void delete(){
       Optional<User> user= userRepository.findById(2L);   //2번 셀렉트
       //2L는 lonlong이고 옵셔널은 제너릭 타입으로 받게 됨.

       user.ifPresent(selectUser->{
           userRepository.delete(selectUser);
       });

       //이제 유저가 진짜 삭제 됐는지 확인해보자,(딜리트 된 유저 삭제 되었는지 확인)
       Optional<User> deleteUser = userRepository.findById(2L);
       if(deleteUser.isPresent()){
           System.out.println("데이터 존재: "+ deleteUser.get());
       }else{
           System.out.println("데이터 삭제 데이터 없음. ");

       }

20210327_160203

20210327_160222

확인해보면 id2 가지고 있던 PPPP 도 삭제된 모습이 보인다.

근데 테스트 코드엔 Assert 사용하는 것이 좋다.

@Test
    public void delete(){
        Optional<User> user= userRepository.findById(1L);   //2번 셀렉트
        //2L는 lonlong이고 옵셔널은 제너릭 타입으로 받게 됨.

        Assert.assertTrue(user.isPresent()); // 반드시 값이 있는 값 통과해서

        user.ifPresent(selectUser->{
            userRepository.delete(selectUser);
        });

        //이제 유저가 진짜 삭제 됐는지 확인해보자,(딜리트 된 유저 삭제 되었는지 확인)
        Optional<User> deleteUser = userRepository.findById(1L);

        Assert.assertFalse(deleteUser.isPresent()); //false 그값이 삭제해서 반드시 false가 된다

20210327_162221

값이 잘 지워진게 보이고 워크벤치에서 확인해도 1번 컬럼이 지워져있다.

Assert.assertTrue(user.isPresent()); // 반드시 값이 있는 값 통과해서 그리고 이제 1번 컬럼이 지워졌으므로 여기에서는 false가 리턴받게 된다.

@Transactional : //마지막 데이터 남으면 그거에 대한 동작은 안 일어남.(아예 비면 그건 데이터베이스의 의미가 없어서)


JPA 연관관계

20210327_173544

20210327_174916

workbench에서 ERD를 그려보자

20210327_174916 20210327_175143 내 정보를 적어주고

20210327_175153 20210327_175214

관련 디비를 체크해주면

20210327_175615

ERD 그릴수 있는 창이 나오게 되며 관련 DB가 생성된다.

20210327_180656

이 관계도를 ERD로 매핑해보자.

실선은 한쪽만 양쪽 다 가지는 경우, 점선은 한쪽만 가지는 경우

20210327_180905

그리고 여기서 상단에 database- forward engineer를 클릭하자.

아까 Reverse 엔지니어로 erd로 만들었다면 이번엔 반대로 erd를 데이터테이블로 만들것

어디스키마에 어떤 테이블 만들게 나오고

기본적으로 워크벤치가 어떤 거 만들어 주는 지 다 나온다.

20210327_181250

20210327_181402

20210327_181659

근데 나같은 경우는 안되서 일일히 study에서 테이블 다 만들고 진행했다.. ㅠ

20210327_191751

그리고 study에 이렇게 3개의 테이블이 생기면 일단은 성공.

(정 안되면 아래 sql 문 실행)

-- MySQL Workbench Forward Engineering

SET @OLD_UNIQUE_CHECKS=@@UNIQUE_CHECKS, UNIQUE_CHECKS=0;
SET @OLD_FOREIGN_KEY_CHECKS=@@FOREIGN_KEY_CHECKS, FOREIGN_KEY_CHECKS=0;
SET @OLD_SQL_MODE=@@SQL_MODE, SQL_MODE='ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION';

-- -----------------------------------------------------
-- Schema study
-- -----------------------------------------------------

-- -----------------------------------------------------
-- Schema study
-- -----------------------------------------------------
CREATE SCHEMA IF NOT EXISTS `study` DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_bin ;
USE `study` ;

-- -----------------------------------------------------
-- Table `study`.`user`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `study`.`user` ;

CREATE TABLE IF NOT EXISTS `study`.`user` (
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `account` VARCHAR(45) NOT NULL,
  `email` VARCHAR(45) NULL DEFAULT NULL,
  `phone_number` VARCHAR(45) NULL DEFAULT NULL,
  `created_at` DATETIME NOT NULL,
  `created_by` VARCHAR(45) NOT NULL,
  `updated_at` DATETIME NULL DEFAULT NULL,
  `updated_by` VARCHAR(45) NULL DEFAULT NULL,
  PRIMARY KEY (`id`))
ENGINE = InnoDB
AUTO_INCREMENT = 7
DEFAULT CHARACTER SET = utf8mb4
COLLATE = utf8mb4_bin;


-- -----------------------------------------------------
-- Table `study`.`item`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `study`.`item` ;

CREATE TABLE IF NOT EXISTS `study`.`item` (
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `name` VARCHAR(45) NOT NULL,
  `price` INT NOT NULL,
  `content` VARCHAR(45) NULL,
  PRIMARY KEY (`id`))
ENGINE = InnoDB;


-- -----------------------------------------------------
-- Table `study`.`order_detail`
-- -----------------------------------------------------
DROP TABLE IF EXISTS `study`.`order_detail` ;

CREATE TABLE IF NOT EXISTS `study`.`order_detail` (
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `order_at` DATETIME NULL,
  `user_id` BIGINT(20) NOT NULL,
  `item_id` BIGINT(20) NOT NULL,
  PRIMARY KEY (`id`))
ENGINE = InnoDB;


SET SQL_MODE=@OLD_SQL_MODE;
SET FOREIGN_KEY_CHECKS=@OLD_FOREIGN_KEY_CHECKS;
SET UNIQUE_CHECKS=@OLD_UNIQUE_CHECKS;


package com.example.study.repository;

import com.example.study.StudyApplicationTests;
import com.example.study.model.entity.OrderDetail;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;

import java.time.LocalDateTime;

public class OrderDetailRepositoryTest extends StudyApplicationTests {

    @Autowired
    private OrderDetailRepository orderDetailRepository;

    @Test
    public void create(){
        OrderDetail orderDetail = new OrderDetail();

        orderDetail.setOrderAt(LocalDateTime.now());
        //어떤 사람? 4번 아이디를 가진 사람이
        orderDetail.setItemId(7L);
        //어떤 상품?    1번의 인덱스 아이디.
        orderDetail.setUserId(1L);
    }
}

1번의 인덱스 아이디를 가진 상품을 7번 아이디 가진 사람이 구매했다.

20210327_203929

20210327_225956


JPA의 쿼리메서드

//1:N //fetch 타입 //Lazy = 지연 로딩 , Eager = 즉시로딩. @OneToMany(fetch = FetchType.LAZY, mappedBy = “item”) private List orderDetailList;

Lazy를 우선으로 쓰는 게 좋으며 eager은 1:1 같은 경우에만 쓰는 게 좋다.(연관관계에 있어 한건만 있을 때) 여러개의 연관관계 존재시 Lazy 써야한다!





© 2021.03. by yacho

Powered by github