JPA - 2 /CRUD로 행복회로 돌려보기(실습)

CRUD

Create, Read, Update, Delete

생성, 조회, 변경, 삭제의 기능을 말한다.

 

JPA 로 CRUD기능을 하는 방법을 익혀보려 한다.

 


public interface CarRepository extends JpaRepository<Car, Long> { }

 

CarRepository 에서 이미 JPA 에서 만들어놓은 메소드들을 이용하기로 했다는 것을 기억할 것이다.

위의 CRUD들 중 생성, 조회, 삭제는 main이 있는 Application 파일에 작성하여 사용하면 된다.

다만 변경의 경우는 Service 라는 부분에서 작성해야 할 것이 있다.

 


 

Spring을 세 부분으로 나눠보면 다음과 같다.

 

  1. Controller (Presentation)
    이전에 RestController를 통해 잠시 언급한 적이 있다. 세 부분들 중 가장 바깥 부분으로, 요청/응답을 처리한다.
  2. Service (Service)
    중간 부분에 위치한, 실제로 작동하는 것이 가장 많은 부분이다.
  3. Repository (Data Access) - 이하Repo
    저번까지 배웠던 DB - SQL 역할을 하는 부분이다. 가장 안쪽에 위치한다.

 

이 중 Repo와 Service 부분에 CRUD를 작성해 자동차 모델과 브랜드를 추가하고, 조회하고, 변경하고, 삭제해보려한다.

 


Car.java

더보기
package com.eunki96.test02.domain;

import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;

@NoArgsConstructor
@Entity
@Getter
public class Car extends Timestamped{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(nullable = false)
    private String model;

    @Column(nullable = false)
    private String brand;

    public Car (String model, String brand){
        this.model = model;
        this.brand = brand;
    }
}

 

CarRepository.java

더보기
package com.eunki96.test02.domain;

import org.springframework.data.jpa.repository.JpaRepository;

public interface CarRepository extends JpaRepository<Car, Long> { }

 

Timestamped.java

더보기
package com.eunki96.test02.domain;

import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;

@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
public abstract class Timestamped {

    @CreatedDate
    private LocalDateTime createdAt;

    @LastModifiedDate
    private LocalDateTime modifiedAt;
}

 

Test02Application.java (프로젝트 생성시 src에 처음 생기는 파일)

더보기
package com.eunki96.test02;

import com.eunki96.test02.domain.Car;
import com.eunki96.test02.domain.CarRepository;
import com.eunki96.test02.service.CarService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing
@SpringBootApplication
public class Test02Application {

    public static void main(String[] args) {
        SpringApplication.run(Test02Application.class, args);
    }

    @Bean
    public CommandLineRunner demo( ) {
        
        
        return (args) -> {
            //CRUD는 여기에 적으면 됨미다
        };
        
        
    }
}

 

이렇게 세팅하고 시작하면 된다.

엄청 많아보이는 import 들은 신경쓰지 않아도 된다. 어노테이션을 넣거나 할 때 알아서 추가해준다.

 

그리고 각각 기능을 입력한 후에 H2콘솔에 들어가서 데이터를 확인해 볼 것이다.

 

아직 추가한 데이터가 없어 비어있는 모습이다.

 


Create

 JPA 에서 생성 역할을 하는 save를 이용해 행복회로를 돌려보려 한다.

 

우연히 아침에 기지를 발휘해 목숨을 구해드린 할아버지가 알고보니 대기업 총수였다.
할아버지께서 나에게 원하는 자동차 5대를 고를 수 있게 해줄테니 JPA를 이용해 모델 데이터를 추가해 H2 콘솔에 목록을 띄워서 보여달라고 했다.

 

고 하자.

 


TestApplication.java 에서,

@Bean
public CommandLineRunner demo(CarRepository carRepository) {
    return (args) -> {

    };
}

repo를 파라미터 값으로 추가한다. 이제 언제든 carRepository를 통해 JPA 메소드를 사용할 수 있게 됐다.

 

carRepository.save(new Car("718 Boxster GTS", "Porsche"));

Porsche 사의 신형 Boxster GTS 모델을 추가했다.

한번 잘 추가되었는지 확인해보려 한다.

 

나이스

예쁘게 추가되어있음을 확인했다.

이 기세를 몰아 나머지 네 종류도 추가해보자.

carRepository.save(new Car("Escalade", "Cadillac"));
carRepository.save(new Car("S Class", "Mercedes benz"));
carRepository.save(new Car("MC20", "Maserati"));
carRepository.save(new Car("Wraith", "Rolls-Royce"));

 

나..이스!!!!!!

 


Read

이제 find들을 이용해 조회를 해보려 한다.

이들 중 findAll 과 findById 두 가지를 사용해보려 한다.

 


List<Car> carList = carRepository.findAll();

for(int i = 0; i< carList.size(); i++){
    Car car_temp = carList.get(i);
    System.out.println(car_temp.getId() + " " + car_temp.getModel() + " " + car_temp.getBrand());
}

 

findAll 로 자동차 리스트를 받아오고, 리스트를 출력하면 다음과 같이 나온다.

 

 

 

 

이번에는 findById를 이용해보려 한다. ID를 이용해서 찾는 것인데,

 

엥. 오류났다. 문제의 원인은 간단하다.

스프링이 만약에 ID가 없으면..? 이라고 물어보고 있는것이다. 예외가 있을 경우 어떻게 할 지 알려주면 된다.

 

Car car_temp = carRepository.findById(1L).orElseThrow(
        () -> new IllegalArgumentException("해당 아이디가 없습니다.")
);

System.out.println(car_temp.getModel());
더보기
자동완성되는것들 중 아무거나 선택하면 된다.

 

 

인덱스 1번 박스터를 조회하는데 성공했다.

 


Update

변경은 생성, 조회, 삭제와 조금 다르다.

 

먼저 Car.java 에 업데이트 메소드를 만들어준다.

public void update(Car car){
    this.model = car.model;
    this.brand = car.brand;
}

 

 

이어서 서비스 파일을 만들어준다.

 

CarService.java

더보기
package com.eunki96.test02.service;

import com.eunki96.test02.domain.Car;
import com.eunki96.test02.domain.CarRepository;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;

@Service //이 클래스가 서비스임을 명시
public class CarService {

    private final CarRepository carRepository;
    //검색, 업데이트시 repo가 필요하기에
    //final 변수 -> 서비스에게 꼭 필요한 녀석임을 명시, 변형 불가능

    public CarService(CarRepository carRepository){
        //생성자
        this.carRepository = carRepository;
        //전달받은 repo를 this.repo에 넣어줌 this.repo는 스프링이 알아서 해줌
        //요약: repo를 언제든 쓸 수 있게 스프링이 잘 생성해서 넘겨주었다.
    }
}

 

 

그리고 서비스파일 내에 update 메소드를 만들어준다.

 

@Service 를 만들어서 나눠주는 이유가 이것인데, 위의 update처럼 중복되는 코드가 생기기 때문이다.

비즈니스 로직은 다른 요청 URL 에서 사용해야 하는 경우가 있는데, 만약 비즈니스 로직코드가 Controller에 구현되어 있는 경우 다른 컨트롤러 메소드에서 똑같은 로직코드를 구현해야 하니 중복 코드가 발생하고 재사용성이 줄어든다.

모듈화 -> 유지보수가 편리하다,  서비스 기능 조합으로 새로운 기능 만들기가 가능하다.

 

CarService.java 에 추가할 내용

더보기
@Transactional //SQL쿼리가 일어남을 스프링에게 알려줌
public void update(Long id, Car car){
    Car car1 = carRepository.findById(id).orElseThrow(
            () -> new IllegalArgumentException("해당 아이디가 존재하지 않음.")
    );
    car1.update(car);
}

 

여러 파일들을 왔다갔다 하니 슬슬 헷갈린다.

한번 update를 실행하며 되짚어보자.

 


 

3번 인덱스의 벤츠 S클래스를 계속 보고 있자니, 어차피 공짜라면 마이바흐로 바꾸고싶다고 생각한 나.
3번 인덱스의 벤츠 S클래스를 벤츠 마이바흐로 바꾸기로 한다.

 

절차는 다음과 같다.

 

 

 

1. Car 클래스 new_car(마이바흐)를 만든다.

Car new_car = new Car("Maybach", "Mercedes benz");
carService.update(3L, new_car);

 

 

2. 마이바흐를 ID와 함께 carService 의 update 메소드로 보낸다.

public void update(Long id, Car car){

 

 

3. (carService 에서) 전해받은 인덱스(3L)에 해당하는 데이터(S클래스)를 찾아내 서비스에서 Car 클래스 car1에 해당하게 한다.

즉, 서비스에서 car = 마이바흐, car1 = S클래스 이다.

ID가 없을 경우에 orElseThrow로 처리한다.

Car car1 = carRepository.findById(id).orElseThrow(
        () -> new IllegalArgumentException("해당 아이디가 존재하지 않음.")

 

 

4. Car 클래스 car1 으로 update 메소드를 사용한다.

public CommandLineRunner demo(CarRepository carRepository, CarService carService) {

Test02Application.java 에 carService를 추가해주고,

car1.update(car);

 

 

5. (Car 클래스에서) car1(S클래스) 의 model, brand 를  car(마이바흐) 의 것들로 변경한다.

public void update(Car car){
    this.model = car.model;
    this.brand = car.brand;
}

 

 

이제 S클래스가 마이바흐로 바뀌었다. 한번 H2 콘솔에 들어가서 확인해보자.

 

S-Class -> Maybach 성공

 

 


Delete

갑작스럽게 구해드렸던 대기업 총수 할아버지한테서 연락이 왔다.
미안하지만 사정이 생겨서 5대가 아닌 4대만 고르라고 하셨다.
알겠다고 하고 곰곰이 고민을 시작하는 나..
아무리 생각해봐도 한국의 과속방지턱은 너무 높다.
바닥에 붙어다니는 MC20을 목록에서 제거해보기로 했다.

 

삭제는 앞의 생성, 조회와 거의 똑같다. 

삭제 역시 조회처럼 전부 삭제하거나( deleteAll() ) ID를 이용해 삭제하거나( deleteById(Long id) ) 다양한 방법이 있다.

이번에는 4번 인덱스의 MC20 만을 제거해보자.

 

carRepository.deleteById(4L);

 

MC20이.. 사라졌다... 성공..

 

 

 

 

이상 JPA로 CRUD 기능을 사용해보았다.

 

 

Test02Application.java

더보기
package com.eunki96.test02;

import com.eunki96.test02.domain.Car;
import com.eunki96.test02.domain.CarRepository;
import com.eunki96.test02.service.CarService;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

import java.util.List;

@EnableJpaAuditing
@SpringBootApplication
public class Test02Application {

    public static void main(String[] args) {
        SpringApplication.run(Test02Application.class, args);
    }

    @Bean
    public CommandLineRunner demo(CarRepository carRepository, CarService carService) {
        return (args) -> {
            carRepository.save(new Car("718 Boxster GTS", "Porsche"));
            carRepository.save(new Car("Escalade", "Cadillac"));
            carRepository.save(new Car("S Class", "Mercedes benz"));
            carRepository.save(new Car("MC20", "Maserati"));
            carRepository.save(new Car("Wraith", "Rolls-Royce"));


//            List<Car> carList = carRepository.findAll();
//
//            for(int i = 0; i< carList.size(); i++){
//                Car car_temp = carList.get(i);
//                System.out.println(car_temp.getId() + " " + car_temp.getModel() + " " + car_temp.getBrand());
//            }
//
//            Car car_temp = carRepository.findById(1L).orElseThrow(
//                    () -> new IllegalArgumentException("해당 아이디가 없습니다.")
//            );
//            System.out.println(car_temp.getModel());

            Car new_car = new Car("Maybach", "Mercedes benz");
            carService.update(3L, new_car);

            carRepository.deleteById(4L);
        };
    }

}

Car.java

더보기
package com.eunki96.test02.domain;

import lombok.Getter;
import lombok.NoArgsConstructor;

import javax.persistence.*;

@NoArgsConstructor
@Entity
@Getter
public class Car extends Timestamped{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private Long id;

    @Column(nullable = false)
    private String model;

    @Column(nullable = false)
    private String brand;

    public Car (String model, String brand){
        this.model = model;
        this.brand = brand;
    }

    public void update(Car car){
        this.model = car.model;
        this.brand = car.brand;
    }

}

CarRepository.java

더보기
package com.eunki96.test02.domain;

import org.springframework.data.jpa.repository.JpaRepository;

public interface CarRepository extends JpaRepository<Car, Long> {
    //Repository는 인터페이스로만 사용 가능하다.
    //interface = 멤버가 없는 메소드 모음집
    //즉, JPA에서 만들어놓은 메소드들을 이용하겠다는 것이다.
}


Timestamped.java

더보기
package com.eunki96.test02.domain;

import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;

@EntityListeners(AuditingEntityListener.class)
//테이블 주시하다가
//수정이 일어날 때 자동으로 반영해줌을 나타냄
//생성일자는 고정이니, 수정일자 변경시 동작
@MappedSuperclass
//이 클래스를 상속한것의 멤버변수도 컬럼으로 인식함을 나타냄
public abstract class Timestamped {
    //추상 클래스 : 상속으로만 사용 가능하다

    @CreatedDate //생성일자임을 나타냄
    private LocalDateTime createdAt;

    @LastModifiedDate //마지막 수정일자임을 나타냄
    private LocalDateTime modifiedAt;

}

CarService.java

더보기
package com.eunki96.test02.service;

import com.eunki96.test02.domain.Car;
import com.eunki96.test02.domain.CarRepository;
import org.springframework.stereotype.Service;

import javax.transaction.Transactional;

//update 만들기
@Service //이 클래스가 서비스임을 명시
public class CarService {

    private final CarRepository carRepository;
    //검색, 업데이트시 repo가 필요하기에
    //final 변수 -> 서비스에게 꼭 필요한 녀석임을 명시, 변형 불가능

    public CarService(CarRepository carRepository){
        //생성자
        this.carRepository = carRepository;
        //전달받은 repo를 this.repo에 넣어줌 this.repo는 스프링이 알아서 해줌
        //요약: repo를 언제든 쓸 수 있게 스프링이 잘 생성해서 넘겨주었다.
    }
    @Transactional //SQL쿼리가 일어남을 스프링에게 알려줌
    public void update(Long id, Car car){
        //1번 인덱스와 마이바흐 데려옴
        Car car1 = carRepository.findById(id).orElseThrow(
                () -> new IllegalArgumentException("해당 아이디가 존재하지 않음.")
        );
        //car1 = 1번 벤츠, car = 마이바흐
        car1.update(car);
        //car1을 car로 (벤츠를 마이바흐로)
    }
}

'Spring > Spring 정리' 카테고리의 다른 글

API - 2 (GET, POST)  (0) 2022.09.21
DTO  (2) 2022.09.19
JPA - 1  (0) 2022.09.17
RestController (브라우저에 클래스 정보 보여주기)  (2) 2022.09.16
API - 1 (JSON)  (0) 2022.09.16