본문 바로가기
서버/Kotlin-Spring_Boot

3. 사용한 Entity 구조와 설명

by HDobby 2023. 6. 29.

글의 요약 설명 부분. 150자를 적어주세요. 글의 요약 설명 부분. 150자를 적어주세요. 글의 요약 설명 부분. 150자를 적어주세요. 글의 요약 설명 부분. 150자를 적어주세요. 글의 요약 설명 부분. 150자를 적어주세요. 글의 요약 설명 부분. 150자입니다

 

목차

     


    대략적인 db 구조

     

    전체 적으로는 UUID를 기본 id값으로 가집니다.

    package com.threemovie.threemovieapi.global.entity
    
    import com.github.f4b6a3.ulid.UlidCreator
    import jakarta.persistence.*
    import org.hibernate.annotations.JdbcTypeCode
    import org.hibernate.proxy.HibernateProxy
    import org.hibernate.type.SqlTypes
    import org.springframework.data.domain.Persistable
    import java.io.Serializable
    import java.util.*
    import kotlin.jvm.Transient
    
    @MappedSuperclass
    abstract class PrimaryKeyEntity : Persistable<UUID> {
    	@Id
    	@Column(name = "id", updatable = false, nullable = false, columnDefinition = "VARCHAR(36)")
    	@JdbcTypeCode(SqlTypes.VARCHAR)
    	private val id: UUID = UlidCreator.getMonotonicUlid().toUuid()
    	
    	@Transient
    	private var _isNew = true
    	
    	override fun getId(): UUID = id
    	
    	override fun isNew(): Boolean = _isNew
    	
    	override fun equals(other: Any?): Boolean {
    		if (other == null)
    			return false
    		
    		if (other !is HibernateProxy && this::class != other::class)
    			return false
    		
    		return id == getIdentifier(other)
    	}
    	
    	private fun getIdentifier(obj: Any): Serializable {
    		return if (obj is HibernateProxy) {
    			(obj.hibernateLazyInitializer.implementation as PrimaryKeyEntity).id
    		} else {
    			(obj as PrimaryKeyEntity).id
    		}
    	}
    	
    	override fun hashCode() = Objects.hashCode(id)
    	
    	@PostPersist
    	@PostLoad
    	protected fun load() {
    		_isNew = false
    	}
    }

     

    저장은 jpaRepository의 saveAll batch insert를 사용하며, showtime과 showtimereserve의 경우 jdbcTemplate 때문에 fk관계가 연결되어 있지 않습니다.

    @JoinColumn(name = "show_time_id", foreignKey = ForeignKey(ConstraintMode.NO_CONSTRAINT))

    ConstraintMode.NO_CONSTRATINT를 사용하면 @ManyToOne과 같이 연관 관계를 사용하면서 fk 조건을 걸지 않을 수 있습니다.


    영화 정보

    영화 정보는 movieData, movieCreator, moviePreview, movieReview로 구성되어 있습니다.

     

    MovieData

    package com.threemovie.threemovieapi.domain.movie.entity.domain
    
    import com.threemovie.threemovieapi.global.entity.PrimaryKeyEntity
    import jakarta.persistence.*
    import jakarta.validation.constraints.NotNull
    import org.hibernate.annotations.SQLInsert
    
    @Entity
    @Table(name = "MovieData")
    @SQLInsert(
    	sql = "INSERT IGNORE INTO movie_data(admission_code, category, country, making_note, movie_id, name_en, name_kr, netizen_avg_rate, poster, release_date, reservation_rank, reservation_rate, running_time, summary, total_audience, id)" +
    			" VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)"
    )
    class MovieData(
    	@NotNull
    	@Column(unique = true)
    	val movieId: String = "",
    
    	val netizenAvgRate: Double? = 0.0,
    
    	val reservationRate: Double? = 0.0,
    
    	@Column(columnDefinition = "longtext")
    	val summary: String? = "",
    
    	@NotNull
    	val nameKr: String = "",
    
    	val nameEn: String? = "",
    
    	val releaseDate: Long = 202303030506,
    
    	val poster: String? = "",
    
    	val category: String? = "",
    
    	@Column(columnDefinition = "longtext")
    	val makingNote: String? = "",
    
    	val runningTime: String? = "",
    
    	val admissionCode: String? = "",
    
    	val country: String? = "",
    
    	val reservationRank: String? = "",
    
    	val totalAudience: String? = "",
    
    	) : PrimaryKeyEntity() {
    
    	@OneToMany(
    		mappedBy = "movieData",
    		orphanRemoval = true,
    		cascade = [CascadeType.ALL],
    		fetch = FetchType.LAZY
    	)
    	var creators: MutableList<MovieCreator> = ArrayList()
    		protected set
    
    	@OneToMany(
    		mappedBy = "movieData",
    		orphanRemoval = true,
    		cascade = [CascadeType.ALL],
    		fetch = FetchType.LAZY
    	)
    	var previews: MutableList<MoviePreview> = ArrayList()
    		protected set
    
    	@OneToMany(
    		mappedBy = "movieData",
    		orphanRemoval = true,
    		cascade = [CascadeType.ALL],
    		fetch = FetchType.LAZY,
    	)
    	var reviews: MutableList<MovieReview> = ArrayList()
    
    	fun addCreators(creators: List<MovieCreator>) {
    		this.creators.addAll(creators)
    	}
    
    	fun addPreviews(previews: List<MoviePreview>) {
    		this.previews.addAll(previews)
    	}
    
    	fun addReviews(reviews: List<MovieReview>) {
    		this.reviews.addAll(reviews)
    	}
    }

    연관 관계의 주인은 movieData이며, 각각 joinColumn으로 movie_id를 fk로 저장을 합니다.

     

    movieCreator

    영화 제작자 정보 입니다. 이름, 역할, 프로필 사진 링크를 저장합니다.

    package com.threemovie.threemovieapi.domain.movie.entity.domain
    
    import com.threemovie.threemovieapi.global.entity.PrimaryKeyEntity
    import jakarta.persistence.*
    import jakarta.validation.constraints.NotNull
    import org.hibernate.annotations.SQLInsert
    
    @Entity
    @Table(
    	name = "MovieCreator",
    	uniqueConstraints = [UniqueConstraint(
    		name = "creator_uk",
    		columnNames = ["nameKr", "roleKr", "movie_id"]
    	)]
    )
    @SQLInsert(
    	sql = "INSERT IGNORE INTO movie_creator(link, movie_id, name_en, name_kr, role_kr, id)"
    			+ " VALUES (?, ?, ?, ?, ?, ?)"
    )
    class MovieCreator(
    	@NotNull
    	val nameKr: String,
    	
    	val nameEn: String?,
    	
    	val roleKr: String?,
    	
    	@Column(length = 500)
    	val link: String?,
    	
    	@NotNull
    	@ManyToOne(fetch = FetchType.LAZY)
    	@JoinColumn(name = "movie_id", referencedColumnName = "movieId")
    	val movieData: MovieData? = null
    ) : PrimaryKeyEntity()

     

    moviePreview

    영화 미리보기 정보입니다. 링크와 비디오인지 이미지인지 타입을 저장합니다.

    package com.threemovie.threemovieapi.domain.movie.entity.domain
    
    import com.threemovie.threemovieapi.global.entity.PrimaryKeyEntity
    import jakarta.persistence.*
    import jakarta.validation.constraints.NotNull
    import org.hibernate.annotations.SQLInsert
    
    @Entity
    @Table(name = "MoviePreview")
    @SQLInsert(
    	sql = "INSERT IGNORE INTO movie_preview(link, movie_id, type, id)"
    			+ " VALUES (?, ?, ?, ?)"
    )
    class MoviePreview(
    	@NotNull
    	val type: String,
    	
    	@NotNull
    	@Column(unique = true, length = 500)
    	val link: String,
    	
    	@NotNull
    	@ManyToOne(fetch = FetchType.LAZY)
    	@JoinColumn(name = "movie_id", referencedColumnName = "movieId")
    	val movieData: MovieData? = null
    ) : PrimaryKeyEntity()

     

    movieReview

    영화 리뷰 정보입니다. cgv를 제외한 megabox와 lotte cinema의 리뷰 정보를 추천도 순으로 10개씩 저장하여 가지고 있습니다. 추천수, 날짜, 리뷰 내용과 영화관 명을 저장합니다.

    package com.threemovie.threemovieapi.domain.movie.entity.domain
    
    import com.threemovie.threemovieapi.global.entity.PrimaryKeyEntity
    import jakarta.persistence.*
    import jakarta.validation.constraints.NotNull
    import org.hibernate.annotations.SQLInsert
    
    @Entity
    @Table(name = "MovieReview")
    @SQLInsert(
    	sql = "INSERT INTO movie_review(date, movie_id, movie_theater, recommendation, review,  id)" +
    			"VALUES (?, ?, ?, ?, ?, ?)" +
    			"ON DUPLICATE KEY UPDATE" +
    			" recommendation = VALUES(recommendation)"
    )
    class MovieReview(
    	@NotNull
    	val recommendation: Int = 0,
    
    	@NotNull
    	val date: Long = 202303030506,
    
    	@Column(length = 500, unique = true)
    	val review: String? = "",
      
    	@NotNull
    	val movieTheater: String = "",
    ) : PrimaryKeyEntity() {
    	@NotNull
    	@ManyToOne(fetch = FetchType.LAZY, cascade = [CascadeType.ALL])
    	@JoinColumn(
    		name = "movie_id",
    		referencedColumnName = "movieId"
    	)
    	var movieData: MovieData? = null
    }

     

    영화관 정보

    theaterData 입니다.

    @Table(
    	name = "TheaterData",
    	uniqueConstraints = [UniqueConstraint(
    		name = "theater_data_value_uk",
    		columnNames = ["movieTheater", "brchKr", "theaterCode"]
    	)]
    )

    위와 같이 여러 컬럼을 묶어 unique key로 사용을 하여 중복 삽입을 막습니다.

    package com.threemovie.threemovieapi.domain.theater.entity.domain
    
    import com.threemovie.threemovieapi.global.entity.PrimaryKeyEntity
    import jakarta.persistence.Column
    import jakarta.persistence.Entity
    import jakarta.persistence.Table
    import jakarta.persistence.UniqueConstraint
    import jakarta.validation.constraints.NotNull
    import org.hibernate.annotations.SQLInsert
    
    @Entity
    @Table(
    	name = "TheaterData",
    	uniqueConstraints = [UniqueConstraint(
    		name = "theater_data_value_uk",
    		columnNames = ["movieTheater", "brchKr", "theaterCode"]
    	)]
    )
    @SQLInsert(
    	sql = "INSERT IGNORE INTO theater_data(addr_en, addr_kr, brch_en, brch_kr, city, movie_theater, theater_code, id)" +
    			"VALUES(?,?,?,?,?,?,?,?) "
    )
    class TheaterData(
    	@NotNull
    	@Column(length = 10)
    	val movieTheater: String = "MT",
    	
    	@NotNull
    	@Column(length = 20)
    	val city: String = "서울",
    	
    	@NotNull
    	@Column(length = 50)
    	val brchKr: String = "",
    	
    	@NotNull
    	val brchEn: String = "",
    	
    	@NotNull
    	val addrKr: String = "",
    	
    	val addrEn: String? = null,
    	
    	@NotNull
    	@Column(length = 20)
    	val theaterCode: String = "1234",
    ) : PrimaryKeyEntity()

     


     

    시간표 정보

    showtime 입니다.

    theater_data의 id와 moviedata의 movie_id를 저장합니다.

    on duplicate key update를 사용해서 최근 수정 시간을 변경 해줍니다.

    package com.threemovie.threemovieapi.domain.showtime.entity.domain
    
    import com.threemovie.threemovieapi.domain.movie.entity.domain.MovieData
    import com.threemovie.threemovieapi.domain.theater.entity.domain.TheaterData
    import com.threemovie.threemovieapi.global.entity.PrimaryKeyEntity
    import jakarta.persistence.*
    import jakarta.validation.constraints.NotNull
    import org.hibernate.annotations.SQLInsert
    
    @Entity
    @Table(
    	name = "ShowTime",
    	uniqueConstraints = [UniqueConstraint(
    		name = "show_time_value_uk",
    		columnNames = ["movie_id", "theater_data_id", "screenKr", "showYmd", "playKind"]
    	)]
    )
    @SQLInsert(
    	sql = "INSERT INTO show_time(movie_id, play_kind, screen_en, screen_kr, show_ymd, theater_data_id, total_seat, updated_at, id)" +
    			"VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)" +
    			"ON DUPLICATE KEY UPDATE" +
    			" updated_at = VALUES(updated_at)"
    )
    class ShowTime(
    	@NotNull
    	@Column(length = 50)
    	val screenKr: String = "",
    	
    	@NotNull
    	val screenEn: String = "",
    	
    	@NotNull
    	val showYmd: Long = 0,
    	
    	@NotNull
    	val totalSeat: Int = 200,
    	
    	@NotNull
    	val playKind: String = "",
    	
    	@OneToOne(fetch = FetchType.LAZY)
    	@JoinColumn(name = "theater_data_id")
    	val theaterData: TheaterData,
    	
    	@NotNull
    	val updatedAt: Long = 0L,
    ) : PrimaryKeyEntity() {
    	
    	@OneToOne(fetch = FetchType.LAZY, cascade = [CascadeType.ALL])
    	@JoinColumn(
    		name = "movie_id",
    		referencedColumnName = "movieId"
    	)
    	var movieData: MovieData? = null
    	
    	@OneToMany(
    		mappedBy = "showTime",
    		orphanRemoval = true,
    		cascade = [CascadeType.ALL],
    		fetch = FetchType.LAZY
    	)
    	var showTimeReserve: MutableList<ShowTimeReserve> = ArrayList()
    		protected set
    	
    	fun addReservation(showTimeReserve: List<ShowTimeReserve>) {
    		this.showTimeReserve.addAll(showTimeReserve)
    	}
    }

     

    showTimeReserve 입니다.

    예약의 자세한 정보(남은 좌석, 시작 시간, 예매 페이지)를 기록하며, 남은 좌석수와 수정 시간을 변경합니다.

    package com.threemovie.threemovieapi.domain.showtime.entity.domain
    
    import com.threemovie.threemovieapi.global.entity.PrimaryKeyEntity
    import jakarta.persistence.*
    import jakarta.validation.constraints.NotNull
    import org.hibernate.annotations.SQLInsert
    
    @Entity
    @Table(name = "ShowTimeReserve")
    @SQLInsert(
    	sql = "INSERT INTO show_time_reserve(end_time, rest_seat, show_time_id, start_time, ticket_page, updated_at, id)" +
    			"VALUES (?, ?, ?, ?, ?, ?, ?)" +
    			"ON DUPLICATE KEY UPDATE" +
    			" rest_seat = VALUES(rest_seat), updated_at = VALUES(updated_at)"
    )
    class ShowTimeReserve(
    	@NotNull
    	val startTime: Long = 0L,
    	
    	@NotNull
    	val endTime: Long = 0L,
    	
    	@NotNull
    	val restSeat: Int = 0,
    	
    	@NotNull
    	val updatedAt: Long = 0L,
    	
    	@Column(unique = true)
    	@NotNull
    	val ticketPage: String = "",
    	
    	@NotNull
    	@ManyToOne(fetch = FetchType.LAZY)
    	@JoinColumn(name = "show_time_id", foreignKey = ForeignKey(ConstraintMode.NO_CONSTRAINT))
    	var showTime: ShowTime
    ) : PrimaryKeyEntity()

     

    유저 정보

     

    userSignUpAuth 입니다.

    유저의 이메일 인증 정보를 저장합니다.

    비밀번호 변경, 회원가입 시에 authSuccess가 true인지 확인한 뒤 가입 혹은 변경이 진행됩니다.

    package com.threemovie.threemovieapi.domain.user.entity.domain
    
    import com.threemovie.threemovieapi.global.entity.PrimaryKeyEntity
    import jakarta.persistence.Column
    import jakarta.persistence.Entity
    import jakarta.persistence.Table
    import jakarta.validation.constraints.NotNull
    import java.time.LocalDateTime
    
    @Entity
    @Table(name = "UserSignUpAuth")
    class UserSignUpAuth(
    	@Column(length = 50)
    	@NotNull
    	var email: String = "",
    	
    	@Column(length = 8)
    	@NotNull
    	var authCode: String = "",
    	
    	@NotNull
    	var expiredDate: LocalDateTime,
    	
    	@NotNull
    	var authSuccess: Boolean = false,
    	
    	) : PrimaryKeyEntity()

     

    useLogin입니다.

    유저의 로그인 정보를 저장합니다.

    password는 passwordEncode를 사용한 뒤 저장합니다.

    package com.threemovie.threemovieapi.domain.user.entity.domain
    
    import com.threemovie.threemovieapi.global.entity.PrimaryKeyEntity
    import com.threemovie.threemovieapi.global.security.config.UserRole
    import jakarta.persistence.*
    import jakarta.validation.constraints.NotNull
    
    @Entity
    @Table(name = "userLogin")
    class UserLogin(
    	@Column(length = 50)
    	@NotNull
    	private val email: String = "",
    	
    	@NotNull
    	val password: String = "",
    ) : PrimaryKeyEntity() {
    	@NotNull
    	@Enumerated(EnumType.STRING)
    	var role: UserRole = UserRole.USER
    	
    	@NotNull
    	@OneToOne(fetch = FetchType.LAZY, orphanRemoval = true, cascade = [CascadeType.ALL], mappedBy = "userLogin")
    	@JoinColumn(name = "user_data_id")
    	var userData: UserData? = null
    }

     

    userData 입니다.

    이메일 정보로 유저 정보를 가지고 오는 것을 막기위해 이메일을 userLogin에 분리하여 저장합니다.

    package com.threemovie.threemovieapi.domain.user.entity.domain
    
    import com.threemovie.threemovieapi.global.entity.PrimaryKeyEntity
    import jakarta.persistence.*
    import jakarta.validation.constraints.NotNull
    import java.time.LocalDate
    
    @Entity
    @Table(name = "UserData")
    class UserData(
    	@Column(length = 20)
    	@NotNull
    	val nickName: String = "",
    	
    	val sex: Boolean? = false,
    	
    	val birth: LocalDate?,
    	
    	@NotNull
    	val categories: String = "",
    	
    	@NotNull
    	val brch: String = "",
    	
    	) : PrimaryKeyEntity() {
    	
    	@NotNull
    	@OneToOne(fetch = FetchType.LAZY)
    	var userLogin: UserLogin? = null
    }

     


     

    마무리

    궁금하신게 있거나, 피드백은 언제나 환영입니다!

     

    자세한 코드는 github에서 확인하시면 됩니다.

    https://github.com/Hangeulkim/MovieThree

     

    GitHub - Hangeulkim/MovieThree

    Contribute to Hangeulkim/MovieThree development by creating an account on GitHub.

    github.com

     

    728x90

    댓글