Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

회원 탈퇴 기능을 기획하고 구현한다. #740

Open
Tracked by #736
woowahan-pjs opened this issue Feb 26, 2024 · 3 comments
Open
Tracked by #736

회원 탈퇴 기능을 기획하고 구현한다. #740

woowahan-pjs opened this issue Feb 26, 2024 · 3 comments
Assignees
Labels
관리 화면 기능 New feature or request 디자인 Improvements or additions to documentation 지원 화면

Comments

@woowahan-pjs
Copy link
Contributor

woowahan-pjs commented Feb 26, 2024

  • 마이페이지에 회원 탈퇴 버튼을 추가한다.
  • 탈퇴한 회원의 경우 UI에 데이터를 노출하는 방법을 고려한다.
  • 와이어프레임

관련 이슈: #359

@woowahan-pjs
Copy link
Contributor Author

  • 회원과 회원 정보는 구조적으로 함께 묶여 있지만 별도의 테이블로 분리하면 회원 정보를 훨씬 쉽게 관리할 수 있다.
  • 데이터베이스에는 반영되어 있지 않지만, 회원 정보(MemberInformation)는 이미 회원(Member)과 모델이 분리되어 있다.
  • 회원이 탈퇴할 때 모든 회원 데이터를 지우는 대신 회원 정보만 지우면 회원 ID를 참조하는 여러 모델에서 일관성을 유지할 수 있다.
  • 회원 정보가 없는 회원을 탈퇴 회원으로 정의하면 탈퇴 회원에 대한 통계 확인 등 다양한 확장이 가능하다.
  • 탈퇴 회원을 회원 테이블에 유지하는 것이 테이블 용량을 차지한다는 점에서 단점이 될 수 있지만, 그 수가 많지 않고 용량도 그리 크지 않을 것이다.

@woowahan-pjs
Copy link
Contributor Author

woowahan-pjs commented May 23, 2024

엔티티 하나에 여러 테이블 매핑

  • @SecondaryTable 또는 @OneToOne을 사용하는 방법이 있다.
  • @SecondaryTable의 경우 @Columntable 요소를 지정하여 해당 컬럼이 저장될 테이블을 지정할 수 있다.1
  • 컴포넌트(@Embeddable가 지정된 객체)의 경우 @AttributeOverride를 사용하여 저장될 테이블을 지정할 수 있다.2
    • 컴포넌트의 재사용이 용이하다.
@SecondaryTable(
    name = "member_information",
    pkJoinColumns = [PrimaryKeyJoinColumn(name = "member_id")],
)
@Entity
class Member(
    @Embedded
    var information: MemberInformation,
    id: Long = 0L,
) : BaseRootEntity<Member>(id)

@Embeddable
data class MemberInformation(
    @Column(nullable = false, table = "member_information")
    val email: String,

    @Column(nullable = false, table = "member_information", length = 30)
    val name: String,
)
@SecondaryTable(
    name = "member_information",
    pkJoinColumns = [PrimaryKeyJoinColumn(name = "member_id")],
)
@Entity
class Member(
    @AttributeOverrides(
        AttributeOverride(name = "email", column = Column(nullable = false, table = "member_information")),
        AttributeOverride(name = "name", column = Column(nullable = false, table = "member_information", length = 30)),
    )
    @Embedded
    var information: MemberInformation,
    id: Long = 0L,
) : BaseRootEntity<Member>(id)

@Embeddable
data class MemberInformation(
    @Column(nullable = false)
    val email: String,

    @Column(nullable = false, length = 30)
    val name: String,
)
  • 단, 하나의 컴포넌트는 하나의 테이블에 매핑될 수 있다.
    • org.hibernate.AnnotationException: A component cannot hold properties split into 2 different tables: apply.domain.member.Member.information

  • ComponentPropertyHolder 코드를 보면, 컴포넌트에 속한 프로퍼티를 이름순으로 정렬할 때 첫 번째 프로퍼티의 table 요소가 해당 컴포넌트에 매핑되는 테이블로 지정된다.
    • 이후, 해당 컴포넌트에 속한 프로퍼티는 해당 컴포넌트에 매핑된 테이블과 다른 테이블에 속해 있어서는 안 된다.
    • table 요소가 생략되면 컴포넌트의 첫 번째 프로퍼티의 table 요소를 따른다.
      • 첫 번째 프로퍼티만 올바르게 설정하면 된다.

Good

@Embeddable
data class MemberInformation(
    @Column(nullable = false, table = "member_information")
    val email: String,

    @Column(nullable = false, length = 30)
    val name: String,
)

Bad

@Embeddable
data class MemberInformation(
    @Column(nullable = false)
    val email: String,

    @Column(nullable = false, table = "member_information", length = 30)
    val name: String,
)

ComponentPropertyHolder.java#L280-L298

/*
* Check table matches between the component and the columns
* if not, change the component table if no properties are set
* if a property is set already the core cannot support that
*/
if (columns != null) {
    Table table = columns[0].getTable();
    if ( !table.equals( component.getTable() ) ) {
        if ( component.getPropertySpan() == 0 ) {
            component.setTable( table );
        }
        else {
            throw new AnnotationException(
                "A component cannot hold properties split into 2 different tables: "
                    + this.getPath()
            );
        }
    }
}
  • foreignKey 요소를 사용하여 보조 테이블에 생성되는 FK키의 이름을 지정할 수 있다.
@SecondaryTable(
    name = "member_information",
    pkJoinColumns = [PrimaryKeyJoinColumn(name = "member_id")],
    foreignKey = ForeignKey(name = "fk_member_information_member_id_ref_member_id"),
)
  • 보조 테이블을 사용할 때 기본 조인 전략은 LEFT JOIN이다.
  • @org.hibernate.annotations.Table을 사용하여 INNER JOIN을 사용하도록 변경할 수 있다.
@org.hibernate.annotations.Table(appliesTo = "member_information", optional = false)

참고 자료

@woowahan-pjs
Copy link
Contributor Author

@SecondaryTable -> @OnetoOne

  • 단일 컬럼을 조회할 때 보조 테이블이 포함되지 않는 Hibernate의 버그가 있다.
    • https://github.com/spring-projects/spring-data-jpa/issues/2209
    • https://hibernate.atlassian.net/browse/HHH-14590
    • 따라서 Spring Data JPA의 쿼리 메소드로 생성된 쿼리 중 exists 명령어는 조인을 수행하지 않는다.
      • could not prepare statement; SQL [select member0_.id as col_0_0_ from member member0_ where member0_2_.email=? limit ?]; nested exception is org.hibernate.exception.SQLGrammarException: could not prepare statement

  • 자바 ORM 표준 JPA 프로그래밍에 따르면
    • 참고로 @SecondaryTable을 사용해서 두 테이블을 하나의 엔티티에 매핑하는 방법보다는 테이블당 엔티티를 각각 만들어서 일대일 매핑하는 것을 권장한다. 이 방법은 항상 두 테이블을 조회하므로 최적화하기 어렵다. 반면에 일대일 매핑은 원하는 부분만 조회할 수 있고 필요하면 둘을 함께 조회하면 된다.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
관리 화면 기능 New feature or request 디자인 Improvements or additions to documentation 지원 화면
Projects
None yet
Development

No branches or pull requests

3 participants