spring initializer로 파일 생성 (https://start.spring.io/)
웹 요청을 처리하기 위해 사용
spring-boot-starter-json
JSON 파싱(JSON↔Java 객체) 및 생성에 사용되는 오픈소스 라이브러리
- 직렬화: Jackson을 사용해 Java→JSON
- 병렬화: Jackson을 사용해 JSON→Java
spring-boot-starter-tomcat
웹 애플리케이션을 빠르게 개발하고 배포할 수 있도록 하는 내장형 서버 / Java 서블릿 컨테이너
Java의 실행 환경인 Java Servlet, JavaServer Pages (JSP), Java Expression Language, Java WebSocket 등을 제공
spring-web-mvc
MVC 기능을 처리
서블릿 API 기반 프레임워크 - DispatcherServelt을 통해 HTTP요청을 받아 적절한 컨트롤러로 매핑
- model: View에 데이터 전달
- view: ViewResolver 객체를 통해 컨트롤러가 반환한 값으로 뷰 템플릿 찾아 최종적으로 클라이언트에게 반환될 응답 렌더링
- controller: 사용자의 요청을 처리하고 적절한 응답을 반환
- 메서드에 @RequestMapping @GetMapping @PostMapping 사용
컨트롤러가 전달하는 데이터를 이용해 동적으로 화면을 만들어주는 역할을 하는 뷰 템플릿 엔진
순수 HTML 구조를 유지
스프링 부트용 Spring Data Jpa 추상화 라이브러리
스프링 부트 버전에 맞춰 자동으로 JPA 관련 라이브러리 버전을 관리한다
자바로 작성된 관계형 데이터베이스 관리 시스템
따로 설치가 필요없어 개발단계나 테스트 코드에서 자주 활용
Java의 라이브러리로 반복되는 메서드를 Anntation을 사용해 자동으로 작성
Getter, Setter, 생성자 등의 코드를 작성
개발에 편리한 도구를 제공
브라우저에 보여주는 내용은 수정 시 자동으로 재시작해주어 브라우저에 업데이트
양방향 관계일 때 외래키를 가지고 값을 변경하는 권한을 가지는 것
주인이 아닌 객체는 조회(SELECT)만 수행
FK 는 orders의 member_id에만 있음
member의 order 값을 바꾸고 싶으면 fk의 값을 바꿔야함
Q. 둘중에 어느 값을 변경해야 테이블에 변경사항이 반영되는지?
A. 변경 포인트가 두개인 객체는 테이블은 하나(FK)만 변경하면 된다
둘 중에 하나를 연관관계 주인으로 지정하면되고( fk가 가까운곳), ordert 테이블에 FK가 있으니까 order의 member로 연관관계 주인 잡으면 된다
→ order의 member(FK)를 바꾸면 내 테이블에 있는 컬럼 값 업데이트 된다
- 일대일관계에서는 FK를 더 많이 조회하는 곳으로 선정한다
package jpabook.jpashop.domain;
import jakarta.persistence.Embeddable;
import lombok.Getter;
@Embeddable
@Getter
public class Address {
private String city;
private String street;
private String zipcode;
...
}
}
delivery > Address > city, street, zipcode 의 여러개 칼럼으로 나누어져 있음
= 객체 지향적이지 않고 응집력을 떨어트린다!
@Embeddable
값 타입을 정의하는 곳에 표시
- 임베디드 타입을 적용하려면
- 새로운 Class(Address)를 만들고
- 해당 클래스에 임베디드 타입으로 묶으려던 Attribute들을 넣어준 뒤
- @Embeddable
→ 주소의 관련된 속성들이 하나의 타입으로 바뀌어 사용
= 객체 지향적이고 응집력 증가!
@Embedded
값 타입을 사용하는 곳에 표시
public class Delivery {
@Embedded // 내장 타입
private Address address;
}
package jpabook.jpashop.domain;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.util.ArrayList;
import java.util.List;
@Entity
@Getter @Setter
public class Member {
@Id @GeneratedValue //pk지정
@Column(name = "member_id")
private Long id;
private String name;
@Embedded //내장타입이다, 둘중에 하나만 써도 됨
private Address address;
@OneToMany(mappedBy = "member")
private List<Order> order = new ArrayList<>();
}
@Entity
JPA가 관리해 DB테이블에 대응하는 하나의 클래스
@GeneratedValue
JPA에서 엔티티 클래스의 주요 키(Primary Key) 값을 자동으로 생성하기 위해 사용되는 어노테이션
@Id: JPA로 테이블과 엔티티를 매핑할 때, 식별자로 사용할 필드 위에 붙여 테이블의 Primary Key와 연결
→ 식별자로 사용될 값을 일일히 수동으로 넣어줘야 하는 불편함 해결
(strategy = GenerationType. _ )
- AUTO(기본) / IDENTITY / SEQUENCE / TABLE
@OneToMany(mappedBy = "member")
private List<Order> order = new ArrayList<>();
💡
Q. 두 코드의 차이점?
List<String> list = new ArrayList<>();
ArrayList<String> list = new ArrayList<>();
A.
package jpabook.jpashop.domain;
import jakarta.persistence.*;
import lombok.Getter;
import lombok.Setter;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;
@Entity
@Table(name = "orders")
@Getter @Setter
public class Order {
@Id @GeneratedValue
@Column(name = "order_id")
private Long id;
@ManyToOne
@JoinColumn(name = "member_id") //fk이름
private Member member; //연관관계 주인은 가만히 두면 됨
@OneToMany(mappedBy = "order")
private List<OrderItem> orderItems = new ArrayList<>();
@OneToOne
@JoinColumn(name = "delivery_id")
private Delivery delivery;
...
}
@ManyToOne (다대일) | @OneToMany (일대다) |
---|---|
@JoinColumn(name = "member_id") // name = FK이름 | (mappedBy = "member") //연관관계 주인 X |
private Member member; //연관관계 주인 | private List<Order> order = new ArrayList<>(); |
Order - member | Member - order |
@Enumerated(EnumType.*STRING*)
엔티티 매핑에서 Enum타입을 사용할 때 사용
- EnumType.ORDINAL: enum 순서 값을 DB에 저장
- EnumType.STRING: enum 이름을 DB에 저장
numType.STRING 으로 할 것!
→ 요구사항이 변경되는 경우 칼럼 값이 중복되는 등 오류가 발생하기 때문
사용자의 요청을 처리 한 후 지정된 View에 Model 객체를 전송(MVC패턴의 C)
Repository에서 받은 데이터들을 가공하여 Controller에 전송
리포지토리를 이용해 CRUD를 구현
DB에 직접 접근해 도메인 객체를 저장하고 관리하는 저장소
엔티티 선언 (@Entity)을 통해 DB에 저장되는 객체 구현
DB 테이블의 각 Column이 하나의 도메인
package jpabook.jpashop.domain.repository;
import jakarta.persistence.EntityManager;
import jakarta.persistence.PersistenceContext;
import jpabook.jpashop.domain.Member;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public class MemberRepository {
@PersistenceContext //jpa
private EntityManager em; // spring이 entitymanager을만들어서 저장
public void save(Member member){
em.persist(member);
}
public Member findOne(Long id){//단건 조회
return em.find(Member.class, id); //타입, pk
}
public List<Member> findAll() {//리스트 조회
return em.createQuery("select m from Member m", Member.class) //jpql??
.getResultList();
}
public List<Member> findByName(String name){
return em.createQuery("Select m from Member m where m.name=:name", Member.class)
.setParameter("name", name)
.getResultList();
}
}
@PersistenceContext
EntityManager를 빈으로 주입할 때 사용하는 어노테이션
private EntityManager em;
EntityManager: 엔티티를 저장하는 메모리상의 데이터베이스
JPA에서 엔티티를 조작(저장, 수정, 삭제, 조회 등)하고 데이터베이스와의 통신을 수행
💡@PersistenceContext를 사용해서 EntityManager를 주입받으면?
스프링에서 EntityManager를 Proxy로 감싼 EntityManager를 생성해서 주입해주기에 Thread-Safe를 보장
package jpabook.jpashop.domain.service;
import jpabook.jpashop.domain.Member;
import jpabook.jpashop.domain.repository.MemberRepository;
import lombok.AllArgsConstructor;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor //final에 있는 필드만 가지고 생성자를 만들어준다
public class MemberService {
private final MemberRepository memberRepository;
//기능1. 회원 가입
@Transactional
public Long join(Member member){
validateDuplicateMember(member);//중복 금지
memberRepository.save(member);
return member.getId();
}
private void validateDuplicateMember(Member member) {
//EXCEPTION
List<Member> findMembers = memberRepository.findByName(member.getName()); //조회되면 안됨
if (!findMembers.isEmpty()){
throw new IllegalStateException("이미 존재하는 회원입니다.");
}
}
//기능2. 회원 전체 조회
//@Transactional(readOnly = true)
public List<Member> findMembers() {
return memberRepository.findAll();
}
//하나 조회
//@Transactional(readOnly = true)
public Member findOne(Long memberId){
return memberRepository.findOne(memberId);
}
}
기존 코드
@Autowired
private MemberRepository memberRepository;
@Autowired
스프링이 스프링빈에 등록되어있는 멤버리포지토리를 인젝션해준다
단점: test할때 바꿔야할수도 있는데 못바꾼다
@AllArgsConstructor
3째줄 이하 코드와 대체 가능
private final MemberRepository memberRepository; @Autowired // 생략가능 public MemberService(MemberRepository memberRepository) { this.memberRepository = memberRepository; }
생성자 injection이란?
@RequiredArgsConstructor
final에 있는 필드만 가지고 생성자를 만들어준다
@Transactional
한가지 일을 하는 일련의 코드들을 한 단위로 묶어서 작업을 처리하는 방법
Transaction이란?
(readOnly = true)
: 조회하는곳에서는 성능을 최적화한다
- 기능2는 모두 조회하는 기능이므로 가장 위에 readOnly임을 명시하고, 기능1에만
@Transactional
을 적어준다
em.persist(item)
Detached 상태(한번이라도 영속화 되었지만 지금은X)의 Entity를 다시 영속화
em.merge(item)
최초 생성된 Entity를 영속화
https://seungh1024.tistory.com/77#google_vignette
+Entity를 영속화 = 영속성 컨텍스트에 저장한다