yepdi / TIL

Today I Learn
0 stars 0 forks source link

JPA - Auditing / JPA 구현체 분석 / Projections / NativeQuery #21

Open yepdi opened 2 years ago

yepdi commented 2 years ago

Auditing

JPA 제공하는 Auditing 기능

@MappedSuperclass
open class JpaBaseEntity(
    @Column(updatable = false)
    var createdDate: LocalDateTime? = null,
    var updatedDate: LocalDateTime? = null
) {

    @PrePersist
    fun prePersist() {
        val now = LocalDateTime.now()
        createdDate = now
        updatedDate = now
    }

    @PreUpdate
    fun preUpdate() {
        updatedDate = LocalDateTime.now()
    }
}

SpringDataJpa 에서 제공하는 Auditing 기능

@MappedSuperclass
@EntityListeners(AuditingEntityListener::class)
open class BaseEntity(
    @CreatedBy
    @Column(updatable = false)
    private var createdBy: String? = null,
    @LastModifiedBy
    private var modifiedBy: String? = null
): BaseTimeEntity()

@MappedSuperclass
@EntityListeners(AuditingEntityListener::class)
open class BaseTimeEntity (
    @CreatedDate
    @Column(updatable = false) private var createdDate: LocalDateTime? = null,
    @LastModifiedDate
    private var updatedDate: LocalDateTime? = null,
)

@EnableJpaAuditing(auditorAwareRef = "auditorProvider")
@SpringBootApplication
class DataJpaApplication {
    @Bean
    fun auditorProvider(): AuditorAware<String> {
        return AuditorAware { Optional.of(UUID.randomUUID().toString()) }
    }
}
yepdi commented 2 years ago

JPA 구현체 분석

@Repository
@Transactional(readOnly = true)
public class SimpleJpaRepository<T, ID> implements JpaRepositoryImplementation<T, ID> {

새로운 엔티티를 판단하는 기본 전략

@Transactional
@Override
public <S extends T> S save(S entity) {

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

    if (entityInformation.isNew(entity)) {
        em.persist(entity);
        return entity;
    } else {
        return em.merge(entity);
    }
}
@Entity
@EntityListeners(AuditingEntityListener::class)
class Item(
    @Id
    val identifier: String,
    @CreatedDate
    private var createdDate: LocalDateTime? = null
): Persistable<String> {

    override fun getId(): String? = identifier

    override fun isNew(): Boolean {
        return createdDate == null
    }
}

개인적인 경험으로는 로그 메시지를 쌓을 때 Persistable 을 사용하여 구현한 경험이 있다. database 내 해당 데이터가 없을 것이라고 확실하게 사용하는 경우에 사용하면 좋을 듯

yepdi commented 2 years ago

kotlin 관련

companion object {
        fun teamName(teamName: String): Specification<Member> {
            return Specification<Member> { root: Root<Member>, criteriaQuery: CriteriaQuery<*>, criteriaBuilder: CriteriaBuilder ->
                if (teamName.isEmpty()) return@Specification null
                val t = root.join<String, JoinType> ("team", JoinType.INNER)
                val name: Path<Set<String>> = t.get("name")
                return@Specification criteriaBuilder.equal(name, teamName )
            }
        }

        fun userName(username: String): Specification<Member> {
            return Specification<Member> { root: Root<Member>, criteriaQuery: CriteriaQuery<*>, criteriaBuilder: CriteriaBuilder ->
                val usernames: Path<Set<String>> = root.get("username")
                return@Specification criteriaBuilder.equal(usernames, username)
            }
        }
    }
yepdi commented 2 years ago

Projections

interface 형식

interface UserNameOnly {
    // username과 age를 둘다 가져와서 넣을 수 있다 entity에 있는 내용을 다 가져온다 (open projection)
    @Value("#{target.username + ' ' + target.age}")
    fun getUsername(): String
    // 실제 구현체는 spring data jpa에서 만들어서 반환한다
}

fun findProjectionsByUsername(@Param("username") username: String): List<UserNameOnly>

class 형식

class UsernameOnlyDto {
    val username: String

    constructor(username: String) {
        this.username = username
    }
}

fun findProjectionsDtoByUsername(@Param("username") username: String): List<UsernameOnlyDto>

Nested Interface 형태

interface NestedClosedProjections {
    // username만 가져오지만
    fun getUsername(): String
    // 전체 entity를 가져온다
    fun getTeam(): TeamInfo

    interface TeamInfo {
        fun getName(): String
    }
}

fun findProjectionsNestedByUsername(@Param("username") username: String): List<NestedClosedProjections>
yepdi commented 2 years ago

NativeQuery

Native + Projections 조합

interface MemberProjection {
    fun getId(): Long
    fun getUsername(): String
    fun getTeamName(): String
}

@Query(value = "select m.member_id as id, m.username as username, t.name as teamName " +
        "from member m left join team t",
        countQuery = "select count(*) from member",
        nativeQuery = true)
    fun findByNativeProjection(pageable: Pageable): Page<MemberProjection>