작성
·
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 에서 확인용 초기 데이터를 추가해줄 때 에러가 발생하는 것을 말씀해주신 것 같습니다!
엔티티 매니저의 persist는 영속성 컨텍스트 안에서 작동해야 하고 영속성 컨텍스트는 트랜잭션 내에 있어야 합니다!
그런데 Repository에 트랜잭션을 걸어주지 않는다면 해당 itemRepository.save()를 호출할 때 em.persist()는 트랜잭션에 없고, 영속성 컨텍스트 내부에 있는 게 아니기 때문에 에러가 발생하고 있습니다!
0
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);
}
}
감사합니다 확인해보겠습니다!