Properites
보다 yaml
파일로 보는게 직관적@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>();
}
@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() {}
}
@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
}
@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; // 주문수량
}
@Entity
@Getter @Setter
public class Item {
@Id @GeneratedValue
@Column(name = "item_id")
private Long id;
private String name;
private int price;
private int stockQuantity;
}
build.gradle 파일에서 라이브러리 추가
implementation 'org.springframework.boot:spring-boot-starter-validation'
validation check 대상인 dto객체에 @NotEmpty
어노테이션 추가
@NotEmpty(message = "회원 이름은 필수입니다.")
BindingResult : validation
다음에 Binding
이 있으면, error
를 Binding
에 담아준다.
@PostMapping("/members/new")
public String create(@Valid MemberForm form, BindingResult result) throws IllegalAccessException {
// error 발생 시
if(result.hasErrors()) {
return "members/createMemberForm";
}
- css 변화를 Thymleaf로 적용 시키기
- DTO의 에러내용 출력
@Getter @Setter
public class MemberForm {
@NotEmpty(message = "회원 이름은 필수입니다.")
private String name;
private String city;
private String street;
private String zipcode;
}
<!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>
<!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>
validation
처리를 이곳에서 검증을 한번 한다.Controller Parameter
가 Valid체크
가 실패했을경우 bindingResult.hasErrors()
를 통해 에러를 담아서 되돌아갈 수 있다.@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";
}
}
entitymanager
를 만들어서 em
에 주입@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();
}
}
@Transactional
이 붙은 메서드는 메서드가 포함하고 있는 작업 중에 하나라도 실패할 경우 전체 작업을 취소한다.@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);
}
}