스프링

 

 

초급자를 위해 준비한
[웹 개발, 백엔드] 강의입니다.

실무에 가까운 예제로, 스프링 부트와 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);
    }


}

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

about author

PHRASE

Level 60  라이트

쥐 소금 나르듯 , 조금씩 조금씩 줄어서 없어진다는 말.

댓글 ( 4)

댓글 남기기

작성