728x90
반응형
728x90

https://woozzang.tistory.com/29

 

📋 값 타입 컬렉션

값 타입 컬렉션(Value Type Collection)은 컬렉션(리스트, 세트, 맵 등) 내부에 단순한 값들을 저장하는 것을 의미합니다.
컬렉션을 사용하여 DB 테이블로 구현할 때 관계형 데이터 베이스는 기본적으로 테이블안에 컬렉션을 담을 수 없습니다. KEY , VALUE로만 담길 수 있습니다.

컬렉션은 일 대 다 개념이기 때문에 해당 컬렉션으로 선언된 객체들을 별도의 테이블로 만들어서 생성합니다. 일반적으로 객체 지향 프로그래밍에서 컬렉션은 객체들을 저장하는데 사용됩니다.
객체는 여러 속성을 가지고 있으며, 이러한 속성들이 객체의 상태를 나타내는 경우가 많습니다. 그래서 컬렉션은 객체들을 담는 용도로 사용됩니다.

그러나 때로는 객체들의 내용 중에서 중요한 부분이 객체의 식별자가 아닌 실제 값 자체인 경우가 있습니다. 예를 들어, 좌표를 표현하는 (x, y) 형태의 객체가 있다면, 이 좌표를 간단히 값 자체로 저장하고 싶을 수 있습니다. 이때 값 타입 컬렉션을 사용합니다.

값 타입 컬렉션은 객체를 저장하는 것이 아니라 그 객체의 내용 자체를 컬렉션에 저장합니다.
이러한 값 타입 컬렉션은 불필요한 객체 생성을 피하고 메모리 사용을 최적화하는 데 도움이 됩니다.
예를 들어, Java에서는 ArrayList과 같은 컬렉션은 객체를 저장합니다. 하지만 List가 아니라, Java 8부터 추가된 IntList와 같은 라이브러리를 사용하면, 정수 값 자체를 저장하는 것이 가능합니다.

값 타입 컬렉션의 주요 특징은 다음과 같습니다.

  1. 지연로딩(Lazy Loading) : 값 타입 컬렉션은 기본적으로 지연로딩을 지원합니다. 즉, 컬렉션에 접근할 때 해당 값들이 실제로 로드되는 것을 말합니다. 이를 통해 필요한 경우에만 데이터를 로드하여 성능을 최적화할 수 있습니다.

  2. 고아 객체 제거(Cascade Remove) : 값 타입 컬렉션에서 특정 값을 삭제할 때, 해당 값을 컬렉션에서 제거하는 것뿐만 아니라 데이터베이스에서도 해당 값의 레코드를 제거하는 기능을 제공합니다. 이를 통해 컬렉션에서 제거된 값은 데이터베이스에 반영됩니다.

  3. 값 타입 컬렉션의 매핑 테이블 : 값 타입 컬렉션은 별도의 중간 테이블을 생성하여 값들을 저장합니다. 이 테이블은 부모 엔티티의 기본 키와 값 타입의 컬럼을 조합하여 복합 기본 키를 갖게 됩니다.

  4. 컬렉션 변경 감지 : 값 타입 컬렉션은 JPA의 변경 감지 기능을 활용하여 컬렉션의 변경 사항을 감지하고 자동으로 데이터베이스에 반영할 수 있습니다.

  5. 동등성 비교 : 값 타입 컬렉션은 각 값의 동등성 비교를 통해 컬렉션의 중복 값을 방지합니다.

 


 

@ElementCollection

 

@ElementCollection은 JPA(Java Persistence API)에서 제공하는 어노테이션 중 하나로, 엔티티 내부에서 컬렉션 타입을 다룰 때 사용됩니다. 
주로 일대다 관계를 가지지 않는 속성을 매핑하고자 할 때 사용되며, 이를 통해 별도의 테이블을 생성하지 않고도 컬렉션 값을 저장할 수 있습니다.

@ElementCollection 어노테이션의 역할과 특징은 다음과 같습니다:

  1. 속성 값 저장: 주로 엔티티 클래스 내부의 컬렉션 타입(리스트, 셋, 맵 등)의 속성을 매핑하는 데 사용됩니다. 이 컬렉션 타입은 해당 엔티티의 테이블과는 별도의 테이블에 저장됩니다.

  2. 중첩 테이블 생성: @ElementCollection을 사용하면 컬렉션 요소들을 담기 위한 중첩 테이블이 생성됩니다. 이 중첩 테이블은 별도의 테이블로 생성되며, 해당 컬렉션 값들을 저장합니다.

  3. 매핑 관계 설정: @ElementCollection을 사용하는 필드에는 @CollectionTable 어노테이션을 함께 사용하여 중첩 테이블의 매핑 관계를 설정할 수 있습니다.

  4. 복합 타입 지원: 컬렉션 내의 각 요소는 복합 타입일 수 있습니다. 즉, 여러 속성으로 구성된 객체를 컬렉션 내에 저장할 수 있습니다.

  5. 생성된 SQL: **@ElementCollection**을 사용하면 JPA는 중첩 테이블을 생성하고 관리하기 위한 SQL을 생성합니다. 이를 통해 컬렉션 값을 데이터베이스에 저장하고 조회할 수 있습니다.

간단한 예시를 통해 이해를 도울 수 있습니다. 예를 들어,Member 엔티티가 있고, 해당 Member가 좋아하는 음식과 Member의 주소지에 대한 정보들을 저장해야한다고 가정해보겠습니다.

 

간단한 예시를 통해 이해를 도울 수 있습니다. 예를 들어,Member 엔티티가 있고, 해당 Member가 좋아하는 음식과 Member의 주소지에 대한 정보들을 저장해야한다고 가정해보겠습니다.

                @Entity
                public class Member {

                    @Id 
                    @GeneratedValue
                    @Column(name = "MEMBER_ID")
                    private Long id;

                    @Column(name = "USERNAME")
                    private String username;


                    //주소에 대한 속성
                    @Embedded
                    private Address homeAddress;

                    @ElementCollection
                    @CollectionTable(name ="FAVORITE_FOOD", joinColumns =
                        @JoinColumn(name = "MEMBER_ID")
                    )
                    @Column(name = "FOOD_NAME")
                    private Set<String> favoriteFoods = new HashSet<>();

                    @ElementCollection
                    @CollectionTable(name = "ADDRESS",  joinColumns =
                        @JoinColumn(name = "MEMBER_ID")
                    )
                    private List<Address> addressHistory = new ArrayList<>();

 

위의 코드에서 FavoriteFoods 필드와 Address는 @ElementCollection 어노테이션을 사용하여 사용자가 좋아하는 음식을 저장하는데, 이 필드의 값들은 FAVORITE_FOOD라는 테이블에 저장됩니다.
그리고 Member 테이블의 Member_ID와 Join하여 외래키로 설정될 수 있도록 지정합니다. 요약하면, @ElementCollection어노테이션은 컬렉션 값들을 별도의 테이블로 저장하고 조회할 수 있도록 도와주는 JPA의 기능입니다.

 

 

 

실행 한 후 생성된 MEMBER 테이블을 확인해보면, 임베디드 타입으로 설정된 homeAddress가 값 타입으로 지정되어 생성됨을 확인할 수 있습니다. 그리고 Favorite_food 테이블도 생성이 되었는데,
Member_Id와 FoodName 컬럼으로 만들어진 걸 확인할 수 있습니다. Member_ID는 각 테이블과의 연관관계로 설정해두어야 어떤 테이블의 어떤 Member_ID에 소속되어 있는지 외래키 값으로 지정되도록 작성해주어야 합니다.

 

728x90
반응형

'[ BACKEND] > Spring' 카테고리의 다른 글

[SPRING] Servlet & MV  (0) 2023.08.22
[SPRING] 기본 값 타입  (0) 2023.08.20
[SPRING] 영속성 전이 & 고아객체  (0) 2023.08.19
[SPRING] 즉시 로딩과 지연 로딩  (0) 2023.08.18
[SPRING] ✅ Proxy  (0) 2023.08.17
728x90

https://velog.io/@yu-jin-song/

 

📋 기본 값 타입 매핑

JPA에서는 데이터 타입을 최 상위 레벨로 분류하면 엔티티 타입과 값 타입으로 분류할 수 있습니다.

엔티티 타입은 @Entity로 정의하는 객체로 데이터가 변경되어도 식별자로 인식이 가능합니다.
ex) 회원 엔티티안에서 키 또는 나이의 값이 변경되어도 식별자를 이용해서 인식하여 내부 데이터가 변경되어도 추적이 가능합니다.

② 값 타입은 int, integer,String 처럼 단순히 값으로 사용하는 자바 기본 타입이나 객체를 의미합니다.
식별자가 없고 값만 있으므로 (int나 integer는 단순한 값이기 때문에) 변경 시 히스토리 추적이 불가능합니다.
ex) 숫자 100을 200으로 변경하면 완전히 다른값으로 대체될 뿐 과거이력을 알 수는 없습니다. 게시판에 String 컨텐츠라는 건 Board라는 엔티티안에서 변경되는 경우 이전 값을 추적하기 어렵습니다.

  1. 기본 값 타입 은 JPA(Java Persistence API)에서 객체의 기본 데이터 타입을 데이터베이스의 열(Column)에 매핑하는 과정을 말합니다. 기본 값 타입에는 자바의 기본 데이터 타입(int, double, boolean 등), 해당 타입들의 래퍼 클래스(Integer, Double, Boolean 등), String 타입 의미합니다.생명주기를 엔티티에 의존하는 특징이 있습니다. 예를 들어, 회원을 삭제하게되면 이름, 나이 필드도 함께 삭제되어집니다. 값 타입에 value 자체는 절대로 공유하면 안됩니다. 회원 이름을 변경할 때, 다른 회원의 이름도 변경되는 사이드 이펙트 같은 부수효과가 일어나서는 안됩니다.

    JPA에서 엔티티(Entity) 클래스의 속성을 데이터베이스의 열에 매핑할 때, 기본 값 타입을 사용하는 경우에는 각 속성의 데이터 타입을 그대로 데이터베이스의 열에 저장합니다. 이러한 기본 값 타입 매핑은 @Column 어노테이션을 통해 열의 이름 및 속성과 매핑 정보를 지정할 수 있습니다.

    이 때, 엔티티 객체의 속성이 객체지향 프로그래밍에서 사용되는 기본 데이터 타입인 경우, 이를 데이터베이스에 매핑하여 영속화합니다. 엔티티 클래스의 속성 중에서 기본값 타입인 경우, 이 속성은 별도의 테이블로 분리되지 않고, 해당 엔티티의 테이블에 열(Column)로 직접 매핑됩니다. 이를 통해 엔티티 속성의 매핑 정보를 지정하고 데이터베이스와의 연결을 설정할 수 있습니다.

 

✍️ 기본 값 타입 예제

 

			int  예시

                        int a = 10;
                int b = a; //b가 초기화될 때, a의 10의 value가 복사가 되어 b에 저장됨

                a = 20; //b value에 20을 넣으면 b의 값만 변하게 됨

                System.out.println("a = " + a);
                System.out.println("b = " + b);

                        ==== 출력 ====
                        a = 20;
                        b = 10;


                    래퍼클래스 Integer 예시

                    Integer a = Integer.valueOf(10);
              Integer b = a; // 이 땐, a의 value가 복사되어 넘어가는 것이 아니라 주소값(참조값)이 전달됩니다.

                기본 값 타입은 항상 값을 복사하게 되어, value가 공유되지 않습니다.
              * Integer같은 래퍼 클래스나 String 같은 특수한 클래스는 레퍼런스를 가지고 가기 때문에 참조값 공유가 가능한 객체이지만
              * 변경자체를 불가능하게 두었기때문에, 사이드이펙트가 발생하지 않아 개발 시 안전하게 개발이 가능합니다. */

 

 


 

 2. 임베디드 타입은 데이터베이스 내에 객체를 저장하는 방식 중 하나입니다. 객체를 데이터베이스의 한 열(column)에 저장하는 것이 아니라, 객체 내부의 필드를 테이블의 열로 매핑하여 저장하는 방식입니다. 이는 관계형 데이터베이스 시스템에서 객체 지향 프로그래밍의 객체를 효율적으로 저장하고 검색하는 방법을 제공하는 기술입니다.JPA는 임베디드 타입이라고 하고, 기본 값 타입을 모아서 만들어서 복합 값 타입이라고도 불립니다.

int나 String 처럼 임베디드 타입도 값 타입으로 변경이 불가하고 추적도 어렵습니다. 예를 들어, 자바의 JPA(Java Persistence API)에서는 
@Embeddable 어노테이션을 사용하여 임베디드 타입을 정의하고, 이를 엔티티 클래스의 필드에 @Embedded 어노테이션을 통해 적용할 수 있습니다. 이를 통해 관계형 데이터베이스의 테이블 내부에 객체의 필드를 중첩하여 저장할 수 있습니다.

여러 엔티티에서 반복적으로 사용되는 기본 값 타입들을 하나의 임베디드 클래스로 새로운 값 타입을 정의하여 중복 코드를 줄이고 유지보수성을 높일 수 있습니다.이를 통해 복잡한 데이터 구조를 단순화하고 재사용성을 높일 수 있습니다.

 

✍️ @Embeddable과 @Embedded

 

@Embeddable : @Embeddable 어노테이션은 임베디드 타입을 정의하는 클래스에 붙입니다. 이 클래스는 데이터베이스 테이블의 열(column)처럼 객체 내부의 필드를 나타내며, 해당 필드들을 중첩해서 저장할 때 사용됩니다.

@Embedded : @Embedded 어노테이션은 엔티티 클래스의 필드에 임베디드 타입을 적용할 때 사용됩니다. 임베디드 타입의 필드들은 해당 엔티티의 테이블에 중첩해서 저장됩니다.

 

            === @Embeddable ===

                    @Embeddable

                    public class Address {
                        private String street;
                        private String city;
                        private String zipCode;
                        // ...
                    }


            === @Embedded ===

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

                private String name;

                @Embedded
                private Address address;

                // ...
            }

 

위의 예시에서 Address 클래스는 @Embeddable로 정의된 임베디드 타입이며, Member 클래스의 address 필드에 @Embedded 어노테이션을 사용하여 해당 임베디드 타입을 적용했습니다. 따
라서 Member 엔티티의 테이블에는 중첩된 형태로 Address의 필드들이 저장됩니다.

 

✅@Embeddable 타입의 장점

 

  1. 복합 객체를 단일 엔티티로 표현: 복합 객체(Composite Object)는 여러 개의 속성으로 구성된 객체를 말합니다. 예를 들어 주소(Address)나 이름(Name) 등은 여러 속성으로 구성될 수 있는데, 이러한 복합 객체를 단일 엔티티에 중첩해서 저장하고 관리할 수 있습니다. 

  2. 코드의 가독성 향상: 여러 엔티티에서 공통적으로 사용되는 복합 객체를 중복 없이 정의하고 재사용할 수 있습니다. 이로써 코드 중복을 줄이고 가독성을 향상시킬 수 있습니다.

  3. 데이터베이스 효율성: 임베디드 타입은 엔티티의 속성을 테이블의 열(column)로 매핑하지 않고 중첩 구조로 저장합니다. 이로써 데이터베이스 테이블의 정규화(normalization) 원칙을 준수할 수 있어 중복 데이터를 효율적으로 관리하고 저장 공간을 절약할 수 있습니다.

  4. 객체 지향 설계 유지: 객체 지향 프로그래밍의 핵심 원칙 중 하나인 "객체는 상태와 행위를 함께 가진다"를 지키며 객체의 구조를 유연하게 설계할 수 있습니다.

  5. 쿼리 간결성: 임베디드 타입을 사용하면 복합 객체의 속성을 편리하게 조회하거나 조건 검색에 활용할 수 있습니다. 이로 인해 복잡한 쿼리를 작성하는 번거로움을 줄일 수 있습니다.

임베디드 타입은 엔티티의 값일 뿐입니다. 크게 의미가 있지 않으며, 임베디드 타입을 적용하기 전과 후의 매핑하는 테이블은 일치하다는 특징이 가장 중요한 특징입니다. 객체와 테이블을 아주 세밀하게 매핑하는 것이 가능하게 됩니다.

728x90
반응형

'[ BACKEND] > Spring' 카테고리의 다른 글

[SPRING] Servlet & MV  (0) 2023.08.22
[SPRING] 값 타입 컬렉션  (0) 2023.08.21
[SPRING] 영속성 전이 & 고아객체  (0) 2023.08.19
[SPRING] 즉시 로딩과 지연 로딩  (0) 2023.08.18
[SPRING] ✅ Proxy  (0) 2023.08.17
728x90

 

https://cupjoo.github.io/

 

💡영속성 전이 (CASCADE)

 

영속성 전이(CASCADE)는 데이터베이스의 관계형 데이터베이스 관리 시스템에서 사용되는 개념으로, 부모 엔티티의 영속성 상태 변화가 자식 엔티티에도 영향을 미치는 것을 말합니다. 이는 주로 ORM(Object-Relational Mapping) 기술을 사용하는 애플리케이션에서 엔티티 사이의 관계를 관리할 때 사용됩니다.

CASCADE 옵션은 부모 엔티티의 영속성 상태 변화가 일어났을 때, 그 영향이 자식 엔티티에 자동으로 전이되도록 설정하는 기능을 제공합니다. 이로써 부모 엔티티의 상태 변화에 따라 자식 엔티티도 일괄적으로 상태 변화를 일으키며, 데이터 일관성을 유지할 수 있습니다.

CASCADE 옵션은 다양한 관계에서 사용될 수 있습니다. 주요한 릴레이션 종류는 다음과 같습니다:

  1. One-to-One 관계: 일반적으로 부모 엔티티가 삭제되면 자식 엔티티도 삭제될 수 있습니다. 반대로 자식 엔티티를 삭제해도 부모 엔티티에는 영향을 미치지 않습니다.

  2. One-to-Many 관계: 일반적으로 부모 엔티티가 삭제되면 연관된 모든 자식 엔티티들도 삭제될 수 있습니다. 그러나 자식 엔티티의 삭제가 부모 엔티티에 영향을 미치지는 않습니다.

  3. Many-to-One 관계: Many-to-One 관계에서는 보통 부모 엔티티의 상태 변화가 자식 엔티티에도 영향을 미치지 않습니다. 부모 엔티티를 삭제해도 자식 엔티티에 영향을 주지 않는 경우가 일반적입니다.

  4. Many-to-Many 관계: Many-to-Many 관계에서 CASCADE 옵션은 다소 복잡할 수 있습니다. 예를 들어 두 엔티티 간의 연관 관계를 해제할 때 어떻게 동작할지 설정하는 등의 다양한 케이스가 있을 수 있습니다.

CASCADE 옵션은 데이터베이스 스키마 설계 및 애플리케이션 동작에서 중요한 역할을 합니다. 그러나 사용할 때 주의해야 할 점도 있습니다:

  • 무분별한 사용으로 데이터 일관성 유지에 문제가 생길 수 있습니다.
  • CASCADE DELETE를 사용할 경우 실수로 연관된 모든 데이터를 삭제하는 상황이 발생할 수 있습니다.
  • 데이터베이스의 CASCADE 옵션을 사용할 때는 그에 따른 쿼리 및 성능 이슈를 고려해야 합니다.

따라서 CASCADE 옵션을 사용할 때는 신중하게 상황을 판단하고 데이터 일관성을 유지하기 위해 적절한 설정을 해야 합니다.

 

📋CASCADE 사용 예제

CASCADE를 사용한 예제를 하나 들어보겠습니다. 회원(Member) 엔티티와 해당 회원이 작성한 게시글(Post) 엔티티 간에 One-to-Many 관계가 있다고 가정해봅시다. CASCADE를 사용하여 회원을 삭제할 때 해당 회원의 모든 게시글도 함께 삭제되도록 설정해볼 것입니다.

 

            ===== 회원 엔티티 =====

                        @Entity
                        public class Member {
                            @Id
                            @GeneratedValue(strategy = GenerationType.IDENTITY)
                            private Long id;

                            private String username;

                            // 다른 멤버필드, Getter,Setter등은 생략

                            @OneToMany(mappedBy = "writer", cascade = CascadeType.ALL, orphanRemoval = true)
                            private List<Post> posts = new ArrayList<>();

                        }




        ===== 게시글 엔티티 =====

                @Entity
                public class Post {
                    @Id
                    @GeneratedValue(strategy = GenerationType.IDENTITY)
                    private Long id;

                    private String title;
                    private String content;

                        // 다른 게시필드, Getter,Setter등은 생략

                    @ManyToOne
                    @JoinColumn(name = "writer_id")
                    private Member writer;

                }

 

위의 코드에서 Member 엔티티의 posts 필드에 @OneToMany 어노테이션에 cascade 속성을 CascadeType.ALL로 설정했습니다.

이는 Member 엔티티의 상태 변화(예: 삭제)가 해당 회원의 모든 게시글에도 전이되도록 지정하는 것입니다. 또한 orphanRemoval = true 설정으로 연관 관계가 끊어질 때 해당 게시글을 자동으로 제거합니다.

이제 만약 특정 회원을 삭제하면, 해당 회원의 모든 게시글도 자동으로 삭제됩니다.

 

            @Service
            @Transactional
            public class MemberService {
                // MemberService methods...

                public void deleteMember(Long memberId) {
                    Member member = memberRepository.findById(memberId).orElse(null);
                    if (member != null) {
                        memberRepository.delete(member);
                    }
                }
            }

 

위의 deleteMember 메서드를 호출하여 특정 회원을 삭제하면, 해당 회원의 CASCADE 설정에 따라 모든 게시글도 함께 삭제될 것입니다.

JPA에서는 다양한 종류의 Cascade 옵션을 제공하여 영속성 전이 동작을 지원합니다. 각 Cascade 옵션은 엔티티의 상태 변화가 다른 연관 엔티티에 어떻게 전파되는지를 정의합니다. 주요한 Cascade 옵션은 다음과 같습니다:

  1. CascadeType.ALL : 모든 상태 변화를 전이합니다. 해당 엔티티의 PERSIST, MERGE, REMOVE, REFRESH, DETACH 등의 모든 작업이 연관된 엔티티에도 적용됩니다.

  2. CascadeType.PERSIST : 엔티티가 영속 상태로 전이될 때, 해당 엔티티의 상태 변화를 연관된 엔티티에도 전이시킵니다.

  3. CascadeType.MERGE : 엔티티의 변경이 병합될 때(merge) 해당 엔티티의 상태 변화를 연관된 엔티티에도 전이시킵니다.

  4. CascadeType.REMOVE : 엔티티가 삭제될 때, 해당 엔티티의 상태 변화를 연관된 엔티티에도 전이시킵니다.

  5. CascadeType.REFRESH : 엔티티를 새로고침(refresh)할 때, 해당 엔티티의 상태 변화를 연관된 엔티티에도 전이시킵니다.

  6. CascadeType.DETACH : 엔티티를 분리(detach)할 때, 해당 엔티티의 상태 변화를 연관된 엔티티에도 전이시킵니다.

  7. CascadeType.NONE : 어떤 상태 변화도 전이하지 않습니다. 연관된 엔티티의 상태는 변경되지 않습니다.

그 외의 사용자 정의 Cascade 옵션: JPA 구현체나 프레임워크에서 사용자가 직접 정의한 Cascade 옵션입니다. 예를 들어 Hibernate에서는 CascadeType.PERSIST와 CascadeType.MERGE를 결합한 CascadeType.PERSIST_AND_MERGE 옵션이 있습니다.

Cascade 옵션은 각각의 상황과 데이터 모델에 따라 적절하게 선택하여 사용해야 합니다. 잘못된 Cascade 옵션 사용은 의도하지 않은 동작을 초래할 수 있으므로 신중하게 선택해야 합니다.

 

💡 고아객체 (Orphan Removal)

고아객체(Orphan Removal)는 JPA에서 관계가 끊어진 엔티티를 자동으로 삭제해주는 기능을 말합니다. 이는 연관 관계에서 부모 엔티티와 연결되어 있는 자식 엔티티가 더 이상 해당 부모와의 연관 관계를 갖지 않을 때, 자동으로 삭제되도록 설정하는 것을 의미합니다.

예를 들어, 부모 엔티티에서 자식 엔티티의 컬렉션을 가지고 있고 이 컬렉션의 연관 관계를 끊게 되면, 고아객체 설정이 활성화되어 있다면 해당 자식 엔티티가 데이터베이스에서 삭제됩니다.

고아객체 설정을 사용하면 불필요한 데이터를 데이터베이스에 남기지 않고 자동으로 관리할 수 있습니다. 하지만 주의해야 할 점은, 고아객체 설정을 사용할 경우에는 해당 자식 엔티티를 사용하는 모든 곳에서 주의해서 다루어야 합니다. 만약에 다른 곳에서 해당 자식 엔티티를 다시 참조하려는 경우에 문제가 발생할 수 있습니다.

 

✍️ 고아객체 사용 예제

고아객체 설정은 @OneToOne, @OneToMany 어노테이션의 orphanRemoval 속성으로 활성화할 수 있습니다. 예를 들어, 다음과 같이 사용할 수 있습니다

 

                    @Entity
                    public class Parent {
                        @OneToMany(mappedBy = "parent", orphanRemoval = true)
                        private List<Child> children = new ArrayList<>();
                        // ...
                    }

                    @Entity
                    public class Child {
                        @ManyToOne
                        private Parent parent;
                        // ...
                    }


        Parent findParent = em.find(Parent.class, parent.getId());
        findParent.getChildList().remove(0);

 

위의 코드에서 orphanRemoval = true로 설정되어 있기 때문에, Parent 엔티티와 연관된 Child 엔티티가 끊어질 경우 자동으로 삭제됩니다.

 

참조가 제거된 엔티티는 다른 곳에서 참조하지 않는 고아객체로 보고 삭제하는 기능입니다. 주의점은 특정 엔티티가 개인 소유할 때 사용해야하며, 참조하는 곳이 하나일 때 사용해야합니다. @OneToOne , @OneToMany만 가능합니다.

이론적으로는 부모를 제거하면 자식이 고아가 되기때문에 객체 제거 기능을 활성화 하면, 부모를 제거할 때 자식도 함께 제거됩니다. CascadeType.REMOVE 처럼 동작합니다.

 


        ===== Member 엔티티에서의 연관관계 설정시, orphanRemoval = true ======

                    @OneToMany(mappedBy = "parent", orphanRemoval = true)
                  private List<Child> childList = new ArrayList<>();


        ===== main메서드에서 parent 객체를 em.remove ======

                    Parent findParent = em.find(Parent.class, parent.getId());
                    em.remove(findParent);

 

Parent만 remove했지만 child도 모두 지워집니다.

 

 

✍️ CascadeType.ALL orphanRemoval = true를 함께 사용

✅ 관련된 엔티티를 완전히 제어하면서 데이터 일관성과 무결성을 유지하기 위해

CascadeType.ALL은 부모 엔티티의 상태 변화가 자식 엔티티에 전이되도록 설정하는 것입니다. 이것은 부모 엔티티에 대한 변경이 자식 엔티티에도 자동으로 전이되어 부모-자식 간의 관계를 편리하게 관리할 수 있게 해줍니다. 예를 들어, 부모 엔티티가 삭제될 때 자식 엔티티도 함께 삭제됩니다.

orphanRemoval = true는 연관 관계가 끊어진 자식 엔티티를 자동으로 삭제하도록 설정하는 것입니다. 이는 자식 엔티티가 더 이상 부모 엔티티와 연관 관계를 갖지 않을 때, 자동으로 삭제되도록 해줍니다. 이것은 자식 엔티티를 효과적으로 관리하면서 데이터베이스의 일관성을 유지하는 데 도움을 줍니다.

따라서 CascadeType.ALL과 orphanRemoval = true를 함께 사용하면, 부모 엔티티의 모든 상태 변화가 자식 엔티티에 전이되고, 자식 엔티티와 부모 엔티티 간의 연관 관계가 끊어진 경우 자식 엔티티가 자동으로 삭제됩니다. 두 옵션을 활성화하게 되면부모 엔티티를 통해서 자식의 생명주기를 관리할 수 있습니다. 이렇게 함으로써 데이터베이스 내의 엔티티 간의 관계를 관리하고 데이터 무결성을 보장할 수 있게 됩니다.

728x90
반응형

'[ BACKEND] > Spring' 카테고리의 다른 글

[SPRING] 값 타입 컬렉션  (0) 2023.08.21
[SPRING] 기본 값 타입  (0) 2023.08.20
[SPRING] 즉시 로딩과 지연 로딩  (0) 2023.08.18
[SPRING] ✅ Proxy  (0) 2023.08.17
[SPRING] 고급 매핑 - 구현 클래스 , 조인 전략  (0) 2023.08.16
728x90

 

📋 즉시 로딩과 지연 로딩

 

즉시로딩(Immediate Loading)과 지연로딩(Lazy Loading)은 데이터베이스에서 데이터를 조회하는 방식 중의 하나로, 객체 간의 연관관계를 어떻게 로딩하고 관리할 것인지에 대한 개념입니다.

  1. 즉시로딩(Immediate Loading) 즉시로딩은 엔티티를 조회할 때 해당 엔티티와 연관된 모든 엔티티를 동시에 조회하는 방식입니다.
    예를 들어, A 엔티티와 B 엔티티가 연관되어 있을 때 A를 조회하면 B도 함께 조회됩니다. 이로 인해 객체 간의 관계를 필요한 시점에 바로 사용할 수 있습니다.
    하지만 조인 등의 복잡한 쿼리가 생성될 수 있고, 불필요한 데이터 로딩으로 인해 성능 문제가 발생할 수도 있습니다.

  2. 지연로딩(Lazy Loading) : 객체를 Proxy로 가져온 후 실제 해당 객체를 사용하는 시점에 초기화 지연로딩은 연관된 엔티티를 처음에는 조회하지 않고, 실제로 해당 엔티티가 필요한 시점에 조회하는 방식입니다.
    예를 들어, A 엔티티를 조회해도 B 엔티티는 초기에 조회되지 않고, B 엔티티를 실제로 사용할 때 데이터베이스에서 조회됩니다. 이를 통해 쿼리의 최적화와 성능 향상을 이룰 수 있습니다.
    그러나 연관 엔티티를 사용하는 과정에서 데이터베이스 쿼리가 추가적으로 발생하게 될 수 있습니다.

지연로딩은 대부분의 JPA 구현체에서 지원되는 기능이며, 엔티티 클래스의 연관 관계 필드에 @ManyToOne, @OneToMany, @OneToOne, @ManyToMany와 같은 어노테이션을 사용할 때
fetch 속성을 지정하여 조절할 수 있습니다. 지연로딩을 사용하려면 데이터베이스 트랜잭션 내에서 연관된 엔티티에 접근해야만 데이터베이스 조회가 일어납니다.

각각의 상황과 필요에 따라 선택하여 전략적으로 사용할 수 있습니다.

즉시로딩(Immediate Loading)을 선택하는 이유

  1. 객체 간의 관계를 활용하기 편리: 즉시로딩을 사용하면 객체를 조회할 때 연관된 모든 객체가 한 번에 로딩되므로 객체 간의 관계를 편리하게 활용할 수 있습니다. 모든 연관된 데이터가 이미 로딩되어 있으므로 어떤 객체를 사용할 때 별다른 데이터베이스 조회 없이 객체 그래프를 따라 이동할 수 있습니다.

  2. 복잡한 조회를 단순화: 데이터베이스에서 조인을 사용하여 복잡한 연관관계를 해결할 필요 없이, 즉시로딩으로 모든 데이터를 한 번에 가져올 수 있습니다.

 

지연로딩(Lazy Loading)을 선택하는 이유

  1. 성능 최적화: 즉시로딩은 모든 연관된 데이터를 한 번에 가져오기 때문에, 필요하지 않은 데이터까지 불필요하게 로딩될 수 있습니다. 이는 성능 저하를 야기할 수 있습니다. 지연로딩은 필요한 시점에만 데이터를 로딩하기 때문에 성능을 최적화할 수 있습니다.

  2. 데이터 접근을 최적화: 사용자가 실제로 해당 데이터를 사용할 때만 로딩하므로 데이터베이스 접근이 최적화됩니다. 따라서 시스템 전체적으로 데이터 로딩에 대한 부하가 분산될 수 있습니다.

  3. 순환 참조 방지: 지연로딩을 사용하면 객체 간의 연관관계에서 순환 참조가 발생할 확률이 줄어듭니다. 객체를 조회할 때 실제로 필요한 데이터만 로딩되므로 무한한 순환 참조를 방지할 수 있습니다.

  4. 메모리 사용 최적화: 즉시로딩은 연관된 모든 데이터를 로딩하기 때문에 메모리를 많이 사용할 수 있습니다. 지연로딩은 필요한 데이터만 로딩하기 때문에 메모리 사용을 최적화할 수 있습니다.

즉시로딩과 지연로딩은 각각의 상황에 따라 선택되며, 객체의 연관관계가 어떻게 사용되는지, 어떤 데이터가 실제로 필요한지 등을 고려하여 결정해야 합니다.
JPA를 사용하는 경우에는 애플리케이션의 성능, 데이터 접근 패턴, 메모리 사용 등을 고려하여 최적의 로딩 전략을 선택하는 것이 중요합니다.

 

✅ 지연로딩의 예제

Member Entity 내부에 Team 객체가 연관관계 매핑이 되어 있는 경우, Member만 조회하고자 할 때, Member 내부의 Team 객체에 지연로딩을 설정하여
해당 엔티티가 실제로 사용되는 경우에 초기화가 발생하며 쿼리문을 호출할 수 있도록 합니다.

    Team team = new Team();
                team.setName("teamA");
                em.persist(team);

                Member member1 = new Member();
                member1.setUsername("Hello1");
                member1.setTeam(team);
                em.persist(member1);

                em.flush();
                em.clear();

                Member m = em.find(Member.class, member1.getId());

                System.out.println("m = " + m.getTeam().getClass());

                System.out.println("==============");
                m.getTeam().getName();
                System.out.println("==============");


                tx.commit();

 

해당 코드 출력 결과 먼저 Team 객체를 사용하기 전엔 Member m = em.find(Member.class, member1.getId()); m 변수에 저장된 getTeam().getClass()가 proxy로 반환 되는걸 확인할 수 있습니다.

 

 

📋 지연 로딩의 활용 예제

  • Member와 Team 객체가 자주 함께 사용 → 즉시 로딩
  • Member와 Order는 가끔 사용 → 지연 로딩
  • Order와 Product는 자주 함께 사용 → 즉시 로딩

하지만 실무에서는 모든 연관관계에 지연로딩을 사용해야 합니다 ! (이론적으로만 분류) 즉시 로딩 XX / JPQL fetch 조인이나, 엔티티 그래프 기능을 사용

 

	

 ============ Member 내에서의 Team과의 연관관계가 Lazy =============
  @ManyToOne(fetch = FetchType.LAZY) //지연로딩으로 설정하게되면 Team 객체를 proxy 객체로 조회하게 됩니다.
	@JoinColumn(name = "TEAM_ID") //어떤 컬럼과 조인할 지 (Team과 Member 테이블의 TEAM_ID(FK)를 매핑)
	private Team team;



	try {
		
					Team team = new Team();
					team.setName("team");
					em.persist(team);
		
					Team teamA = new Team();
					teamA.setName("teamB");
					em.persist(teamA);
		
					Member member1 = new Member();
					member1.setUsername("Hello1");
					member1.setTeam(team);
					em.persist(member1);
		
					Member member2 = new Member();
					member2.setUsername("Hello2");
					member2.setTeam(teamA);
					em.persist(member2);
		
					em.flush();
					em.clear();
		
					//Member m = em.find(Member.class, member1.getId());
		
					->JPQL 문에 join fetch 설정
					List<Member> members = em.createQuery("select m from Member m join fetch m.team", Member.class)
							.getResultList();
		
		
		
			SQL : select * from Member가 일단 먼저 DB에 전송되는데 이 때, Team 엔티티와의 연관관계가 있으며,
		  로딩이 즉시로딩 (EAGER)로 설정되어 있기 때문에 Member 반환 시 Team이 바로 조회될 수 있도록
			SQL : select * from Team where TEAM_ID = member에 있는 Team과 관련된 데이터를 불러옴

 

Member와 Team의 연관관계가 지연로딩으로 맺어져있지만, JPQL 쿼리문에서 join fetch 설정을 통해 한번에 연관된 쿼리문을 함께 조회가 가능합니다.

 

 

✅ 즉시 로딩의 예제

비즈니스 로직 상 엔티티들끼리 연관관계가 맺어져 있기때문에 , 두 개의 엔티티가 함께 사용되는 경우가 많습니다. 이 때 즉시로딩을 사용하여 연관되어 있는 모든 엔티티들을 한번에 조회하여 편리한 활용이 가능하지만, 성능이 저하될 수 있는 단점이 있습니다.

 

    ===== Member 엔티티의 Team 엔티티와의 연관관계 설정====
    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "TEAM_ID") //어떤 컬럼과 조인할 지 (Team과 Member 테이블의 TEAM_ID(FK)를 매핑)
    private Team team;



    try {

                Team team = new Team();
                team.setName("teamA");
                em.persist(team);

                Member member1 = new Member();
                member1.setUsername("Hello1");
                member1.setTeam(team);
                em.persist(member1);

                em.flush();
                em.clear();

                Member m = em.find(Member.class, member1.getId());

                System.out.println("m = " + m.getTeam().getClass());

                System.out.println("==============");
                m.getTeam().getName();
                System.out.println("==============");


                tx.commit();

 

 

즉시로딩으로 설정 후 실행하면 Member와 Team을 조인을 한 쿼리를 한번에 DB에서 조회하고, 이 때 Team의 클래스 타입을 출력하면 Proxy 객체가 아닌 실제 Team 객체가 출력됩니다.

JPA 구현는 가능하면 조인을 사용해서 SQL을 한번에 함께 조회합니다.

 

💥 Proxy와 즉시 로딩에서의 주의점!

가급적 실무에서는 지연 로딩만 사용하는 것이 좋습니다. 즉시 로딩을 적용하게 되면 예상하지 못한 SQL이 발생할 수 있습니다.

  1. N+1 쿼리 문제 : 즉시로딩을 사용하면 부모 객체를 가져올 때 연관된 모든 자식 객체들도 함께 가져오게 됩니다. 이로 인해 부모 객체의 수만큼 추가 쿼리가 발생할 수 있으며, 이를 N+1 쿼리 문제라고 합니다. 이는 데이터베이스 부하와 성능 저하를 초래할 수 있습니다. 이러한 상황을 피하기 위해서는 지연로딩(Lazy Loading)을 사용하거나, 페치 조인(fetch join) 등을 활용하여 쿼리를 최적화해야 합니다.
  2. 메모리 부하 : 즉시로딩을 사용하면 연관된 모든 자식 객체들도 함께 메모리에 로딩됩니다. 이는 메모리 부하를 증가시킬 수 있습니다. 필요한 경우에만 로딩되도록 지연로딩을 고려하거나 페치 조인을 활용하여 필요한 데이터만 가져오도록 할 수 있습니다.
  3. 데이터 무결성 : 프록시를 사용한 즉시로딩은 객체의 연관 관계를 보장하며, 데이터베이스의 무결성을 유지하기 위해 자식 객체들도 부모 객체와 함께 관리되어야 합니다. 만약 부모 객체와 자식 객체 간의 데이터 무결성이 깨진다면, 프록시 즉시로딩을 사용하는 것보다는 지연로딩과 명시적인 트랜잭션 처리를 고려해야 합니다.
  4. 데이터베이스 성능 : 즉시로딩은 데이터베이스에서 모든 필요한 데이터를 한 번에 가져오기 때문에 성능에 영향을 줄 수 있습니다. 데이터베이스의 쿼리 성능을 고려하여 필요한 경우 쿼리 최적화를 수행하고 인덱스를 활용하는 등의 작업을 진행해야 합니다.

 

✍️ N+1 문제

N+1 문제는 데이터베이스에서 발생하는 성능 이슈 중 하나로, 주로 ORM(Object-Relational Mapping) 기술을 사용하는 애플리케이션에서 발생하는 문제입니다. 이 문제는 데이터베이스 쿼리 실행 횟수가 데이터의 개수에 비례하여 증가하게 되는 상황을 의미합니다.

N+1 문제는 다음과 같은 상황에서 발생합니다:

  1. 부모 객체 조회: 부모 객체(예: 게시물)를 한 번 조회합니다(N 번).
  2. 자식 객체들 조회: 부모 객체들에 연관된 모든 자식 객체(예: 댓글)를 각각 별도의 쿼리로 추가 조회합니다(1 번씩 총 N 번)

이런 방식으로 데이터를 가져오게 되면 총 N+1번의 쿼리 실행이 발생하게 됩니다. 이로 인해 데이터베이스에 불필요한 부하가 발생하며, 애플리케이션의 성능이 저하될 수 있습니다. 또한 자식 객체들이 많은 경우에는 많은 쿼리가 발생하게 되므로 성능 이슈가 더욱 악화될 수 있습니다.

N+1 문제를 해결하는 방법 중 하나는 페치 조인(Fetch Join)을 사용하는 것입니다. 페치 조인은 연관된 데이터를 한 번의 쿼리로 모두 가져오는 방식으로, 데이터베이스의 성능을 향상시키고 N+1 문제를 방지할 수 있습니다. 페치 조인을 사용하면 부모 객체와 연관된 모든 자식 객체를 함께 가져오므로 추가적인 쿼리 호출 없이도 필요한 데이터를 한 번에 가져올 수 있습니다.

N+1 문제를 방지하기 위해 ORM 사용 시 페치 조인을 고려하고, 필요한 데이터만 적절하게 가져오는 쿼리 최적화를 신중하게 진행하는 것이 중요합니다.

728x90
반응형

'[ BACKEND] > Spring' 카테고리의 다른 글

[SPRING] 기본 값 타입  (0) 2023.08.20
[SPRING] 영속성 전이 & 고아객체  (0) 2023.08.19
[SPRING] ✅ Proxy  (0) 2023.08.17
[SPRING] 고급 매핑 - 구현 클래스 , 조인 전략  (0) 2023.08.16
[SPRING] 연관관계 고급 매핑  (0) 2023.08.15
728x90

 

✍️ Proxy란 ?

 

프록시(Proxy)는 다른 객체를 대리하거나 중간 역할을 하는 객체로, 해당 객체에 대한 접근을 제어하거나 추가 기능을 제공하기 위해 사용됩니다. 프록시를 사용하는 주된 목적은 클라이언트와 실제 객체 간의 상호작용을 관리하거나 제어하는 것입니다.

 

프록시는 실체 클래스를 상속받아서 만들어집니다. 실제 객체의 동작을 대신하며, 클라이언트 코드가 프록시와 실제 객체를 동일하게 다룰 수 있도록 합니다. 프록시는 지연 로딩, 보안, 캐싱, 로깅과 같은 다양한 목표를 달성하는 데 활용됩니다.

주로 다음과 같은 상황에서 사용됩니다

  1. 지연 로딩 (Lazy Loading): 프록시는 객체의 일부를 미리 로딩하지 않고 필요한 시점에 로딩하여 성능을 개선할 수 있습니다. 예를 들어, 데이터베이스에서 데이터를 가져오는 시간이 오래 걸릴 수 있는데, 프록시를 사용하면 실제 데이터 로딩을 최대한 늦출 수 있습니다.

  2. 보안 및 접근 제어: 프록시를 사용하여 객체에 대한 접근을 제한하거나 보안 검사를 수행할 수 있습니다. 클라이언트는 프록시를 통해서만 실제 객체에 접근하므로 프록시에서 접근 권한을 검사할 수 있습니다.

  3. 트랜잭션 관리: 프록시는 트랜잭션을 관리하거나 적절한 시점에 데이터베이스에 변경 사항을 반영하는 데 사용될 수 있습니다.

  4. 캐싱: 프록시는 객체의 메서드 호출을 가로채어 캐싱을 수행하거나, 이미 로딩된 데이터를 재사용하는 등의 작업을 수행할 수 있습니다.

https://velog.io/@jm1225/네트워크-Proxy

 

 


 

✍️ em.find( ) 와 em.getReference( )의 차이점 !

 

EntityManager의 find()와 getReference() 메서드는 JPA에서 엔티티를 조회할 때 사용되는 두 가지 주요 방법입니다. 그러나 두 메서드는 동작 방식과 사용 시 주의할 점에서 차이가 있습니다.

 

em.find() 메서드

find() 메서드는 실제 데이터베이스에서 데이터를 조회하여 엔티티 객체를 가져옵니다. 데이터 베이스에서 실제 엔티티 객체를 바로 쿼리를 통해 조회하고, 해당 엔티티가 데이터베이스에 존재하지 않는 경우, null을 반환합니다. 데이터베이스에서 데이터를 가져와서 엔티티를 영속성 컨텍스트에 등록합니다.

 

getReference() 메서드

getReference() 메서드는 실제 데이터베이스에 쿼리를 전송하지 않고, 프록시 객체를 반환합니다. DB 조회를 미루는 가짜 (proxy)엔티티를 조회합니다. 실제 데이터 로딩을 최대한 지연시키며, 프록시 객체를 사용하는 동안에는 데이터베이스 조회를 지연시킵니다.

 

해당 엔티티가 데이터베이스에 존재하지 않는 경우에도 프록시 객체를 생성하며, 프록시 객체를 사용하는 시점에서 실제 데이터 로딩을 시도합니다. getReference() 메서드는 프록시 객체를 생성하여 엔티티 매니저에 등록하며, 프록시 객체의 메서드를 호출하면 실제 데이터 로딩이 이루어집니다.

💥 주의점

getReference() 메서드를 사용할 때, 프록시 객체를 반환하므로 해당 객체의 메서드 호출 시 실제 데이터 로딩이 발생합니다. 이 때 데이터베이스 조회가 실패하면 EntityNotFoundException이 발생할 수 있습니다. getReference()로 얻은 프록시 객체는 즉시 사용하지 않고 영속성 컨텍스트 내에서 사용해야 합니다. 트랜잭션 컨텍스트가 종료된 후에 프록시를 사용하면 예외가 발생할 수 있습니다.

실제 데이터 로딩이 필요한지 여부에 따라 find()나 getReference()를 선택해야 합니다. 데이터를 즉시 로딩해야 하는 경우에는 find()를 사용하는 것이 적절하고, 데이터 로딩을 최대한 늦추고 싶을 때는 getReference()를 사용할 수 있습니다.

요약하면, find()는 실제 데이터를 즉시 로딩하여 엔티티를 가져오는 메서드이며, getReference()는 프록시 객체를 반환하여 실제 데이터 로딩을 최대한 늦추는 메서드입니다.

 

			
            
            Member member = new Member();
			member.setUsername("Hello");
			
			em.persist(member);
			
			em.flush();
			em.clear();
			
	
			Member findMember = **em.find**(Member.class, member.getId());
			
			-> 콘솔 출력물 (조회 시 즉시 쿼리문 생성)
            select
                m1_0.MEMBER_ID,
                m1_0.INSERT_MEMBER,
                m1_0.createdDate,
                m1_0.UPDATE_MEMBER,
                m1_0.lastModifiedDate,
                t1_0.TEAM_id,
                t1_0.INSERT_MEMBER,
                t1_0.createdDate,
                t1_0.UPDATE_MEMBER,
                t1_0.lastModifiedDate,
                t1_0.USERNAME,
                m1_0.USERNAME



		 	//proxy 지연 로딩을 시도
			Member findMember = **em.getReference**(Member.class, member.getId());
			
			-> 콘솔 출력물 (데이터베이스에 쿼리를 전송하지 않고, 프록시 객체를 반환)
				 값이 실제로 사용되는 시점(가져올 때)에 JPA가 DB에 쿼리를 전송합니다.
                 
			insert 
			    into
			        Member
			        (INSERT_MEMBER,createdDate,UPDATE_MEMBER,lastModifiedDate,USERNAME,MEMBER_ID) 
			    values
			        (?,?,?,?,?,?)

 

가짜 Proxy 객체가 생성됨을 볼 수 있습니다. ↓
(Member findMember = em.getReference(Member.class, member.getId());

 

 


 

 

 

✍️ Proxy의 특징

  1. 지연 로딩 (Lazy Loading): 프록시는 실제 데이터 로딩을 필요한 시점으로 늦추는 데 사용됩니다. 객체의 연관 관계나 컬렉션을 실제로 사용할 때까지 데이터베이스 조회를 지연시킵니다. 이는 성능 향상을 위해 사용됩니다.

  2. 동일한 인터페이스 구조: 프록시는 실제 객체와 동일한 인터페이스를 구현합니다. 따라서 클라이언트는 프록시와 실제 객체를 동일한 방식으로 다룰 수 있습니다.

  3. 투명성: 클라이언트는 프록시를 사용하는지 실제 객체를 사용하는지 알 수 없습니다. 프록시는 실제 객체를 대신하므로 클라이언트 코드를 변경하지 않고도 추가 기능을 제공하거나 제어할 수 있습니다.

  4. 디바이스 제어: 프록시는 실제 객체에 대한 접근을 제어하고 추가 동작을 수행할 수 있습니다. 이를 통해 접근 권한 관리, 로깅, 보안 검사 등을 구현할 수 있습니다.

  5. 성능 최적화: 프록시를 사용하여 실제 데이터 로딩을 지연시키면, 실제로 데이터가 필요한 시점에만 데이터베이스 조회를 수행하므로 성능이 향상될 수 있습니다.

  6. 캐싱: 프록시는 데이터베이스 조회 결과를 캐싱하여 빠른 액세스를 제공할 수 있습니다. 이를 통해 반복적인 조회 작업의 성능을 개선할 수 있습니다.

  7. 보안 및 로깅: 프록시를 사용하여 접근 권한을 관리하거나 메서드 호출 로깅을 수행할 수 있습니다. 실제 객체에 접근하기 전에 보안 검사를 수행하거나 로그를 남길 수 있습니다.

프록시는 주로 객체 지향 프로그래밍, AOP(Aspect-Oriented Programming), 원격 통신, 데이터베이스 ORM(Object-Relational Mapping) 등 다양한 분야에서 사용됩니다. 프록시 패턴은 객체의 동작을 변경하거나 보완할 때 유용하며, 코드의 재사용성과 유지보수성을 높일 수 있습니다. 

728x90
반응형
728x90

 

 

  1. 서브타입 테이블로 구현 (구현 클래스마다 테이블을 만드는 전략) - @Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) 각각의 클래스마다 별도의 데이터베이스 테이블을 생성하여 클래스에 정의된 속성을 저장하는 방법입니다. 각 클래스에 대한 테이블은 해당 클래스의 속성을 직접 매핑하여 저장하며, 부모 클래스와 자식 클래스 간의 관계를 데이터베이스 수준에서 처리합니다.

장점 

  1. 클래스별 정규화: 각 클래스에 대한 별도의 테이블을 사용하므로 데이터를 정규화하여 중복을 줄일 수 있습니다. 클래스에 특화된 속성만 저장하여 데이터 일관성을 유지하기 쉽습니다.
  2. 각 테이블에 특화된 제약 조건: 각 테이블에 클래스에 특화된 제약 조건을 선언적으로 추가할 수 있습니다. 이로써 데이터 무결성과 검증이 강화됩니다.
  3. 선언적인 관계: 관계를 선언적으로 정의하여 객체 간의 연결성을 유지할 수 있습니다.

단점

  1. 복잡성: 각 클래스마다 테이블이 생성되므로 데이터베이스 스키마가 복잡해질 수 있습니다. 쿼리 시 조인을 사용해야 하며, 복잡한 쿼리 작성이 필요할 수 있습니다.
  2. 조회 성능: 데이터가 여러 테이블에 분산되어 있기 때문에 쿼리 시 조인을 사용해야 하며, 이로 인해 성능이 저하될 수 있습니다.
  3. 변경 관리: 클래스의 변경이 발생하면 해당 클래스에 대응하는 테이블도 변경해야 합니다. 클래스 구조의 변경이 데이터베이스 스키마 변경으로 이어지므로 유지 관리에 주의가 필요합니다.
  4. 비효율적인 공간 사용: 테이블 간 중복 데이터가 발생할 수 있고, 인덱스 및 제약 조건도 중복될 수 있어 공간 사용이 비효율적일 수 있습니다.

 

2. 조인 테이블로 구현 (조인 전략) - @Inheritance(strategy = InheritanceType.JOINED) 각각의 클래스에 대해 별도의 데이터베이스 테이블을 생성하되, 부모 클래스와 자식 클래스 간의 관계를 조인을 통해 처리하는 방법을 의미합니다. 이로써 부모 클래스와 각 하위 클래스 간의 데이터 일관성을 유지하면서도 각 클래스에 특화된 속성을 저장할 수 있습니다.

장점

  1. 정규화와 일관성: 각 클래스에 특화된 테이블을 사용하므로 데이터를 정규화하여 중복을 줄일 수 있습니다. 데이터 일관성을 유지하면서 각 클래스의 특성을 저장할 수 있습니다.
  2. 유연성: 새로운 하위 클래스를 추가하거나 기존 클래스를 변경해도 데이터베이스 스키마를 변경하지 않아도 됩니다. 클래스 구조의 변경이 데이터베이스 스키마 변경으로 이어지지 않습니다.
  3. 쿼리 성능: 각 클래스의 특화된 테이블을 사용하기 때문에 필요한 속성만 조회하면 됩니다. 조인이 필요하지 않으므로 쿼리 성능이 향상될 수 있습니다.
  4. 클래스 간 분리: 각 클래스에 해당하는 테이블을 별도로 유지하므로 클래스 간의 결합도가 낮아집니다. 각 클래스를 독립적으로 관리하고 확장할 수 있습니다.

단점

  1. 복잡한 쿼리: 필요한 속성을 조회하려면 조인을 사용해야 하므로 쿼리가 복잡해질 수 있습니다. 특히 클래스 계층이 복잡한 경우에는 조인이 많이 발생할 수 있습니다.
  2. 조인의 영향: 조인은 데이터베이스 성능에 영향을 미칠 수 있습니다. 조인된 테이블의 크기와 인덱스 사용 등을 고려하여 성능을 최적화해야 합니다.
  3. 데이터 일관성 유지: 여러 테이블에 데이터가 분산되어 있기 때문에 데이터 일관성을 유지하는 데 주의가 필요합니다. 부모 클래스와 하위 클래스 간의 관계를 올바로 설정해야 합니다.
  4. 스키마 복잡성: 각 클래스에 특화된 테이블을 유지하면서도 클래스 간의 관계를 조인으로 처리하기 때문에 스키마가 복잡해질 수 있습니다.

 

Join 전략에서는 @DiscriminatorColumn어노테이션을 사용하는데, 상속 계층이 관계형 데이터베이스에 저장되는 방법의 세부 정보를 지정하는 데 사용됩니다. 일반적으로 단일 테이블 상속, 조인 테이블 상속 또는 클래스별 테이블 상속과 같은 상속 전략과 함께 사용됩니다.

 

	
    @Entity
		@Inheritance(strategy = InheritanceType.SINGLE_TABLE)
		@DiscriminatorColumn(name = "dtype", discriminatorType = DiscriminatorType.STRING)
		public class Vehicle {
		    // 모든 차량에 공통적인 속성 및 메서드
		}
		
		@Entity
		@DiscriminatorValue("CAR")
		public class Car extends Vehicle {
		    // 자동차에만 해당하는 속성 및 메서드
		}
		
		@Entity
		@DiscriminatorValue("BIKE")
		public class Bike extends Vehicle {
		    // 자전거에만 해당하는 속성 및 메서드
		}

 

이 예시에서 Vehicle 클래스는 두 개의 하위 클래스 Car와 Bike를 가진 부모 클래스입니다. @DiscriminatorColumn 주석은 디스크리미네이터 컬럼의 이름이 "dtype"이고 데이터 유형이 문자열임을 지정합니다. 하위 클래스는 @DiscriminatorValue 주석을 사용하여 디스크리미네이터 컬럼에서 해당 유형을 나타내는 값을 지정합니다.

@DiscriminatorColumn이 작동하는 방식은 다음과 같습니다.

상속 전략 : 관계형 데이터베이스에 매핑하려는 클래스의 계층 구조(부모 및 자식 클래스)가 있는 경우 이를 어떻게 저장할지 결정해야 합니다. JPA는 이를 위한 다양한 전략을 제공하며, 그 중 하나가 디스크리미네이터 기반 전략입니다.

DiscriminatorColumn : 데이터베이스 테이블에서 각 행에 저장된 객체 유형에 대한 정보를 보유하는 열입니다. 이는 어떤 하위 클래스가 해당 행에 해당하는지를 나타냅니다. 이를 통해 JPA 공급자는 데이터베이스를 쿼리할 때 올바른 유형의 객체를 검색할 수 있습니다.

@DiscriminatorColumn : 디스크리미네이터 컬럼을 구성하려면 @DiscriminatorColumn 을 사용합니다. 이 주석은 일반적으로 계층 구조의 부모 클래스에 배치됩니다. 이를 사용하여 디스크리미네이터 컬럼의 이름, 데이터 유형 및 필요한 경우 각 하위 클래스에 해당하는 값을 지정할 수 있습니다.

 

DB입장에서는 어떤 전략을 사용하여 구현을 하더라도 JPA는 매핑이 가능하도록 지원을 해줍니다. 일반적으로 많은 서브타입이 존재하고, 모든 서브타입 데이터에 접근해야 하는 경우 조인 전략을 선택합니다. 서브타입 데이터에 대한 접근이 빠른 것이 중요한 경우 단일 테이블 전략을 선택합니다. 테이블이 작고 효율적인 것이 중요한 경우 테이블 당 클래스 전략을 선택합니다.

각 방법은 장단점을 가지고 있으며, 데이터베이스의 크기와 복잡성, 애플리케이션의 요구사항에 따라 선택되어야 합니다. 가장 적합한 방법을 결정할 때는 데이터베이스 설계 원칙과 성능 최적화를 함께 고려해야 합니다.

728x90
반응형

+ Recent posts