DB - JPA

박진은·2023년 3월 5일
0

개발 후기

목록 보기
1/1

약 3개월 동안 개발에 착수해서 현재 개발이 종료된 FACHIEV프로젝트에 대해서 혼자서 코드 리뷰와 사용했던 스택을 정리하기 위해서 이글을 작성한다! 우선 데이터 베이스 부터 작성하려고 한다.

데이터 베이스

위에 ERD 에 대해서 명세해 놨으니 참고 바란다 일일이 다 설명하기엔 너무 DB가 거대하다...
개발한 애플리케이션은 패션 전공자들을 위한 패션 깃허브 컨셉의 공간이다. 이점을 참고한다면 분명히 이해할 수 있을 것이다.

DB

@EntityListeners(value={AuditingEntityListener.class})
@MappedSuperclass
@Getter
public abstract class BaseEntity extends BaseTimeEntity {
    @CreatedBy
    @Column(updatable = false)
    private String createdBy;

    @LastModifiedBy
    private String modifiedBy;
}

@EntityListeners(value={AuditingEntityListener.class})
@MappedSuperclass
@Getter
@Setter
public abstract class BaseTimeEntity {
    @CreatedDate
    @Column
    private LocalDateTime regTime;//등록시간

    @LastModifiedDate
    private LocalDateTime updateTime;//수정시간
}

위의 두개 클래스는 베이트 엔티티로 사용한 클래스이다. 작성한 사람과 작성된 시간을 알기 위해서 사용했다.

@MappedSuperclass
이 어노테이션은 객체의 입정에서 공통의 매핑을 용하고 싶을 때 사용하는 것이다. DB의 테이블에는 영향을 주지 않으면서 상속해서 사용할 수 있다.

@EnableJpaAuditing으로 Auditing 기능을 활성화합니다.

EntityListener는 Entity가 DB로 load / persist되기 전후에 커스텀 로직을 선언하는 인터페이스입니다.커스텀 리스너를 구현 후 @EntityListeners를 통해 해당 엔티티에 등록해줄 수 있는데,이번 예제에서는 Spring에서 제공하는 구현클래스인 AuditingEntityListener라는 Auditing기능을 수행하는 리스너를 등록했습니다.

AuditingEntityListener는 해당 엔티티에 선언된 CreatedDateLastModifiedDateCreatedByLastModifiedBy 어노테이션을탐색해 엔티티 변경 시 해당값을 자동으로 업데이트 해줍니다.

@MappedSuperClass는 해당 클래스가 상속될 속성을 포함하고 있는 SuperClass 라는걸 알리는 마커 어노테이션입니다.

@Configurable
public class AuditingEntityListener {

   private @Nullable ObjectFactory<AuditingHandler> handler;

/**
    * Configures the {@linkAuditingHandler} to be used to set the current auditor on the domain types touched.
    *
    *@paramauditingHandlermust not be {@literalnull}.
    */
public void setAuditingHandler(ObjectFactory<AuditingHandler> auditingHandler) {

      Assert.notNull(auditingHandler, "AuditingHandler must not be null!");
      this.handler = auditingHandler;
   }

/**
    * Sets modification and creation date and auditor on the target object in case it implements {@linkAuditable} on
    * persist events.
    *
    *@paramtarget
*/
@PrePersist
   public void touchForCreate(Object target) {

      Assert.notNull(target, "Entity must not be null!");

      if (handler != null) {

         AuditingHandler object = handler.getObject();
         if (object != null) {
            object.markCreated(target);
         }
      }
   }

/**
    * Sets modification and creation date and auditor on the target object in case it implements {@linkAuditable} on
    * update events.
    *
    *@paramtarget
*/
@PreUpdate
   public void touchForUpdate(Object target) {

      Assert.notNull(target, "Entity must not be null!");

      if (handler != null) {

         AuditingHandler object = handler.getObject();
         if (object != null) {
            object.markModified(target);
         }
      }
   }
}

위의 인터페이스를 참고바란다.

package com.facaieve.backend.entity.user;

import com.facaieve.backend.Constant.UserRole;
import com.facaieve.backend.entity.basetime.BaseEntity;
import com.facaieve.backend.Constant.UserActive;
import com.facaieve.backend.entity.comment.FashionPickUpCommentEntity;
import com.facaieve.backend.entity.comment.FundingCommentEntity;
import com.facaieve.backend.entity.comment.PortfolioCommentEntity;
import com.facaieve.backend.entity.etc.MyPickEntity;
import com.facaieve.backend.entity.image.ImageEntityProfile;
import com.facaieve.backend.entity.image.S3ImageInfo;
import com.facaieve.backend.entity.post.FashionPickupEntity;
import com.facaieve.backend.entity.post.FundingEntity;
import com.facaieve.backend.entity.post.PortfolioEntity;
import javax.persistence.*;
import javax.transaction.Transactional;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.*;
import org.hibernate.annotations.ColumnDefault;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;

import java.io.Serializable;
import java.util.*;

@Entity
@Getter
@Setter
@NoArgsConstructor
@AllArgsConstructor
@Builder
@Table(uniqueConstraints = {@UniqueConstraint(columnNames = "email")})// 이메일 기준으로 사용자 구분
public class UserEntity extends BaseEntity implements UserDetails, Serializable {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)//개별 엔티티 적용
    @Schema(description = "유저 식별ID")
    Long userEntityId;

    @Schema(description = "유저 권한")
    @Enumerated(EnumType.STRING)
    @Column
    UserRole role;//todo관리자와 일반 사용자를 구분하는 로직을 구현할 것

@Schema(description = "유저 닉네임")
    @Column
    String displayName;

    @Schema(description = "유저 이메일")
    @Column
    String email;

    @Schema(description = "비밀번호")
    @Column
    String password;

    @Schema(description = "광역시, 도")
    @Column
    String state;

    @Schema(description = "시,군,구")
    @Column
    String city;

    @Schema(description = "간단한 자기소개")
    @Column
    String userInfo;

    @Schema(description = "커리어")
    @Column
    String career;

    @Schema(description = "학력 및 교육사항")
    @Column
    String education;

    @Schema(description = "재직회사")
    @Column
    String Company;

    @Schema(description = "유저 활동 상태")
    @Enumerated(value = EnumType.STRING)
    UserActive userActive = UserActive.Active;

    @Schema(description = "이메일 인증 상태", defaultValue = "false")
    private boolean emailVerified = false;

    //패션픽업 댓글, 펀딩 댓글, 포폴 댓글 엔티티 매핑
    @Schema(description = "패션 픽업 게시물에 단 댓글 목록")
    @OneToMany(mappedBy = "userEntity", cascade = CascadeType.PERSIST)
    List<FashionPickUpCommentEntity>  fashionPickUpCommentEntities = new ArrayList<FashionPickUpCommentEntity>();

    @Schema(description = "펀딩 게시물에 단 댓글 목록")
    @OneToMany(mappedBy = "userEntity", cascade = CascadeType.PERSIST)
    List<FundingCommentEntity>  fundingCommentEntities = new ArrayList<FundingCommentEntity>();

    @Schema(description = "포트폴리오 게시물에 단 댓글 목록")
    @OneToMany(mappedBy = "userEntity", cascade = CascadeType.PERSIST)
    List<PortfolioCommentEntity>  portfolioCommentEntities = new ArrayList<PortfolioCommentEntity>();

    //패션픽업, 펀딩, 포폴 엔티티 매핑
    @Schema(description = "작성한 패션 픽업 게시물 목록")
    @OneToMany(mappedBy = "userEntity", cascade = CascadeType.PERSIST)
    List<FashionPickupEntity>  fashionPickupEntities = new ArrayList<FashionPickupEntity>();

    @Schema(description = "작성한 펀딩 게시물 목록")
    @OneToMany(mappedBy = "userEntity", cascade = CascadeType.PERSIST)
    List<FundingEntity>  fundingEntities = new ArrayList<FundingEntity>();

    @Schema(description = "작성한 포트폴리오 게시물 목록")
    @OneToMany(mappedBy = "userEntity", cascade = CascadeType.PERSIST)
    List<PortfolioEntity>  portfolioEntities = new ArrayList<PortfolioEntity>();

    @OneToMany(mappedBy = "followingUserEntity", cascade = CascadeType.ALL)
    List<FollowEntity> followingList = new ArrayList<FollowEntity>(); // 팔로우 정보 저장을 위한 셀프 참조

    @OneToMany(mappedBy = "followedUserEntity", cascade = CascadeType.ALL)
    List<FollowEntity> followerList = new ArrayList<FollowEntity>(); // 팔로우 정보 저장을 위한 셀프 참조

    @Schema(description = "프로필 이미지")
    @OneToOne(mappedBy = "userEntity", cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
    S3ImageInfo profileImg; // 프로필 이미지 매핑

    @Schema(description = "마이픽을 설정한 게시물 및 댓글")
    @OneToMany(mappedBy = "pickingUser", cascade = CascadeType.PERSIST)
    List<MyPickEntity>  myPickEntityList = new ArrayList<MyPickEntity>();

    //사진과 이름을 업데이트함
    public UserEntity update(String displayName, String picture ){
        this.displayName = displayName;
        this.profileImg = S3ImageInfo.builder()
                .userEntity(this)
                .fileURI(picture)
                .build();
        return this;
    }

    @Override
    public Collection<? extends GrantedAuthority> getAuthorities() {
        return List.of(new SimpleGrantedAuthority(role.getUserRole()));//권한 반환
    }

    @Override
    public String getUsername() {
        return email;
    }//위에서 email 을 제약 조건으로 설정했기 때문에 email 을 이름으로 반환함.

    @Override
    public boolean isAccountNonExpired() {
        return this.userActive.equals("활동 상태");
    }

    @Override
    public boolean isAccountNonLocked() {
        return true;
    }

    @Override
    public boolean isCredentialsNonExpired() {
        return true;
    }

    @Override
    public boolean isEnabled() {
        return true;
    }

    public void emailVerifiedSuccess(){//email 인증을 위한
        this.emailVerified = true;
    }
}
  @Schema(description = "작성한 포트폴리오 게시물 목록")
    @OneToMany(mappedBy = "userEntity", cascade = CascadeType.PERSIST)
    List<PortfolioEntity>  portfolioEntities = new ArrayList<PortfolioEntity>();

이 코드가 사실 전부이다. 패션 게시글에 대한 정보를 담고 있는 리스트를 가지고 있어서 마이페이지에서 조회가능하도록 구현했다. @OneToMany 어노테이션을 사용해서 일대다 관계를 맵핑했다 또한 CascadeType.PERSIST 를 사용해서 자식객체에 동시에 영속성을 부여했다.

profile
코딩

0개의 댓글