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;
}
}
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;
}
}
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();
}
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;
}
}
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
}
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;
}
- PUT은 전체 업데이트를 할 때 사용
- URI 상에서 '{ }' 로 감싸여있는 부분과 동일한 변수명을 사용하는 방법
-> 해당 데이터가 있으면 업데이트를 하기에 PUT요청이 여러번 실행되어도 해당 데이터는 같은 상태이기에 멱등
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;
}
}
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;
}
}
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);
}
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;
}
}
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에서 복붙
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;
}
}
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();
}
}
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;
}
}
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;
}
}
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;
}
}
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
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();
}
}
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());
}
}
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;
}
}
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;
}
}
- 일반 Join : join 조건을 제외하고 실제 질의하는 대상 Entity에 대한 컬럼만 Select
- Fetch Join : 실제 질의하는 대상 Entity의 Fetch Join이 걸려있는 Entity를 포함한 컬럼 함께 Select
-> 장점 : 쿼리를 한번에 조회(연관된 엔티티나 컬렉션을 한번에 같이 조회)
-> 단점 :
- 페이징(자동화된 페이징) 불가
-> Query dsl join- 둘 이상의 컬렉션을 패치 할 수 없다.
- 패치 조인 대상에는 별칭을 줄 수 없다.
(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;
}
}
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;
}
}
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;
}
}
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;
}
}
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;
}
}
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건 출력됨