국비학원_74일차(Spring API2-Fetch Join)

써니·2022년 11월 7일
0

spring

목록 보기
16/23

API

📝회원가입

[com.oracle.oBootJpaApi01.controller]

  • JpaRestApiController.java
package com.oracle.oBootJpaApi01.controller;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.oracle.oBootJpaApi01.domain.Member;
import com.oracle.oBootJpaApi01.service.MemberService;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

// Controller + Repository
// 사용 목적 -> Ajax + RestApi
@RestController
@Slf4j
@RequiredArgsConstructor
public class JpaRestApiController {
	
	private final MemberService memberService;
	
	// 회원가입
	// postman ---> Body --> raw---> JSON	 
    //  예시    {	    "name" : "kkk222"	    }
	// Json(member)으로 온 것을 --> Member member Setting
	@PostMapping("/restApi/v1/memberSave")
	public CreateMemberResponse saveMemberV1(@RequestBody @Valid Member member) {
		log.info("/restApi/v1/memberSave member.getName()->{}",member.getName());
		log.info("/restApi/v1/memberSave member.getSal()->{}",member.getSal());
		
		Long id = memberService.saveMember(member);
		return new CreateMemberResponse(id);
	}
	
	
	// 목적 : Entity Member member를 직접 사용 금지 --> 직접 (화면이나 API를 위한 Setting 금지)
	// 예시 : @NotEmpty --> @Column(name = "userName")
	@PostMapping("/restApi/v2/memberSave")
	public CreateMemberResponse saveMemberV2(@RequestBody @Valid CreateMemberRequest cMember) {
		log.info("/restApi/v2/memberSave cMember.getName()->{}",cMember.getName());
		log.info("/restApi/v2/memberSave cMember.getSal()->{}",cMember.getSal());
		Member member = new Member();
		member.setName(cMember.getName());
		member.setSal(cMember.getSal());
		
		Long id = memberService.saveMember(member);
		return new CreateMemberResponse(id);
	}
	
	@Data
	static class CreateMemberRequest {
		@NotEmpty
		private String name;
		private Long sal;
	}
	
	// 객체로 리턴
	@Data
	@RequiredArgsConstructor
	class CreateMemberResponse {
		private final Long id;
//		public CreateMemberResponse(Long id) {
//			this.id = id;
//		}
	}
	
	// Bad Api
	@GetMapping("/restApi/v1/members")
	public List<Member> membersVer1(){
		log.info("/restApi/v1/members Start...");
		List<Member> listMember = memberService.getListAllMember();
		return listMember;
	}
	
	// Good API  Easy Version
	// 목표 : 이름 & 급여 만 전송 
	@GetMapping("/restApi/v21/members")
	public Result membersVer21() {
		List<Member> findMembers = memberService.getListAllMember();
		log.info("JpaRestApiController restApi/v21/members  findMembers.size()->{}",findMembers.size());
		List<MemberRtnDto>   resultList = new ArrayList<MemberRtnDto>();
		// List<Member> findMembers를 --> List<MemberRtnDto> resultList 이전
		// 이전 목적  : 반드시 필요한 Data 만 보여준다(외부 노출 최대한 금지)
		for(Member member : findMembers) {
			MemberRtnDto memberRtnDto = new MemberRtnDto(member.getName(), member.getSal());
			log.info("restApi/v21/members  getName->{}",memberRtnDto.getName());
			log.info("restApi/v21/members  getSal->{}",memberRtnDto.getSal());
			resultList.add(memberRtnDto);
		}
		log.info("restApi/v21/members resultList.size()->{}",resultList.size());
		return new Result(resultList.size(),resultList);
	}
	
	
	// Good API  람다  Version
	// 목표 : 이름 & 급여 만 전송 
	@GetMapping("/restApi/v22/members")
	public Result membersVer22() {
		List<Member> findMembers = memberService.getListAllMember();
		log.info("JpaRestApiController restApi/v22/members  findMembers.size()->{}",findMembers.size());
		 //  자바 8에서 추가한 스트림(Streams)은 람다를 활용할 수 있는 기술 중 하나
		List<MemberRtnDto>   memberCollect  = findMembers.stream()
				                              .map(m->new MemberRtnDto(m.getName(), m.getSal()))  
				                              .collect(Collectors.toList())
				                              ;
		log.info("restApi/v22/members memberCollect.size()->{}",memberCollect.size());
		return new Result(memberCollect.size(), memberCollect);
	}

	// T는 인스턴스를 생성할 때 구체적인 타입으로 변경  --> 유연성 
	@Data
	@AllArgsConstructor
	class Result<T> {
		private final int totCount;  // 총 인원수 추가 
		private final T  data;
	}
	
	@Data
	@AllArgsConstructor
	class MemberRtnDto{
		private String name;
		private Long   sal;
	}
}

[com.oracle.oBootJpaApi01.service]

  • MemberService.java
package com.oracle.oBootJpaApi01.service;

import java.util.List;

import javax.validation.Valid;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.oracle.oBootJpaApi01.domain.Member;
import com.oracle.oBootJpaApi01.repository.MemberRepository;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
@RequiredArgsConstructor
@Transactional
public class MemberService {
	
	private final MemberRepository memberRepository;
	
	// 전체회원조회
	public List<Member> getListAllMember(){
		List<Member> listMember = memberRepository.findAll();
		log.info("getListAllMember listMember.size->{}",listMember.size());
		return listMember;
	}
	
	// 회원가입 api
	public Long saveMember(@Valid Member member) {
		log.info("saveMember member.getName()->{}", member.getName());
		Long id = memberRepository.save(member);
		return id;
	}
	
}

[com.oracle.oBootJpaApi01.repository]

  • MemberRepository.java
package com.oracle.oBootJpaApi01.repository;

import java.util.List;

import com.oracle.oBootJpaApi01.domain.Member;

public interface MemberRepository {
	Long			save(Member member);
	List<Member> 	findAll();
}
  • JpaMemberRepository.java
package com.oracle.oBootJpaApi01.repository;

import java.util.List;

import javax.persistence.EntityManager;

import org.springframework.stereotype.Repository;

import com.oracle.oBootJpaApi01.domain.Member;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Repository
@Slf4j
@RequiredArgsConstructor
public class JpaMemberRepository implements MemberRepository {
	
	private final EntityManager em;
	
	@Override
	public Long save(Member member) {
		log.info("save Before");
		em.persist(member);
		return member.getId();
	}

	@Override
	public List<Member> findAll() {
		log.info("findAll Before");
		List<Member> memberList = em.createQuery("select m from Member m", Member.class)
									.getResultList();
		return memberList;
	}

}

[com.oracle.oBootJpaApi01.domain]

  • Member.java
package com.oracle.oBootJpaApi01.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;

import lombok.Data;

@Entity
@Data
@SequenceGenerator(
					name = "member_seq_gen5",
					sequenceName = "member_seq_generator5",
					initialValue = 1,
					allocationSize = 1
				  )
@Table(name = "member5")
public class Member {
	@Id
	@GeneratedValue(
					strategy = GenerationType.SEQUENCE,
					generator = "member_seq_gen5"
				   )
	@Column(name = "member_id")
	private Long   id;
	@Column(name = "userName")
	private String name;
	private Long   sal;
	
	@ManyToOne
	//@ManyToOne(fetch = FetchType.LAZY)
	@JoinColumn(name = "team_id")
	private Team   team; // join
	
}
  • Team.java
package com.oracle.oBootJpaApi01.domain;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.SequenceGenerator;
import javax.persistence.Table;

import lombok.Data;

@Entity
@Data
@SequenceGenerator(
					name = "team_seq_gen5",
					sequenceName = "team_seq_generator5",
					initialValue = 1,
					allocationSize = 1
				  )
@Table(name = "team5")
public class Team {
	@Id
	@GeneratedValue(
					strategy = GenerationType.SEQUENCE,
					generator = "team_seq_gen5")
	private Long teamId;
	@Column(name = "teamname", length = 50)
	private String name;
}




✍회원수정

@PutMapping

  • PUT은 전체 업데이트를 할 때 사용
  • URI 상에서 '{ }' 로 감싸여있는 부분과 동일한 변수명을 사용하는 방법
    -> 해당 데이터가 있으면 업데이트를 하기에 PUT요청이 여러번 실행되어도 해당 데이터는 같은 상태이기에 멱등

[com.oracle.oBootJpaApi01.controller]

  • JpaRestApiController.java
package com.oracle.oBootJpaApi01.controller;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import javax.validation.Valid;
import javax.validation.constraints.NotEmpty;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RestController;

import com.oracle.oBootJpaApi01.domain.Member;
import com.oracle.oBootJpaApi01.service.MemberService;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

// Controller + Repository
// 사용 목적 -> Ajax + RestApi
@RestController
@Slf4j
@RequiredArgsConstructor
public class JpaRestApiController {
	
	private final MemberService memberService;
	
	// 회원가입
	// postman ---> Body --> raw---> JSON	 
    //  예시    {	    "name" : "kkk222"	    }
	// Json(member)으로 온 것을 --> Member member Setting
	@PostMapping("/restApi/v1/memberSave")
	public CreateMemberResponse saveMemberV1(@RequestBody @Valid Member member) {
		log.info("/restApi/v1/memberSave member.getName()->{}",member.getName());
		log.info("/restApi/v1/memberSave member.getSal()->{}",member.getSal());
		
		Long id = memberService.saveMember(member);
		return new CreateMemberResponse(id);
	}
	
	
	// 목적 : Entity Member member를 직접 사용 금지 --> 직접 (화면이나 API를 위한 Setting 금지)
	// 예시 : @NotEmpty --> @Column(name = "userName")
	@PostMapping("/restApi/v2/memberSave")
	public CreateMemberResponse saveMemberV2(@RequestBody @Valid CreateMemberRequest cMember) {
		log.info("/restApi/v2/memberSave cMember.getName()->{}",cMember.getName());
		log.info("/restApi/v2/memberSave cMember.getSal()->{}",cMember.getSal());
		Member member = new Member();
		member.setName(cMember.getName());
		member.setSal(cMember.getSal());
		
		Long id = memberService.saveMember(member);
		return new CreateMemberResponse(id);
	}
	
	@Data
	static class CreateMemberRequest {
		@NotEmpty
		private String name;
		private Long sal;
	}
	
	// 객체로 리턴
	@Data
	@RequiredArgsConstructor
	class CreateMemberResponse {
		private final Long id;
//		public CreateMemberResponse(Long id) {
//			this.id = id;
//		}
	}
	
	// Bad Api
	@GetMapping("/restApi/v1/members")
	public List<Member> membersVer1(){
		log.info("/restApi/v1/members Start...");
		List<Member> listMember = memberService.getListAllMember();
		return listMember;
	}
	
	// Good API  Easy Version
	// 목표 : 이름 & 급여 만 전송 
	@GetMapping("/restApi/v21/members")
	public Result membersVer21() {
		List<Member> findMembers = memberService.getListAllMember();
		log.info("JpaRestApiController restApi/v21/members  findMembers.size()->{}",findMembers.size());
		List<MemberRtnDto>   resultList = new ArrayList<MemberRtnDto>();
		// List<Member> findMembers를 --> List<MemberRtnDto> resultList 이전
		// 이전 목적  : 반드시 필요한 Data 만 보여준다(외부 노출 최대한 금지)
		for(Member member : findMembers) {
			MemberRtnDto memberRtnDto = new MemberRtnDto(member.getName(), member.getSal());
			log.info("restApi/v21/members  getName->{}",memberRtnDto.getName());
			log.info("restApi/v21/members  getSal->{}",memberRtnDto.getSal());
			resultList.add(memberRtnDto);
		}
		log.info("restApi/v21/members resultList.size()->{}",resultList.size());
		return new Result(resultList.size(),resultList);
	}
	
	
	// Good API  람다  Version
	// 목표 : 이름 & 급여 만 전송 
	@GetMapping("/restApi/v22/members")
	public Result membersVer22() {
		List<Member> findMembers = memberService.getListAllMember();
		log.info("JpaRestApiController restApi/v22/members  findMembers.size()->{}",findMembers.size());
		 //  자바 8에서 추가한 스트림(Streams)은 람다를 활용할 수 있는 기술 중 하나
		List<MemberRtnDto>   memberCollect  = findMembers.stream()
				                              .map(m->new MemberRtnDto(m.getName(), m.getSal()))  
				                              .collect(Collectors.toList())
				                              ;
		log.info("restApi/v22/members memberCollect.size()->{}",memberCollect.size());
		return new Result(memberCollect.size(), memberCollect);
	}

	// T는 인스턴스를 생성할 때 구체적인 타입으로 변경  --> 유연성 
	@Data
	@AllArgsConstructor
	class Result<T> {
		private final int totCount;  // 총 인원수 추가 
		private final T  data;
	}
	
	@Data
	@AllArgsConstructor
	class MemberRtnDto{
		private String name;
		private Long   sal;
	}
	
	// PUT 방식을사용했는데, PUT은 전체 업데이트를 할 때 사용
	// URI 상에서 '{ }' 로 감싸여있는 부분과 동일한 변수명을 사용하는 방법
	// 해당 데이터가 있으면 업데이트를 하기에 
	// PUT요청이 여러번 실행되어도 해당 데이터는 같은 상태이기에 멱등
	@PutMapping("/restApi/v21/members/{id}")
	public UpdateMemberResponse updateMemberV21(@PathVariable("id") Long id,
												@RequestBody @Valid UpdateMemberRequest uMember) {
		System.out.println("updateMemberV21 id->"+id);
		memberService.updateMember(id, uMember.getName(), uMember.getSal());
		Member findMember = memberService.findByMember(id);
		return new UpdateMemberResponse(findMember.getId(),findMember.getName(), findMember.getSal());
	}
	
	@Data
	static class UpdateMemberRequest {
		private String name;
		private Long sal;
	}
	
	@Data
	@AllArgsConstructor
	class UpdateMemberResponse {
		private Long id;
		private String name;
		private Long sal;
	}
}

[com.oracle.oBootJpaApi01.service]

  • MemberService.java
package com.oracle.oBootJpaApi01.service;

import java.util.List;

import javax.validation.Valid;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.oracle.oBootJpaApi01.domain.Member;
import com.oracle.oBootJpaApi01.repository.MemberRepository;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
@RequiredArgsConstructor
@Transactional
public class MemberService {
	
	private final MemberRepository memberRepository;
	
	// 전체회원조회
	public List<Member> getListAllMember(){
		List<Member> listMember = memberRepository.findAll();
		log.info("getListAllMember listMember.size->{}",listMember.size());
		return listMember;
	}
	
	// 회원가입 api
	public Long saveMember(@Valid Member member) {
		log.info("saveMember member.getName()->{}", member.getName());
		Long id = memberRepository.save(member);
		return id;
	}
	
	// 회원 수정
	public void updateMember(Long id, String name, Long sal) {
		Member member = new Member();
		member.setId(id);
		member.setName(name);
		member.setSal(sal);
		log.info("updateMember member.getName() -> {}",member.getName());
		log.info("updateMember member.getSal() -> {}",member.getSal());
		memberRepository.updateByMember(member);
		return;
	}
	
	// 회원 조회
	public Member findByMember(Long memberId) {
		Member member = memberRepository.findByMember(memberId);
		log.info("findByMember member.getId()->{}",member.getId());
		log.info("findByMember member.getName()->{}",member.getName());
		return member;
	}
	
}

[com.oracle.oBootJpaApi01.service]

  • MemberRepository.java
package com.oracle.oBootJpaApi01.repository;

import java.util.List;

import com.oracle.oBootJpaApi01.domain.Member;

public interface MemberRepository {
	Long			save(Member member);
	List<Member> 	findAll();
	int				updateByMember(Member member);
	Member 			findByMember(Long memberId);
}
  • JpaMemberRepository.java
package com.oracle.oBootJpaApi01.repository;

import java.util.List;

import javax.persistence.EntityManager;

import org.springframework.stereotype.Repository;

import com.oracle.oBootJpaApi01.domain.Member;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Repository
@Slf4j
@RequiredArgsConstructor
public class JpaMemberRepository implements MemberRepository {
	
	private final EntityManager em;
	
	// 회원가입
	@Override
	public Long save(Member member) {
		log.info("save Before");
		em.persist(member);
		return member.getId();
	}
	
	// 회원명단
	@Override
	public List<Member> findAll() {
		log.info("findAll Before");
		List<Member> memberList = em.createQuery("select m from Member m", Member.class)
									.getResultList();
		return memberList;
	}
	
	// 회원수정
	@Override
	public int updateByMember(Member member) {
		int result = 0;
		Member member3 = em.find(Member.class, member.getId());
		if(member3 != null) {
			// 회원저장
			member3.setName(member.getName());
			member3.setSal(member.getSal());
			result = 1;
			log.info("updateByMember Update...");
		} else {
			result = 0;
			log.info("updateByMember No Exist...");
		}
		return result;
	}
	
	// 회원조회
	@Override
	public Member findByMember(Long memberId) {
		Member member = em.find(Member.class, memberId);
		log.info("findByMember find...");
		return member;
	}

}




🔻oBootJpaApi02

  • application.yml
server:
  port : 8389
  
# Oracle Connect
spring:
  datasource:
    url: jdbc:oracle:thin:@localhost:1521/xe
    username: scottjpa
    password: tiger
    driver-class-name: oracle.jdbc.driver.OracleDriver
    
  # Jpa Setting
  jpa:
    hibernate: 
      ddl-auto: update # none create update
    properties: 
      hibernate:
      show_sql: true      # System.out에 하이버네이트 실행 SQL
      format_sql: true

logging.level:
  org.hibernate.SQL: debug   # logger를 통해 하이버네이트 실행 SQL



domain / domain.item --> oBootJpa03에서 복붙

[com.oracle.oBootJpaApi02.domain]

  • Order.java
package com.oracle.oBootJpaApi02.domain;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;

import com.oracle.oBootJpaApi02.domain.item.OrderItem;

import lombok.Data;
import lombok.Getter;
import lombok.Setter;

@Entity
@Setter
@Getter
@Data
@Table(name = "orders")
public class Order {
	@Id
	@GeneratedValue
	@Column(name = "order_id")
	private Long id;
	
	// Member Entity의 member_id에 의해 연결
	// @ManyToOne(fetch = FetchType.LAZY)
	@ManyToOne
	@JoinColumn(name = "member_id")
	private Member member;
	
	// 배송 정보
	// @OneToOne(fetch = FetchType.LAZY)
	@OneToOne
	@JoinColumn(name = "delivery_id")
	private Delivery delivery;
	
	// 주문 시간
	private LocalDateTime orderdate;
	
	// 주문 상태[ ORDER / CANCEL ]
	@Enumerated(EnumType.STRING)
	private OrderStatus status;
	
	// OrderItem Entity의 order_id field에 의해서 mapper 당함 --> 읽기 전용
	@OneToMany(mappedBy = "order")
	private List<OrderItem> orderItems = new ArrayList<>();
	
	//==연관관계 메서드==//
	public void setMember(Member member) {
		this.member = member;
		member.getOrders().add(this);
	}
	
	// 생성자를 만들어 외부에서 저장
	public static Order createOrder(Member member, Delivery delivery) {
		Order order = new Order();
		order.setMember(member);
		order.setDelivery(delivery);
		order.setStatus(OrderStatus.ORDER); // 현재 주문 상태
		order.setOrderdate(LocalDateTime.now()); // now -> 현재 날짜
		// 필요시 재고 처리
		// item.removeStock(count)
		return order;
	}
}

[com.oracle.oBootJpaApi02.dto]

  • SimpleOrderDto.java
package com.oracle.oBootJpaApi02.dto;

import java.time.LocalDateTime;

import com.oracle.oBootJpaApi02.domain.Address;
import com.oracle.oBootJpaApi02.domain.Order;
import com.oracle.oBootJpaApi02.domain.OrderStatus;

import lombok.Data;

@Data
public class SimpleOrderDto {
	private Long orderId;
	private String name;
	private LocalDateTime orderDate; // 주문시간
	private OrderStatus orderStatus;
	private Address address;
	
	public SimpleOrderDto(Order order) {
		orderId = order.getId();
		name = order.getMember().getName();
		orderDate = order.getOrderdate();
		orderStatus = order.getStatus();
		address = order.getDelivery().getAddress();
	}
	
}

[com.oracle.oBootJpaApi02.controller]

  • OrderRestApiController.java
package com.oracle.oBootJpaApi02.controller;

import java.util.List;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.oracle.oBootJpaApi02.dto.SimpleOrderDto;
import com.oracle.oBootJpaApi02.service.OrderService;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@RestController
@Slf4j
@RequiredArgsConstructor
public class OrderRestApiController {
	private final OrderService orderService;
	
	// M:1 엔티티를 조회해서 DTO로 전환 (fetch join 사용X)
	// 단점 : 관련 쿼리 N번 호출
	@GetMapping("/restApi/ordersV21")
	public List<SimpleOrderDto> ordersV21() {
		List<SimpleOrderDto> result = null;
		log.info("/restApi/ordersV21 Start...");
		result = orderService.serviceOrdersV21();
		return result;
	}
}

[com.oracle.oBootJpaApi02.service]

  • OrderService.java
package com.oracle.oBootJpaApi02.service;

import java.util.List;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.oracle.oBootJpaApi02.domain.Order;
import com.oracle.oBootJpaApi02.dto.SimpleOrderDto;
import com.oracle.oBootJpaApi02.repository.OrderRepository;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
@Transactional
@RequiredArgsConstructor
public class OrderService {
	private final OrderRepository orderRepository;

	public List<SimpleOrderDto> serviceOrdersV21() {
		log.info("serviceOrdersV21 Start...");
		List<Order> orders = orderRepository.findAll();
		List<SimpleOrderDto> result = orders.stream()
											.map(o->new SimpleOrderDto(o))
											.collect(Collectors.toList());
		log.info("serviceOrdersV21 result.size()->{}",result.size());
		return result;
	}
}

[com.oracle.oBootJpaApi02.repository]

  • OrderRepository.java
package com.oracle.oBootJpaApi02.repository;

import java.util.List;

import javax.persistence.EntityManager;

import org.springframework.stereotype.Repository;

import com.oracle.oBootJpaApi02.domain.Order;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Repository
@Slf4j
@RequiredArgsConstructor
public class OrderRepository {
	private final EntityManager em;

	public List<Order> findAll() {
		log.info("findAll Start...");
		List<Order> orderList = em.createQuery("select o from Order o",Order.class)
								  .getResultList();
		return orderList;
	}
}




  • application.yml
    default_batch_fetch_size:500->추가
server:
  port : 8389
  
# Oracle Connect
spring:
  datasource:
    url: jdbc:oracle:thin:@localhost:1521/xe
    username: scottjpa
    password: tiger
    driver-class-name: oracle.jdbc.driver.OracleDriver
    
  # Jpa Setting
  jpa:
    hibernate: 
      ddl-auto: update # none create update
    properties: 
      hibernate:
        default_batch_fetch_size:500
      show_sql: true      # System.out에 하이버네이트 실행 SQL
      format_sql: true

logging.level:
  org.hibernate.SQL: debug   # logger를 통해 하이버네이트 실행 SQL
    

💡otherItems 조회

[com.oracle.oBootJpaApi02.dto.api]

  • OrderItemDto.java
package com.oracle.oBootJpaApi02.dto.api;

import com.oracle.oBootJpaApi02.domain.item.OrderItem;

import lombok.Data;

@Data
public class OrderItemDto {
	
	private String itemName; // 상품명
	private int orderPrice; // 주문 가격
	private int count; // 주문 수량
	
	public OrderItemDto(OrderItem orderItem) {
		itemName = orderItem.getItem().getName();
		orderPrice = orderItem.getOrderPrice();
		count = orderItem.getCount();
	}
}
  • OrderDto.java
package com.oracle.oBootJpaApi02.dto.api;

import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

import com.oracle.oBootJpaApi02.domain.Address;
import com.oracle.oBootJpaApi02.domain.Order;
import com.oracle.oBootJpaApi02.domain.OrderStatus;

import lombok.Data;

@Data
public class OrderDto {
	private Long orderId;
	private String name;
	private LocalDateTime orderDate; // 주문시간
	private OrderStatus orderStatus;
	private Address address;
	// 1 : M 관계 추가
	private List<OrderItemDto> orderItems;
	
	public OrderDto(Order order) {
		orderId = order.getId();
		name = order.getMember().getName();
		orderDate = order.getOrderdate();
		orderStatus = order.getStatus();
		address = order.getDelivery().getAddress();
		orderItems = order.getOrderItems().stream()
										  .map(orderItem-> new OrderItemDto(orderItem))
										  .collect(Collectors.toList());
	}
}

[com.oracle.oBootJpaApi02.controller]

  • OrderRestApiController.java
package com.oracle.oBootJpaApi02.controller;

import java.util.List;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.oracle.oBootJpaApi02.dto.SimpleOrderDto;
import com.oracle.oBootJpaApi02.dto.api.OrderDto;
import com.oracle.oBootJpaApi02.service.OrderService;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@RestController
@Slf4j
@RequiredArgsConstructor
public class OrderRestApiController {
	private final OrderService orderService;
	
	// M:1 엔티티를 조회해서 DTO로 전환 (fetch join 사용X)
	// 단점 : 관련 쿼리 N번 호출
	@GetMapping("/restApi/ordersV21")
	public List<SimpleOrderDto> ordersV21() {
		List<SimpleOrderDto> result = null;
		log.info("/restApi/ordersV21 Start...");
		result = orderService.serviceOrdersV21();
		return result;
	}
	
	
	// M:1 엔티티를 조회해서 DTO로 전환 (fetch join 사용X)
	// 단점 : 관련 쿼리 N번 호출
	@GetMapping("/restApi/ordersV22")
	public List<OrderDto> ordersV22() {
		List<OrderDto> result = null;
		log.info("/restApi/ordersV22 Start...");
		result = orderService.serviceOrdersV22();
		return result;
	}
}

[com.oracle.oBootJpaApi02.service]

  • OrderService.java
package com.oracle.oBootJpaApi02.service;

import java.util.List;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.oracle.oBootJpaApi02.domain.Order;
import com.oracle.oBootJpaApi02.dto.SimpleOrderDto;
import com.oracle.oBootJpaApi02.dto.api.OrderDto;
import com.oracle.oBootJpaApi02.repository.OrderRepository;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
@Transactional
@RequiredArgsConstructor
public class OrderService {
	private final OrderRepository orderRepository;

	public List<SimpleOrderDto> serviceOrdersV21() {
		log.info("serviceOrdersV21 Start...");
		List<Order> orders = orderRepository.findAll();
		List<SimpleOrderDto> result = orders.stream()
											.map(o->new SimpleOrderDto(o))
											.collect(Collectors.toList());
		log.info("serviceOrdersV21 result.size()->{}",result.size());
		return result;
	}

	public List<OrderDto> serviceOrdersV22() {
		log.info("serviceOrdersV22 Start...");
		List<Order> orders = orderRepository.findAll();
		List<OrderDto> result = orders.stream()
											.map(o->new OrderDto(o))
											.collect(Collectors.toList());
		log.info("serviceOrdersV22 result.size()->{}",result.size());
		return result;
	}
}




⭐Fetch Join

  • 일반 Join : join 조건을 제외하고 실제 질의하는 대상 Entity에 대한 컬럼만 Select
  • Fetch Join : 실제 질의하는 대상 Entity의 Fetch Join이 걸려있는 Entity를 포함한 컬럼 함께 Select
    -> 장점 : 쿼리를 한번에 조회(연관된 엔티티나 컬렉션을 한번에 같이 조회)
    -> 단점 :
  1. 페이징(자동화된 페이징) 불가
    -> Query dsl join
  2. 둘 이상의 컬렉션을 패치 할 수 없다.
  3. 패치 조인 대상에는 별칭을 줄 수 없다.

[com.oracle.oBootJpaApi02.domain]

  • Order.java
    (fetch = FetchType.LAZY)->로 변경
package com.oracle.oBootJpaApi02.domain;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.List;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;

import com.oracle.oBootJpaApi02.domain.item.OrderItem;

import lombok.Data;
import lombok.Getter;
import lombok.Setter;

@Entity
@Setter
@Getter
@Data
@Table(name = "orders")
public class Order {
	@Id
	@GeneratedValue
	@Column(name = "order_id")
	private Long id;
	
	// Member Entity의 member_id에 의해 연결
	@ManyToOne(fetch = FetchType.LAZY)
	// @ManyToOne
	@JoinColumn(name = "member_id")
	private Member member;
	
	// 배송 정보
	@OneToOne(fetch = FetchType.LAZY)
	// @OneToOne
	@JoinColumn(name = "delivery_id")
	private Delivery delivery;
	
	// 주문 시간
	private LocalDateTime orderdate;
	
	// 주문 상태[ ORDER / CANCEL ]
	@Enumerated(EnumType.STRING)
	private OrderStatus status;
	
	// OrderItem Entity의 order_id field에 의해서 mapper 당함 --> 읽기 전용
	@OneToMany(mappedBy = "order")
	private List<OrderItem> orderItems = new ArrayList<>();
	
	//==연관관계 메서드==//
	public void setMember(Member member) {
		this.member = member;
		member.getOrders().add(this);
	}
	
	// 생성자를 만들어 외부에서 저장
	public static Order createOrder(Member member, Delivery delivery) {
		Order order = new Order();
		order.setMember(member);
		order.setDelivery(delivery);
		order.setStatus(OrderStatus.ORDER); // 현재 주문 상태
		order.setOrderdate(LocalDateTime.now()); // now -> 현재 날짜
		// 필요시 재고 처리
		// item.removeStock(count)
		return order;
	}
}

[com.oracle.oBootJpaApi02.controller]

  • OrderRestApiController.java
package com.oracle.oBootJpaApi02.controller;

import java.util.List;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.oracle.oBootJpaApi02.dto.SimpleOrderDto;
import com.oracle.oBootJpaApi02.dto.api.OrderDto;
import com.oracle.oBootJpaApi02.service.OrderService;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@RestController
@Slf4j
@RequiredArgsConstructor
public class OrderRestApiController {
	private final OrderService orderService;
	
	// M:1 엔티티를 조회해서 DTO로 전환 (fetch join 사용X)
	// 단점 : 관련 쿼리 N번 호출
	@GetMapping("/restApi/ordersV21")
	public List<SimpleOrderDto> ordersV21() {
		List<SimpleOrderDto> result = null;
		log.info("/restApi/ordersV21 Start...");
		result = orderService.serviceOrdersV21();
		return result;
	}
	
	
	// M:1 엔티티를 조회해서 DTO로 전환 (fetch join 사용X)
	// 단점 : 관련 쿼리 N번 호출
	// M:1 & 1:M Entity 포함, 조회해서 DTO 변환
	@GetMapping("/restApi/ordersV22")
	public List<OrderDto> ordersV22() {
		List<OrderDto> result = null;
		log.info("/restApi/ordersV22 Start...");
		result = orderService.serviceOrdersV22();
		return result;
	}
	
	// M:1 엔티티를 조회해서 DTO로 전환 (fetch join 사용X)
	// 단점 --> 관련 쿼리 N번 호출 문제 해결
	// M:1 & 1:M Entity 포함, 조회해서 DTO 변환
	@GetMapping("/restApi/ordersV31")
	public List<SimpleOrderDto> ordersV31() {
		List<SimpleOrderDto> result = null;
		log.info("/restApi/ordersV31 Start...");
		result = orderService.serviceOrdersV31();
		return result;
	}
}

[com.oracle.oBootJpaApi02.service]

  • OrderService.java
package com.oracle.oBootJpaApi02.service;

import java.util.List;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.oracle.oBootJpaApi02.domain.Order;
import com.oracle.oBootJpaApi02.dto.SimpleOrderDto;
import com.oracle.oBootJpaApi02.dto.api.OrderDto;
import com.oracle.oBootJpaApi02.repository.OrderRepository;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
@Transactional
@RequiredArgsConstructor
public class OrderService {
	private final OrderRepository orderRepository;

	public List<SimpleOrderDto> serviceOrdersV21() {
		log.info("serviceOrdersV21 Start...");
		List<Order> orders = orderRepository.findAll();
		List<SimpleOrderDto> result = orders.stream()
											.map(o->new SimpleOrderDto(o))
											.collect(Collectors.toList());
		log.info("serviceOrdersV21 result.size()->{}",result.size());
		return result;
	}

	public List<OrderDto> serviceOrdersV22() {
		log.info("serviceOrdersV22 Start...");
		List<Order> orders = orderRepository.findAll();
		List<OrderDto> result = orders.stream()
											.map(o->new OrderDto(o))
											.collect(Collectors.toList());
		log.info("serviceOrdersV22 result.size()->{}",result.size());
		return result;
	}
	
	// 엔티티 조회해서 DTO로 변환(Fetch join 사용O)
	// fetch join으로 쿼리 1번 호출
	// 많은 Query로 인한 성능 저하 개선
	public List<SimpleOrderDto> serviceOrdersV31() {
		log.info("serviceOrdersV31() Start...");
		List<Order> orders = orderRepository.findAllWithItem();
		List<SimpleOrderDto> results = orders.stream()
											.map(o->new SimpleOrderDto(o))
											.collect(Collectors.toList());
		log.info("serviceOrdersV31 results.size()->{}",results.size());
		return results;
	}
}

[com.oracle.oBootJpaApi02.repository]

  • OrderRepository.java
package com.oracle.oBootJpaApi02.repository;

import java.util.List;

import javax.persistence.EntityManager;

import org.springframework.stereotype.Repository;

import com.oracle.oBootJpaApi02.domain.Order;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Repository
@Slf4j
@RequiredArgsConstructor
public class OrderRepository {
	private final EntityManager em;

	public List<Order> findAll() {
		log.info("findAll Start...");
		List<Order> orderList = em.createQuery("select o from Order o",Order.class)
								  .getResultList();
		return orderList;
	}
	
	
	// Lazy --> JPA fetch Join
	public List<Order> findAllWithItem() {
		log.info("findAllWithItem Start...");
		List<Order> findOrders = em.createQuery("select distinct o from Order o "
								                + " join fetch o.member m  "
								                + " join fetch o.delivery d "
								                + " join fetch o.orderItems oi"
								                + " join fetch oi.item i ", Order.class
								                   )
									.getResultList();
		log.info("findAllWithItem findOrders.size()->{}",findOrders.size());
		return findOrders;
	}
}




➕1:M 관계추가

[com.oracle.oBootJpaApi02.controller]

  • OrderRestApiController.java
package com.oracle.oBootJpaApi02.controller;

import java.util.List;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;

import com.oracle.oBootJpaApi02.dto.SimpleOrderDto;
import com.oracle.oBootJpaApi02.dto.api.OrderDto;
import com.oracle.oBootJpaApi02.service.OrderService;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@RestController
@Slf4j
@RequiredArgsConstructor
public class OrderRestApiController {
	private final OrderService orderService;
	
	// M:1 엔티티를 조회해서 DTO로 전환 (fetch join 사용X)
	// 단점 : 관련 쿼리 N번 호출
	@GetMapping("/restApi/ordersV21")
	public List<SimpleOrderDto> ordersV21() {
		List<SimpleOrderDto> result = null;
		log.info("/restApi/ordersV21 Start...");
		result = orderService.serviceOrdersV21();
		return result;
	}
	
	
	// M:1 엔티티를 조회해서 DTO로 전환 (fetch join 사용X)
	// 단점 : 관련 쿼리 N번 호출
	// M:1 & 1:M Entity 포함, 조회해서 DTO 변환
	@GetMapping("/restApi/ordersV22")
	public List<OrderDto> ordersV22() {
		List<OrderDto> result = null;
		log.info("/restApi/ordersV22 Start...");
		result = orderService.serviceOrdersV22();
		return result;
	}
	
	// M:1 엔티티를 조회해서 DTO로 전환 (fetch join 사용)
	// 단점 --> 관련 쿼리 N번 호출 문제 해결
	// M:1 & 1:M Entity 포함, 조회해서 DTO 변환
	@GetMapping("/restApi/ordersV31")
	public List<SimpleOrderDto> ordersV31() {
		List<SimpleOrderDto> result = null;
		log.info("/restApi/ordersV31 Start...");
		result = orderService.serviceOrdersV31();
		return result;
	}
	
	// M:1 엔티티를 조회해서 DTO로 전환 (fetch join 사용)
	// 단점 --> 관련 쿼리 N번 호출 문제 해결
	// M:1 & 1:M Entity 포함, 조회해서 DTO 변환
	@GetMapping("/restApi/ordersV32")
	public List<OrderDto> ordersV32() {
		List<OrderDto> result = null;
		log.info("/restApi/ordersV32 Start...");
		result = orderService.serviceOrdersV32(); 
		return result;
	}
}

[com.oracle.oBootJpaApi02.service]

  • OrderService.java
package com.oracle.oBootJpaApi02.service;

import java.util.List;
import java.util.stream.Collectors;

import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.oracle.oBootJpaApi02.domain.Order;
import com.oracle.oBootJpaApi02.dto.SimpleOrderDto;
import com.oracle.oBootJpaApi02.dto.api.OrderDto;
import com.oracle.oBootJpaApi02.repository.OrderRepository;

import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;

@Service
@Slf4j
@Transactional
@RequiredArgsConstructor
public class OrderService {
	private final OrderRepository orderRepository;

	public List<SimpleOrderDto> serviceOrdersV21() {
		log.info("serviceOrdersV21 Start...");
		List<Order> orders = orderRepository.findAll();
		List<SimpleOrderDto> result = orders.stream()
											.map(o->new SimpleOrderDto(o))
											.collect(Collectors.toList());
		log.info("serviceOrdersV21 result.size()->{}",result.size());
		return result;
	}

	public List<OrderDto> serviceOrdersV22() {
		log.info("serviceOrdersV22 Start...");
		List<Order> orders = orderRepository.findAll();
		List<OrderDto> result = orders.stream()
											.map(o->new OrderDto(o))
											.collect(Collectors.toList());
		log.info("serviceOrdersV22 result.size()->{}",result.size());
		return result;
	}
	
	// 엔티티 조회해서 DTO로 변환(Fetch join 사용O)
	// fetch join으로 쿼리 1번 호출
	// 많은 Query로 인한 성능 저하 개선
	public List<SimpleOrderDto> serviceOrdersV31() {
		log.info("serviceOrdersV31() Start...");
		List<Order> orders = orderRepository.findAllWithItem();
		List<SimpleOrderDto> results = orders.stream()
											.map(o->new SimpleOrderDto(o))
											.collect(Collectors.toList());
		log.info("serviceOrdersV31 results.size()->{}",results.size());
		return results;
	}
	
	// 엔티티 조회해서 DTO로 변환(Fetch join 사용O)
	// fetch join으로 쿼리 1번 호출
	// 많은 Query로 인한 성능 저하 개선
	// 1:M 관계 추가
	public List<OrderDto> serviceOrdersV32() {
		log.info("serviceOrdersV32() Start...");
		List<Order> orders = orderRepository.findAllWithItem();
		List<OrderDto> results = orders.stream()
											 .map(o->new OrderDto(o))
											 .collect(Collectors.toList());
		log.info("serviceOrdersV32 results.size()->{}",results.size());
		return results;
	}
}
SELECT DISTINCT
    order0_.order_id              AS order_id1_4_0_,
    member1_.member_id            AS member_id1_2_1_,
    delivery2_.delivery_id        AS delivery_id1_0_2_,
    orderitems3_.order_item_id    AS order_item_id1_3_3_,
    item4_.item_id                AS item_id2_1_4_,
    order0_.delivery_id           AS delivery_id4_4_0_,
    order0_.member_id             AS member_id5_4_0_,
    order0_.orderdate             AS orderdate2_4_0_,
    order0_.status                AS status3_4_0_,
    member1_.city                 AS city2_2_1_,
    member1_.street               AS street3_2_1_,
    member1_.zipcode              AS zipcode4_2_1_,
    member1_.name                 AS name5_2_1_,
    delivery2_.city               AS city2_0_2_,
    delivery2_.street             AS street3_0_2_,
    delivery2_.zipcode            AS zipcode4_0_2_,
    delivery2_.status             AS status5_0_2_,
    orderitems3_.count            AS count2_3_3_,
    orderitems3_.item_id          AS item_id4_3_3_,
    orderitems3_.order_id         AS order_id5_3_3_,
    orderitems3_.order_price      AS order_price3_3_3_,
    orderitems3_.order_id         AS order_id5_3_0__,
    orderitems3_.order_item_id    AS order_item_id1_3_0__,
    item4_.name                   AS name3_1_4_,
    item4_.price                  AS price4_1_4_,
    item4_.stock_quantity         AS stock_quantity5_1_4_,
    item4_.artist                 AS artist6_1_4_,
    item4_.etc                    AS etc7_1_4_,
    item4_.author                 AS author8_1_4_,
    item4_.isbn                   AS isbn9_1_4_,
    item4_.actor                  AS actor10_1_4_,
    item4_.director               AS director11_1_4_,
    item4_.dtype                  AS dtype1_1_4_
FROM
         orders order0_
    INNER JOIN member3     member1_ ON order0_.member_id = member1_.member_id
    INNER JOIN delivery    delivery2_ ON order0_.delivery_id = delivery2_.delivery_id
    INNER JOIN order_item  orderitems3_ ON order0_.order_id = orderitems3_.order_id
    INNER JOIN item        item4_ ON orderitems3_.item_id = item4_.item_id;

orderItems까지 같이 조회

order관점의 distinct라서 3건이지만 위에 2건 출력됨

0개의 댓글