ํฐ์คํ ๋ฆฌ ๋ทฐ
๐ ๋๋ฉ์ธ ๋ถ์ ์ค๊ณ
0๏ธโฃ ์๊ตฌ์ฌํญ ๋ถ์
- ์ค์ ๋์ํ๋ ํ๋ฉด ํ์ธ
- ๊ธฐ๋ฅ ๋ชฉ๋ก ํ์ธ
1๏ธโฃ ๋๋ฉ์ธ ๋ชจ๋ธ๊ณผ ํ ์ด๋ธ ์ค๊ณ
- ๋ค๋๋ค ๊ด๊ณ(ex. ์ํ๊ณผ ์ฃผ๋ฌธ)๋ ์ผ๋๋ค(ex. ์ฃผ๋ฌธ๊ณผ ์ฃผ๋ฌธ์ํ), ๋ค๋์ผ(ex. ์ฃผ๋ฌธ์ํ๊ณผ ์ํ) ๊ด๊ณ๋ก ํ๊ธฐ
- ์นดํ ๊ณ ๋ฆฌ๋ parent, child๋ก ๋ถ๋ชจ, ์์ ์นดํ ๊ณ ๋ฆฌ๋ฅผ ๊ตฌํํ ์ ์์!
- ์ธ๋ํค๋ฅผ ๊ด๋ฆฌ(์์ )ํ๊ณ ์์ผ๋ฉด ์ฐ๊ด๊ด๊ณ์ ์ฃผ์ธ์
- ์ผ๋๋ค ๊ด๊ณ์์ '๋ค'๊ฐ ๋ณดํต ์ธ๋ํค๋ฅผ ๊ฐ์ง๊ณ ์์!
2๏ธโฃ ์ํฐํฐ ํด๋์ค ๊ฐ๋ฐ
- ์ด๋ก ์ ์ผ๋ก Getter, Setter ๋ชจ๋ ์ ๊ณตํ์ง ์๊ณ , ๊ผญ ํ์ํ ๋ณ๋์ ๋ฉ์๋๋ฅผ ์ ๊ณตํ๋๊ฒ ๊ฐ์ฅ ์ด์์ !
- but ์ค๋ฌด์์๋ Getter๋ฅผ ๋ชจ๋ ์ด์ด๋๊ณ Setter ๋์ ์ ๋ณ๊ฒฝ ์ง์ ์ด ๋ช ํํ๋๋ก ๋ณ๊ฒฝ์ ์ํ ๋น์ฆ๋์ค ๋ฉ์๋๋ฅผ ๋ณ๋๋ก ์ ๊ณต
@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<>(); //์ปฌ๋ ์
์ ํ๋์์ ์ด๊ธฐํ
}
- ์ปฌ๋ ์ ์ ํ๋์์ ์ด๊ธฐํ
@Entity
@Table(name = "orders")
@Getter @Setter
public class Order {
@Id @GeneratedValue
@Column(name = "order_id")
private Long id;
@ManyToOne(fetch = FetchType.LAZY) //๋ฐ์ดํฐ๋ฅผ ์กฐํํ ๋, ํ์ํ ์์ ์ ์ฐ๊ด๋ ๋ฐ์ดํฐ๋ง ๊ฐ์ ธ์ด!
@JoinColumn(name = "member_id")
private Member member; //์ฃผ๋ฌธ ํ์
@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();
@OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JoinColumn(name = "delivery_id")
private Delivery delivery; //๋ฐฐ์ก์ ๋ณด
private LocalDateTime orderDate; //์ฃผ๋ฌธ์๊ฐ
@Enumerated(EnumType.STRING)
private OrderStatus status; //์ฃผ๋ฌธ์ํ [ORDER, CANCEL]
//==์ฐ๊ด๊ด๊ณ ๋ฉ์๋==//
public void setMember(Member member) {
this.member = member;
member.getOrders().add(this);
}
public void addOrderItem(OrderItem orderItem) {
orderItems.add(orderItem);
orderItem.setOrder(this);
}
public void setDelivery(Delivery delivery) {
this.delivery = delivery;
delivery.setOrder(this);
}
//==์์ฑ ๋ฉ์๋==//
public static Order createOrder(Member member, Delivery delivery,
OrderItem... orderItems) {
Order order = new Order();
order.setMember(member);
order.setDelivery(delivery);
for (OrderItem orderItem : orderItems) {
order.addOrderItem(orderItem);
}
order.setStatus(OrderStatus.ORDER);
order.setOrderDate(LocalDateTime.now());
return order;
}
//==๋น์ฆ๋์ค ๋ก์ง==//
/** ์ฃผ๋ฌธ ์ทจ์ */
public void cancel() {
if (delivery.getStatus() == DeliveryStatus.COMP) {
throw new IllegalStateException("์ด๋ฏธ ๋ฐฐ์ก์๋ฃ๋ ์ํ์ ์ทจ์๊ฐ ๋ถ๊ฐ๋ฅํฉ๋๋ค.");
}
this.setStatus(OrderStatus.CANCEL);
for (OrderItem orderItem : orderItems) {
orderItem.cancel();
}
}
//==์กฐํ ๋ก์ง==//
/**์ ์ฒด ์ฃผ๋ฌธ ๊ฐ๊ฒฉ ์กฐํ*/
public int getTotalPrice() {
int totalPrice = 0;
for (OrderItem orderItem : orderItems) {
totalPrice += orderItem.getTotalPrice();
}
return totalPrice;
}
}
- ๋ชจ๋ ์ฐ๊ด๊ด๊ณ๋ ์ง์ฐ๋ก๋ฉ์ผ๋ก ์ค์ (fetch = FetchType.LAZY)
- cascade = CascadeType.ALL๋ก ํ๋ฉด ์ฐ๊ด๊ด๊ณ์ ์๋ ์ํฐํฐ๋ฅผ ๋ชจ๋ persist(์์)ํด์ค
@Entity
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "dtype")
@Getter @Setter
public abstract class Item {
@Id @GeneratedValue
@Column(name = "item_id")
private Long id;
private String name;
private int price;
private int stockQuantity;
@ManyToMany(mappedBy = "items")
private List<Category> categories = new ArrayList<Category>();
//==๋น์ฆ๋์ค ๋ก์ง==//
public void addStock(int quantity) {
this.stockQuantity += quantity;
}
public void removeStock(int quantity) {
int restStock = this.stockQuantity - quantity;
if (restStock < 0) {
throw new NotEnoughStockException("need more stock");
}
this.stockQuantity = restStock;
}
}
- ๋น์ฆ๋์ค ๋ก์ง์ ์ํฐํฐ์ ์์!(= ๋๋ฉ์ธ ๋ชจ๋ธ ํจํด, ๊ฐ์ฒด ์งํฅ์ ํน์ฑ↑)
@Entity
@DiscriminatorValue("B")
@Getter @Setter
public class Book extends Item {
private String author;
private String isbn;
}
@Entity
@Getter @Setter
public class Category {
@Id @GeneratedValue
@Column(name = "category_id")
private Long id;
private String name;
@ManyToMany
@JoinTable(name = "category_item",
joinColumns = @JoinColumn(name = "category_id"),
inverseJoinColumns = @JoinColumn(name = "item_id"))
private List<Item> items = new ArrayList<>();
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "parent_id")
private Category parent;
@OneToMany(mappedBy = "parent")
private List<Category> child = new ArrayList<>();
//==์ฐ๊ด๊ด๊ณ ๋ฉ์๋==//
public void addChildCategory(Category child) {
this.child.add(child);
child.setParent(this);
}
}
- ์ค๋ฌด์์๋ @ManyToMany๋ฅผ ์ฌ์ฉํ์ง ๋ง์!
@Embeddable
@Getter
public class Address {
private String city;
private String street;
private String zipcode;
protected Address() {
}
public Address(String city, String street, String zipcode) {
this.city = city;
this.street = street;
this.zipcode = zipcode;
}
}
- ๊ฐ ํ์ ์ ๋ณ๊ฒฝ ๋ถ๊ฐ๋ฅํ๊ฒ ์ค๊ณ!
- @Setter๋ฅผ ์ญ์ ํ๊ณ ์๋ฐ ๊ธฐ๋ณธ ์์ฑ์๋ฅผ protected๋ก ์ค์ !
๐ ์ ํ๋ฆฌ์ผ์ด์ ๊ตฌํ ์ค๋น
0๏ธโฃ ๊ตฌํ ์๊ตฌ์ฌํญ ๋ถ์
1๏ธโฃ ์ ํ๋ฆฌ์ผ์ด์ ์ํคํ ์ฒ ์ค๊ณ
๊ณ์ธตํ ๊ตฌ์กฐ
- controller: ์น ๊ณ์ธต
- service: ๋น์ฆ๋์ค ๋ก์ง, ํธ๋์ญ์ ์ฒ๋ฆฌ
- repository: JPA๋ฅผ ์ง์ ์ฌ์ฉ, ์ํฐํฐ ๋งค๋์ ์ฌ์ฉ
- domain: ์ํฐํฐ๊ฐ ๋ชจ์ฌ ์๋ ๊ณ์ธต, ๋ชจ๋ ๊ณ์ธต์์ ์ฌ์ฉ
ํจํค์ง ๊ตฌ์กฐ
- jpabook.jpashop
- domain
- exception
- repository
- service
- controller(web)
โ๋๋ฉ์ธ ๊ฐ๋ฐ → ์๋น์ค, ๋ฆฌํฌ์งํ ๋ฆฌ ๊ณ์ธต ๊ฐ๋ฐ → ํ ์คํธ ์ผ์ด์ค ์์ฑ ๋ฐ ๊ฒ์ฆ → ์น ๊ณ์ธต(์ปจํธ๋กค๋ฌ) ์ ์ฉ!
2๏ธโฃ ๋ฆฌํฌ์งํ ๋ฆฌ ๊ฐ๋ฐ
@Repository
public class MemberRepository {
@PersistenceContext
private EntityManager em;
public void save(Member member) {
em.persist(member);
}
public Member findOne(Long id) {
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();
}
}
- ์คํ๋ง ๋ฐ์ดํฐ JPA๋ฅผ ์ฌ์ฉํ๋ฉด ↓ ์ด๋ ๊ฒ ๋ณ๊ฒฝ ๊ฐ๋ฅ!
@Repository
@RequiredArgsConstructor
public class MemberRepository {
private final EntityManager em;
...
}
3๏ธโฃ ์๋น์ค ๊ฐ๋ฐ
@Service
@Transactional(readOnly = true
@RequiredArgsConstructor
public class MemberService {
private final MemberRepository memberRepository;
/**
* ํ์๊ฐ์
*/
@Transactional //๋ณ๊ฒฝ
public Long join(Member member) {
validateDuplicateMember(member); //์ค๋ณต ํ์ ๊ฒ์ฆ
memberRepository.save(member);
return member.getId();
}
private void validateDuplicateMember(Member member) {
List<Member> findMembers = memberRepository.findByName(member.getName());
if (!findMembers.isEmpty()) {
throw new IllegalStateException("์ด๋ฏธ ์กด์ฌํ๋ ํ์์
๋๋ค.");
}
}
/**
* ์ ์ฒด ํ์ ์กฐํ
*/
public List<Member> findMembers() {
return memberRepository.findAll();
}
public Member findOne(Long memberId) {
return memberRepository.findOne(memberId);
}
}
- ์คํ๋ง ํ๋ ์ฃผ์ ๋ณด๋ค๋ ์์ฑ์ ์ฃผ์ ์ ์ฌ์ฉํ์!
4๏ธโฃ ๊ธฐ๋ฅ ํ ์คํธ
@RunWith(SpringRunner.class) //์คํ๋ง๊ณผ ํ
์คํธ ํตํฉ
@SpringBootTest //์คํ๋ง ๋ถํธ๋ฅผ ๋์ฐ๊ณ ํ
์คํธ
@Transactional //๊ฐ๊ฐ์ ํ
์คํธ๋ฅผ ์คํํ ๋๋ง๋ค ํธ๋์ญ์
์ ์์ํ๊ณ ํ
์คํธ๊ฐ ๋๋๋ฉด ํธ๋์ญ์
์ ๊ฐ์ ๋ก ๋กค๋ฐฑ
public class MemberServiceTest {
@Autowired MemberService memberService;
@Autowired MemberRepository memberRepository;
@Test
public void ํ์๊ฐ์
() throws Exception {
//Given
Member member = new Member();
member.setName("kim");
//When
Long saveId = memberService.join(member);
//Then
assertEquals(member, memberRepository.findOne(saveId));
}
@Test(expected = IllegalStateException.class)
public void ์ค๋ณต_ํ์_์์ธ() throws Exception {
//Given
Member member1 = new Member();
member1.setName("kim");
Member member2 = new Member();
member2.setName("kim");
//When
memberService.join(member1);
memberService.join(member2); //์์ธ๊ฐ ๋ฐ์ํด์ผ ํ๋ค.
//Then
fail("์์ธ๊ฐ ๋ฐ์ํด์ผ ํ๋ค.");
}
}
- ํ ์คํธ ์ผ์ด์ค๋ฅผ ์ํ ์คํ๋ง ํ๊ฒฝ๊ณผ ์ ํ๋ฆฌ์ผ์ด์ ์ ์คํํ๋ ํ๊ฒฝ์ ๋ณดํต ๋ค๋ฅด๋ฏ๋ก ์ค์ ํ์ผ์ ๋ค๋ฅด๊ฒ ์ฌ์ฉํ์! (test/resources/application.yml)
โ๏ธ ์ฃผ๋ฌธ ๊ฒ์ ๊ธฐ๋ฅ ๊ฐ๋ฐ(๋์ ์ฟผ๋ฆฌ)
1๏ธโฃ JPQL
2๏ธโฃ JPA Criteria
3๏ธโฃ Querydsl
๐ ์น ๊ณ์ธต ๊ฐ๋ฐ
โํผ ๊ฐ์ฒด๋ฅผ ์ฌ์ฉํด์ ํ๋ฉด ๊ณ์ธต๊ณผ ์๋น์ค ๊ณ์ธต์ ๋ช ํํ๊ฒ ๋ถ๋ฆฌ!
- ์ค๋ฌด์์ ์ํฐํฐ๋ ํต์ฌ ๋น์ฆ๋์ค ๋ก์ง๋ง ๊ฐ์ง๊ณ ์๊ณ , ํ๋ฉด์ ์ํ ๋ก์ง์ ์์ด์ผ ํจ! ํ๋ฉด์ด๋ API์ ๋ง๋ ํผ ๊ฐ์ฒด๋ DTO๋ฅผ ์ฌ์ฉํ์! ๊ทธ๋์ ํ๋ฉด์ด๋ API ์๊ตฌ์ฌํญ์ ์ด๊ฒ๋ค๋ก ์ฒ๋ฆฌํ๊ณ , ์ํฐํฐ๋ ์ต๋ํ ์์ํ๊ฒ ์ ์งํ์!
@Getter @Setter
public class MemberForm {
@NotEmpty(message = "ํ์ ์ด๋ฆ์ ํ์ ์
๋๋ค")
private String name;
private String city;
private String street;
private String zipcode;
}
0๏ธโฃ ์ปจํธ๋กค๋ฌ ๊ฐ๋ฐ
@Controller
@RequiredArgsConstructor
public class MemberController {
private final MemberService memberService;
@GetMapping(value = "/members/new")
public String createForm(Model model) {
model.addAttribute("memberForm", new MemberForm());
return "members/createMemberForm";
}
@PostMapping(value = "/members/new")
public String create(@Valid MemberForm form, BindingResult result) {
if (result.hasErrors()) {
return "members/createMemberForm";
}
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:/";
}
@GetMapping(value = "/members")
public String list(Model model) {
List<Member> members = memberService.findMembers();
model.addAttribute("members", members);
return "members/memberList";
}
}
๐ ๋ณ๊ฒฝ ๊ฐ์ง์ ๋ณํฉ(merge)
๐๐ปโ๏ธ ์ค์์ ์ํฐํฐ?
: ์์์ฑ ์ปจํ ์คํธ(entity manager)๊ฐ ๋๋ ๊ด๋ฆฌํ์ง ์๋ ์ํฐํฐ. ๋ฐ์ดํฐ๋ฅผ ์์ ํด๋ ๋ณ๊ฒฝ ๊ฐ์ง ๊ธฐ๋ฅ ๋์X
๋ฐ๋ผ์, 1๏ธโฃ ๋ณ๊ฒฝ ๊ฐ์ง ๊ธฐ๋ฅ์ ์ฌ์ฉํ๊ฑฐ๋ 2๏ธโฃ ๋ณํฉ(merge)์ ์ฌ์ฉํด์ ์ค์์ ์ํฐํฐ๋ฅผ ์์
1๏ธโฃ ๋ณ๊ฒฝ ๊ฐ์ง
@Transactional
void update(Item itemParam) { //itemParam: ํ๋ฆฌ๋ฏธํฐ๋ก ๋์ด์จ ์ค์์ ์ํ์ ์ํฐํฐ
Item findItem = em.find(Item.class, itemParam.getId()); //๊ฐ์ ์ํฐํฐ ์กฐํ
findItem.setPrice(itemParam.getPrice()); //๋ฐ์ดํฐ ์์
}
- ์์์ฑ ์ปจํ ์คํธ์์ ์ํฐํฐ๋ฅผ ๋ค์ ์กฐํํ ํ์ ๋ฐ์ดํฐ๋ฅผ ์์
2๏ธโฃ ๋ณํฉ(merge)
@Transactional
void update(Item itemParam) { //itemParam: ํ๋ฆฌ๋ฏธํฐ๋ก ๋์ด์จ ์ค์์ ์ํ์ ์ํฐํฐ
Item mergeItem = em.merge(itemParam);
}
โ๋ณํฉ ์ ๋ชจ๋ ์์ฑ์ด ๋ณ๊ฒฝ๋จ, ๊ฐ์ด ์์ผ๋ฉด null๋ก ์ ๋ฐ์ดํธ (← ๋ณํฉ์ ์ฌ์ฉํ์ง ๋ง์์ผ ํ๋ ์ด์ )
@Repository
public class ItemRepository {
@PersistenceContext
EntityManager em;
public void save(Item item) {
if (item.getId() == null) {
em.persist(item);
} else {
em.merge(item);
}
}
//...
}
↑ ์๋ก์ด ์ํฐํฐ ์ ์ฅ๊ณผ ์ค์์ ์ํฐํฐ ๋ณํฉ์ ํธ๋ฆฌํ๊ฒ ํ๋ฒ์ ์ฒ๋ฆฌ! (๊ทธ๋๋ ๋ณํฉ์ ์ฌ์ฉํ์ง ๋ง์...)
โ๏ธ ์ปจํธ๋กค๋ฌ์์ ์ด์คํ๊ฒ ์ํฐํฐ ์์ฑX
โ๏ธ ์๋น์ค ๊ณ์ธต์ ์๋ณ์(id)์ ๋ณ๊ฒฝํ ๋ฐ์ดํฐ๋ฅผ ๋ช ํํ๊ฒ ์ ๋ฌ(ํ๋ผ๋ฏธํฐ or DTO)
โ๏ธ ์๋น์ค ๊ณ์ธต์์ ์์ ์ํ์ ์ํฐํฐ๋ฅผ ์กฐํํ๊ณ ์ํฐํฐ์ ๋ฐ์ดํฐ๋ฅผ ์ง์ ๋ณ๊ฒฝ! (์๋น์ค ๊ณ์ธต์ ํธ๋์ญ์ ์ด ์๊ธฐ๋๋ฌธ!)
โ๏ธ ํธ๋์ญ์ ์ปค๋ฐ ์์ ์ ๋ณ๊ฒฝ ๊ฐ์ง๊ฐ ์คํ
'๋ฐฑ์๋ > Spring' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
๊ถํ ๊ด๋ฆฌ - ์คํ๋ง ์ธํฐ์ ํฐ (0) | 2023.06.15 |
---|---|
๋ก๊ทธ์ธ 1 - ์ฟ ํค์ ์ธ์ (1) | 2023.06.14 |
HTTP ์น ๊ธฐ๋ณธ ์ง์ (0) | 2023.04.29 |
[์คํ๋ง ํต์ฌ ์๋ฆฌ] ๊ธฐ๋ณธํธ2 (0) | 2023.04.09 |
[์คํ๋ง ํต์ฌ ์๋ฆฌ] ๊ธฐ๋ณธํธ1 (0) | 2023.04.06 |