초급자를 위해 준비한
[웹 개발, 백엔드] 강의입니다.
실무에 가까운 예제로, 스프링 부트와 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)
댓글 남기기