코드 그라데이션

shop 구현 (7) 상품 등록 본문

Spring/SpringShop

shop 구현 (7) 상품 등록

완벽한 장면 2023. 7. 13. 10:46

ItemController

@Controller
public class ItemController {

  @GetMapping(value = "/admin/item/new")
  public String itemForm(Model model) {
    model.addAttribute("itemFormDto", new ItemFormDto());
    return "/item/itemForm";
  }
}

복습하자면 Model은  MVC 패턴에서의 Model을 의미하는 게 맞고, 

컨트롤러에서 뷰에 데이터를 전달할 때 Model에 담아서 전달함.

Model은 데이터를 뷰로 전달하기 위해 사용되는 객체. 

컨트롤러에서 데이터를 추가하고, 이를 뷰에 전달할 수 있다.

Map과 똑같다.

 

ItemFormDto

@Getter
@Setter
public class ItemFormDto {

  private Long id;

  @NotBlank(message = "상품명은 필수 입력 값입니다.")
  private String itemNm;

  @NotNull(message = "가격은 필수 입력 값입니다.")
  private Integer price;

  @NotBlank(message = "상품 상세는 필수 입력 값입니다.")
  private String itemDetail;

  @NotNull(message = "재고는 필수 입력 값입니다.")
  private Integer stockNumber;

  private ItemSellStatus itemSellStatus;

  private List<ItemImgDto> itemImgDtoList=new ArrayList<>();

  private List<Long> itemImgIds = new ArrayList<>();

  private static ModelMapper modelMapper = new ModelMapper();

  public Item createItem(){
    return modelMapper.map(this, Item.class);
  }

  public static ItemFormDto of(Item item){
    return modelMapper.map(item,ItemFormDto.class);
  }
}

웹 핸들러에 @Valid 어노테이션과 함께 씀.

결과까지 받고 싶다면... 아래에서 설명!!! ItemController부분

 


ModelMapper

ItemFormDto라고 하면 얘는 Item을 만들기 위한 정보를 담고 있다. 

그러면 지루한 과정이 반드시 선행되어야 하는데, 

ItemDto가 가지고 있는 name을 꺼내서 Item에 넣어주고, ItemDto가 가지고 있는 price를 꺼내서 Item에 넣어주고.......

이 작업을 편하게 해주는 것이 ModelMapper.

 

기본적으로 필드명을 비교!!!

필드명이 같으면 자동으로 얘가 매핑을 시켜준다.

 modelMapper.map(this, Item.class);

 


ItemImg

@Entity
@Table(name = "item_img")
@Getter
@Setter
public class ItemImg extends BaseEntity {

  @Id
  @Column(name = "item_img_id")
  @GeneratedValue(strategy = GenerationType.AUTO)
  private Long id;

  private String imgName;

  private String oriImgName;

  private String imgUrl;

  private String repImgYn;

  @ManyToOne(fetch = FetchType.LAZY)
  @JoinColumn(name = "item_id")
  private Item item;

  public void updateItemImg(String oriImgName, String imgName, String imgUrl){
    this.oriImgName = oriImgName;
    this.imgName = imgName;
    this.imgUrl = imgUrl;
  }
}

하나의 아이템이 여러 개의 ItemImg를 가질 수 있으므로 ManyToOne

아이템과 아이템이미지의 관계는 단방향. 다대일

끝이 One이니까 기본이 Eager. 따라서 LAZY 반드시 적어줘야 한다.

 

 

이건 어떤 요청이 들어왔을 때 해결을 어떻게 할 지를 설정

  // 이건 상대경로를 만드는 것.
  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/images/**") // 이런 요청이 들어왔을 때
        // images로 시작하는 경우 uploadPath에 설정한 폴더를 기준으로 파일을 읽어 오도록 설정
        .addResourceLocations(uploadPath); // 로컬 pc에서 root 경로를 설정.(리소스의 경로는 여기다.)
  }

 

전체 코드는

@Configuration
public class WebMvcConfig implements WebMvcConfigurer  {

  @Value("${uploadPath}") // application.properties 설정한 uploadPath
  String uploadPath;
  //uploadPath = "C:/shop1"
  // images / item /xxx.jpg --> images를 C:/shop1 로 치환.
  //=> 이렇게 하면 실제경로는 C:/shop1/item/xxx.jpg


  // 이건 상대경로를 만드는 것.
  @Override
  public void addResourceHandlers(ResourceHandlerRegistry registry) {
    registry.addResourceHandler("/images/**")
        // images로 시작하는 경우 uploadPath에 설정한 폴더를 기준으로 파일을 읽어 오도록 설정
        .addResourceLocations(uploadPath); // 로컬 pc에서 root 경로를 설정.
  }
}

즉 예를 들어 /images/shop/a.jpg를 하면 

실제 경로는 C:shop1/item/a.jpg

(위에 적혀있는것처럼)

이런 매핑을 해주지 않았다면 그냥 images라는 api를 찾는다.


다시 ItemController 에서의 valid.

의존성 주입을 @AutoWired하는 것보단

@RequiredArgsConstructor + private final 조합으로 쓴다.

 // 새로운 상품 등록
  @PostMapping(value = "/admin/item/new")
  public String itemNew(@Valid ItemFormDto itemFormDto, BindingResult bindingResult, Model model,
                        @RequestParam("itemImgFile") List<MultipartFile> itemImgFileList) {
    if (bindingResult.hasErrors()) {
      return "item/itemForm";
    }
    if (itemImgFileList.get(0).isEmpty() && itemFormDto.getId() == null) {
      model.addAttribute("errorMessage", "첫번째 상품 이미지는 필수 입력 값입니다.");
      return "item/itemForm";
    }
    try {
      itemService.saveItem(itemFormDto, itemImgFileList);
    } catch (Exception e) {
        model.addAttribute("errorMessage", "상품 등록 중 오류가 발생하였습니다.");
        return "item/itemForm";
    }
    return "redirect:/";
  }

 

@Valid ItemFormDto itemFormDto 이렇게 붙이는 것만으로도 ItemDto에 있는 필드들이 검증이 되는 것이고,

검증 결과를 뒤에 BindingResult bindingResult 여기로 받는다.

이것만으로 에러나거나 하는 게 아니라 검증이 되는 것이고, 검증 결과에 따라 어떤 작업을 해야하기 때문에 BindingResult로 받아야 하는 것임.

여기서는 bindingResult를 받은 다음에 아무것도 하지 않고 에러가 있는지를 그냥 물어보는 것. 왜? 검증 결과가 이미 바인딩리절트에 담겨져서 왔으니까.

return "item/itemForm"; 에러가 있으면 여기로 바로 돌려보낸다.

 

그럼 에러가 있으면 에러 메시지 등을 담아서 보내면 좋은데 왜 바로 돌려보낼까?

어딘가, 모델 등에 넣어줄 필요도 사실 없이 그냥 검증 결과가 잘 담긴다. 따로 설정해줄 필요 없이.

그런데 bindingResult가 잡아낸 에러가 아니라 우리가 잡아낸 에러라면,

if (itemImgFileList.get(0).isEmpty() && itemFormDto.getId() == null) <- 어노테이션으로 검증할 수 있는 에러가 아니라면 별도로 우리가 검증하는게 필요.

그렇게 찾아낸 건 별도로 모델에 담아줘야 한다.

 

아래도 마찬가지

try {
      itemService.saveItem(itemFormDto, itemImgFileList);
    } catch (Exception e) {
        model.addAttribute("errorMessage", "상품 등록 중 오류가 발생하였습니다.");
        return "item/itemForm";
    }

아이템 저장할 때 오류가 생기면, 

그냥 item/itemForm으로 돌아가는 게 아니라

model.addAttribute("errorMessage", "상품 등록 중 오류가 발생하였습니다."); 왜 실패했는지 원인이라도 일부러 담아서 같이 보낸다.

 


참고, Repository 본문에 적어주는 기준은, JPA에서 기본으로 제공하는 메서드가 아니고 우리가 의도적으로 만든 기준인 경우 적는다.

public interface ItemImgRepository extends JpaRepository<ItemImg, Long> {

  List<ItemImg> findByItemIdOrderByIdAsc(Long itemId);

}

JPA리포지토리를 눌러보면

package org.springframework.data.jpa.repository;

import java.util.List;

import javax.persistence.EntityManager;

import org.springframework.data.domain.Example;
import org.springframework.data.domain.Sort;
import org.springframework.data.repository.NoRepositoryBean;
import org.springframework.data.repository.PagingAndSortingRepository;
import org.springframework.data.repository.query.QueryByExampleExecutor;

/**
 * JPA specific extension of {@link org.springframework.data.repository.Repository}.
 *
 * @author Oliver Gierke
 * @author Christoph Strobl
 * @author Mark Paluch
 * @author Sander Krabbenborg
 * @author Jesse Wouters
 * @author Greg Turnquist
 */
@NoRepositoryBean
public interface JpaRepository<T, ID> extends PagingAndSortingRepository<T, ID>, QueryByExampleExecutor<T> {

	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.repository.CrudRepository#findAll()
	 */
	@Override
	List<T> findAll();

	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.repository.PagingAndSortingRepository#findAll(org.springframework.data.domain.Sort)
	 */
	@Override
	List<T> findAll(Sort sort);

	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.repository.CrudRepository#findAll(java.lang.Iterable)
	 */
	@Override
	List<T> findAllById(Iterable<ID> ids);

	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.repository.CrudRepository#save(java.lang.Iterable)
	 */
	@Override
	<S extends T> List<S> saveAll(Iterable<S> entities);

	/**
	 * Flushes all pending changes to the database.
	 */
	void flush();

	/**
	 * Saves an entity and flushes changes instantly.
	 *
	 * @param entity entity to be saved. Must not be {@literal null}.
	 * @return the saved entity
	 */
	<S extends T> S saveAndFlush(S entity);

	/**
	 * Saves all entities and flushes changes instantly.
	 *
	 * @param entities entities to be saved. Must not be {@literal null}.
	 * @return the saved entities
	 * @since 2.5
	 */
	<S extends T> List<S> saveAllAndFlush(Iterable<S> entities);

	/**
	 * Deletes the given entities in a batch which means it will create a single query. This kind of operation leaves JPAs
	 * first level cache and the database out of sync. Consider flushing the {@link EntityManager} before calling this
	 * method.
	 *
	 * @param entities entities to be deleted. Must not be {@literal null}.
	 * @deprecated Use {@link #deleteAllInBatch(Iterable)} instead.
	 */
	@Deprecated
	default void deleteInBatch(Iterable<T> entities) {
		deleteAllInBatch(entities);
	}

	/**
	 * Deletes the given entities in a batch which means it will create a single query. This kind of operation leaves JPAs
	 * first level cache and the database out of sync. Consider flushing the {@link EntityManager} before calling this
	 * method.
	 *
	 * @param entities entities to be deleted. Must not be {@literal null}.
	 * @since 2.5
	 */
	void deleteAllInBatch(Iterable<T> entities);

	/**
	 * Deletes the entities identified by the given ids using a single query. This kind of operation leaves JPAs first
	 * level cache and the database out of sync. Consider flushing the {@link EntityManager} before calling this method.
	 *
	 * @param ids the ids of the entities to be deleted. Must not be {@literal null}.
	 * @since 2.5
	 */
	void deleteAllByIdInBatch(Iterable<ID> ids);

	/**
	 * Deletes all entities in a batch call.
	 */
	void deleteAllInBatch();

	/**
	 * Returns a reference to the entity with the given identifier. Depending on how the JPA persistence provider is
	 * implemented this is very likely to always return an instance and throw an
	 * {@link javax.persistence.EntityNotFoundException} on first access. Some of them will reject invalid identifiers
	 * immediately.
	 *
	 * @param id must not be {@literal null}.
	 * @return a reference to the entity with the given identifier.
	 * @see EntityManager#getReference(Class, Object) for details on when an exception is thrown.
	 * @deprecated use {@link JpaRepository#getReferenceById(ID)} instead.
	 */
	@Deprecated
	T getOne(ID id);

	/**
	 * Returns a reference to the entity with the given identifier. Depending on how the JPA persistence provider is
	 * implemented this is very likely to always return an instance and throw an
	 * {@link javax.persistence.EntityNotFoundException} on first access. Some of them will reject invalid identifiers
	 * immediately.
	 *
	 * @param id must not be {@literal null}.
	 * @return a reference to the entity with the given identifier.
	 * @see EntityManager#getReference(Class, Object) for details on when an exception is thrown.
	 * @deprecated use {@link JpaRepository#getReferenceById(ID)} instead.
	 * @since 2.5
	 */
	@Deprecated
	T getById(ID id);

	/**
	 * Returns a reference to the entity with the given identifier. Depending on how the JPA persistence provider is
	 * implemented this is very likely to always return an instance and throw an
	 * {@link javax.persistence.EntityNotFoundException} on first access. Some of them will reject invalid identifiers
	 * immediately.
	 *
	 * @param id must not be {@literal null}.
	 * @return a reference to the entity with the given identifier.
	 * @see EntityManager#getReference(Class, Object) for details on when an exception is thrown.
	 * @since 2.7
	 */
	T getReferenceById(ID id);

	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example)
	 */
	@Override
	<S extends T> List<S> findAll(Example<S> example);

	/*
	 * (non-Javadoc)
	 * @see org.springframework.data.repository.query.QueryByExampleExecutor#findAll(org.springframework.data.domain.Example, org.springframework.data.domain.Sort)
	 */
	@Override
	<S extends T> List<S> findAll(Example<S> example, Sort sort);
}

 

@NoRepositoryBean
public interface PagingAndSortingRepository<T, ID> extends CrudRepository<T, ID> {

	/**
	 * Returns all entities sorted by the given options.
	 *
	 * @param sort the {@link Sort} specification to sort the results by, can be {@link Sort#unsorted()}, must not be
	 *          {@literal null}.
	 * @return all entities sorted by the given options
	 */
	Iterable<T> findAll(Sort sort);

	/**
	 * Returns a {@link Page} of entities meeting the paging restriction provided in the {@link Pageable} object.
	 *
	 * @param pageable the pageable to request a paged result, can be {@link Pageable#unpaged()}, must not be
	 *          {@literal null}.
	 * @return a page of entities
	 */
	Page<T> findAll(Pageable pageable);
}
@NoRepositoryBean public interface CrudRepository<T, ID> extends Repository<T, ID> { /** * Saves a given entity. Use the returned instance for further operations as the save operation might have changed the * entity instance completely. * * @param entity must not be {@literal null}. * @return the saved entity; will never be {@literal null}. * @throws IllegalArgumentException in case the given {@literal entity} is {@literal null}. * @throws OptimisticLockingFailureException when the entity uses optimistic locking and has a version attribute with * a different value from that found in the persistence store. Also thrown if the entity is assumed to be * present but does not exist in the database. */ <S extends T> S save(S entity); /** * Saves all given entities. * * @param entities must not be {@literal null} nor must it contain {@literal null}. * @return the saved entities; will never be {@literal null}. The returned {@literal Iterable} will have the same size * as the {@literal Iterable} passed as an argument. * @throws IllegalArgumentException in case the given {@link Iterable entities} or one of its entities is * {@literal null}. * @throws OptimisticLockingFailureException when at least one entity uses optimistic locking and has a version * attribute with a different value from that found in the persistence store. Also thrown if at least one * entity is assumed to be present but does not exist in the database. */ <S extends T> Iterable<S> saveAll(Iterable<S> entities); /** * Retrieves an entity by its id. * * @param id must not be {@literal null}. * @return the entity with the given id or {@literal Optional#empty()} if none found. * @throws IllegalArgumentException if {@literal id} is {@literal null}. */ Optional<T> findById(ID id); /** * Returns whether an entity with the given id exists. * * @param id must not be {@literal null}. * @return {@literal true} if an entity with the given id exists, {@literal false} otherwise. * @throws IllegalArgumentException if {@literal id} is {@literal null}. */ boolean existsById(ID id); /** * Returns all instances of the type. * * @return all entities */ Iterable<T> findAll(); /** * Returns all instances of the type {@code T} with the given IDs. * <p> * If some or all ids are not found, no entities are returned for these IDs. * <p> * Note that the order of elements in the result is not guaranteed. * * @param ids must not be {@literal null} nor contain any {@literal null} values. * @return guaranteed to be not {@literal null}. The size can be equal or less than the number of given * {@literal ids}. * @throws IllegalArgumentException in case the given {@link Iterable ids} or one of its items is {@literal null}. */ Iterable<T> findAllById(Iterable<ID> ids); /** * Returns the number of entities available. * * @return the number of entities. */ long count(); /** * Deletes the entity with the given id. * <p> * If the entity is not found in the persistence store it is silently ignored. * * @param id must not be {@literal null}. * @throws IllegalArgumentException in case the given {@literal id} is {@literal null} */ void deleteById(ID id); /** * Deletes a given entity. * * @param entity must not be {@literal null}. * @throws IllegalArgumentException in case the given entity is {@literal null}. * @throws OptimisticLockingFailureException when the entity uses optimistic locking and has a version attribute with * a different value from that found in the persistence store. Also thrown if the entity is assumed to be * present but does not exist in the database. */ void delete(T entity); /** * Deletes all instances of the type {@code T} with the given IDs. * <p> * Entities that aren't found in the persistence store are silently ignored. * * @param ids must not be {@literal null}. Must not contain {@literal null} elements. * @throws IllegalArgumentException in case the given {@literal ids} or one of its elements is {@literal null}. * @since 2.5 */ void deleteAllById(Iterable<? extends ID> ids); /** * Deletes the given entities. * * @param entities must not be {@literal null}. Must not contain {@literal null} elements. * @throws IllegalArgumentException in case the given {@literal entities} or one of its entities is {@literal null}. * @throws OptimisticLockingFailureException when at least one entity uses optimistic locking and has a version * attribute with a different value from that found in the persistence store. Also thrown if at least one * entity is assumed to be present but does not exist in the database. */ void deleteAll(Iterable<? extends T> entities); /** * Deletes all entities managed by the repository. */ void deleteAll(); }

 

이런 것들이 기본 기준으로 제공되고 있음.

어떤 엔티티이든 간에 공통으로 적용될 수 있는 것들만 기본 메서드로 제공.

 


ItemService와 FileService를 분리해서(아이템서비스에 파일저장까지 다 때려박으렴 로직이 너무 난잡하니까.)

아이템이미지를 저장할 때는 fileService를 이용만 하도록

@Service
@Log
public class FileService {
  // 이미지 파일이 실제 저장소에 생성

  // byte[] fileData 이게 실제 데이터
  public String uploadFile(String uploadPath, String originalFileName, byte[] fileData) throws Exception {
    UUID uuid = UUID.randomUUID(); //랜덤으로 UUID를 생성
    // 파일명을 의도적으로 변경(랜덤으로 만들어내니까 해킹에서 조금 더 보호)
    String extension = originalFileName.substring(originalFileName.lastIndexOf("."));
    String savedFileName = uuid.toString() + extension;
    String fileUploadFullUrl = uploadPath+ "/" + savedFileName;
    System.out.println(fileUploadFullUrl);

    FileOutputStream fos = new FileOutputStream(fileUploadFullUrl);
    fos.write(fileData);
    fos.close();
    return savedFileName;
  }

  public void deleteFile(String filePath) throws Exception {
    File deleteFile = new File(filePath);
    if (deleteFile.exists()) {
      deleteFile.delete();
      log.info("파일을 삭제하였습니다.");
    } else {
      log.info("파일이 존재하지 않습니다.");
    }
  }
}
@Service
@RequiredArgsConstructor
@Transactional
public class ItemImgService {

  @Value("${itemImgLocation}")
  private String itemImgLocation;

  private final ItemImgRepository itemImgRepository;
  private final FileService fileService;

  public void saveItemImg(ItemImg itemImg, MultipartFile itemImgFile) throws Exception {

    String oriImgName = itemImgFile.getOriginalFilename(); // 오리지널 이미지 경로
    String imgName ="";
    String imgUrl = "";
    System.out.println(oriImgName); // 뉴진스.jpg

    //파일 업로드
    if(!StringUtils.isEmpty(oriImgName)) { //oriImgName이 문자열로 비어 있지 않으면 실행
      System.out.println("******");
      imgName = fileService.uploadFile(itemImgLocation, oriImgName, itemImgFile.getBytes());
      System.out.println(imgName);
      imgUrl = "/images/item/" + imgName;
    }
    System.out.println("1111");
    // 상품 이미지 정보 저장
    // oriImgName : 상품 이미지 파일의 오리지널 이름.
    // imgName : 실제 로컬에 저장된 상품 이미지 파일의 이름.
    // imgUrl : 로컬에 저장된 상품 이미지 파일을 불러오는 경로.
    itemImg.updateItemImg(oriImgName, imgName, imgUrl);
    System.out.println("(((((");
    itemImgRepository.save(itemImg);
  }
}

imgName = fileService.uploadFile(itemImgLocation, oriImgName, itemImgFile.getBytes()); 이렇게 이용만!

 


테스트에 트랜잭셔널이 붙으면 디폴트 : 롤백

일반 코드에서는 일괄 커밋

 

왜 트랜잭션으로 묶어야 하는가?

@Service
@Transactional
@RequiredArgsConstructor
public class ItemService {

  private final ItemRepository itemRepository;
  private final ItemImgService itemImgService;
  private final ItemImgRepository itemImgRepository;

  public Long saveItem(ItemFormDto itemFormDto, List<MultipartFile> itemImgFileList)
      throws Exception {
    // 상품 등록
    Item item = itemFormDto.createItem();
    itemRepository.save(item);

    //이미지 등록
    for (int i = 0; i < itemImgFileList.size(); i++) {
      ItemImg itemImg = new ItemImg();
      itemImg.setItem(item);

      if (i == 0)
        itemImg.setRepImgYn("Y"); // 대표이미지
      else
        itemImg.setRepImgYn("N"); // 나머지 이미지

      // 같은 명령단(한 줄)이면 중괄호 생략해도 된다.
      itemImgService.saveItemImg(itemImg, itemImgFileList.get(i));
    }
    return item.getId();
  }
}

 

만약 이러한 상황에서 item은 DB에 저장되고 이미지는 저장이 안 될 수도 있다.

-> 이 경우는 정상적으로 저장되었다고 보기 어렵다.

이러한 경우 부분적으로 무언가만 되게 하는 것보다는 일괄적으로 처리하게 로그를 남기는 게 더 낫다.

 


이미지 경로 키 / 값으로 읽어오기

spring.servlet.multipart.maxFileSize=20MB
spring.servlet.multipart.maxRequestSize=100MB
itemImgLocation=C:/shop1/item
uploadPath=file:///C:/shop1/
@Value("${uploadPath}") // application.properties 설정한 uploadPath
  String uploadPath;

이렇게 읽어올 수 있다는 것.

 


테스트

MockMultipartFile => 가짜 파일 만들기

@SpringBootTest
@Transactional
@TestPropertySource(locations = "classpath:application-test.properties")
class ItemServiceTest {

  @Autowired
  ItemService itemService;

  @Autowired
  ItemRepository itemRepository;

  @Autowired
  ItemImgRepository itemImgRepository;

  List<MultipartFile> createMultipartFiles() throws Exception{

    List<MultipartFile> multipartFileList = new ArrayList<>();

    for(int i=0;i<5;i++){
      String path = "C:/shop/item/";
      String imageName = "image" + i + ".jpg";
      MockMultipartFile multipartFile =
          new MockMultipartFile(path, imageName, "image/jpg", new byte[]{1,2,3,4});
          //1, 2, 3, 4로 넣음으로서 멀티파트 파일을 강제로 만듦.
      multipartFileList.add(multipartFile);
    }

    return multipartFileList;
  }

  @Test
  @DisplayName("상품 등록 테스트")
  @WithMockUser(username = "admin", roles = "ADMIN")
  void saveItem() throws Exception {
    ItemFormDto itemFormDto = new ItemFormDto();
    itemFormDto.setItemNm("테스트상품");
    itemFormDto.setItemSellStatus(ItemSellStatus.SELL);
    itemFormDto.setItemDetail("테스트 상품 입니다.");
    itemFormDto.setPrice(1000);
    itemFormDto.setStockNumber(100);

    List<MultipartFile> multipartFileList = createMultipartFiles();
    Long itemId = itemService.saveItem(itemFormDto, multipartFileList);
    // DB에 적재
    List<ItemImg> itemImgList = itemImgRepository.findByItemIdOrderByIdAsc(itemId);

    Item item = itemRepository.findById(itemId)
        .orElseThrow(EntityNotFoundException::new);

    assertEquals(itemFormDto.getItemNm(), item.getItemNm());
    assertEquals(itemFormDto.getItemSellStatus(), item.getItemSellStatus());
    assertEquals(itemFormDto.getItemDetail(), item.getItemDetail());
    assertEquals(itemFormDto.getPrice(), item.getPrice());
    assertEquals(itemFormDto.getStockNumber(), item.getStockNumber());
    assertEquals(multipartFileList.get(0).getOriginalFilename(), itemImgList.get(0).getOriImgName());
  }

}

여기서

      MockMultipartFile multipartFile =
          new MockMultipartFile(path, imageName, "image/jpg", new byte[]{1,2,3,4});

가짜파일의 경로, 이름, 형식, 값 등을 넣어주면 가짜파일이 알아서 생성.

 

왜 DB에 적재 라는 표현을 썼는가.

 Long itemId = itemService.saveItem(itemFormDto, multipartFileList); 요기서 DB에 저장이 되는가?

 

이 상황에서 JPA만 혼자 알고 있다. 1차캐시에만 두고 반영을 안한다.

언제 하느냐? 트랜잭션이 끝날 때 일괄 반영.

 

    Item item = itemRepository.findById(itemId)
        .orElseThrow(EntityNotFoundException::new); 여기서도 1차캐시를 조회해서 그걸 가져다준다.

 


상품 등록 시퀀스

 

728x90
Comments