๋ณธ๋ฌธ ๋ฐ”๋กœ๊ฐ€๊ธฐ
Back-end ๋ฐ๋ธŒ์ฝ”์Šค/week 08 TIL (Jpa)

[TIL] 221207 - JPA : ์—ฐ๊ด€๊ด€๊ณ„ ๋งคํ•‘, ๊ณ ๊ธ‰ ๋งคํ•‘, ํ”„๋ก์‹œ

by young-ji 2022. 12. 14.

SpringBoot Part4(3)

 

์—ฐ๊ด€๊ด€๊ณ„ ๋งคํ•‘

๊ฐ์ฒด ์—ฐ๊ด€๊ด€๊ณ„ VS ํ…Œ์ด๋ธ” ์—ฐ๊ด€๊ด€๊ณ„

ํ…Œ์ด๋ธ”์€ ์™ธ๋ž˜ํ‚ค๋กœ ์—ฐ๊ด€ ๊ด€๊ณ„๋ฅผ ๋งบ๋Š”๋‹ค.

๊ฐ์ฒด๋Š” ์ฐธ์กฐ(์ฃผ์†Œ)๋กœ ์—ฐ๊ด€ ๊ด€๊ณ„๋ฅผ ๋งบ๋Š”๋‹ค.

 

ํ•ต์‹ฌ ํ‚ค์›Œ๋“œ

๋ฐฉํ–ฅ์„ฑ (๋‹จ๋ฐฉํ–ฅ, ์–‘๋ฐฉํ–ฅ)

  • ํ…Œ์ด๋ธ”์—์„œ ๊ด€๊ณ„๋Š” ํ–ฅ์ƒ ์–‘๋ฐฉํ–ฅ์ด๋‹ค.

์™ธ๋ž˜ํ‚ค๋ฅผ ์ด์šฉํ•ด์„œ ์–‘๋ฐฉํ–ฅ์œผ๋กœ ์กฐ์ธ์ด ๊ฐ€๋Šฅ

SELECT * FROM orders AS o JOIN member AS m
ON o.member._id = m.id

 

 

  • ๊ฐ์ฒด์—์„œ์˜ ๋‹จ,์–‘๋ฐฉํ–ฅ ๊ด€๊ณ„
// ํšŒ์› -> ์ฃผ๋ฌธ ๋‹จ๋ฐฉํ–ฅ ๋งคํ•‘ ๊ฒฝ์šฐ. ํšŒ์›์—์„œ ์ฃผ๋ฌธ๋งŒ ์ฐธ์กฐ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค.
class Member {
	private long id;
	private List<Order> orders; // ํšŒ์› -> ์ฃผ๋ฌธ
}

class Order {
	private String id;
}

Member member = new Member();
Order order = meber.getOrders().get(0);

// ์ฃผ๋ฌธ -> ํšŒ์› ๋‹จ๋ฐฉํ–ฅ ๋งคํ•‘
class Member {
	private long id;
}

class Order {
	private String id;
	private Member member;
}

// ํšŒ์› -> ์ฃผ๋ฌธ, ์ฃผ๋ฌธ -> ํšŒ์› ๋ชจ๋‘ ์ฐธ์กฐ๊ฐ€ ๊ฐ€๋Šฅํ•œ ์–‘๋ฐฉํ–ฅ ๋งคํ•‘
class Member {
	private long id;
	private List<Order> orders;
}

class Order {
	private String id;
	private Member member; 
}

 

๋‹ค์ค‘์„ฑ (๋‹ค๋Œ€๋‹ค, ์ผ๋Œ€๋‹ค, ๋‹ค๋Œ€๋‹ค)

  • ํšŒ์›์€ ์—ฌ๋Ÿฌ ์ฃผ๋ฌธ์„ ํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ํšŒ์›(1)๊ณผ ์ฃผ๋ฌธ(N)์€ ์ผ๋Œ€๋‹ค ๊ด€๊ณ„
  • ์ฃผ๋ฌธ์€ ์—ฌ๋Ÿฌ ํšŒ์›์— ์˜ํ•ด ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, ์ฃผ๋ฌธ(N)๊ณผ ํšŒ์›(1)์€ ๋‹ค๋Œ€์ผ ๊ด€๊ณ„์ด๋‹ค.

 

 

์—ฐ๊ด€๊ด€๊ณ„ ์ฃผ์ธ (mappedBy)

  • ๊ฐ์ฒด๋ฅผ ์–‘๋ฐฉํ–ฅ ์—ฐ๊ด€๊ด€๊ณ„๋กœ ๋งŒ๋“ค๋ฉด, ์—ฐ๊ด€๊ด€๊ณ„์˜ ์ฃผ์ธ์„ ์ •ํ•ด์•ผ ํ•œ๋‹ค.
  • ์™ธ๋ž˜ํ‚ค๋ฅผ ๊ด€๋ฆฌํ•  ๊ฐ์ฒด๋ฅผ ์ง€์ •ํ•œ๋‹ค. (INSERT, UPDATE, DELETE)
  • ์—ฐ๊ด€๊ด€๊ณ„ ์ฃผ์ธ๋งŒ์ด, ์™ธ๋ž˜ํ‚ค๋ฅผ ๋“ฑ๋ก ์ˆ˜์ • ์‚ญ์ œ ํ•  ์ˆ˜ ์žˆ๋‹ค. (์ฃผ์ธ์ด ์•„๋‹Œ์ชฝ์€ ์ฝ๊ธฐ SELECT๋งŒ ๊ฐ€๋Šฅํ•˜๋‹ค.)
  • @OneToMany(mappedBy = ์—ฐ๊ด€๊ด€๊ณ„ ์ฃผ์ธ ๊ฐ์ฒด์˜ ๋งคํ•‘ ํ•„๋“œ๋ช…)
  • @ManyToOne์€ ํ•ญ์ƒ ์—ฐ๊ด€๊ด€๊ณ„์˜ ์ฃผ์ธ์ด ๋˜๋ฏ€๋กœ mappedBy๋ฅผ ์„ค์ •ํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.
// Order_Item ํ…Œ์ด๋ธ”์€ order_id๋ฅผ ์™ธ๋ž˜ํ‚ค๋กœ ๊ฐ–๋Š”๋‹ค. ์ž์‹ ์ด ์—ฐ๊ด€๊ด€๊ณ„์˜ ์ฃผ์ธ
@Entity
@Table
public class OrderItem extends BaseEntity{
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private int id;

    @ManyToOne
    private Order order;
}

// ์–‘๋ฐฉํ–ฅ ์—ฐ๊ด€๊ด€๊ณ„ ์ฒ˜๋Ÿผ ๋ณด์ด์ง€๋งŒ ์‹ค์ œ 2๊ฐœ์˜ ๋‹จ๋ฐฉํ–ฅ ์—ฐ๊ด€๊ด€๊ณ„
// ํ…Œ์ด๋ธ”์€ ๋‹จ๋ฐฉํ–ฅ ๋งคํ•‘์ด๊ธฐ์— ์‹ค์ œ Order ํ…Œ์ด๋ธ”์—๋Š” orderItem ์ •๋ณด๊ฐ€ ์—†๋‹ค. 
// ์—ฐ๊ด€๊ด€๊ณ„์˜ ์ฃผ์ธ orderItem.order์˜ ํ•„๋“œ๋ช…์„ mappedBy์— ๊ธฐ์žฌํ•ด์ค€๋‹ค. 
@Entity
@Table(name = "orders")
public class Order extends BaseEntity{

    @Id
    @Column(name = "id")
    private String uuid;

    @OneToMany(mappedBy = "Order")
    private List<OrderItem> orderItems;
}

 

๊ฐ์ฒด ๊ทธ๋ž˜ํ”„ ํƒ์ƒ‰

๊ฐ์ฒด์˜ ์ฐธ์กฐ๋ฅผ ํ†ตํ•ด ๊ฐ์ฒด๋ฅผ ๊ฐ€์ ธ์˜จ๋‹ค.

class Member {
	private long id;
	private List<Order> orders;
}

class Order {
	private String id;
	private Member member;
}

... 
@Test
void graph() {
	Member member1 = new Mebmer(1);
	Order order1 = new Order(1)

	member1.setOrders(Lists.newArrayList(order1));

	Order findOrder= member1.getOrders().get(o); // ๊ฐ์ฒด ๊ทธ๋ž˜ํ”„ ํƒ์ƒ‰์ด๋ผ ํ•œ๋‹ค.
	findOrder.getMember();
}

 


 

๋‹จ๋ฐฉํ–ฅ ์—ฐ๊ด€ ๊ด€๊ณ„

@Entity
@Table(name = "orders")
@Getter
@Setter
public class Order {
    @Id
    @Column(name = "id")
    private String uuid;

    @Column(name = "order_datetime", columnDefinition = "TIMESTAMP")
    private LocalDateTime orderDatetime;

    @Enumerated(EnumType.STRING)
    private OrderStatus orderStatus;

    @Lob
    private String memo;

    @Column(name = "member_id", insertable = false, updatable = false) // fk
    private Long memberId;

    @ManyToOne // ํšŒ์› ํ•œ๋ช…์—๊ฒŒ ์—ฌ๋Ÿฌ ์ฃผ๋ฌธ
    @JoinColumn(name = "member_id", referencedColumnName = "id") // ๋ช…์‹œํ•˜์ง€ ์•Š์„๊ฒฝ์šฐ ํ•ด๋‹นํ•„๋“œ๋ช…_PKํ•„๋“œ๋ช…
    private Member member;
}

 

์–‘๋ฐฉํ–ฅ ์—ฐ๊ด€ ๊ด€๊ณ„

@Entity
@Table(name = "member")
@Getter
@Setter
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;

    @Column(name = "name", nullable = false, length = 30)
    private String name;

    @Column(nullable = false, length = 30, unique = true)
    private String nickName;

    @Column
    private int age;

    @Column(name = "address", nullable = false)
    private String address;

    @Column(name = "description")
    private String description;

    @OneToMany(mappedBy = "member") // ์—ฐ๊ด€๊ด€๊ณ„ ์ฃผ์ธ ์„ค์ • -> ์—ฐ๊ด€๊ด€๊ณ„ ์ฃผ์ธ ๊ฐ์ฒด์˜ ํ•„๋“œ๊ฐ’
    private List<Order> orders = new ArrayList<>();
}

 

ํ™•์ธ

@Test
void ์—ฐ๊ด€๊ด€๊ณ„_ํ…Œ์ŠคํŠธ(){

    EntityManager entityManager = emf.createEntityManager();
    EntityTransaction transaction = entityManager.getTransaction();
    transaction.begin();

    Member member = new Member();
    member.setName("kanghonggu");
    member.setAddress("์„œ์šธ์‹œ ๋™์ž‘๊ตฌ(๋งŒ) ์›€์ง์ด๋ฉด ์œ๋‹ค.");
    member.setAge(33);
    member.setNickName("guppy.kang");

    entityManager.persist(member);

    Order order = new Order();
    order.setUuid(UUID.randomUUID().toString());
    order.setOrderDatetime(LocalDateTime.now());
    order.setOrderStatus(OPENED);
    order.setMemo("๋ถ€์žฌ์‹œ ์ „ํ™”์ฃผ์„ธ์š”.");
    order.setMember(member); // setMemberId๋ฅผ ํ•˜์ง€์•Š์•„๋„ ํ…Œ์ด๋ธ”์— FK๊ฐ€ ๋งคํ•‘๋จ.

    entityManager.persist(order);

    transaction.commit();

		entityManager.clear();
    Order entity = entityManager.find(Order.class, order.getUuid());

    log.info("{}", entity.getMember().getName());  // ๊ฐ์ฒด ๊ทธ๋ž˜ํ”„ ํƒ์ƒ‰์„ ํ†ตํ•ด member ๊ฐ€์ ธ์˜ค๊ธฐ
    log.info("{}", entity.getMember().getOrders().size());

		 log.info("{}", order.getMember().getOrders().size()); 
			// 0 -> ์ฝ”๋“œ๋ ˆ๋ฒจ memder์—๋Š” setOrders๋ฅผ ์•ˆํ•ด์ค˜์„œ ์‚ฌ์ด์ฆˆ๊ฐ€ 0์ด ๋‚˜์˜จ๋‹ค.
			// ํ•ด๋‹น ์ž‘์—…์ด ๋ฒˆ๊ฑฐ๋กœ์šฐ๋‹ˆ ์—ฐ๊ด€๊ด€๊ณ„ ํŽธ์˜ ๋ฉ”์†Œ๋“œ๋ฅผ ๋งŒ๋“ค์ˆ˜ ์žˆ๋‹ค. 
}

 

์ฟผ๋ฆฌ ํ™•์ธ

Hibernate: insert into member (address, age, description, name, nickName, id) values (?, ?, ?, ?, ?, ?)
Hibernate: insert into orders (member_id, memo, order_datetime, orderStatus, id) values (?, ?, ?, ?, ?)
Hibernate: select o1_0.id,m1_0.id,m1_0.address,m1_0.age,m1_0.description,m1_0.name,m1_0.nickName,o1_0.member_id,o1_0.memo,o1_0.order_datetime,o1_0.orderStatus from orders o1_0 left join member m1_0 on m1_0.id=o1_0.member_id where o1_0.id=?

 

์—ฐ๊ด€๊ด€๊ณ„ ํŽธ์˜ ๋ฉ”์†Œ๋“œ

: ์—ฐ๊ด€๊ด€๊ณ„ ํŽธ์˜ ๋ฉ”์†Œ๋“œ๋ฅผ ๋งŒ๋“ค์–ด์„œ ๋†“์น  ์ˆ˜ ์žˆ๋Š” ๋ถ€๋ถ„์„ ์ตœ์†Œํ™”ํ•œ๋‹ค.

@Entity
@Table(name = "orders")
@Getter
@Setter
public class Order {
    @Id
    @Column(name = "id")
    private String uuid;

    @Column(name = "order_datetime", columnDefinition = "TIMESTAMP")
    private LocalDateTime orderDatetime;

    @Enumerated(EnumType.STRING)
    private OrderStatus orderStatus;

    @Lob
    private String memo;

    @ManyToOne // ํšŒ์› ํ•œ๋ช…์—๊ฒŒ ์—ฌ๋Ÿฌ ์ฃผ๋ฌธ
    @JoinColumn(name = "member_id", referencedColumnName = "id") // ๋ช…์‹œํ•˜์ง€ ์•Š์„๊ฒฝ์šฐ ํ•ด๋‹นํ•„๋“œ๋ช…_PKํ•„๋“œ๋ช…
    private Member member;

    public void setMember(Member member){
        if(Objects.nonNull(this.member)){
            this.member.getOrders().remove(this);
        }
        this.member = member;
        member.getOrders().add(this);
    }
}
@Entity
@Table(name = "member")
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;

    @Column(name = "name", nullable = false, length = 30)
    private String name;

    @Column(nullable = false, length = 30, unique = true)
    private String nickName;

    @Column
    private int age;

    @Column(name = "address", nullable = false)
    private String address;

    @Column(name = "description")
    private String description;

    @OneToMany(mappedBy = "member") // ์—ฐ๊ด€๊ด€๊ณ„ ์ฃผ์ธ ์„ค์ • -> ์—ฐ๊ด€๊ด€๊ณ„ ์ฃผ์ธ ๊ฐ์ฒด์˜ ํ•„๋“œ๊ฐ’
    private List<Order> orders = new ArrayList<>();

    public void addOrder(Order order){
        order.setMember(this);
    }
}

 


 

๊ณ ๊ธ‰ ๋งคํ•‘

JPA๋Š” RDB ํ…Œ์ด๋ธ”๊ณผ ๋ฏธํ•‘๋œ ๊ฐ์ฒด(Entity)๋ฅผ ๊ฐ์ฒด๋‹ต๊ฒŒ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋„๋ก ์—ฌ๋Ÿฌ๊ฐ€์ง€ ๊ณ ๊ธ‰ ๋งคํ•‘ ์ „๋žต์„ ์ œ๊ณตํ•ด์ค€๋‹ค. → ์ƒ์† ๊ด€๊ณ„ ๋งคํ•‘, ์‹๋ณ„์ž ํด๋ž˜์Šค (๋ณตํ•ฉํ‚ค) ๋“ฑ

 

์ƒ์†๊ด€๊ณ„๋งคํ•‘

์กฐ์ธํ…Œ์ด๋ธ” ์ „๋žต (InheritanceType.JOINED)

 

Item.class

@Entity
@Table(name = "item")
@Getter
@Setter
@Inheritance(strategy = InheritanceType.JOINED)
public abstract class Item { // ์ถ”์ƒ class
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;
    private int price;
    private int stockQuantity;
}

 

Food.class

@Entity
@Getter
@Setter
public class Food extends Item {
    private String chef;
}

 

ํ™•์ธ

@Test
void inheritance_test() {
    EntityManager entityManager = emf.createEntityManager();
    EntityTransaction transaction = entityManager.getTransaction();

    transaction.begin();

    Food food = new Food();
    food.setPrice(5000);
    food.setStockQuantity(100);
    food.setChef("๋ฐฑ์ข…์›");

    entityManager.persist(food);
    transaction.commit();
    entityManager.clear();
    entityManager.find(Food.class, food.getId());

}

→ item ํ…Œ์ด๋ธ”์„ ๋งŒ๋“ค๊ณ  food ํ…Œ์ด๋ธ”์„ ๋งŒ๋“ ๋‹ค.

→ join์„ ํ•ด์„œ find๋ฅผ ํ•œ๋‹ค.

 

 

์‹ฑ๊ธ€ํ…Œ์ด๋ธ” ์ „๋žต, ๋‹จ์ผํ…Œ์ด๋ธ” ์ „๋žต(InheritanceType.SINGLE_TABLE)

Item.class

@Entity
@Table(name = "item")
@Getter
@Setter
@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name = "DTYPE") // ์–ด๋–ค ์ปฌ๋Ÿผ์„ ๊ตฌ๋ถ„์ž๋กœ ์‚ฌ์šฉํ• ์ง€
public abstract class Item { // ์ถ”์ƒ class
    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE)
    private Long id;
    private int price;
    private int stockQuantity;
}

 

Food.class

@Entity
@Getter
@Setter
@DiscriminatorValue("FOOD")
public class Food extends Item {
    private String chef;
}

 

ํ™•์ธ

create table item(
	DTYPE varchar(31) not null,
	id bigint not null, 
	price integer not null, 
	stockQuantity integer not null, 
	power integer, 
	chef varchar(255), 
	height integer, 
	width integer, 
	primary key (id)
)

→ item table๋งŒ ์ƒ์„ฑ๋จ

: ๋‹จ์ผ table์ด ์ƒ์„ฑ๋˜๊ณ  DiscriminatorColumn ์ปฌ๋Ÿผ ๊ฐ’์œผ๋กœ ๊ตฌ๋ถ„์„ ํ•ด์ค€๋‹ค.

: ์ž์‹ ์—”ํ‹ฐํ‹ฐ๊ฐ€ ๋งคํ•‘ํ•œ ์ปฌ๋Ÿผ์€ ๋ชจ๋‘ null ์„ ํ—ˆ์šฉํ•ด์•ผํ•œ๋‹ค๋Š” ๋‹จ์ ์ด ์žˆ๋‹ค.

 

 

: ์—ฌ๋Ÿฌ ํ…Œ์ด๋ธ”์ด ์ƒ์„ฑ๋˜๋ฉด ๊ด€๋ฆฌํ•ด์•ผํ•  ํ…Œ์ด๋ธ”์ด ๋งŽ์•„์ง€๊ณ  ๊ฐ์ฒด์ง€ํ–ฅ์Šค๋Ÿฝ์ง€์•Š์•„, ํ˜„์—…์—์„ ๋Š” ์‹ฑ๊ธ€ํ…Œ์ด๋ธ” ์ „๋žต์„ ๋งŽ์ด ์‚ฌ์šฉํ•œ๋‹ค.

 

 

MappedSuperClass

๋ถ€๋ชจ class๊ฐ€ ์‹ค์ œ Entity๊ฐ€ ๋˜๋Š” class๋Š” ์•„๋‹ˆ์ง€๋งŒ ์ƒ์†์„ ํ•˜๋ฉด ์ž์‹ clss์—์„œ ๋ถ€๋ชจ class์˜ ํ•„๋“œ๊ฐ€ ์ปฌ๋Ÿผ ๊ฐ’์œผ๋กœ ์ถ”๊ฐ€ mapping๋œ๋‹ค.

๊ณตํ†ต์œผ๋กœ ์‚ฌ์šฉ๋œ ํ•„๋“œ๊ฐ€ ์žˆ์„ ๋•Œ ์ž์ฃผ ์‚ฌ์šฉ๋œ๋‹ค.

@MappedSuperclass
@Getter
@Setter
public class BaseEntity {
    @Column(name = "created_by")
    private String createdBy;
    @Column(name = "created_at", columnDefinition = "TIMESTAMP")
    private LocalDateTime cratedAt;
}

//--------

@Entity
@Table(name = "orders")
@Getter
@Setter
public class Order extends BaseEntity{
    @Id
    @Column(name = "id")
    private String uuid;

    @Column(name = "order_datetime", columnDefinition = "TIMESTAMP")
    private LocalDateTime orderDatetime;

    @Enumerated(EnumType.STRING)
    private OrderStatus orderStatus;

    @Lob
    private String memo;

    @Column(name = "member_id", insertable = false, updatable = false) // fk
    private Long memberId;

    @ManyToOne // ํšŒ์› ํ•œ๋ช…์—๊ฒŒ ์—ฌ๋Ÿฌ ์ฃผ๋ฌธ
    @JoinColumn(name = "member_id", referencedColumnName = "id") // ๋ช…์‹œํ•˜์ง€ ์•Š์„๊ฒฝ์šฐ ํ•ด๋‹นํ•„๋“œ๋ช…_PKํ•„๋“œ๋ช…
    private Member member;

    public void setMember(Member member){
        if(Objects.nonNull(this.member)){
            member.getOrders().remove(this);
        }
        this.member = member;
        member.getOrders().add(this);
    }
}

 

์‹๋ณ„์ž ํด๋ž˜์Šค

JPA์—์„œ ์‹œ๋ณ„์ž๋ฅผ ๋‘˜ ์ด์ƒ ์‚ฌ์šฉํ•˜๋ ค๋ฉด ๋ณ„๋„์˜ ์‹๋ณ„์ž ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค์–ด์•ผํ•œ๋‹ค.

๋ณตํ•ฉํ‚ค ๊ด€๋ฆฌ๋ฅผ ์œ„ํ•œ 2๊ฐ€์ง€ ์ „๋žต ์ œ๊ณตํ•œ๋‹ค.

 

@Id 2๊ฐœ๋ฅผ ์‚ฌ์šฉํ•œ๋Š”๊ฑด ์™œ ์•ˆ๋ ๊นŒ?

→ JPA๋Š” ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์—์„œ ์—”ํ‹ฐํ‹ฐ๋ฅผ ๋น„๊ตํ• ๋•Œ, Id๋ฅผ key ๊ฐ’์œผ๋กœ ๋ณด๊ด€ํ•œ๋‹ค. ํ•ด๋‹น key๋Š” Equals & hashCode๋ฅผ ์ด์šฉํ•˜์—ฌ ๋™๋“ฑ์„ฑ ๋น„๊ต๋ฅผ ํ•œ๋‹ค. ๊ทธ๋ ‡๊ธฐ๋•Œ๋ฌธ์— 2๊ฐœ์˜ ํ•„๋“œ๋ฅผ ๋”ฐ๋กœ ์ง€์ •ํ•˜๋ฉด runtime error๋ฐœ์ƒ.

 

 

์‹ค๋ณ„์ž ํด๋ž˜์Šค ์‚ฌ์šฉ์‹œ

  • Serializable ์ธํ„ฐํŽ˜์ด์Šค๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.
  • eqauls, hashCode๋ฅผ ๊ตฌํ˜„ํ•ด์•ผ ํ•œ๋‹ค.
  • ๊ธฐ๋ณธ ์ƒ์„ฑ์ž๊ฐ€ ์žˆ์–ด์•ผ ํ•œ๋‹ค.
  • ์‹๋ณ„์ž ํด๋ž˜์Šค๋Š” public ์ด์–ด์•ผ ํ•œ๋‹ค.

 

@IdClass

@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
public class ParentId implements Serializable {
    private String id1;
    private String id2;
}

// ---

@Entity
@Getter
@Setter
@IdClass(ParentId.class)
public class Parent {
    @Id
    private String id1;
    @Id
    private String id2;
}

 

์กฐํšŒ

Parent parent = new Parent();
parent.setId1("id1");
parent.setId2("id2");
entityManager.persist(parent);
transaction.commit();

Parent entity = entityManager.find(Parent.class, new ParentId("id1", "id2"));
log.info("{}, {}", entity.getId1(), entity.getId2());

 

 

@Embeddedld (์ถ”์ฒœ)

์‹๋ณ„์ž ์ž์ฒด๋ฅผ ๋ช…์‹œ. ๋” ๋งŽ์ด ์‚ฌ์šฉํ•˜๋Š” ๋ฐฉ์‹์ด๋‹ค.

@Getter
@Setter
@EqualsAndHashCode
@NoArgsConstructor
@AllArgsConstructor
@Embeddable
public class ParentId implements Serializable {
    private String id1;
    private String id2;
}

// ---

@Entity
@Getter
@Setter
public class Parent {
    @EmbeddedId
    private ParentId id;
}

 

์กฐํšŒ

Parent parent = new Parent();
parent.setId(new ParentId("id1","id2"));
entityManager.persist(parent);
transaction.commit();

Parent entity = entityManager.find(Parent.class, new ParentId("id1", "id2"));
log.info("{}, {}", entity.getId().getId2(), entity.getId().getId1());

 


 

ํ”„๋ก์‹œ ๊ฐ์ฒด

 

๊ฐ์ฒด ๊ทธ๋ž˜ํ”„ ํƒ์ƒ‰

๊ฐ์ฒด๋Š” ๊ฐ์ฒด ๊ทธ๋ž˜ํ”„๋กœ ์—ฐ๊ด€๋œ ๊ฐ์ฒด๋ฅผ ํƒ์ƒ‰ํ•œ๋‹ค. (order.getMember(), member.getOrders())

Entity๋Š” ๊ฐ์ฒด๊ฐ€ RDS์™€ ๋งคํ•‘๋˜์–ด ์žˆ์–ด ์ž์œ ๋กญ๊ฒŒ ๊ฐ์ฒด๋ฅผ ํƒ์ƒ‰ํ•˜๋Š”๋ฐ ์ œํ•œ์ด ์žˆ๋‹ค.

JPA๋Š” ํ”„๋ก์‹œ ๊ฐ์ฒด๋ผ๋Š” ๊ธฐ์ˆ ์„ ์ƒ์šฉํ•˜์—ฌ ์—ฐ๊ด€๋œ ๊ฐ์ฒด๋ฅผ ์ฒ˜์Œ๋ถ€ํ„ฐ ์กฐํšŒํ•˜์ง€์•Š๊ณ , ์‹ค์ œ ์‚ฌ์šฉํ•˜๋Š” ์‹œ์ ์— ์กฐํšŒํ•  ์ˆ˜ ์žˆ๋‹ค.

 

ํ”„๋ก์‹œ ๊ฐ์ฒด

@Entity
@Table(name = "member")
public class Member extends BaseEntity {
		...

    @OneToMany(mappedBy = "member", fetch = FetchType.LAZY)
    private List<Order> orders = new ArrayList<>(); // proxy

    ...
}
@Entity
@Table(name = "orders")
public class Order extends BaseEntity {
   ...

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "member_id", referencedColumnName = "id")
    private Member member;

    ...
}

 

ํ…Œ์ŠคํŠธ

@Test
void proxy(){
    EntityManager entityManager = emf.createEntityManager();

    Order order = entityManager.find(Order.class,uuid);

    Member member = order.getMember();
    log.info("Member use before is-Loaded : {}",emf.getPersistenceUnitUtil().isLoaded(member)); // ์‚ฌ์šฉํ•˜๊ธฐ์ „ member ๊ฐ์ฒด๋Š” proxy ๊ฐ์ฒด - false

    var orders = member.getOrders();
    log.info("Member use after is-Loaded : {}",emf.getPersistenceUnitUtil().isLoaded(member));
    log.info(orders.toString());
}

 

ํ”„๋ก์‹œ์˜ ํŠน์ง•

  • ํ”„๋ก์‹œ ๊ฐ์ฒด๋Š” Entity class๋ฅผ ์ƒ์† ๋ฐ›์•„ ๋งŒ๋“ค์–ด์ง„๋‹ค. (jpa๊ฐ€ ๋‚ด๋ถ€์ ์œผ๋กœ ์ƒ์†์„ ๋ฐ›์•„ ๋งŒ๋“ฌ)
  • ์‹ค์ œ ํด๋ž˜์Šค์™€ ๊ฒ‰ ๋ชจ์–‘์ด ๊ฐ™๋‹ค. (์‚ฌ์šฉ์ž ์ž…์žฅ์—์„œ ์ง„์งœ ๊ฐ์ฒด์ธ์ง€ ํ”„๋ก์‹œ ๊ฐ์ฒด์ธ์ง€ ๊ตฌ๋ถ„ํ•˜์ง€ ์•Š๊ณ  ์‚ฌ์šฉ)

  • ํ”„๋ก์‹œ ๊ฐ์ฒด๋Š” ์‹ค์ œ ๊ฐ์ฒด์˜ ์ฐธ์กฐ๋ฅผ ๋ณด๊ด€ํ•œ๋‹ค.(target)
  • ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ํ˜ธ์ถœํ•˜๋ฉด ํ”„๋ก์‹œ ๊ฐ์ฒด๋Š” ์‹ค์ œ ๊ฐ์ฒด์˜ ๋ฉ”์†Œ๋“œ๋ฅผ ํ˜ธ์ถœํ•œ๋‹ค.

 

์ดˆ๊ธฐํ™”

  • ํ”„๋ก์‹œ ๊ฐ์ฒด๋Š” ์ฒ˜์Œ ์‚ฌ์šฉํ• ๋•Œ ํ•œ๋ฒˆ๋งŒ ์ดˆ๊ธฐํ™” ๋œ๋‹ค. (์ดˆ๊ธฐํ™”์ „์—๋Š” target๊ฐ’์ด ์กด์žฌํ•˜์ง€์•Š๋Š”๋‹ค.)
  • ํ”„๋ก์‹œ ๊ฐ์ฒด๊ฐ€ ์ดˆ๊ธฐํ™”๋˜๋ฉด, ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ํ†ตํ•ด ์‹ค์ œ Entity์— ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์˜์†์„ฑ ์ปจํ…์ŠคํŠธ์˜ ๋„์›€์„ ๋ฐ›์•„ ์ดˆ๊ธฐํ™”ํ•œ๋‹ค. ๋”ฐ๋ผ์„œ ์ค€์˜์† ์ƒํƒœ์˜ ํ”„๋ก์‹œ๋ฅผ ์ดˆ๊ธฐํ™”ํ•˜๋ฉด (ํŠธ๋žœ์žญ์…˜ ๋ฐ–์—์„œ ํ”„๋ก์‹œ ๊ฐ์ฒด๋ฅผ ์กฐํšŒํ•  ๊ฒฝ์šฐ) LazyInitializationException ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค. - OSIV

 

 

์ง€์—ฐ๋กœ๋”ฉ(Lazy) vs ์ฆ‰์‹œ๋กœ๋”ฉ(EAGER)

  • ์ง€์—ฐ ๋กœ๋”ฉ → ์—ฐ๊ด€๋œ Entity๋Š” ์‹ค์ œ ์‚ฌ์šฉํ• ๋•Œ ์กฐํšŒํ•œ๋‹ค.
  • @OneToMany, @ManyToMany์ฒ˜๋Ÿผ Many๋กœ ๋๋‚˜๋Š” ๊ฒƒ๋“ค์€ ๊ธฐ๋ณธ๊ฐ’์ด ์ง€์—ฐ ๋กœ๋”ฉ์ด๋‹ค.

  • ์ฆ‰์‹œ ๋กœ๋”ฉ → ์—”ํ‹ฐํ‹ฐ๋ฅผ ์กฐํšŒํ• ๋•Œ, ์—ฐ๊ด€๋œ Entity๋„ ํ•จ๊ป˜ ์กฐํšŒํ•œ๋‹ค.
  • @ManyToOne, @OneToOne์ฒ˜๋Ÿผ One์œผ๋กœ ๋๋‚˜๋Š” ๊ฒƒ๋“ค์€ ๊ธฐ๋ณธ๊ฐ’์ด ์ฆ‰์‹œ ๋กœ๋”ฉ์ด๋‹ค.

 

ํ”„๋ก์‹œ ๊ด€๋ จ util ๋ฉ”์†Œ๋“œ

  • PersistenceUnitUtil.isLoaded(Object entity) → ํ”„๋ก์‹œ ์ธ์Šคํ„ด์Šค ์ดˆ๊ธฐํ™” ์—ฌ๋ถ€ ํ™•์ธ
  • entity.getClass().getName() ์ถœ๋ ฅ → ํ”„๋ก์‹œ ํด๋ž˜์Šค ํ™•์ธ

์ถ”๊ฐ€ ํ•™์Šต → n+1 ๋ฌธ์ œ, ์ง€์—ฐ ๋กœ๋”ฉ๊ณผ ํŽ˜์น˜ ์กฐ์ธ

 

 

์˜์†์„ฑ ์ „์ด(CASCADE)

ํŠน์ • ์—”ํ‹ฐํ‹ฐ๋ฅผ ์˜์† ์ƒํƒœ๋กœ ๋งŒ๋“ค ๋•Œ, ์—ฐ๊ด€๋œ ์—”ํ‹ฐํ‹ฐ๋„ ํ•จ๊ป˜ ์˜์†์ƒํƒœ๋กœ ๋งŒ๋“ค๊ณ  ์‹ถ์„๋•Œ, ์‚ฌ์šฉํ•œ๋‹ค.

public enum CascadeType {
    ALL,      // ๋ชจ๋‘ ์ ์šฉ
    PERSIST,  // ์˜์†
    MERGE,    // ๋ณ‘ํ•ฉ
    REMOVE,   // ์‚ญ์ œ
    REFRESH,  // REFRESH
    DETACH;   // DETACH  

    private CascadeType() {
    }
}
@Test
void move_persist(){
    EntityManager entityManager = emf.createEntityManager();
    EntityTransaction transaction = entityManager.getTransaction();

    transaction.begin();
    Order order = entityManager.find(Order.class,uuid);

    OrderItem item = new OrderItem();
    item.setQuantity(10);
    item.setPrice(10000);

    order.addOrderItem(item);
    transaction.commit();
}

 

์˜์†์„ฑ ์ „์ด๋ฅผ ํ•˜์ง€ ์•Š์•˜์„ ๊ฒฝ์šฐ

→ Order์€ ์˜์†์„ฑ ์ƒํƒœ์ด์ง€๋งŒ, OrderItem์€ ์˜์†์ƒํƒœ๊ฐ€ ๋˜์ง€ ๋ชปํ•˜์˜€๋‹ค.

→ addOrderItem๋ฅผ ํ–ˆ์ง€๋งŒ OrderItem์— insert ์ฟผ๋ฆฌ๊ฐ€ ๋‚ ๋ผ๊ฐ€์ง€ ์•Š์Œ.

 

์˜์†์„ฑ ์ „์ด ์ถ”๊ฐ€

@Entity
@Table(name = "orders")
public class Order extends BaseEntity {
    ...

    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL) 
    private List<OrderItem> orderItems = new ArrayList<>();
		...
}

๋ณดํ†ต ์‹๋ณ„๊ด€๊ณ„(identifying)์— ์žˆ๋Š” Entity๋“ค ์ค‘ @OneToMany์˜ต์…˜์— ๊ฑด๋‹ค.

 

 

๊ณ ์•„๊ฐ์ฒด

@Test
void orphan() {
    EntityManager entityManager = emf.createEntityManager();

    // ํšŒ์› ์กฐํšŒ -> ํšŒ์›์˜ ์ฃผ๋ฌธ ๊นŒ์ง€ ์กฐํšŒ
    Order order = entityManager.find(Order.class, uuid);
    order.getOrderItems().remove(0); // orderItem๋ฅผ ์ œ๊ฑฐํ•œ๋‹ค. (๊ณ ์•„๊ฐ์ฒด ๋ฐœ์ƒ)

    EntityTransaction transaction = entityManager.getTransaction();

    transaction.begin();
    transaction.commit();
}

delete ์ฟผ๋ฆฌ๊ฐ€ ๋‚ ๋ผ๊ฐ€์ง€ ์•Š๊ณ  ๊ณ ์•„๊ฐ์ฒด ๋ฐœ์ƒ

@Entity
@Table(name = "orders")
public class Order extends BaseEntity {
    ...

		// ๊ณ ์•„๊ฐ์ฒด๋ฅผ flush ์ˆœ๊ฐ„ RDS์—์„œ๋„ ์‚ญ์ œํ•˜๊ฒ ๋‹ค.
    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL, orphanRemoval = true) 
    private List<OrderItem> orderItems = new ArrayList<>();
		...
}

delete ์ฟผ๋ฆฌ๊ฐ€ ๋‚ ๋ผ๊ฐ€๊ฒŒ๋œ๋‹ค.

 

 

orphanRemoval=true์™€ Cascade.REMOVE ์ฐจ์ด

https://tecoble.techcourse.co.kr/post/2021-08-15-jpa-cascadetype-remove-vs-orphanremoval-true/

Cascade.REMOVE๋Š” one์— ํ•ด๋‹นํ•˜๋Š” ์—”ํ‹ฐํ‹ฐ๊ฐ€ remove ๋ ๋•Œ, many์— ํ•ด๋‹น๋˜๋Š” ์—”ํ‹ฐํ‹ฐ๋“ค๋„ ๋ชจ๋‘ ์‚ญ์ œ๋˜๋Š”๊ฒƒ. orphanRemoval=true๋Š” ์œ„์˜ ๊ฒฝ์šฐ ํฌํ•จ, one์˜ list์˜ ์š”์†Œ๋“ค์„ ์‚ญ์ œํ•˜๊ธฐ๋งŒ ํ•ด๋„(์ฆ‰, ๊ด€๊ณ„๋ฅผ ์ œ๊ฑฐํ•˜๊ธฐ๋งŒ ํ•ด๋„) ํ•ด๋‹น ์—”ํ‹ฐํ‹ฐ๊นŒ์ง€ ์‚ญ์ œ๋˜๋Š” ๊ธฐ๋Šฅ.

 

→ ์ฆ‰ ๋ถ€๋ชจ entity๊ฐ€ ์ž์‹์˜ ์ƒ๋ช…์ฃผ๊ธฐ๋ฅผ ๊ด€๋ฆฌ ํ•˜๊ฒŒ๋œ๋‹ค. ์ด๋Ÿด ๊ฒฝ์šฐ ์ž์‹ ์š”์†Œ์˜ repository๊ฐ€ ์•„์˜ˆ ํ•„์š”์—†๊ฒŒ๋„ ๋œ๋‹ค.

 

 

 

์ถœ์ฒ˜ - backend dev course ๊ฐ•ํ™๊ตฌ๋‹˜

๋Œ“๊ธ€