두 손끝의 창조자

Domain과 Web프로젝트 분리시 트랜잭션 문제 본문

Spring

Domain과 Web프로젝트 분리시 트랜잭션 문제

codinglog 2021. 12. 8. 08:47

소스 코드

  • domain project
    • 소스코드 
        package cothe.service;
      
        import cothe.entity.Yo;
      
        import javax.transaction.Transactional;
      
        public class YoService {
            private final YoRepository yoRepository;
      
            public YoService(YoRepository yoRepository) {
                this.yoRepository = yoRepository;
            }
      
            @Transactional
            public void updateName(Long id, String name) {
                Yo yo = yoRepository.findById(id).get();
                yo.updateName(name);
            }
      
            @Transactional
            public void addYo(String name) {
                Yo yo = new Yo(name);
                yoRepository.save(yo);
            }
        }
    • package cothe.service; import cothe.entity.Yo; import java.util.Optional; public interface YoRepository { Optional<Yo> findById(Long id); Yo save(Yo yo); }
    • package cothe.entity; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @Entity public class Yo { @Id @GeneratedValue private Long id; private String name; protected Yo() { } public void updateName(String name) { this.name = name; } public Yo(String name) { this.name = name; } }
    • 의존성 설정
    • dependencies { api group: 'jakarta.persistence', name: 'jakarta.persistence-api', version: '2.2.3' api group: 'jakarta.transaction', name: 'jakarta.transaction-api', version: '1.3.3' }
  • Entity와 Transaction을 표현할 수 있는 최소한의 의존성을 가지고 도메인 코드를 작성
  • web project
    • 소스코드
        package cothe.repository.repository;
      
        import cothe.entity.Yo;
        import cothe.service.YoRepository;
        import org.springframework.stereotype.Repository;
      
        import java.util.Optional;
      
        @Repository
        public class JpaYoRepository implements YoRepository {
            private final SpringJpaYoRepository springJpaYoRepository;
      
            public JpaYoRepository(SpringJpaYoRepository springJpaYoRepository) {
                this.springJpaYoRepository = springJpaYoRepository;
            }
      
            @Override
            public Optional<Yo> findById(Long id) {
                return springJpaYoRepository.findById(id);
            }
      
            @Override
            public Yo save(Yo yo) {
                return springJpaYoRepository.save(yo);
            }
        }
        package cothe.repository.repository;
      
        import cothe.entity.Yo;
        import org.springframework.data.jpa.repository.JpaRepository;
      
        public interface SpringJpaYoRepository extends JpaRepository<Yo, Long> {
        }
    • package cothe.controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import cothe.service.YoRepository; import cothe.service.YoService; @RestController public class WebRestController { private final YoRepository jpaYoRepository; public WebRestController(YoRepository yoRepository) { this.jpaYoRepository = yoRepository; } @RequestMapping("/update") public void updateName(Long id, String name) { YoService yoService = new YoService(jpaYoRepository); yoService.updateName(id, name); } @RequestMapping("/add") public void addYo(String name) { YoService yoService = new YoService(jpaYoRepository); yoService.addYo(name); } }
    • 의존성 설정
    • dependencies { implementation project(":domain-jakarta") implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' runtimeOnly 'mysql:mysql-connector-java' }
  • Spring-Boot 2.4.0 를 이용하여 간단하게 웹 서비스 구현

테스트 요청

  • localhost:8080/add?name=hahaha
  • localhost:8080/update?id=1&name=kkkkk

실행 결과

insert 
    into
        yo
        (name, id) 
    values
        (?, ?)

select
        yo0_.id as id1_0_0_,
        yo0_.name as name2_0_0_ 
    from
        yo yo0_ 
    where
        yo0_.id=?

insert 쿼리는 수행되었지만 update는 수행되지 않았음

추가 실험

domain 프로젝트에 api group: 'org.springframework.data', name: 'spring-data-jpa', version: '2.4.1' 추가

cothe.service.YoService@Transactionalorg.springframework.transaction.annotation.Transactional 로 변경

실행 결과

똑같은 결과 나옴

추가 실행

cothe.service 패키지를 web project로 옮김

실행 결과

똑같은 결과 나옴

추가 실행

cothe.service.YoService@Service 애노테이션을 이용해 빈으로 등록

실행 결과

정상 수행됨

결론

@Transactional 은 Spring Container에 속해 있는 빈일 때만 동작한다.

최종 소스

  • domain project
    • 소스코드
    • package cothe.entity; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.Id; @Entity public class Yo { @Id @GeneratedValue private Long id; private String name; protected Yo() { } public void updateName(String name) { this.name = name; } public Yo(String name) { this.name = name; } }
    • 의존성 설정
    • dependencies { api group: 'jakarta.persistence', name: 'jakarta.persistence-api', version: '2.2.3' }
  • Entity와 Transaction을 표현할 수 있는 최소한의 의존성을 가지고 도메인 코드를 작성
  • web project
    • 소스코드
        package cothe.repository.repository;
      
        import cothe.entity.Yo;
        import cothe.service.YoRepository;
        import org.springframework.stereotype.Repository;
      
        import java.util.Optional;
      
        @Repository
        public class JpaYoRepository implements YoRepository {
            private final SpringJpaYoRepository springJpaYoRepository;
      
            public JpaYoRepository(SpringJpaYoRepository springJpaYoRepository) {
                this.springJpaYoRepository = springJpaYoRepository;
            }
      
            @Override
            public Optional<Yo> findById(Long id) {
                return springJpaYoRepository.findById(id);
            }
      
            @Override
            public Yo save(Yo yo) {
                return springJpaYoRepository.save(yo);
            }
        }
        package cothe.service;
      
        import cothe.entity.Yo;
      
        import java.util.Optional;
      
        public interface YoRepository {
            Optional<Yo> findById(Long id);
      
            Yo save(Yo yo);
        }
        package cothe.service;
      
        import cothe.entity.Yo;
        import org.springframework.stereotype.Service;
        import org.springframework.transaction.annotation.Transactional;
      
        @Service
        public class YoService {
            private final YoRepository yoRepository;
      
            public YoService(YoRepository yoRepository) {
                this.yoRepository = yoRepository;
            }
      
            @Transactional
            public void updateName(Long id, String name) {
                Yo yo = yoRepository.findById(id).get();
                yo.updateName(name);
            }
      
            @Transactional
            public void addYo(String name) {
                Yo yo = new Yo(name);
                yoRepository.save(yo);
            }
        }
        package cothe.repository.repository;
      
        import cothe.entity.Yo;
        import org.springframework.data.jpa.repository.JpaRepository;
      
        public interface SpringJpaYoRepository extends JpaRepository<Yo, Long> {
        }
    • package cothe.controller; import cothe.service.YoService; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController public class WebRestController { private final YoService yoService; public WebRestController(YoService yoService) { this.yoService = yoService; } @RequestMapping("/update") public void updateName(Long id, String name) { yoService.updateName(id, name); } @RequestMapping("/add") public void addYo(String name) { yoService.addYo(name); } }
    • 의존성 설정
    • dependencies { implementation project(":domain-jakarta") implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' runtimeOnly 'mysql:mysql-connector-java' }
  • Spring-Boot 2.4.0 를 이용하여 간단하게 웹 서비스 구현
반응형
Comments