트랜잭션 서비스 계층에서의 문제
816
작성한 질문수 60
트랜잭션을 리포지토리 말고 서비스 계층에 달면 문제가 생기네요ㅠㅠ
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 에서 확인용 초기 데이터를 추가해줄 때 에러가 발생하는 것을 말씀해주신 것 같습니다!
엔티티 매니저의 persist는 영속성 컨텍스트 안에서 작동해야 하고 영속성 컨텍스트는 트랜잭션 내에 있어야 합니다!
그런데 Repository에 트랜잭션을 걸어주지 않는다면 해당 itemRepository.save()를 호출할 때 em.persist()는 트랜잭션에 없고, 영속성 컨텍스트 내부에 있는 게 아니기 때문에 에러가 발생하고 있습니다!
0
안녕하세요. 리어스리님, 공식 서포터즈 y2gcoder입니다.
번거롭게 해드려 정말 죄송합니다!
실제 동작하는 전체 프로젝트를 압축해서 구글 드라이브로 공유해서 링크를 남겨주세요.
구글 드라이브 업로드 방법은 다음을 참고해주세요.
주의: 업로드시 링크에 있는 권한 문제 꼭 확인해주세요
추가로 다음 내용도 코멘트 부탁드립니다.
1. 문제 영역을 실행할 수 있는 방법
2. 문제가 어떻게 나타나는지에 대한 상세한 설명
링크: 공식 서포터즈
링크: 자주하는 질문
감사합니다.
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);
}
}
RepositoryTest의 패키지 위치가 domain인 이유
0
29
2
REQUIRES_NEW 해결 방법에 대해서 질문있습니다!!
0
29
1
update()에 사용하는 setter 질문드립니다.
0
47
1
SQL 중심적 개발의 문제점에 대한 질문
0
72
1
혹시 Containing 을 안쓰신 이유가 있을까요?
0
83
2
[공유] 스프링부트 4.x 버전 mybatis 연동
0
173
1
@repository 어노테이션
0
89
3
ItemService
0
58
1
논리 커밋, 물리 커밋 질문드립니다.
0
54
1
내부 트랜잭션 커밋은 필수인가요?
0
57
1
프록시 커넥션 객체를 반환할 때 생성하는건가요?
0
54
1
Transaction readOnly 성능 개선 (김영한님의 대한 감사인사)
2
178
2
JPQL 대신 네이티브 쿼리를 사용해야 하는 경우
0
77
1
@EventListener(ApplicationReadyEvent.class) 관련
0
88
1
트랜잭션 동기화 매니저와 데이터 소스
0
76
1
DB 관련 강의 개설 계획은 없으신건가요?
0
133
2
물리 트랜잭션 과 논리트랜잭션 용어를 맞게 이해한걸까요
0
94
1
스프링 3 버전 이상 rollbackFor 변경된듯요
1
112
1
트랜잭션 전파 질문.
0
87
1
프로젝트 오픈 에러
0
126
1
외부 트랜잭션에서 isNewTransaction이 false로 나오는거에 대해 질문드립니다
0
83
2
같은 스레드를 사용하면 트랜잭션 동기화 매니저는 같은 커넥션을 반환
0
72
1
h2 인메모리 테스트중 예약어 충돌날 경우 대처방법
0
102
1
커스텀aop와 트랜잭션을 같이 사용할때 우선순위에 관한 질문
0
98
2





