[Spring_Boot] 프로젝트 설정, JPA SHOP - Member & Validation Check

최현석·2022년 12월 12일
0

Spring_Boot

목록 보기
27/31


🧩 엔티티 클래스 설계

🧩 애플리케이션 아키텍처

  • controller, web : 웹 계층
  • service : 비지니스 로직, 트랜잭션 처리
  • repository : JPA를 직접 사용하는 계층, 엔티티 매니저 사용
  • domain : 엔티티가 모여있는 계층, 모든 계층에서 사용가능

🧩 Domain

Member

@Entity
@Getter @Setter
public class Member {
	
	@Id @GeneratedValue
	@Column(name = "member_id")
	private Long id;
	private String name;
	
	@Embedded
	private Address address;
	
	@OneToMany(mappedBy = "member")
	private List<Order> orders = new ArrayList<Order>();
	
}

Address

  • 파라미터 3개가 있는 생성자를 통해서만 생성가능, Getter 획득만 가능하게
@Embeddable
@Getter
public class Address {

	private String city;
	private String street;
	private String zipcode;
    
	public Address(String city, String street, String zipcode) {
		super();
		this.city = city;
		this.street = street;
		this.zipcode = zipcode;
	}
	
	// jpa스펙상 만들어 놓은 기본 생성자
	// 함부로 new를 통해서 생성하지 못하도록 한다.
	protected Address() {}
}

Order

@Entity
@Getter @Setter
@Table(name = "orders")
public class Order {

	@Id @GeneratedValue
	@Column(name="order_id")
	private Long id;
	
	@ManyToOne
	@JoinColumn(name = "member_id")
	private Member member;
	private LocalDateTime orderDate;
	
	@OneToMany(mappedBy = "order")
	private List<OrderItem> orderItems
		= new ArrayList<OrderItem>();
	
	// 주문상태(ORDER, CANCEL) -> Enum
	@Enumerated(EnumType.STRING)
	private OrderStatus orderStatus;
	
	//////// 연관관계 메서드
	public void setMember(Member member) {
		this.member = member;
		member.getOrders().add(this);
		System.out.println(this);
	}
	
	public void addOrderItem(OrderItem orderItem) {
		orderItems.add(orderItem);
		orderItem.setOrder(this);
	}
	//////// 연관관계 메서드
	
}
////////////////////////////////////////////


OrderStatus
// 주문 상태
public enum OrderStatus {
	ORDER, CANCEL
}

OrderItem

@Entity
@Getter @Setter
public class OrderItem {

	@Id @GeneratedValue
	@Column(name = "order_item_id")
	private Long id;
	
	@ManyToOne
	@JoinColumn(name = "order_id")
	private Order order;
	
	@ManyToOne
	@JoinColumn(name = "item_id")
	private Item item;
	
	private int orderPrce; 	// 주문가격
	private int count;		// 주문수량
	
	
}

Item

@Entity
@Getter @Setter
public class Item {

	@Id @GeneratedValue
	@Column(name = "item_id")
	private Long id;
	
	private String name;
	private int price;
	private int stockQuantity;
	
}

🧩 DTO

  • Entity를 있는 그대로 수정하지 않은 상태에서 작업하려면 Entity에 담아도 괜찮다.
  • 비즈니스 로직적인게 추가가 된다면 DTO를 생성하는 것이 좋다.

validation 체크

  • build.gradle 파일에서 라이브러리 추가
    implementation 'org.springframework.boot:spring-boot-starter-validation'

  • validation check 대상인 dto객체에 @NotEmpty 어노테이션 추가
    @NotEmpty(message = "회원 이름은 필수입니다.")

  • BindingResult : validation 다음에 Binding이 있으면, errorBinding에 담아준다.

	@PostMapping("/members/new")
	public String create(@Valid MemberForm form, BindingResult result) throws IllegalAccessException {
		
		// error 발생 시
		if(result.hasErrors()) {
			return "members/createMemberForm";
		}
  • html적용
    • css 변화를 Thymleaf로 적용 시키기
    • DTO의 에러내용 출력

MemberForm

@Getter @Setter
public class MemberForm {
	
	@NotEmpty(message = "회원 이름은 필수입니다.")
	private String name;
	private String city;
	private String street;
	private String zipcode;
	
	
}

🧩 Web

createMember

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header" />
<style>
.fieldError {
	border-color: #bd2130;
}
</style>
<body>
	<div class="container">
		<div th:replace="fragments/bodyHeader :: bodyHeader" />
		
		<form role="form" action="/members/new" th:object="${memberForm}" method="post">
			<div class="form-group">
				<label th:for="name">이름</label> 
				<input type="text" th:field="*{name}" class="form-control" placeholder="이름을 입력하세요" 
					th:class="${#fields.hasErrors('name')}? 'form-control fieldError' :'form-control'"/>
				<p th:if="${#fields.hasErrors('name')}" th:errors="*{name}">Incorrect data</p>
			</div>
			
			<div class="form-group">
				<label th:for="city">도시</label> 
				<input type="text" th:field="*{city}" class="form-control" placeholder="도시를 입력하세요">
			</div>
			<div class="form-group">
				<label th:for="street">거리</label> 
				<input type="text" th:field="*{street}" class="form-control" placeholder="거리를 입력하세요">
			</div>
			<div class="form-group">
				<label th:for="zipcode">우편번호</label> 
				<input type="text" th:field="*{zipcode}" class="form-control" placeholder="우편번호를 입력하세요">
			</div>
			<button type="submit" class="btn btn-primary">Submit</button>
		</form>
		<br/>
		<div th:replace="fragments/footer :: footer" />
	</div>
	<!-- /container -->
</body>
</html>

memberList

<!DOCTYPE HTML>
<html xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments/header :: header" />
<body>

	<div class="container">
		<div th:replace="fragments/bodyHeader :: bodyHeader" />
		<div>
			<table class="table table-striped">
				<thead>
					<tr>
						<th>#</th>
						<th>이름</th>
						<th>도시</th>
						<th>주소</th>
						<th>우편번호</th>
					</tr>
				</thead>
				<tbody>
					<tr th:each="member : ${members}">
						<td th:text="${member.id}"></td>
						<td th:text="${member.name}"></td>
						<td th:text="${member.address?.city}"></td>
						<td th:text="${member.address?.street}"></td>
						<td th:text="${member.address?.zipcode}"></td>
					</tr>
				</tbody>

			</table>
		</div>

		<div th:replace="fragments/footer :: footer" />

	</div>
	<!-- /container -->

</body>
</html>

🧩 Controller

MemberController

  • @Valid : validation 처리를 이곳에서 검증을 한번 한다.
  • BindingResult
    Controller ParameterValid체크가 실패했을경우 bindingResult.hasErrors()를 통해 에러를 담아서 되돌아갈 수 있다.
    → 이 때 form객체도 작성되있던 내용들이 같이 되돌아가서 html페이지에서는 이전 작성내용이 저장될 수 있다
@Controller
@RequiredArgsConstructor
public class MemberController {

	private final MemberService memberService;
	
	@GetMapping("/members/new")
	public String createForm(Model model) {
		model.addAttribute("memberForm", new MemberForm());
		return "members/createMemberForm";
	}
	
	// BindingResult : validation 다음에 Binding이 있으면,
	// error를 Binding에 담아준다.
	@PostMapping("/members/new")
	public String create(@Valid MemberForm form, BindingResult result) {
		
        // error 발생 시
		if(result.hasErrors()) {
			return "members/createMemberForm";
		}
		
		// 정상로직, service
		Address address = new Address(form.getCity(), form.getStreet(),form.getZipcode());
		Member member = new Member();
		member.setName(form.getName());
		member.setAddress(address);
		
		memberService.join(member);
		
        // 원래 페이지 보내기
		return "redirect:/";
	}
    
    // request  : members
	// response : members/memberList
	// 회원 목록 조회
	@GetMapping("/members")
	public String list(Model model) {
		List<Member> members = memberService.findMembers();
		model.addAttribute("members", members);
		return "members/memberList";
	}
}

🧩 Repository

MemberRepository

  • @PersistenceContext : jpa가 지원해주는 표준, spring이 entitymanager를 만들어서 em에 주입
  • @Autowired : spring boot lib 사용시 @Autowired을 지원한다.(최종적으로 사용)

    • 위 JPA의 Manager 작업들이 아래 작업으로 대체된다.
@Repository
@RequiredArgsConstructor
public class MemberRepository {


	// @PersistenceContext : jpa가 지원해주는 표준,
	// 						 spring이 entitymanager를 만들어서 em에 주입
//	@PersistenceContext
//	@Autowired			 : spring boot lib 사용시 @Autowired을 지원한다.(최종적으로 사용)
	@Autowired
	private final EntityManager em;
	
	// 생성자 주입
//	private MemberRepository(EntityManager em) {
//		this.em = em;
//	}
	
	// 저장
	public void save(Member member) {
		em.persist(member);
	}
	
	// 1건 조회
	public Member findOne(Long id) {
//		Member member = em.find(Member.class, id);
//		return member;
		return em.find(Member.class, id);
	}
	
	// 여러건 조회
	public List<Member> findAll(){
		return em.createQuery("select m from Member m", Member.class).getResultList();
	}
	
	// 이름으로 조회
	public List<Member> findByName(String name){
		return em.createQuery("select m from Member m where m.name = :name", Member.class)
				.setParameter("name", name)
				.getResultList();
	}
	
}

🧩 Service

  • 비즈니스적 로직은 여기서 처리!!

MemberService

  • @Transactional : framework로 선택해준다.
    • @Transactional이 붙은 메서드는 메서드가 포함하고 있는 작업 중에 하나라도 실패할 경우 전체 작업을 취소한다.
  • @Transactional(readOnly = true )
    : 읽기 전용일 떄 넣어주면 비용을 아낀다.
@Service
@RequiredArgsConstructor
@Transactional(readOnly = true )

public class MemberService {

	
	private final MemberRepository memberRepository;
	
	// 회원가입
	@Transactional
	public Long join(Member member) throws IllegalAccessException {
		// 중복회원 검증 로직 추가
		validateMemberCheck(member);
		memberRepository.save(member);
		return member.getId();
	}
    
	// 중복체크, void이므로 return타입이 업다.
	private void validateMemberCheck(Member member) throws IllegalAccessException {
		List<Member> findMembers = memberRepository.findByName(member.getName());
		
		if(!findMembers.isEmpty()) {
			throw new IllegalAccessException("이미 존재하는 회원입니다.");
		}
	}
	
	/*
	 * @Transactional(readOnly = true )
	 *  : 읽기 전용일 떄 넣어주면 비용을 아낀다.	
	 */
	// 회원 목록 조회
//	@Transactional(readOnly = true )
	public List<Member> findMembers() {
		return memberRepository.findAll();
	}

	// 회원 단건 조회
//	@Transactional(readOnly = true )
	public Member findOne(Long memberId) {
		return memberRepository.findOne(memberId);
	}
}

0개의 댓글