본문 바로가기

Spring/SpringBoot

SpringBoot- @Transactional

트랜잭션

 트랜잭션 과정에서는 어떠한 동작이 완료되기 전까지 방해가 존재해서는 안된다.

 예를 들어서 송금을 하는 일련의 트랜잭션이 있다고 하자. 그 과정은 송금자의 계좌에서 돈이 감소하고, 수신인의 계좌에선 돈이 증가해야한다. 만일 송금자의 계좌의 돈만 감소하고 수신인의 돈은 증가하지 않는 상황은 발생해서는 안된다는 것이다.

 

@Transactional

 스프링에서는 @Transactional 어노테이션을 지원한다. 이를 클래스 단위에 붙이면(클래스 선언위에 어노테이션을 사용하면) 해당 클래스의 메소드 모두 트랜잭션을 보장한다. 메소드 개별적으로도 사용이 가능한데 @Transactional에는 readOnly 설정을 할 수 있는데, 만일 어떠한 엔티티의 조회와 같이 데이터의 변경이 필요하지 않은 메소드에 readOnly = true를 해주면, 좀 더 성능이 최적화된다. 근데 만일 데이터 변경하는 메소드에 readOnly = true 하면 데이터 변경이 안되어서 망하니 주의하자.

 

1) 메소드 전체에 트랜잭션 적용

package jpabook.jpashop.service;

import jpabook.jpashop.domain.Member;
import jpabook.jpashop.repository.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.validation.constraints.Null;
import java.util.List;

@Service
//트랜잭션 상황에서는 꼭 @Transactional 어노테이션 필요
//클래스 범위에서 @Transactional 어노테이션이 있으면 메서드들도 모두 트랜잭션 안에서 작동
@Transactional
public class MemberService {

    @Autowired
    private MemberRepository memberRepository;

    /**
     * 회원 가입
     */
    public Long join(Member member) {
        validateDuplicateMember(member); // 중복 회원 가입 방지
        memberRepository.save(member);
        return member.getId();
    }

    private void validateDuplicateMember(Member member) {
        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.find(memberId);
    }
}

2) 필요한 메소드에 readOnly 설정 (좀더 성능 최적화)

package jpabook.jpashop.service;

import jpabook.jpashop.domain.Member;
import jpabook.jpashop.repository.MemberRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.validation.constraints.Null;
import java.util.List;

@Service
//트랜잭션 상황에서는 꼭 @Transactional 어노테이션 필요
//클래스 범위에서 @Transactional 어노테이션이 있으면 메서드들도 모두 트랜잭션 안에서 작동
@Transactional
public class MemberService {

    @Autowired
    private MemberRepository memberRepository;

    /**
     * 회원 가입
     */
    public Long join(Member member) {
        validateDuplicateMember(member); // 중복 회원 가입 방지
        memberRepository.save(member);
        return member.getId();
    }

    private void validateDuplicateMember(Member member) {
        List<Member> findMembers = memberRepository.findByName(member.getName());
        if (!findMembers.isEmpty()) {
            throw new IllegalStateException("이미 존재하는 회원입니다");
        }
    }

    //회원 전체 조회
    @Transactional(readOnly = true)
    // 조회 같이 데이터 변경이 되지 않는곳에서 readOnly = true를 하면 성능이 좀더 좋음.
    // 만일 데이터 변경이 되는 곳에서 readOnly = true하면 데이터 변경이 안되서 망함.
    public List<Member> findMembers() {

        return memberRepository.findAll()
    }

    //회원 단건 조회
    @Transactional(readOnly = true)
    public Member findOne(Long memberId) {
        return memberRepository.find(memberId);
    }
}

클래스단에 @Transactional을 선언해도 메서드 위에 따로 선언한 @Transactional이 우선순위를 가진다.