주문목록검색 취소

1. OrderController 클래스에서

 

@GetMapping 작성

@PostMapping작성

사진

 

2. OrderList.html 파일 작성

 

 

3. 특정이름으로 검색이

마주한 에러 및 문제해결

-OrderRepositoryJPA Criteria 구문 작성

-OrderService 클래스에 findAllCriteria로 변경

 

실습

화면출력

정리

1.orderList.html파일

-검색조건 양식(form)이 있어야 한다.

 

회원명 구문

주문상태 구문

검색 구문

->검색 버튼을 누르면

 

위에 구문에서 작성하고 선택한 옵션값들을 보낸 값들이

OrderController 클래스에 OrderSearchmemberNameOrderStatus에 바인딩이 된다.

다시 OrderController에 와서 바인딩 된 상태로 return에 지정한 order/orderList로 넘어가게 된다.

 

enum typeorder,cancel을 둘다 값을 OrderStatus 루프를 통해 뿌리고 select를 해야 한다.

타임리프의 문법을 사용해서 값을 뿌릴 수 있다.

, enum에 잇는 values를 가져와서 값을 뿌리게 된다.

 

 

<tr th:each="item : ${orders}"> 구문 설명

<td th:text="${item.id}"></td>

<td th:text="${item.member.name}"></td>

<td th:text="${item.orderItems[0].item.name}"></td>

<td th:text="${item.orderItems[0].orderPrice}"></td>

<td th:text="${item.orderItems[0].count}"></td>

<td th:text="${item.status}"></td>

<td th:text="${item.orderDate}"></td>

<td>

 

-orders를 주문으로 돌려서 나오게 한다.

 

 

<a th:if="${item.status.name() == 'ORDER'}" 구문설명

href="#" th:href="'javascript:cancel('+${item.id}+')'"

class="btn btn-danger">CANCEL</a>

 

-상태가 order이면 cancel버튼이 나오도록 자바 스크립트로 작성을 했다.

cancel을 누를 시에 자바스크립트 캔슬이 호출되어 하기구문으로 넘어간다.

 

<script>

function cancel(id) {

var form = document.createElement("form");

form.setAttribute("method", "post");

form.setAttribute("action", "/orders/" + id + "/cancel");

document.body.appendChild(form);

form.submit();

}

</script>

 

- 구문에서 form 내에 해당하는 내용들을 위해 구문작성을 만들어야 한다.

 

-@PostMapping 방식으로 cancelOrder을 받아와서

redirect으로 화면에 cancel상태의 화면을 출력한다.

 

 

cancel버튼클릭시 화면 및 cancel상태의 목록검색

 

특정이름없이 전체목록출력

상품주문

1. OrderController 클래스 생성 및 세팅

 

사진

 

화면출력

 

*번외로 알아본것들

ordercontroller에서 memberid를 직접찾아서 보내면 되지 않을까에 대한 고민들

=>controller 로직도 지저분해진다.

 

커맨드성

외부에서 식별자만 넘기고

서비스에서 엔티티를 찾는 것부터 거기서 하면

엔티티의 값들도 엔티티를 조회해야 영속상태로 진행을 한다.

 

가급적이면 핵심비즈니스 로직이 있는 경우엔

밖에서 엔티티를 찾아서 넣는 것 보다 식별자만 넘겨주고

핵심비즈니스 로직을 안에서 찾게 되면 영속성 컨텍스트가 존재하는 상태에서 조회가 가능하다.

주문하면서 멤버가 바뀌게 되더라도 더티체킹이 되어 자연스럽게 적용이 된다.

밖에서 가지고 오게 되면 트랜잭션 없이 이루어지는 것이라 더티체킹이 되지 않는다.

 

마주한문제

상품주문버튼 클릭시 에러

코드수정

 

에러가 발생하지 않는다.

상품수정

 

등록이나 조회는 쉬운편이지만, 수정은 복잡하다.

jpa에서 어떠한 방법으로 수정하는 것이 좋을 것인지(정석적인지)에 대해

고민해서 선택해야 한다. 방법중엔 변경감지, 병합이라는 방법이 있다고 한다.

 

jpa의 가이드는 변경감지를 best practice라고 권한다.

 

사진(item controller 수정구문)

 

updateItemForm.html 파일

-createItemForm 파일과의 차이점은 기존에 있는 데이터를 보유하고 있다의 정도이다.

 

*form-control : formcontrol로 명시할 경우 form이 제대로 설정이 되지 않는다.

 

사진

 

사진(수정버튼)

사진(수정화면)

 

*itemId 취약점

-브라우저창에서 상품수정시에 format이 넘어올 때에 인위적으로 아이디를 조작해서 넘길 수 있다.

 서비스계층이든 뒷단이든 유저가 item에 대해 권한이 있는지 체크하는 로직이 있어야 한다.

 

 

2. 변경감지와 병합

JPA에서 변경감지를 모르면 시간을 많이 낭비할 수 있다.

 

JPA기본 메커니즘

a. JPA값 변경시 예제

 

@RunWith(SpringRunner.class)

@SpringBootTest

public class ItemUpdateTest {

 

@Autowired

EntityManager em;

 

@Test

public void updateTest() throws Exception{

Book book = em.find(Book.class, primarykey:1L); //1번 데이터베이스에 있다고 가정한다.

 

//트랜잭션(TX)

book.setName("asdfasdf");

->트랜잭션안에서 이름을 변경하고 트랜잭션 커밋할때에 JPA가 자동으로 변경된구문을 찾아서

업데이트 쿼리를 자동 생성해서 데이터베이스에 생성한다.

이것을 변경감지 == dirty checking이라고 한다.

이 매커니즘을 기본으로 jpa 의 엔티티를 바꿀 수 있다.

 

이것과 비슷한 예제가 했었던 실습구문에 있다.

setStatus의 상태만 바꾸고 jpa가 트랜잭션 커밋시점에 바뀐 것을 찾아서 업데이트 쿼리를 이용해 디비에 전달하여 커밋을 완료한다. 엔티티매니저 merge와 같은 행위를 따로 해주지 않아도 된다.

 

정리

-엔티티가 영속성상태로 관리 되면 값만 바꾸면 변경된 JPA가 트랜잭션 커밋시점에 변경된값을 알고,

 디비에 반영시킨다.

 

문제는 준영속 엔티티를 이해해야 한다.

-jpa 영속성 컨텍스트가 더 이상 관리하지 않는 엔티티를 말한다.

 

새로운 객체는 새로운 객체이지만 아이디는 세팅이 된 상태이다.

JPA에 작업을 한번 거친 식별자(itemId)가 있는 것은 준영속 엔티티라고 한다.

데이터베이스에 들어갔다 나온 데이터라 JPA가 식별할 수 있는 아이디를 가지고 있다.

여기에서 준영속 엔티티는 book이다.

포트폴리에서는 내가 직접 만든 new Book이고, JPA가 관리하지 않는 준영속 엔티티이다.

jpa 영속성 컨텍스트가 관리하는 엔티티는 변경된 것을 알지만 준영속 엔티티는 그렇지 않다.

아무리 준영속엔티티에 정보를 변경해도 변경이 되지 않는다.

 

어떻게 하면 준영속 엔티티를 수정할 수 있을까에 대해 알아보기로 한다.

 

준영속 엔티티를 수정하는 2가지 방법

-변경감지기능사용(dirty checking)

-병합(merge)사용

 

변경감지 기능 사용

@Transactional

void update(Item itemParam) { //itemParam: 파라미터로 넘어온 준영속 상태엔티티이다.

 

Item findItem = em.find(Item.class, itemParam.getId()); 다시 동일한 엔티티를 조회한다.

 

findItem.setPrice(itemParam.getPrice()); //데이터를 수정한다.

}

 

== 비슷한 예제

@Transactional

public void updateItem(Long itemId, Book param) {

 

private final ItemRepository itemRepository;

 

@Transactional

public void saveItem(Item item) {

itemRepository.save(item);

}

 

@Transactional

public void updateItem(Long itemId, Book param) {

//파라미터로 넘어온 준영속 상태엔티티이다.

 

Item findItem = itemRepository.findOne(itemId);

//다시 동일한 엔티티를 조회한다.

 

findItem.setPrice(param.getPrice());

findItem.setName(param.getName());

findItem.setStockQuantity(param.getStockQuantity());

//데이터를 수정한다.

}

 

 

설명

-@Transactional 안에서 파라미터로 넘어온 준영속 상태엔티티를 부른다.

-다시 동일한 엔티티를 조회한다.(findItem)

-넘어온 준영속상태의 엔티티의 값을 다시 재세팅(변경/수정)을 한다.

-트랜잭션이 커밋할 시에, flush를 보내어, 변경된 값을 찾아( 변경감지 : dirty checking)내어 감지한다.

-데이터베이스 update sql 실행하여 변경된 값이 전달 된다.(데이터를 조회 한다.)

 

->이것이 더 나은 수정방법이다.

 

 

2. 병합방법

-포트폴리오에 사용한 방법은 병합방법이다.

-준영속 상태 엔티티를 영속 상태로 변경해주고자 할 때 사용하는 기능 방법이다.

 

예제1)

@Transactional

vodi update(Item itemParam) { //itemParam : 파라미터로 넘어온 준영속 상태의 엔티티

Item mergeItem = em.merge(item);

}

 

병합 : 기존에 있는 엔티티

병합 동작 방식 정리

1. 준속 엔티티의 식별자 값으로 속 엔티티를 조회한다.

2. 속 엔티티의 값을 준속 엔티티의 값으로 모두 교체한다.(병합한다.)

3. 트랜잭션 커밋 시점에 변경 감지 기능이 동작해서 데이터베이스에 UPDATE SQL이 실행

 

주의

-변경 감지 기능을 사용하면 원하는 속성만 선택해서 변경할 수 있지만,

 병합을 사용하면 모든 속성이 변경된다. 병합시 값이 없으면 null로 업데이트 할 위험도 있다.

 (병합은 모든 필드를 교체한다.) -> 그래서 실무에서 병합기능은 위험하다.

 

예를들어

 

Book book = new Book();

book.setId(form.getId());

book.setName(form.getName());

book.setPrice(form.getPrice()); -> 이부분을 빠뜨리면 pricenull로 업데이트가 된다. book.setStockQuantity(form.getStockQuantity());

book.setAuthor(form.getAuthor());

book.setIsbn(form.getIsbn());

 

itemService.saveItem(book);

return "redirect:/items";

}

 

*자세한 원리는 매뉴얼에 보면 있다.

 

유지보수성 측면에서

@Transactional

public void updateItem(Long itemId, Book param) {

//파라미터로 넘어온 준영속 상태엔티티이다.

 

Item findItem = itemRepository.findOne(itemId);

//다시 동일한 엔티티를 조회한다.

//findItem.change(name, price, stockQuantity);

->이렇게 해주어야 change만 확인하면 어디가 변경된지 쉽게 추적할 수있다.

 

findItem.setPrice(param.getPrice());

findItem.setName(param.getName());

findItem.setStockQuantity(param.getStockQuantity());

//데이터를 수정한다.

}

 

가장 좋은 해결 방법

엔티티를 변경할 때는 항상 변경 감지를 사용할 것.

컨트롤러에서 어설프게 엔티티를 생성하지 마세요.

트랜잭션이 있는 서비스 계층에 식별자(id)와 변경할 데이터를 명확하게 전달할것.

+ Recent posts