스프링부트와 JPA 활용1

권영태·2023년 5월 9일
0

스프링

목록 보기
9/18
post-thumbnail

스프링부트와 JPA를 활용해 웹 애플리케이션을 개발한다.
다음 코드 및 진행 방법은 인프런 김영한 강사님의 유로 강의 내용을 발췌한 내용이다.
강의 : <실전! 스프링 부트와 JPA 활용 1 - 웹 애플리케이션 개발>

출처 : https://inf.run/zzKt

  • JPA와 DB 설정 및 동작 확인 * 띄어쓰기 주의!
spring:
  datasource:
    url: jdbc:h2:tcp://localhost/~/jpashop;
    username: sa
    password:
    driver-class-name: org.h2.Driver

 jpa:
    hibernate:
      ddl-auto: create
    properties:
      hibernate:
#        show_sql: true
        format_sql: true

logging.level:
  org.hibernate.SQL: debug
  org.hibernate.orm.jdbc.bind: trace
``

👤 회원 리포지토리

@Repository
public class MemberRepository {
	@PersistenceContext
	EntityManager em;
	
  	public Long save(Member member) {
		em.persist(member);
		return member.getId();
	}

	public Member find(Long id) {
		return em.find(Member.class, id);
	}
}
  • 전 포스팅 글을 참고하면 @PersistenceContext 및 EntityManager을 이해할 수 있다!

🫙 실제 동작 화면 및 기능 확인

  • 실제 동작 화면

  • 도메인 모델 및 테이블 설계

  • ORDER가 아닌 ORDERS로 쓰는 이유는 예약어 ORDER가 있기 때문에 관례상 ORDERS로 사용한다.

  • 싱글 테이블 전략을 사용해 자식 테이블을 부모 테이블에 다 넣어 한 개 테이블만 사용하고, 구분 컬럼(DTYPE)을 이용해 어떤 자식 데이터가 저장되었는지 확인한다.

  • 객체는 컬랙션을 이용해 N:M 관계로 바로 연결할 수 있지만 관계형 데이터는 일반적인 설계로 안되고 매핑 테이블을 두고 1:N & N:1로 연결해야된다.

    • 여기선 CATEGORY_ITEM 테이블이 매핑 테이블 역할.
  • 1:N 또는 N:1의 양방향 관계면 연관 관계 주인을 정해야 한다.
    주로 다방향 부분에 외래키가 존재하는데 외래키가 있는 다방향을 주인으로 정하는게 좋다.

    • 마찬가지로 1:1 단방향 관계에서도 외래키를 연관관계의 주인으로 정하는게 좋다!
  • Entity 설계 시 유지보수 및 변경 포인트를 줄이기 위해 가급적 Setter를 사용하지 않는것이 좋다.

@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "dtype")
@Getter @Setter
public abstract class Item {
 @Id @GeneratedValue
 @Column(name = "item_id")
 private Long id;
 
 private String name;
 
 private int price;
 
 private int stockQuantity;
 
 @ManyToMany(mappedBy = "items")
 private List<Category> categories = new ArrayList<Category>();
}
  • @Inheritance : 상속 매핑 어노테이션. 부모 클래스에 어노테이션 적용하며,
    이때 조인 전략 3가지가 존재
    • InheritanceType 종류
      • JOINED : 자식 테이블이 부모 테이블의 PK를 외래키로 받음
      • SINGLE_TABLE : 하나의 테이블로 합침
      • TABLE_PER_CLASS : 부모 테이블 속성들을 자식 테이블의 속성들로 갖는 방식
  • Order domain
@Entity
@Table(name = "orders")
@Getter @Setter
public class Order {

 @Id @GeneratedValue
 @Column(name = "order_id")
 private Long id;
 
 @ManyToOne(fetch = FetchType.LAZY)
 @JoinColumn(name = "member_id")
 private Member member; //주문 회원
 
 @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
 private List<OrderItem> orderItems = new ArrayList<>();
 
 @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
 @JoinColumn(name = "delivery_id")
 private Delivery delivery; //배송정보
 
 private LocalDateTime orderDate; //주문시간
 
 @Enumerated(EnumType.STRING)
 private OrderStatus status; //주문상태 [ORDER, CANCEL]
 
 //==연관관계 메서드==//
 public void setMember(Member member) {
 	this.member = member;
 	member.getOrders().add(this);
 }
 
 public void addOrderItem(OrderItem orderItem) {
 	orderItems.add(orderItem);
 	orderItem.setOrder(this);
 }
 
 public void setDelivery(Delivery delivery) {
 	this.delivery = delivery;
 	delivery.setOrder(this);
 }
}
  • @Enumerated : Enum값을 관리하기 위한 어노테이션
    • Enum : 열거형 타입으로 static final로 상속 불가능!
    • EnumType은 2가지
      1) ORIDINAL : enum 순서 값을 DB에 저장
      2) SPRING : enum 이름을 DB에 저장
  • 모든 연관관계는 지연로딩으로 설정해야된다.
    -> 즉시로딩(EAGER) 사용 시 연관된 모든 Table들을 끌어와 예측이 어려워지고 이로 인해 N+1 문제가 발생한다.
    • @XToOne 관계는 기본적으로 즉시로딩으로 되어 있어 직접 지연로딩으로 변경해야 한다.
      • @XToOne(fetch = LAZY)
  • cascades는 @OneToMany과 @ManyToOne로 양방향 관계를 맺는
    Entity의 영속성을 전이한다.
    • 부모 엔티티 영속 → 자식 엔티티도 영속
    • 부모 엔티티 삭제 → 자식 엔티티도 삭제
      • CascadeType.ALL : 상위 엔티티에서 하위 엔티티로 모든 작업 전파
      • CascadeType.PERSIST : 하위 엔티티까지 영속성 전달
  • Flush : Transaction을 commit 하기 전까지 실제 DB에 저장하지 않고 내부 쿼리 저장소에 쿼리들을 모아 두었다가 commit 시 모아둔 쿼리들을 저장하는데, 실제 DB에 저장하는 작업을 Flush라고 함.

🛠️ 회원 서비스 구현

@Service
@Transactional(readOnly = true)
public class MemberService {
 
 @Autowired
 MemberRepository memberRepository;
 
 /**
 * 회원가입
 */
 @Transactional //변경
 public Long join(Member member) {
 	validateDuplicateMember(member); //중복 회원 검증
 	memberRepository.save(member);
 	return member.getId();
 }
 
 private void validateDuplicateMember(Member member) {
 	List<Member> findMembers =
	memberRepository.findByName(member.getName());
 	if (!findMembers.isEmpty()) {
 		throw new IllegalStateException("이미 존재하는 회원입니다.");
	 }
 }
 
 /**
 * 전체 회원 조회
 */
 public List<Member> findMembers() {
 	return memberRepository.findAll();
 }
 
 public Member findOne(Long memberId) {
 	return memberRepository.findOne(memberId);
 }
}
  • @Transactional(readOnly = true)
    : 데이터 변경이 없고 주로 읽는 테이블에 사용한다.
    • @Transactional readOnly의 default는 false다.

📄 Embedded

  • 새로운 값 타입을 직접 정의해서 사용 가능한 것을 JPA에서 embedded type이라고함.
    • int 또는 String와 같이 값타입이며, 값 타입을 위한 메서드 정의 가능
      또한 임베디드 타입은 기본 생성자가 필수임

    @Embeddable : 값 타입 정의하는 곳
    @Embedded : 값 타입 사용하는 곳

profile
GitHub : https://github.com/dudxo

0개의 댓글