초급자를 위해 준비한
[웹 개발, 백엔드] 강의입니다.
실무에 가까운 예제로, 스프링 부트와 JPA를 활용해서 웹 애플리케이션을 설계하고 개발합니다. 이 과정을 통해 스프링 부트와 JPA를 실무에서 어떻게 활용해야 하는지 이해할 수 있습니다.
✍️
이런 걸
배워요!
스프링 부트와 JPA를 활용해서 실무에서 자바 웹 애플리케이션을 개발할 수 있습니다.
스프링 부트와 JPA를 활용하는 최적의 방법을 이해합니다.
도메인 모델을 이해하고 설계할 수 있습니다.
도메인 주도 설계를 이해합니다.
강의 :
https://www.inflearn.com/course/lecture?courseSlug=스프링부트-JPA-활용-1&unitId=24769&tab=curriculum
수업자료 :
https://github.com/braverokmc79/jpa-basic-lecture-file/blob/main/실전!%20스프링%20부트와%20JPA%20활용1.pdf
강의 소스 코드 :
https://github.com/braverokmc79/jpashop-v20210728
소스코드
https://github.com/braverokmc79/spring-boot-and-jpa-jpabook-practice1
[3] 애플리케이션 구현 준비
11.구현 요구사항
강의 :
https://www.inflearn.com/course/lecture?courseSlug=스프링부트-JPA-활용-1&unitId=24286&tab=curriculum
12.애플리케이션 아키텍처
강의 :
https://www.inflearn.com/course/lecture?courseSlug=스프링부트-JPA-활용-1&unitId=24287&tab=curriculum
[4] 회원 도메인 개발
13.회원 리포지토리 개발
강의 :
https://www.inflearn.com/course/lecture?courseSlug=스프링부트-JPA-활용-1&unitId=24289&tab=curriculum
package jpabook.jpashop.repository; import jpabook.jpashop.domain.Member; import org.springframework.stereotype.Repository; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import java.util.List; @Repository public class MemberRepository { @PersistenceContext private EntityManager em; public void save(Member member){ em.persist(member); } public Member findOne(Long id){ return em.find(Member.class, id); } public List<Member> findAll(){ return em.createQuery("select m from Member m ", Member.class).getResultList(); } public List<Member> findByName(String name){ return em.createQuery("select m from Member m where m.name = :name ") .setParameter("name", name).getResultList(); } }
기술 설명
@Repository : 스프링 빈으로 등록, JPA 예외를 스프링 기반 예외로 예외 변환
@PersistenceContext : 엔티티 메니저( EntityManager ) 주입
@PersistenceUnit : 엔티티 메니터 팩토리( EntityManagerFactory ) 주입
기능 설명
save()
findOne()
findAll()
findByName()
14.회원 서비스 개발
강의 :
https://www.inflearn.com/course/lecture?courseSlug=스프링부트-JPA-활용-1&unitId=24290&tab=curriculum
MemberService
package jpabook.jpashop.service; import jpabook.jpashop.domain.Member; import jpabook.jpashop.repository.MemberRepository; 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; @Transactional(readOnly = true) @RequiredArgsConstructor @Service public class MemberService { private final MemberRepository memberRepository; /** 회원 가입 */ @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("이미 존재하는 회원입니다."); } } //회원 전체 조회 public List<Member> findMembers(){ return memberRepository.findAll(); } //회원 전체 조회 public Member findOne(Long memberId){ return memberRepository.findOne(memberId); } }
기술 설명
@Service
@Transactional : 트랜잭션, 영속성 컨텍스트
readOnly=true : 데이터의 변경이 없는 읽기 전용 메서드에 사용, 영속성 컨텍스트를 플러시 하지 않으므로 약간의 성능 향상(읽기 전용에는 다 적용)
데이터베이스 드라이버가 지원하면 DB에서 성능 향상
@Autowired
생성자 Injection 많이 사용, 생성자가 하나면 생략 가능
기능 설명
join()
findMembers()
findOne()
> 참고: 실무에서는 검증 로직이 있어도 멀티 쓰레드 상황을 고려해서 회원 테이블의 회원명 컬럼에 유니크 제약 조건을 추가하는 것이 안전하다.
> 참고: 스프링 필드 주입 대신에 생성자 주입을 사용하자
생성자 주입 방식을 권장
변경 불가능한 안전한 객체 생성 가능
생성자가 하나면, @Autowired 를 생략할 수 있다.
final 키워드를 추가하면 컴파일 시점에 memberRepository 를 설정하지 않는 오류를 체크할 수 있다.
(보통 기본 생성자를 추가할 때 발견)
lombok
@RequiredArgsConstructor public class MemberService { private final MemberRepository memberRepository; ... }
참고: 스프링 데이터 JPA를 사용하면 EntityManager 도 주입 가능
@Repository @RequiredArgsConstructor public class MemberRepository { private final EntityManager em; ... }
15.회원 기능 테스트
강의 :
https://www.inflearn.com/course/lecture?courseSlug=스프링부트-JPA-활용-1&unitId=24290&tab=curriculum
테스트 요구사항
회원가입을 성공해야 한다.
회원가입 할 때 같은 이름이 있으면 예외가 발생해야 한다.
MemberServiceTest
package jpabook.jpashop.service; import jpabook.jpashop.domain.Member; import jpabook.jpashop.repository.MemberRepository; import org.junit.Assert; import org.junit.Test; import org.junit.runner.RunWith; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.test.context.junit4.SpringRunner; import org.springframework.transaction.annotation.Transactional; import javax.persistence.EntityManager; import static org.junit.Assert.fail; @RunWith(SpringRunner.class) @SpringBootTest @Transactional // @Transactional 이 테스트 클래스에 존재하면 기본적으로 롤백 처리한다. public class MemberServiceTest { @Autowired MemberService memberService; @Autowired MemberRepository memberRepository; @Autowired EntityManager em; @Test //@Rollback(false) 롤백 처리 하지 않는다. public void 회원가입() throws Exception{ //given Member member=new Member(); member.setName("kim"); //when Long savedId=memberService.join(member); //then em.flush();// 실질적으로 DB 에 반영후 롤백 처리 한다. insert 문을 볼수 있다. DB 에 가면 데이터는 안보인다. Assert.assertEquals(member,memberRepository.findOne(savedId)); } @Test(expected = IllegalStateException.class) public void 중복_회원_예외() throws Exception{ //given Member member1=new Member(); member1.setName("kim"); Member member2=new Member(); member2.setName("kim"); //when memberService.join(member1); memberService.join(member2); //예외가 발생해야 한다!! //then fail("예외가 발생해야 한다."); } }
현재 메인 패키지에서 application.properties 으로 사용하고 있기에 application.yml 은 적용이 안되고
application.properties 가 적용 처리된다.
application.properties
spring.devtools.restart.enabled=true spring.devtools.livereload.enabled=true #spring.driver-class-name=org.h2.Driver #spring.datasource.url= jdbc:h2:mem:test #spring.datasource.username=sa #spring.password= logging.level.org.hibernate.SQL=debug logging.level.org.hibernate.type=trace
application.yml
spring.devtools.restart.enabled=true spring.devtools.livereload.enabled=true #spring.driver-class-name=org.h2.Driver #spring.datasource.url= jdbc:h2:mem:test #spring.datasource.username=sa #spring.password= logging.level.org.hibernate.SQL=debug logging.level.org.hibernate.type=trace
jdbc:h2:mem:f4f9262a-6871-4db1-9a77-fa91b2493553 DB 가 사용된것을 볼 수 있다.
2023-03-04 11:01:26.325 DEBUG 1888 --- [ main] org.hibernate.SQL : drop table if exists member CASCADE 2023-03-04 11:01:26.325 INFO 1888 --- [ main] p6spy : #1677895286325 | took 0ms | statement | connection 2| url jdbc:h2:mem:f4f9262a-6871-4db1-9a77-fa91b2493553
이제 테스트에서 스프링을 실행하면 이 위치에 있는 설정 파일을 읽는다.
(만약 이 위치에 없으면 src/resources/application.yml 을 읽는다.)
스프링 부트는 datasource 설정이 없으면, 기본적을 메모리 DB를 사용하고, driver-class도 현재 등록된
라이브러를 보고 찾아준다. 추가로 ddl-auto 도 create-drop 모드로 동작한다. 따라서 데이터소스나,
JPA 관련된 별도의 추가 설정을 하지 않아도 된다
[5] 상품 도메인 개발
16.상품 엔티티 개발(비즈니스 로직 추가)
강의 :
https://www.inflearn.com/course/lecture?courseSlug=스프링부트-JPA-활용-1&unitId=24290&tab=curriculum
비즈니스 로직 분석
addStock() 메서드는 파라미터로 넘어온 수만큼 재고를 늘린다. 이 메서드는 재고가 증가하거나 상품
주문을 취소해서 재고를 다시 늘려야 할 때 사용한다.
removeStock() 메서드는 파라미터로 넘어온 수만큼 재고를 줄인다. 만약 재고가 부족하면 예외가
발생한다. 주로 상품을 주문할 때 사용한다
Item
package jpabook.jpashop.domain.item; import jpabook.jpashop.domain.Category; import jpabook.jpashop.domain.exception.NotEnoughStockException; import lombok.Getter; import lombok.Setter; import javax.persistence.*; import java.util.ArrayList; import java.util.List; @Entity @Getter @Inheritance(strategy = InheritanceType.SINGLE_TABLE) @DiscriminatorColumn(name="dtype") public abstract class Item { @Id @GeneratedValue( strategy = GenerationType.IDENTITY) @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<>(); //==비즈니스 로직==// /** * stock 증가 */ public void addStock(int quantity){ this.stockQuantity += quantity; } /** * stock 감소 */ public void removeStock(int quantity){ int restStock=this.stockQuantity -quantity; if(restStock<0){ throw new NotEnoughStockException("need more stock"); } this.stockQuantity =restStock; } }
NotEnoughStockException
package jpabook.jpashop.domain.exception; public class NotEnoughStockException extends RuntimeException { public NotEnoughStockException() { super(); } public NotEnoughStockException(String message) { super(message); } public NotEnoughStockException(String message, Throwable cause) { super(message, cause); } public NotEnoughStockException(Throwable cause) { super(cause); } }
17.상품 리포지토리 개발
강의 :
https://www.inflearn.com/course/lecture?courseSlug=스프링부트-JPA-활용-1&unitId=24294&tab=curriculum
ItemRepository
package jpabook.jpashop.repository; import jpabook.jpashop.domain.item.Item; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Repository; import javax.persistence.EntityManager; import java.util.List; @Repository @RequiredArgsConstructor public class ItemRepository { private final EntityManager em; public void save(Item item){ if(item.getId()==null){ //신규 등록 em.persist(item); }else{ //item.getId() 있으면 업데이트 의미라 생각 em.merge(item); } } public Item findOne(Long id){ return em.find(Item.class, id); } public List<Item> findAll(){ return em.createQuery("select i from Item i" , Item.class).getResultList(); } }
기능 설명
save()
id 가 없으면 신규로 보고 persist() 실행
id 가 있으면 이미 데이터베이스에 저장된 엔티티를 수정한다고 보고, merge() 를 실행, 자세한 내용은 뒤에 웹에서 설명(그냥 지금은 저장한다 정도로 생각하자)
18.상품 서비스 개발
강의 :
https://www.inflearn.com/course/lecture?courseSlug=스프링부트-JPA-활용-1&unitId=24295&tab=curriculum
상품 서비스는 상품 리포지토리에 단순히 위임만 하는 클래스
상품 기능 테스트
상품 테스트는 회원 테스트와 비슷하므로 생략
ItemService
package jpabook.jpashop.service; import jpabook.jpashop.domain.item.Item; import jpabook.jpashop.repository.ItemRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; @Service @Transactional(readOnly = true) @RequiredArgsConstructor public class ItemService { private final ItemRepository itemRepository; public void saveItem(Item item){ itemRepository.save(item); } public List<Item> findItems(){ return itemRepository.findAll(); } public Item findOne(Long itemId){ return itemRepository.findOne(itemId); } }
댓글 ( 4)
댓글 남기기