spring project 구조 TIL

Youngkwon Kim·2022년 7월 16일
0

spring

목록 보기
2/3
post-thumbnail

들어가며..

본격적인 Software Maestro 프로젝트 개발을 시작하기에 앞서, Spring 프로젝트의 대략적인 구조정도는 알고 시작하고자 벼락치기로 Spring 관련 강의를 수강하고 있다.
핵심적인 키워드나 소스코드 정도는, 실습했던 코드를 레퍼런스 삼아 진행할 수 있겠지만, 코드의 아키텍쳐 및 폴더링 정도는 충분히 익숙해지고자 글을 작성한다.


Controller

Spring에서 Controller는 NodeJS에서의 Router, Django의 urls.py 정도의 역할이다.
결국 Request에 알맞게 요청을 Direction 하는 역할.
코드는 다음과 같다.

@RestController
@RequiredArgsConstructor // Lombok library
public class MemberApiController {

	private final MemberService memberService;
    
    @GetMapping("/api/v2/members")
    public GetMemberResponseDto getMember() {
    	...
    }
}

Service

Service는 Repository class를 활용하여 Database에 access하며 본격으로 logic을 수행하는 코드가 담겨있다.
Service에는 Transactional 태그가 붙는데 의미는 마지막에 정리하겠다.
코드는 다음과 같다.

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class ItemService {
	private final MemberRepository memberRepository;
    
    @Transactional // Write operation이 필요하기 때문에 readOnly를 false로 변경
    public Long join(Member member) {
    	validateDuplicationMember(member);
        memberRepository.save(member);
        return member.getId();
    }
    
    
    public List<Member> findAllMembers() {
		return memberRepository.findAll();    	
    }
    
    public Member findOne(Long memberId) {
    	return memberRepository.findOne(memberid);
    }
    
    @Transactional
    public void update(Long id, String name) {
        Member member = memberRepository.findOne(id);
        member.setName(name);
    }
}

Repository

Repository는 Database에 직접 접근하여 logic에 필요한 데이터 연산을 수행한다.
EntityManager에 대한 내용은 마찬가지로 마지막에 정리하겠다.

@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", Member.class)
                .setParameter("name", name)
                .getResultList();
    }
}

Domain

Database table에 mapping될 Entity를 담고있는 패키지.
패키지 내부에 Entity 애노테이션을 사용해서 Entity를 정의한다.
코드는 다음과 같다.

@Entity
@Getter @Setter
public class Member {

    @Id @GeneratedValue
    @Column(name = "member_id")
    private Long id;

    @NotEmpty
    private String name;

    @Embedded
    private Address address;

    // order table에 있는 member에 의해 mapping
    @JsonIgnore
    @OneToMany(mappedBy = "member")
    private List<Order> orders = new ArrayList<>();

}
  • Setter는 사용하지 않는 것이 좋으나, 예제코드에서는 사용
  • GeneratedValue는 Auto Increment 속성을 부여하는 애노테이션
  • @NotEmpty 애노테이션은 Controller에서 1차적으로 Request validation에 활용됨
	@PostMapping("/api/v2/members")
    public CreateMemberResponse saveMemberV2(@RequestBody @Valid CreateMemberRequest request) {
        Member member = new Member();
        member.setName(request.getName());
        Long id = memberService.join(member);
        return new CreateMemberResponse(id);
    }

부가적인 기능

@Transactional

스프링은 Transactional 애노테이션을 통해 선언적 트랜잭션 처리를 지원한다.
Database에서의 트랜잭션의 정의는 다음과 같다.

쪼갤 수 없는 업무 처리 연산의 최소 단위

트랜잭션을 설명할때 가장 흔하게 다뤄지는 예시는 은행문제이다.
다음 예시를 보자.

  1. A가 B에게 500원을 송금한다.
  2. A의 통장에서 500원을 차감한다.
  3. B의 통장에 500원을 추가한다.

정확하게 은행 거래 시스템이 어떤 단계로 구성되었는지는 모르겠지만, 대략적으로 위와 같을 것이다.
이때 만약 2번 단계까지 수행 후, 시스템 오류가 발생했다고 가정하자.
그렇다면 A는 분명 500원을 송금하여 돈이 빠져나갔으나, 그 돈이 B에게 정상적으로 도착하지 못한 상황이 발생했을 것이다.
DB는 이와같은 경우를 방지하기 위해 트랜잭션 과정 중에 오류가 발생할 경우, 원래 상태로 rollback 처리한다.
만약 트랜잭션의 모든 과정이 성공적으로 수행되었다면 commit을 통해 트랜잭션 결과를 반영한다.


@EntityManager

이전에 Repository 코드를 작성할 때, 다음과 같이 필드를 정의하였다.

@PersistenceContext
private EntityManager em;

@Autowired를 통해 의존성을 주입받는 경우와 위의 경우는 어떤 차이점이 있을까?

Autowired의 경우, Spring에서 제공하는 의존성 주입 방식으로서, Spring Bean은 모든 스레드가 공유한다.
하지만 PersistenceContext 애노테이션은 JPA 표준에서 제공하며, Thread-Safe하기 때문에 동시성 문제로부터 안전하다.

PersistenceContext의 동작 방식은 대략적으로 다음과 같다.

  1. 컨테이너 초기화 시점에서 PersistenceContext로 주입받은 EntityMange를 Proxy로 감싼다
  2. 이후 EntityManager 호출 시, 매번 Proxy를 통해 EntityManager를 생성, Thread-Safe 상태를 유지한다.

0개의 댓글