인프런 커뮤니티 질문&답변

플하님의 프로필 이미지
플하

작성한 질문수

스프링 DB 2편 - 데이터 접근 활용 기술

JPA 적용1 - 개발

트랜잭션 서비스 계층에서의 문제

작성

·

581

0

트랜잭션을 리포지토리 말고 서비스 계층에 달면 문제가 생기네요ㅠㅠ

org.springframework.dao.InvalidDataAccessApiUsageException: No EntityManager with actual transaction available for current thread - cannot reliably process 'persist' call; nested exception is javax.persistence.TransactionRequiredException: No EntityManager with actual transaction available for current thread - cannot reliably process 'persist' call

 

서비스 계층서 클래스레벨에 트랜잭션 달고 실행시 이렇게 ..나오는데 레포지토리에달면 잘 동작하는데.. 서비스 계층에 달면 왜그런걸까요..

답변 11

0

보내주신 코드 살펴보았습니다!

아마도 메인 클래스 실행 시

TestDataInit 에서 확인용 초기 데이터를 추가해줄 때 에러가 발생하는 것을 말씀해주신 것 같습니다!

image엔티티 매니저의 persist는 영속성 컨텍스트 안에서 작동해야 하고 영속성 컨텍스트는 트랜잭션 내에 있어야 합니다!
그런데 Repository에 트랜잭션을 걸어주지 않는다면 해당 itemRepository.save()를 호출할 때 em.persist()는 트랜잭션에 없고, 영속성 컨텍스트 내부에 있는 게 아니기 때문에 에러가 발생하고 있습니다!

플하님의 프로필 이미지
플하
질문자

감사합니다 확인해보겠습니다!

0

플하님의 프로필 이미지
플하
질문자

프로젝트 전체 코드올립니다 ~~

0

안녕하세요. 리어스리님, 공식 서포터즈 y2gcoder입니다.

번거롭게 해드려 정말 죄송합니다!

실제 동작하는 전체 프로젝트를 압축해서 구글 드라이브로 공유해서 링크를 남겨주세요.

구글 드라이브 업로드 방법은 다음을 참고해주세요.

https://bit.ly/3fX6ygx


주의: 업로드시 링크에 있는 권한 문제 꼭 확인해주세요


추가로 다음 내용도 코멘트 부탁드립니다.

1. 문제 영역을 실행할 수 있는 방법

2. 문제가 어떻게 나타나는지에 대한 상세한 설명


링크: 공식 서포터즈

링크: 자주하는 질문

감사합니다.

0

플하님의 프로필 이미지
플하
질문자

왜그런걸가요..ㅠㅠ

0

플하님의 프로필 이미지
플하
질문자

spring.profiles.active=local

#spring.datasource.url=jdbc:h2:tcp://localhost/~/test
spring.datasource.url=jdbc:h2:mem:test2
spring.datasource.username=sa
#spring.datasource.password=
#이렇게 설정만 하면 스프링 부트가 해당 설정을 사용해서 커넥션 풀과 DataSource , 트랜잭션 매니저를 스프링빈으로 자동 등록한다.

#jdbcTemplate sql log
logging.level.org.springframework.jdbc=debug

#MyBatis
mybatis.type-aliases-package=hello.itemservice.domain
#마이바티스에서 타입 정보를 사용할 때는 패키지 이름을 적어주어야 하는데, 여기에 명시하면 패키지 이름을 생략할 수 있다.
#지정한 패키지와 그 하위 패키지가 자동으로 인식된다
mybatis.configuration.map-underscore-to-camel-case=true
logging.level.hello.itemservice.repository.mybatis=trace
#쿼리 로그 출력

#JPA log
logging.level.org.hibernate.SQL=DEBUG
#하이버네이트가 생성하고 실행하는 SQL 확인할수있음
#spring.jpa.show-sql=true
#이 설정은 System.out 콘솔을 통해서 SQL이 출력된다. 따라서 이 설정은 권장하지는 않는다. (
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE
#SQL에 바인딩되는 파라미터 확인

 

0

플하님의 프로필 이미지
플하
질문자

package hello.itemservice;

import hello.itemservice.config.*;
import hello.itemservice.repository.ItemRepository;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.Profile;
import org.springframework.jdbc.datasource.DriverManagerDataSource;
import javax.sql.DataSource;

//@Import(MemoryConfig.class)//MemoryConfig 를 설정 파일로 사용한다.
//@Import(JdbcTemplateV1Config.class)
//@Import(JdbcTemplateV2Config.class)
//@Import(JdbcTemplateV3Config.class)
//@Import(MyBatisConfig.class)
@Import(JpaConfig.class)
@Slf4j
@SpringBootApplication(scanBasePackages = "hello.itemservice.web")//이패키지 이하만 컴포넌트 스캔하고 나머지 수동등록
public class ItemServiceApplication {

   public static void main(String[] args) {
      SpringApplication.run(ItemServiceApplication.class, args);
   }

   @Bean
   @Profile("local")//특정 프로필의 경우에만 해당 스프링 빈을 등록한다
   public TestDataInit testDataInit(ItemRepository itemRepository) {
      return new TestDataInit(itemRepository);
   }

   /*@Bean
   @Profile("test")
   public DataSource dataSource() {
      log.info("메모리 데이터베이스 초기화");
      DriverManagerDataSource dataSource = new DriverManagerDataSource();
      dataSource.setDriverClassName("org.h2.Driver");
      dataSource.setUrl("jdbc:h2:mem:db;DB_CLOSE_DELAY=-1");
      dataSource.setUsername("sa");
      dataSource.setPassword("");
      return dataSource;
   }*/
}

 

0

플하님의 프로필 이미지
플하
질문자

package hello.itemservice.config;

import hello.itemservice.repository.ItemRepository;
import hello.itemservice.repository.jpa.JpaItemRepository;
import hello.itemservice.service.ItemService;
import hello.itemservice.service.ItemServiceV1;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.persistence.EntityManager;

@Configuration
public class JpaConfig {

    private final EntityManager em;

    public JpaConfig(EntityManager em) {
        this.em = em;
    }

    @Bean
    public ItemService itemService() {
        return new ItemServiceV1(itemRepository());
    }

    @Bean
    public ItemRepository itemRepository() {
        return new JpaItemRepository(em);
    }

}

0

플하님의 프로필 이미지
플하
질문자

package hello.itemservice.repository.jpa;

import hello.itemservice.domain.Item;
import hello.itemservice.repository.ItemRepository;
import hello.itemservice.repository.ItemSearchCond;
import hello.itemservice.repository.ItemUpdateDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils;
import javax.persistence.EntityManager;
import javax.persistence.TypedQuery;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

@RequiredArgsConstructor
@Slf4j
@Repository
public class JpaItemRepository implements ItemRepository {

    private final EntityManager em;
    //em이 jpa나 마찬가지, datasource, 트랙잭션 관련 모든 과정 들어감


    @Override
    public Item save(Item item) {
        em.persist(item);
        return item;
    }

    @Override
    public void update(Long itemId, ItemUpdateDto updateParam) {
        Item findItem = em.find(Item.class, itemId);
        findItem.setItemName(updateParam.getItemName());
        findItem.setPrice(updateParam.getPrice());
        findItem.setQuantity(updateParam.getQuantity());
        //트랜잭션 커밋될때 업데이트 쿼리 날림
    }

    @Override
    public Optional<Item> findById(Long id) {
        Item item = em.find(Item.class, id);
        return Optional.ofNullable(item);//혹시 null일수 있으니까
    }

    @Override
    public List<Item> findAll(ItemSearchCond cond) {
        String jpql = "select i from Item i";

        Integer maxPrice = cond.getMaxPrice();
        String itemName = cond.getItemName();

        if (StringUtils.hasText(itemName) || maxPrice != null) {
            jpql += " where";
        }

        boolean andFlag = false;
        List<Object> param = new ArrayList<>();
        if (StringUtils.hasText(itemName)) {
            jpql += " i.itemName like concat('%',:itemName,'%')";
            param.add(itemName);
            andFlag = true;
        }

        if (maxPrice != null) {
            if (andFlag) {
                jpql += " and";
            }
            jpql += " i.price <= :maxPrice";
            param.add(maxPrice);
        }

        log.info("jpql={}", jpql);

        TypedQuery<Item> query = em.createQuery(jpql, Item.class);
        if (StringUtils.hasText(itemName)) {
            query.setParameter("itemName", itemName);
        }
        if (maxPrice != null) {
            query.setParameter("maxPrice", maxPrice);
        }
        return query.getResultList();
    }
}

 

 

0

플하님의 프로필 이미지
플하
질문자

package hello.itemservice.service;

import hello.itemservice.domain.Item;
import hello.itemservice.repository.ItemRepository;
import hello.itemservice.repository.ItemSearchCond;
import hello.itemservice.repository.ItemUpdateDto;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;

@Slf4j
@Service
@Transactional
@RequiredArgsConstructor//사실 서비스는 인터페이스 구현 잘안함 바뀔일이 없기 때문에 하지만 여기선 v1,2로 이어지기 때문
public class ItemServiceV1 implements ItemService {//단순한 위임뿐

    private final ItemRepository itemRepository;


    @Override
    public Item save(Item item) {
        return itemRepository.save(item);
    }

    @Override
    public void update(Long itemId, ItemUpdateDto updateParam) {
        itemRepository.update(itemId, updateParam);
    }

    @Override
    public Optional<Item> findById(Long id) {
        return itemRepository.findById(id);
    }

    @Override
    public List<Item> findItems(ItemSearchCond cond) {
        log.info("itemRepositoryclass={}",itemRepository.getClass());
        return itemRepository.findAll(cond);
    }
}

 

0

안녕하세요. 리어스리님, 공식 서포터즈 y2gcoder입니다.

서비스 코드와 리포지토리 코드를 보여주시겠습니까?
서비스 에서 @Transactional 을 열어주면 보통 위에 적어주신 예외는 발생하지 않는 것이 맞습니다!

감사합니다.

플하님의 프로필 이미지
플하

작성한 질문수

질문하기