*인프런 '김영한'님의 실전! 스프링 부트와 JPA 활용1 - 웹 애플리케이션 개발 강의를 듣고 정리한 내용입니다.
회원 도메인 개발
- 회원 서비스 개발@Transactional(readOnly = true) 조회(읽기)용일 때 사용하면 성능에 좋다.@RequiredArgsConstructor
- private final MemberRepository memberRepository**;**
- 생성자 주입 방법(아래와 같이 많이 사용)
- 🌟 @Transactional jpa의 모든 데이터 변경이나 로직은 가급적 트랜잭션 안에서 실행되야 함.
- 회원 테스트db에 쿼리 나가는 걸 보고 싶다면, @Rollback(false)한다.test > resources > application.yml
- 이유가? 좀 더 찾아봐야겠다
- 끝나면 데이터 초기화하는 게 좋은데
- 아래 또는 spring안의 내용을 모두 주석처리하면, 알아서 인메모리로 실행함.
- 로컬 DB - H2, In-memory Test DB - H2
spring: datasource: url: jdbc:h2:mem:test 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.type: trace #스프링 부트 2.x, hibernate5 # org.hibernate.orm.jdbc.bind: trace #스프링 부트 3.x, hibernate6
- 이유가? 좀 더 찾아봐야겠다
- 인메모리
- @Transactional 은 기본이 rollback을 하므로 db에 쿼리가 나가지 않는다.
상품 도메인 개발
- 상품 엔티티
- 🤔 엔티티 안에 비즈니스 로직? → service에서 .set해서 변경하는 것보다, 데이터를 가지고 있는 쪽에 비즈니스 로직이 있는 것이 좋다!!
🌟주문 도메인 개발(가장 중요)
- 주문
- 생성 메서드, 주문 취소, 전체 주문 가격 조회 → 엔티티 클래스에 위치함
- cascade = CascadeType.*ALL 때문에 주문 저장시 주문상품, 배송도 함께 저장됨*
- 주의) 배송이나 주문상품이 중요하고 다른데서도 참조한다면 이렇게 사용하지 말자!
@Transactional public Long order(Long memberId,Long itemId,int count) { //엔티티 조회 Member member = memberRepository.findOne(memberId); Item item = itemRepository.findOne(itemId); //배송정보 생성 Delivery delivery = new Delivery(); delivery.setAddress(member.getAddress()); //주문상품 생성 OrderItem orderItem = OrderItem.createOrderItem(item,item.getPrice(),count); //주문 생성 Order order = Order.createOrder(member,delivery,orderItem); //주문 저장 orderRepository.save(order); return order.getId(); }
- for 일관성? 생성 메서드를 따로 만들어 놨으므로 기본 생성자로 생성하는 것을 막기 위해
- @NoArgsConstructor(access = AccessLevel.*PROTECTED*) 추가해보자.
- 더티체킹
- sql문을 따로 날리지 않아도 변경된 부분 체크해서 반영해준다~~
- 주문 서비스의 주문과 주문 취소 메서드를 보면 비즈니스 로직 대부분이 엔티티에 있다. 서비스 계층은 단순히 엔티티에 필요한 요청을 위임하는 역할을 한다. 이처럼 엔티티가 비즈니스 로직을 가지고 객체 지향의 특성을 적극 활용하는 것을 도메인 모델 패턴(http://martinfowler.com/eaaCatalog/ domainModel.html)이라 한다.
- 주문 기능 테스트
- 발생할 예외가 있으면, (expected = NotEnoughStockException.class)처럼 적어준다.
@Test(expected = NotEnoughStockException.class) public void 상품주문_재고수량초과() throws Exception { //given Member member = createMember(); Book book = createBook("시골 JPA", 10000, 10); int orderCount = 11; //when orderService.order(member.getId(), book.getId(), orderCount); //then fail("재고 수량 부족 예외가 발생해야 한다."); }
- 🤔 단위 테스트가 좋다?
- 주문 검색 기능 개발만약에, 검색할때 name이나 상태(ORDER, CANCEL)이 없다면? → 동적 쿼리로 해결해야한다.
방법 1) → 안쓴다고 하심..방법 3) QueryDsl(뒤 영상에서 자세히 다룸)public List<Order> findAll(OrderSearch orderSearch) { return em.createQuery("select o from Order o join o.member m" + " where o.status =:status" + " and m.name like :name",Order.class) .setParameter("status",orderSearch.getOrderStatus()) .setParameter("name",orderSearch.getMemberName()) .setMaxResults(1000) // 최대 1000건 .getResultList(); }
- 방법 2) → 권장x
- 아래 코드는 있을때!
- ❗️동적쿼리
웹 계층 개발
- 회원 등록
- validation(@Valid 사용) + BindingResult → 예외 발생시 처리 함께 할 수 있음
@PostMapping("/members/new") public String create(@Valid MemberForm form, BindingResult result) { if (result.hasErrors()) { return "members/createMemberForm"; } }
🤔 참고) 폼 객체 vs 엔티티 직접 사용
- 참고: 요구사항이 정말 단순할 때는 폼 객체( MemberForm ) 없이 엔티티( Member )를 직접 등록과 수정
- 화면에서 사용해도 된다. 하지만 화면 요구사항이 복잡해지기 시작하면, 엔티티에 화면을 처리하기 위한 기능이 점점 증가한다. 결과적으로 엔티티는 점점 화면에 종속적으로 변하고, 이렇게 화면 기능 때문에 지저분해진 엔티티는 결국 유지보수하기 어려워진다.
- 실무에서 엔티티는 핵심 비즈니스 로직만 가지고 있고, 화면을 위한 로직은 없어야 한다. 화면이나 API에 맞는 폼 객체나 DTO를 사용하자. 그래서 화면이나 API 요구사항을 이것들로 처리하고, 엔티티는 최대한 순수하게 유지하자.
- 🌟 중요! 엔티티는 최대한 순수하게 유지하자
API를 만들때는 아래 처럼엔티티 절대 반환 XXXXXX (템플릿 엔진은 가능)
@GetMapping("/members")
public String list(Model model) {
List<Member> members = memberService.findMembers();
model.addAttribute("members",members);
return "members/memberList";
}
🌟상품 수정 (변경 감지와 병합 2가지 방법)
- controller
@PostMapping("items/{itemId}/edit")
public String updateItem(@ModelAttribute("form") BookForm form) {
Book book = new Book();
book.setId(form.getId());
book.setName(form.getName());
book.setPrice(form.getPrice());
book.setStockQuantity(form.getStockQuantity());
book.setAuthor(form.getAuthor());
book.setIsbn(form.getIsbn());
itemService.saveItem(book);
return "redirect:/items";
}
- ItemRepository
public void save(Item item) {
if (item.getId() == null) {
em.persist(item);
} else {
em.merge(item);
}
}
🌟❗️ 변경 감지와 병합 (매우매우매우 중요하다고 강조)
준영속 엔티티?
영속성 컨텍스트가 더는 관리하지 않는 엔티티를 말한다. (여기서는 itemService.saveItem(book) 에서 수정을 시도하는 Book 객체다. Book 객체는 이미 DB 에 한번 저장되어서 식별자가 존재한다. 이렇게 임의로 만들어낸 엔티티도 기존 식별자를 가지고 있으면 준영속 엔티티로 볼 수 있다.)
- 아래에서 book은 DB에 한 번 저장되어있던 걸 불러온 것(ID라는 식별자가 있음)
- 따라서 준영속 엔티티이다.
@PostMapping("items/{itemId}/edit")
public String updateItem(@ModelAttribute("form") BookForm form) {
Book book = new Book();
book.setId(form.getId());
book.setName(form.getName());
book.setPrice(form.getPrice());
book.setStockQuantity(form.getStockQuantity());
book.setAuthor(form.getAuthor());
book.setIsbn(form.getIsbn());
itemService.saveItem(book);
return "redirect:/items";
}
**준영속 엔티티를 수정하는 2가지 방법
①**변경 감지 기능 사용(이걸 사용하자!!)
@Transactional
//변경 감지
public void updateItem(Long itemId,Book bookParam) {//bookParam : 파라미터로 넘어온 준영속 상태의 엔티티
Item one = itemRepository.findOne(itemId);// one은 영속성 상태(DB에서 꺼내옴)
one.setPrice(bookParam.getPrice());
one.setName(bookParam.getName());
one.setStockQuantity(bookParam.getStockQuantity());
}
영속성 컨텍스트에서 엔티티를 다시 조회한 후에 데이터를 수정하는 방법 트랜잭션 안에서 엔티티를 다시 조회, 변경할 값 선택 트랜잭션 커밋 시점에 변경 감지(Dirty Checking) 이 동작해서 데이터베이스에 UPDATE SQL 실행
②병합( merge ) 사용
public void save(Item item) {
if (item.getId() == null) {
em.persist(item);
} else {
Item item1 = em.merge(item); //item1은 영속성 컨텍스트에서 관리, item은 준영속
}
}
주의: 변경 감지 기능을 사용하면 원하는 속성만 선택해서 변경할 수 있지만, 병합을 사용하면 모든 속성이 변경된다. 병합시 값이 없으면 null 로 업데이트 할 위험도 있다. (병합은 모든 필드를 교체한다.)
- .set()으로 데이터 변경x → 데이터를 변경하는 코드가 뭔지 헷갈릴 수 있음. 의미있는 메소드를 두고 변경하자.
컨트롤러에서 어설프게 엔티티를 생성하지 마세요. 트랜잭션이 있는 서비스 계층에 식별자( id )와 변경할 데이터를 명확하게 전달하세요.(파라미터 or dto) 트랜잭션이 있는 서비스 계층에서 영속 상태의 엔티티를 조회하고, 엔티티의 데이터를 직접 변경하세요. 트랜잭션 커밋 시점에 변경 감지가 실행됩니다.
- service 단에서 별로 하는게 없으면 controller에서 repository 바로 호출해도 됨.
기본편
객체를 관계를 DB에 저장
sql 중심적인 것의 문제점
sql→ 객체,, 객체→ sql…
- 객체의 상속관계를 데이터베이스에 표현하는 방법?
- 슈퍼타입-서브타입 관계
- 연관관계
- 객체는 참조를 사용: member.getTeam()
- 테이블은 외래키 사용: JOIN ON M.TEAM_ID = T.TEAM_ID
- 객체를 자바 컬렉션에 저장 하듯이 DB에 저장할 수는 없을까?
ORM?
- 객체 관계 매핑
- 객체는 객체대로, RDB는 RDB대로 설계
- 중간에서 JPA가 매핑해줌
JPA는 에플리케이션과 JDBC사이에서 동작
'백엔드 > SpringBoot' 카테고리의 다른 글
① 실전! 스프링 부트와 JPA 활용1 (2) | 2023.11.06 |
---|---|
② JPA 공부 By 실전! 스프링 데이터 JPA (0) | 2023.03.14 |
① JPA 공부 By 실전! 스프링 데이터 JPA (0) | 2023.03.10 |