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를 누르면 해당 클래스 내용을 볼 수 있다. |
'Experience > 항해99' 카테고리의 다른 글
022 - 본수업 3주차 / 스프링 입문 주차 : 개인 과제 구현편 정리 (2) | 2022.12.01 |
---|---|
021 - 본수업 15일차 / 스프링 입문 주차 : 개인 과제 질문편 (2) | 2022.11.30 |
017 - 본수업 11일차 / 1-2 SpringBoot 및 서버 이해 (0) | 2022.11.25 |
017 - 본수업 11일차 / Spring 1-1 웹 동작 방식 이해하기 (1) | 2022.11.25 |
017 - 본수업 11일차 / 주특기 시작 S.A. (0) | 2022.11.25 |