[Basic-7] 프록시와 연관관계 관리

2026. 1. 6. 13:44·Spring/JPA

1. 프록시

1.1. 프록시의 필요성

문제 상황: Member를 조회할 때 항상 Team도 함께 조회해야 할까?

// 케이스 1: 회원과 팀 정보를 함께 사용하는 경우
public void printUserAndTeam(String memberId) {
    Member member = em.find(Member.class, memberId);
    Team team = member.getTeam();
    System.out.println("회원 이름: " + member.getUsername());
    System.out.println("소속팀: " + team.getName());  // Team 정보 필요
}
// 케이스 2: 회원 정보만 사용하는 경우
public void printUser(String memberId) {
    Member member = em.find(Member.class, memberId);
    System.out.println("회원 이름: " + member.getUsername());  // Team 정보 불필요
}

위 두 케이스에서 em.find()는 항상 Member와 연관된 Team까지 함께 조회하게 된다. 이는 성능상 비효율적이다.

1.2. 프록시 기초

JPA는 이 문제를 해결하기 위해 프록시(Proxy)라는 가짜 엔티티 객체를 제공한다.

// 실제 엔티티 조회
Member member = em.find(Member.class, "member1");  // 즉시 DB 조회
System.out.println("member.getClass() = " + member.getClass());
// 출력: class hellojpa.Member (실제 엔티티)

// 프록시 엔티티 조회
Member proxyMember = em.getReference(Member.class, "member1");  // DB 조회 지연
System.out.println("proxyMember.getClass() = " + proxyMember.getClass());
// 출력: class hellojpa.Member$HibernateProxy$xxx (프록시 객체)

em.find() vs em.getReference()

  • em.find(): 데이터베이스를 통한 즉시 실제 엔티티 객체 조회
  • em.getReference(): 데이터베이스 조회를 미루는 프록시 엔티티 객체 반환

1.3. 프록시 특징

프록시는 실제 클래스를 상속받아 만들어지기 때문에 겉모양이 실제 클래스와 동일하다.

// 프록시 객체 구조 (개념적)
class MemberProxy extends Member {
    private Member target = null;  // 실제 엔티티 참조

    public String getName() {
        if (target == null) {
            // 초기화: 실제 엔티티 조회
            this.target = ...;  // DB에서 실제 Member 조회
        }
        return target.getName();  // 실제 엔티티의 메소드 호출
    }
}

1.4. 프록시 객체의 초기화

Member proxyMember = em.getReference(Member.class, "member1");  // DB 조회 X

// 프록시 객체 사용
proxyMember.getName();  // 이 시점에서 DB 조회 (초기화)

초기화 과정

  1. 프록시 객체의 메소드 호출 (getName())
  2. 프록시 객체가 영속성 컨텍스트에 초기화 요청
  3. 영속성 컨텍스트가 데이터베이스 조회
  4. 실제 엔티티 생성 및 프록시 객체와 연결
  5. 프록시 객체가 실제 엔티티의 메소드 호출

1.5. 프록시의 특징 정리

  1. 초기화는 한 번만: 프록시 객체가 초기화되면 실제 엔티티에 접근 가능
  2. 타입 비교 주의: 프록시는 원본을 상속받으므로 == 비교 실패
    Member m1 = em.find(Member.class, memberId);
    Member m2 = em.getReference(Member.class, memberId);
    
    System.out.println(m1 == m2);  // false (프록시 vs 실제)
    System.out.println(m1 instanceof Member);  // true
    System.out.println(m2 instanceof Member);  // true (프록시도 Member 타입)
    
  3. 영속성 컨텍스트에 있으면 실제 엔티티 반환
    Member m1 = em.find(Member.class, memberId);  // 실제 엔티티
    Member m2 = em.getReference(Member.class, memberId);  // 실제 엔티티 반환
    
  4. 준영속 상태에서 초기화 불가
    Member proxyMember = em.getReference(Member.class, "member1");
    em.detach(proxyMember);  // 준영속 상태로 변경
    
    proxyMember.getName();  // LazyInitializationException 발생
    

1.6. 프록시 확인

// 프록시 인스턴스 초기화 여부 확인
PersistenceUnitUtil util = em.getEntityManagerFactory().getPersistenceUnitUtil();
boolean isLoaded = util.isLoaded(entity);

// 프록시 클래스 확인
System.out.println(entity.getClass().getName());
// 출력 예: com.example.Member$HibernateProxy$...

// 프록시 강제 초기화 (하이버네이트 전용)
Hibernate.initialize(entity);

// JPA 표준 강제 초기화 방법
entity.getSomeField();  // 메소드 호출로 초기화

2. 즉시 로딩과 지연 로딩

2.1. 지연 로딩 (LAZY)

연관된 엔티티를 실제 사용할 때까지 조회를 지연시키는 전략이다.

@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;

    private String name;

    @ManyToOne(fetch = FetchType.LAZY)  // 지연 로딩 설정
    @JoinColumn(name = "TEAM_ID")
    private Team team;  // 프록시 객체로 조회
}

지연 로딩 사용 예시

// Member만 조회 (Team은 조회 안됨)
Member member = em.find(Member.class, "member1");
System.out.println("member = " + member.getName());

// Team 사용 시점에 조회
Team team = member.getTeam();  // 이 시점에서 Team 조회 (프록시 초기화)
System.out.println("team = " + team.getName());

실행되는 SQL

-- em.find(Member.class, "member1") 실행 시
SELECT m.* FROM MEMBER m WHERE m.ID = ?

-- member.getTeam().getName() 실행 시
SELECT t.* FROM TEAM t WHERE t.ID = ?

2.2. 즉시 로딩 (EAGER)

연관된 엔티티를 함께 조회하는 전략이다.

@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;

    private String name;

    @ManyToOne(fetch = FetchType.EAGER)  // 즉시 로딩 설정
    @JoinColumn(name = "TEAM_ID")
    private Team team;  // 함께 조회
}

즉시 로딩 사용 예시

// Member와 Team을 함께 조회
Member member = em.find(Member.class, "member1");
System.out.println("member = " + member.getName());
System.out.println("team = " + member.getTeam().getName());  // 이미 조회됨

실행되는 SQL (조인 사용)

-- em.find(Member.class, "member1") 실행 시
SELECT m.*, t.*
FROM MEMBER m
LEFT OUTER JOIN TEAM t ON m.TEAM_ID = t.ID
WHERE m.ID = ?

2.3. 프록시와 즉시로딩 주의사항

중요 규칙

  1. 가급적 지연 로딩만 사용 (특히 실무에서)
  2. 즉시 로딩은 JPQL에서 N+1 문제 발생
  3. 기본 페치 전략 변경 필요
    • @ManyToOne, @OneToOne: 기본이 즉시 로딩 → LAZY로 변경
    • @OneToMany, @ManyToMany: 기본이 지연 로딩 → 변경 불필요

N+1 문제 예시

// JPQL 사용 시
List<Member> members = em.createQuery(
    "SELECT m FROM Member m", Member.class)
    .getResultList();

// 실행되는 SQL (Member 수만큼 Team 조회 쿼리 추가)
// 1. SELECT m.* FROM MEMBER m
// 2. SELECT t.* FROM TEAM t WHERE t.ID = ?  (N번 실행)

3. 지연 로딩 활용

3.1. 다양한 연관관계 로딩 전략

@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;
    private String name;

    // 자주 함께 사용 -> 즉시 로딩 (이론적)
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "TEAM_ID")
    private Team team;

    // 가끔 사용 -> 지연 로딩
    @OneToMany(mappedBy = "member", fetch = FetchType.LAZY)
    private List<Order> orders = new ArrayList<>();
}

@Entity
public class Order {
    @Id @GeneratedValue
    private Long id;

    // 자주 함께 사용 -> 즉시 로딩 (이론적)
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "PRODUCT_ID")
    private Product product;
}

3.2. 실무 적용 가이드라인

핵심 원칙: 모든 연관관계에 지연 로딩을 사용하라!

// 올바른 설정 예시
@Entity
public class Member {
    @Id @GeneratedValue
    private Long id;
    private String name;

    @ManyToOne(fetch = FetchType.LAZY)  // 항상 LAZY
    @JoinColumn(name = "TEAM_ID")
    private Team team;

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

@Entity
public class Order {
    @Id @GeneratedValue
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)  // 항상 LAZY
    @JoinColumn(name = "PRODUCT_ID")
    private Product product;
}

즉시 로딩 대체 방법

  1. Fetch Join: JPQL에서 함께 조회
    String jpql = "SELECT m FROM Member m JOIN FETCH m.team";
    List<Member> members = em.createQuery(jpql, Member.class).getResultList();
    
  2. 엔티티 그래프: @NamedEntityGraph 사용
  3. 배치 사이즈: @BatchSize 애노테이션 사용

4. 영속성 전이: CASCADE

4.1. 개념

특정 엔티티를 영속 상태로 만들 때, 연관된 엔티티도 함께 영속 상태로 만드는 기능이다.

@Entity
public class Parent {
    @Id @GeneratedValue
    private Long id;
    private String name;

    @OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
    private List<Child> children = new ArrayList<>();
}

@Entity
public class Child {
    @Id @GeneratedValue
    private Long id;
    private String name;

    @ManyToOne
    @JoinColumn(name = "PARENT_ID")
    private Parent parent;
}

4.2. 영속성 전이 저장 (⭐⭐)

// CASCADE 없을 때
Parent parent = new Parent();
Child child1 = new Child();
Child child2 = new Child();

parent.addChild(child1);
parent.addChild(child2);

em.persist(parent);    // Parent 저장
em.persist(child1);    // Child1 저장
em.persist(child2);    // Child2 저장 (총 3번 persist)

// CASCADE 있을 때
Parent parent = new Parent();
Child child1 = new Child();
Child child2 = new Child();

parent.addChild(child1);
parent.addChild(child2);

em.persist(parent);    // Parent만 저장하면 Child도 자동 저장

4.3. CASCADE 종류

CASCADE 타입 설명
ALL 모든 영속성 전이 적용 (PERSIST + REMOVE + MERGE + REFRESH + DETACH)
PERSIST 부모 저장 시 자식도 저장
REMOVE 부모 삭제 시 자식도 삭제
MERGE 부모 병합 시 자식도 병합
REFRESH 부모 refresh 시 자식도 refresh
DETACH 부모 detach 시 자식도 detach

4.4. CASCADE 주의사항

CASCADE는 연관관계 매핑과 무관하다!

  • 단순히 편의 기능 제공
  • 부모-자식 관계에서 사용 (라이프사이클이 동일할 때)
  • 다른 엔티티에서도 참조하면 사용하지 말 것
// 잘못된 사용 예시
@Entity
public class Member {
    @OneToMany(mappedBy = "member", cascade = CascadeType.ALL)
    private List<Order> orders = new ArrayList<>();  // Order는 다른 곳에서도 참조 가능
}

5. 고아 객체

5.1. 개념

부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동으로 삭제하는 기능이다.

@Entity
public class Parent {
    @Id @GeneratedValue
    private Long id;
    private String name;

    @OneToMany(mappedBy = "parent",
               cascade = CascadeType.ALL,
               orphanRemoval = true)  // 고아 객체 제거 활성화
    private List<Child> children = new ArrayList<>();
}

@Entity
public class Child {
    @Id @GeneratedValue
    private Long id;
    private String name;

    @ManyToOne
    @JoinColumn(name = "PARENT_ID")
    private Parent parent;
}

5.2. 고아 객체 제거 동작

Parent parent = em.find(Parent.class, parentId);
parent.getChildren().remove(0);  // 컬렉션에서 자식 제거

// 실행되는 SQL
// DELETE FROM CHILD WHERE ID = ?

5.3. 고아 객체 주의사항

  1. 참조하는 곳이 하나일 때만 사용
    // 여러 곳에서 참조하는 경우 사용 금지
    @Entity
    public class Product {
        @ManyToOne
        private Category category;  // 여러 OrderItem에서 참조 가능
    }
    
  2. @OneToOne, @OneToMany에서만 사용 가능
  3. 개념적 소유 관계일 때 사용
  4. 부모 삭제 시 자식도 함께 삭제됨 (CascadeType.REMOVE와 유사)

6. 영속성 전이 + 고아 객체, 생명주기

6.1. 조합 사용

CascadeType.ALL + orphanRemoval = true를 함께 사용하면 부모 엔티티를 통해 자식의 생명주기를 완전히 관리할 수 있다.

@Entity
public class Parent {
    @Id @GeneratedValue
    private Long id;
    private String name;

    @OneToMany(mappedBy = "parent",
               cascade = CascadeType.ALL,
               orphanRemoval = true)
    private List<Child> children = new ArrayList<>();

    // 연관관계 편의 메서드
    public void addChild(Child child) {
        children.add(child);
        child.setParent(this);
    }

    public void removeChild(Child child) {
        children.remove(child);
        child.setParent(null);
    }
}

6.2. 생명주기 관리

// 자식의 생명주기를 부모가 관리
Parent parent = new Parent("부모");
Child child1 = new Child("자식1");
Child child2 = new Child("자식2");

// 자식 추가 (생성)
parent.addChild(child1);
parent.addChild(child2);
em.persist(parent);  // 자식도 함께 저장

// 자식 제거 (삭제)
parent.removeChild(child1);  // 자식1 삭제 (고아 객체)

// 부모 수정 (자식 업데이트)
Child child3 = new Child("자식3");
parent.addChild(child3);  // 자식3 추가
parent.getChildren().get(0).setName("수정된 자식");  // 자식 수정

em.merge(parent);  // 모든 변경사항 적용

6.3. DDD Aggregate Root 구현

도메인 주도 설계(DDD)의 Aggregate Root 개념을 구현할 때 유용하다.

// 주문 Aggregate Root
@Entity
@Table(name = "orders")
public class Order {
    @Id @GeneratedValue
    private Long id;

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

    // 비즈니스 메서드들
    public void addOrderItem(Product product, int count) {
        OrderItem orderItem = new OrderItem(this, product, count);
        orderItems.add(orderItem);
    }

    public void removeOrderItem(OrderItem orderItem) {
        orderItems.remove(orderItem);
    }

    public void cancel() {
        // 주문 취소 로직
        this.status = OrderStatus.CANCELLED;
    }

    // 주문 금액 계산
    public int getTotalPrice() {
        return orderItems.stream()
                .mapToInt(OrderItem::getTotalPrice)
                .sum();
    }
}

7. 실전 예제 - 연관관계 관리

7.1. 글로벌 페치 전략 설정

모든 연관관계를 지연 로딩으로 설정

// 회원 엔티티
@Entity
@Table(name = "member")
public class Member {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @Embedded
    private Address address;

    @OneToMany(mappedBy = "member")  // 기본이 LAZY
    private List<Order> orders = new ArrayList<>();
}

// 주문 엔티티
@Entity
@Table(name = "orders")
public class Order {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)  // LAZY로 명시적 설정
    @JoinColumn(name = "member_id")
    private Member member;

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

    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)  // LAZY 설정
    @JoinColumn(name = "delivery_id")
    private Delivery delivery;

    private LocalDateTime orderDate;

    @Enumerated(EnumType.STRING)
    private OrderStatus status;
}

// 주문상품 엔티티
@Entity
@Table(name = "order_item")
public class OrderItem {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)  // LAZY 설정
    @JoinColumn(name = "order_id")
    private Order order;

    @ManyToOne(fetch = FetchType.LAZY)  // LAZY 설정
    @JoinColumn(name = "item_id")
    private Item item;

    private int orderPrice;
    private int count;
}

// 배송 엔티티
@Entity
@Table(name = "delivery")
public class Delivery {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToOne(mappedBy = "delivery", fetch = FetchType.LAZY)  // LAZY 설정
    private Order order;

    @Embedded
    private Address address;

    @Enumerated(EnumType.STRING)
    private DeliveryStatus status;
}

7.2. 영속성 전이 설정

연관된 엔티티의 생명주기를 함께 관리

// 주문 엔티티 - 연관된 엔티티들에 영속성 전이 설정
@Entity
@Table(name = "orders")
public class Order {
    @Id @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

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

    // OrderItem에 영속성 전이 ALL 설정
    @OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
    private List<OrderItem> orderItems = new ArrayList<>();

    // Delivery에 영속성 전이 ALL 설정
    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    @JoinColumn(name = "delivery_id")
    private Delivery delivery;

    // 연관관계 편의 메서드
    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);
        order.setOrderDate(LocalDateTime.now());
        order.setStatus(OrderStatus.ORDER);

        for (OrderItem orderItem : orderItems) {
            order.addOrderItem(orderItem);
        }

        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() {
        return orderItems.stream()
                .mapToInt(OrderItem::getTotalPrice)
                .sum();
    }
}

7.3. 서비스 레이어 예시

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class OrderService {

    private final EntityManager em;
    private final MemberRepository memberRepository;
    private final ItemRepository itemRepository;
    private final OrderRepository orderRepository;

    // 주문 생성
    @Transactional
    public Long order(Long memberId, Long itemId, int count) {
        // 엔티티 조회
        Member member = memberRepository.findById(memberId)
                .orElseThrow(() -> new EntityNotFoundException("회원을 찾을 수 없습니다."));

        Item item = itemRepository.findById(itemId)
                .orElseThrow(() -> new EntityNotFoundException("상품을 찾을 수 없습니다."));

        // 배송정보 생성
        Delivery delivery = new Delivery();
        delivery.setAddress(member.getAddress());
        delivery.setStatus(DeliveryStatus.READY);

        // 주문상품 생성
        OrderItem orderItem = OrderItem.createOrderItem(item, item.getPrice(), count);

        // 주문 생성 (영속성 전이로 인해 delivery, orderItem 자동 저장)
        Order order = Order.createOrder(member, delivery, orderItem);

        // 주문 저장
        em.persist(order);
        return order.getId();
    }

    // 주문 취소
    @Transactional
    public void cancelOrder(Long orderId) {
        Order order = orderRepository.findById(orderId)
                .orElseThrow(() -> new EntityNotFoundException("주문을 찾을 수 없습니다."));

        // 주문 취소 (연관된 엔티티들도 함께 업데이트)
        order.cancel();
    }

    // 주문 조회 (지연 로딩 최적화)
    public OrderDto getOrder(Long orderId) {
        // 페치 조인으로 한 번에 조회
        Order order = em.createQuery(
            "SELECT o FROM Order o " +
            "JOIN FETCH o.member m " +
            "JOIN FETCH o.delivery d " +
            "JOIN FETCH o.orderItems oi " +
            "JOIN FETCH oi.item i " +
            "WHERE o.id = :orderId", Order.class)
            .setParameter("orderId", orderId)
            .getSingleResult();

        return new OrderDto(order);
    }
}

8. 정리

8.1. 핵심 원칙

  1. 지연 로딩을 기본으로 사용
    • 모든 @ManyToOne, @OneToOne에 fetch = FetchType.LAZY 설정
    • 필요할 때만 Fetch Join이나 엔티티 그래프 사용
  2. 영속성 전이 신중하게 사용
    • 라이프사이클이 완전히 동일한 부모-자식 관계에서만 사용
    • CascadeType.ALL + orphanRemoval = true 조합으로 생명주기 관리
  3. 프록시 이해 필수
    • 타입 비교는 instanceof 사용
    • 초기화 여부 확인 가능
    • 준영속 상태에서 초기화 불가

8.2. 실무 적용 체크리스트

✅ 해야 할 것

  • 모든 연관관계에 지연 로딩 적용
  • 필요할 때만 Fetch Join 사용
  • 영속성 전이는 진짜 부모-자식 관계에서만
  • 연관관계 편의 메서드 구현
  • 서비스 레이어에서 트랜잭션 관리

❌ 하지 말 것

  • 즉시 로딩 사용
  • 불필요한 영속성 전이 설정
  • 여러 곳에서 참조하는 엔티티에 고아 객체 제거 설정
  • 프록시를 ==로 비교

프록시와 연관관계 관리는 JPA 성능 최적화의 핵심이다. 지연 로딩을 기본으로 사용하고, 필요할 때만 적절한 최적화 기법을 적용하는 것이 실무에서 안정적인 애플리케이션을 구축하는 길이다.

'Spring > JPA' 카테고리의 다른 글

[Basic-9] 객체지향 쿼리 언어(JPQL)  (0) 2026.01.06
[Basic-8] 값 타입  (0) 2026.01.06
[Basic-6] 고급 관계 매핑  (0) 2026.01.06
[Basic-5] 연관관계 매핑 심화  (0) 2026.01.06
[Basic-4] 연관관계 매핑 기초  (0) 2026.01.06
'Spring/JPA' 카테고리의 다른 글
  • [Basic-9] 객체지향 쿼리 언어(JPQL)
  • [Basic-8] 값 타입
  • [Basic-6] 고급 관계 매핑
  • [Basic-5] 연관관계 매핑 심화
h6bro
h6bro
백엔드 개발자의 기술 블로그
  • h6bro
    Jun's Tech Blog
    h6bro
  • 전체
    오늘
    어제
    • 분류 전체보기 (250) N
      • Java (18)
        • Core (9)
        • Design Pattern (9)
      • Spring (80)
        • Core (24)
        • MVC (6)
        • DB (10)
        • JPA (26)
        • Monitoring (3)
        • Security (11)
        • WebSocket (0)
      • Database (33)
        • Redis (15)
        • MySQL (18)
      • MSA (25) N
        • MSA 기본 (11)
        • MSA 아키텍처 (14) N
      • Kafka (30)
        • Core (18)
        • Connect (12)
      • ElasticSearch (11)
        • Search (11)
        • Logging (0)
      • Test (4)
        • k6 (4)
      • Docker (9)
      • CI&CD (10)
        • GitHub Actions (6)
        • ArgoCD (4)
      • Kubernetes (18)
        • Core (12)
        • Ops (6)
      • Cloud Engineering (4)
        • AWS Infrastructure (3)
        • AWS EKS (1)
        • Terraform (0)
      • Project (8)
        • LinkFolio (1)
        • Secondhand Market (7)
  • 블로그 메뉴

    • 홈
    • 태그
    • 방명록
  • 링크

  • 공지사항

    • Cloud Engineering 포스팅 정리
  • 인기 글

  • 태그

    ㅈ
  • 최근 댓글

  • 최근 글

  • hELLO· Designed By정상우.v4.10.5
h6bro
[Basic-7] 프록시와 연관관계 관리
상단으로

티스토리툴바