JPA(Java Persistence API) 란? 

자바 진영에서 ORM(Object-Relational Mapping) 표준 기술로 Hibernate, Spring JPA, EcliplseLink 등 과 같은 구현체가 있고 이것의 표준 인터페이스가 JPA이다.

 

+ ORM(Object-Relational Mapping)이란 자바의 객체와 관계형 DB를 맵핑하는 것으로 DB의 특정 테이블이 자바의 객체로 맵핑되어 SQL문을 일일이 작성하지 않고 객체로 구현할 수 있도록 하는 프레임워크. DB에서 쿼리로 해야할 것을 스프링에서 접근하여 데이터를 다를 수 있게 구현한 것

 

사용 예제)

@Entity // DB 테이블과 연결될 객체 설정해주는 어노테이션
public class User {

	// ID가 자동으로 생성
    @ID  	
    @GeneratedValue(strategy = GenerationType.AUTO)	 // 자동 증가 auto_increment 역할
    private Long id;
    
    // 컬럼 설정
    @Column(nullable = false; unique = true) //nullable : null 허용, unique : 유일성
    private String username;
    
    @Column(nullable = false)
    @Enumerated(value = EnumType.STRING)
    private UserRoleEnum role;

	....
}

 

테이블 관계 선언) 

Entity 클래스 필드 위에 연관관계 어노테이션을 설정해 주는 것으로 연관관계가 형성된다. 

 

+ @가 붙어있는 코드를  Annotation이라고 한다.

용도 : 컴파일러에게 문법 에러 체크하도록 정보 제공, 개발툴이 빌드나 배치시 코드를 자동으로 생성할 수 있도록 정보제공, 런타임시 특정 기능을 실행하도록 정보를 제공하는 기능을 하며 자바에서는 @ (at sign)을 앞에 붙여서 사용

 

- 연관관계 어노테이션

일대다 (1:N) @OneToMany
다대일(N:1) @ManyToOne
일대일(1:1) @OneToOne
다대다(N:N) @ManyToMany

 

실습 예제) 

- Entity 생성  : 일반 클래스로 생성

package com.example.demo.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;

@NoArgsConstructor  // 기본 생성자 만들어주는 어노테이션
@Getter             // 값을 가져오는 어노테이션
@Entity             // 테이블 생성하는 어노테이션
public class Member {

    @Id // ID생성
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 키 생성 전략 선택   : IDENTITY 기본키 생성을 데이터베이스에 위임
    private Long id;                                    // 키 이름과 타입을 변수로 선언

    @Column(nullable = false)                           // null값 허용 옵션
    private String memberName;

    // @OneToMany 1:N,              테이블간의 연관관계 설정
    // mappedBy = 컬럼명,            어떤 컬럼에 맵핑될 건지 즉, 연관관계의 주인이 누군지 설정
    // fetch = FetchType.EAGER      글로벌 패치전략, EAGER은 즉시로딩, LAZY는 지연로딩
    @OneToMany(mappedBy = "member", fetch = FetchType.EAGER)
    private List<Orders> orders = new ArrayList<>();            // 여러오더를 받아야하므로 list 형식으로 하며 초기값을 주기위해 new

    public Member(String memberName) {
        this.memberName = memberName;       // 이 연관관계의 연결될 키를 설정
    }
}

package com.example.demo.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;


@Entity
@Getter
@NoArgsConstructor
public class Orders {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne
    @JoinColumn(name = "food_id")   // 외래키 설정
    private Food food;

    @ManyToOne
    @JoinColumn(name = "member_id")
    private Member member;

    public Orders(Food food, Member member) { //두개의 외래키에 조인 대상을 설정
        this.food = food;
        this.member = member;
    }
}

package com.example.demo.entity;

import jakarta.persistence.*;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;

@Getter
@Entity
@NoArgsConstructor
public class Food {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    @Column(nullable = false)
    private String foodName;
    @Column(nullable = false)
    private int price;

    @OneToMany(mappedBy = "food",fetch = FetchType.EAGER)
    private List<Orders> orders = new ArrayList<>();

    public Food(String foodName, int price) {
        this.foodName = foodName;
        this.price = price;
    }
}

 

- repository 생성 : 인터페이스로 생성

아래 세가지는 테이블 이름만 다를 뿐 ID타입 마저도 모두 동일

package com.example.demo.repository;

import com.example.demo.entity.Food;
import org.springframework.data.jpa.repository.JpaRepository;

public interface FoodRepository extends JpaRepository<Food, Long> { // extend의 Food는 연결할 테이블 이름, 뒤에 Long은 ID의 타입
}

package com.example.demo.repository;

import com.example.demo.entity.Member;
import org.springframework.data.jpa.repository.JpaRepository;

public interface MemberRepository extends JpaRepository<Member, Long> {
}

package com.example.demo.repository;

import com.example.demo.entity.Orders;
import org.springframework.data.jpa.repository.JpaRepository;

public interface OrdersRepository extends JpaRepository<Orders, Long> {
}

 

- ApplicationRunner 상속 클래스 : 앞에서 DB를 구성하고 사용할 준비를 하는 부분이라면 이 부분이 실질적으로 진짜 데이터를 조회하고 수정하고 사용하는 부분.

package com.example.demo;

import com.example.demo.entity.*;
import com.example.demo.repository.*;
import lombok.RequiredArgsConstructor;
import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;

@Component
@RequiredArgsConstructor
public class Restaurant implements ApplicationRunner {

    private final FoodRepository foodRepository;
    private final OrdersRepository ordersRepository;
    private final MemberRepository memberRepository;

    @Override
    public void run(ApplicationArguments args) throws Exception {

        List<Food> foods = new ArrayList<>();
        Food food1 = new Food("후라이드", 10000);
        foods.add(food1);
        Food food2 = new Food("양념치킨", 12000);
        foods.add(food2);
        Food food3 = new Food("반반치킨", 13000);
        foods.add(food3);
        Food food4 = new Food("고구마피자", 9000);
        foods.add(food4);
        Food food5 = new Food("아보카도피자", 110000);
        foods.add(food5);
        foodRepository.saveAll(foods);

        List<Member> members = new ArrayList<>();
        Member member1 = new Member("삼식이");
        members.add(member1);
        Member member2 = new Member("먹깨비");
        members.add(member2);
        memberRepository.saveAll(members);

        System.out.println("==================================================================");

        System.out.println("Member 데이터");
        List<Member> findMembers = memberRepository.findAll();
        for (Member findMember : findMembers) {
            System.out.println("findMember = " + findMember.getMemberName());
        }

        System.out.println("==================================================================");

        System.out.println("Food 데이터");
        List<Food> findFoods = foodRepository.findAll();
        for (Food findFood : findFoods) {
            System.out.println("findFood = " + findFood.getFoodName());
        }

        List<Orders> ordersList = new ArrayList<>();
        Orders orders1 = new Orders(findFoods.get(0), findMembers.get(0));
        ordersList.add(orders1);
        Orders orders2 = new Orders(findFoods.get(3), findMembers.get(1));
        ordersList.add(orders2);
        Orders orders3 = new Orders(findFoods.get(4), findMembers.get(1));
        ordersList.add(orders3);
        Orders orders4 = new Orders(findFoods.get(2), findMembers.get(0));
        ordersList.add(orders4);
        Orders orders5 = new Orders(findFoods.get(2), findMembers.get(0));
        ordersList.add(orders5);
        Orders orders6 = new Orders(findFoods.get(1), findMembers.get(1));
        ordersList.add(orders6);
        Orders orders7 = new Orders(findFoods.get(1), findMembers.get(0));
        ordersList.add(orders7);
        Orders orders8 = new Orders(findFoods.get(3), findMembers.get(1));
        ordersList.add(orders8);
        ordersRepository.saveAll(ordersList);

        System.out.println("==================================================================");
        int num = 1;

        System.out.println("Orders 데이터");
        List<Orders> orderList = ordersRepository.findAll();

        for (Orders orders : orderList) {
            System.out.println(num);
            System.out.println("주문한 사람 = " + orders.getMember().getMemberName());
            System.out.println("주문한 음식 = " + orders.getFood().getFoodName());
            num++;
        }

        System.out.println("==================================================================");
        System.out.println("삼식이 주문한 음식");
        Member samsik = memberRepository.findById(1L).orElseThrow(
                ()->new RuntimeException("없음")
        );

        num = 1;
        for (Orders orders : samsik.getOrders()) {
            System.out.println(num);
            System.out.println("주문한 음식 = " + orders.getFood().getFoodName());
            System.out.println("주문한 음식 가격 = " + orders.getFood().getPrice());
            num++;
        }


        System.out.println("==================================================================");
        System.out.println("아보카도피자 주문한 사람");
        Food abocado = foodRepository.findById(5L).orElseThrow(
                ()->new RuntimeException("없음")
        );

        for (Orders order : abocado.getOrders()) {
            System.out.println("주문한 사람 = " + order.getMember().getMemberName());
        }




    }
}

 

 

 

실제 저장된 걸 확인하려면 http://localhost:8080/h2-console/ 에 들어가 설정해준 user로 적어주고

JDBC URL 부분에 위에 엔터티와 리포지토리를 생성하며 실행한 로그에 나와있는 url을 적용해 수정주어 connect하면된다. 

연결이 성공하면 

 

위처럼 확인할 수 있다.

 

 


엔터티 상속 ) 

엔터티 클래스를 다른 엔터티 클래스에 상속시켜 하나의 엔터티를 여러곳에서 공통적으로 사용할 수 있다. 

 

 

Spring Data JPA  란? )

JPA를 편하게 사용하기 위해, 스프링에서 JPA를 *Wrapping한 *Dependency

 

+ Wrapping / 래핑 : 활동범위를 설정하고 좀더 중요한 다른 프로그램의 실행을 가능하게 포장한 것을 말한다. 

+ Dependency가 무슨 뜻인질 모르겠다.

 

 

 


Spring Data JPA  제공하는 기능) 

 

  • SAVE : 데이터 삽입
리포지토리이름.save(변수);			// 1개 insert
리포지토리이름.saveAll(리스트변수)		// 여러개 insert
  • FIND : 데이터 조회
리포지토리이름.find();			// 1개 찾기, 조건은?
리포지토리이름.findAll()		// 모두 찾기, 조건은?
  • COUNT : 데이터 개수 조회
리포지토리이름.count();  // 조건은?
  • DELETE : 데이터 삭제
리포지토리이름.delete(변수명);
  • ID외의 필드값에 대한 추가 기능은 interface에서 선언해주면 Spring Data JPA가 대신해주어 메소드로 사용이 가능해진다. 아래처럼 interface에 선언해주면 
public interface ProductRepository extends JpaRepository<Product, Long> {
	// 회원 ID 로 등록된 상품들 조회
	List<Product> findAllByUserId(Long userId);

	// 상품명이 title 인 관심상품 1개 조회
	Product findByTitle(String title);

	// 상품명에 word 가 포함된 모든 상품들 조회
	List<Product> findAllByTitleContaining(String word);

	// 최저가가 fromPrice ~ toPrice 인 모든 상품들을 조회
	List<Product> findAllByLpriceBetween(int fromPrice, int toPrice);
}

ApplicationRunner을 상속한 클래스에서 여기서 정의한 함수를 사용할 수 있다.

 

 

 

 

+ Spring Data JPA 추가기능 구현방법에 관한 공식 문서

 

Spring Data JPA - Reference Documentation

Example 119. Using @Transactional at query methods @Transactional(readOnly = true) interface UserRepository extends JpaRepository { List findByLastname(String lastname); @Modifying @Transactional @Query("delete from User u where u.active = false") void del

docs.spring.io

 

 

 

 


Tip
+ 클래스 이름에 커서를 놓고 F4를 누르면 해당 클래스 내용을 볼 수 있다.

 

+ Recent posts