연관관계(R_DB)

예를 들어 기존 user 테이블에 저장된 데이터 중 이름이 변경되어 있다고 한다면

다른 테이블에서 user 테이블에 이름을 조회 한다고 한다면 이름을 직접적으로 조회하지 않고 

user 테이블에 pk인 id를 조회함으로써 이름이 변경되었다고 하더라도 변경된 값을 이상없이 가져올 수 있다.

 

ERD

[DB] ERD(Entity-Relationship Diagram) : 네이버 블로그 (naver.com)

 

[DB] ERD(Entity-Relationship Diagram)

ERD(Entity Relationship Diagram)란? ERD는 말로서 되어있는 요구분석사항을 그림으로 그려내어 ...

blog.naver.com

 

툴: drow.io

배워봅시다 Draw.io (tistory.com)

 

배워봅시다 Draw.io

'백문이 불여일견'이라는 말이 있습니다. '백 번 듣는 것이 한 번 보는 것만 못하다'는 뜻입니다. 이와 완전히 동일하지는 않지만, 백번 읽는 것보다 시각적으로 한 번 보는 것이 이해가 쉬운 경

sjquant.tistory.com

 

drow.io 사이트를 이용하여 entity 테이블을 생성

처음 생성하면 3줄이 나오지만 ctrl + enter 키를 누르면 라인이 하나씩 생성된다.

 

'Spring > JPA' 카테고리의 다른 글

1 대 1 관계 설정하는 방법  (0) 2021.07.24
연관 관계 설정 -1  (0) 2021.07.24
Entity Listener 사용 예제 2  (0) 2021.07.18
Entity Listener  (0) 2021.07.18
Enum 사용 예제  (0) 2021.07.18

history 기능을 사용할 때 유용하다

 

history를 담을 객체를 새로 생성 (원본과 동일하게 선언)

package com.example.bookmanager.domain;

import com.example.bookmanager.domain.listener.Auditable;
import lombok.*;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.time.LocalDateTime;

@Data
// 상속받은 BaseEntity를 사용하기 위해서 ToString, EqualsAndHashCode 재정의
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)

@Entity
@NoArgsConstructor
// 자동으로 생성날짜, 수정날짜를 입력해주기 위해서 선언
//@EntityListeners(value = MyEntityListener.class)
// 공통 클래스 사용
//@EntityListeners(value = AuditingEntityListener.class)
// User객체에 History를 저장할 객체 생성
// BaseEntity를 상속받아 그 안에 있는 설정들을 사용할 수 있다.
public class UserHistory extends BaseEntity {
    @Id
    @GeneratedValue
//    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NonNull
    private String name;

    @NonNull
    private String email;

    // update 할 경우 값을 제외하도록 설정
//    @Column(updatable = false)
    // 자동으로 해당값을 넣어준다.
//    @CreatedDate
//    private LocalDateTime createdAt;

    // 자동으로 해당값을 넣어준다.
//    @LastModifiedDate
//    private LocalDateTime updatedAt;
}

공통으로 수정한 날짜와 생성한 날짜를 처리해줄 BaseEntity를 생성

package com.example.bookmanager.domain;

import lombok.Data;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.Column;
import javax.persistence.EntityListeners;
import javax.persistence.MappedSuperclass;
import java.time.LocalDateTime;

// 공통으로 사용할 수정날짜, 생성날짜를 자동으로 처리해줄 Entity 선언
@EntityListeners(value = AuditingEntityListener.class)
// get, set 선언
@Data
// 해당 클래스를 상속받는 클래스에 현재 변수를 사용할 수 있도록 해주는 어노테이션
@MappedSuperclass
public class BaseEntity {
    @Column(updatable = false)
    // jpa에서 기본적으로 선언된 기능을 사용
    @CreatedDate
    private LocalDateTime createdAt;

    // jpa에서 기본적으로 선언된 기능을 사용
    @LastModifiedDate
    private LocalDateTime updatedAt;
}

 

history에 관한 jpa를 사용하기 위한 repository 생성

package com.example.bookmanager.repository;

import com.example.bookmanager.domain.UserHistory;
import org.springframework.data.jpa.repository.JpaRepository;

public interface UserHistoryRepository extends JpaRepository<UserHistory, Long> {

}

프로젝트 메인 클래스에 @EnableJpaAuditing(Auditing을 사용하겠다는 선언) 어노테이션 선언

https://velog.io/@aidenshin/%EC%97%94%ED%8B%B0%ED%8B%B0-%EC%9E%91%EC%84%B1-JPA-Auddit-%EC%A0%81%EC%9A%A9

 

JPA 엔티티 작성 - JPA Auddit 적용

실무에서 엔티티를 생성하다보면 auddit(감사) 목적으로 거의 모든 엔티티에 들어가는 필드(컬럼)들이 있다. 일일이 작성하기 매우 귀찮은데.. Spring Data Auddit 기능을 활용하여 귀찮을 일을 줄일 수

velog.io

package com.example.bookmanager;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@SpringBootApplication
@EnableJpaAuditing
public class BookmanagerApplication {

    public static void main(String[] args) {
        SpringApplication.run(BookmanagerApplication.class, args);
    }

}

 

원본 DB에 데이터가 생성, 수정되었을때 처리해주기 위한 클래스 생성

package com.example.bookmanager.domain.listener;

import com.example.bookmanager.domain.User;
import com.example.bookmanager.domain.UserHistory;
import com.example.bookmanager.repository.UserHistoryRepository;
import com.example.bookmanager.support.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;

// Autowired를 사용할 경우 spring bean 등록을 진행해야 하기 때문에 Component 어노테이션을 사용한다.
//@Component
public class UserEntityListener {
//    @Autowired
//    private UserHistoryRepository userHistoryRepository;

    // 업데이트, 생성를 진행할 경우 자동으로 history에 저장되도록 설정
    @PreUpdate
    @PrePersist
    public void prePersistAndPreUpdate(Object o) {
        // UserHistoryRepository의 bean을 가져온다.
        UserHistoryRepository userHistoryRepository = BeanUtils.getBean(UserHistoryRepository.class);
        User user = (User) o;

        UserHistory userHistory = new UserHistory();
        userHistory.setId(user.getId());
        userHistory.setName(user.getName());
        userHistory.setEmail(user.getEmail());

        userHistoryRepository.save(userHistory);
    }
}

위 코드가 정상적으로 동작하기 위해서는 Repository에 bean값을 가져와야하기 때문에 그 기능을 사용하게 해 줄 클래스 생성

package com.example.bookmanager.support;

import org.springframework.beans.BeansException;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

// Bean 클래스를 생성해주기위한 클래스
@Component
public class BeanUtils implements ApplicationContextAware {
    private static ApplicationContext applicationContext;

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        BeanUtils.applicationContext = applicationContext;
    }

    public static <T> T getBean(Class<T> clazz) {
        return applicationContext.getBean(clazz);
    }
}

 

User 클래스에 history기능을 사용하기 위해서 @EntityListeners 어노테이션에 추가

//공통 Entity 사용
@EntityListeners(value = {AuditingEntityListener.class, UserEntityListener.class})
//@Table(name = "user", indexes = {@Index(columnList = "name")}, uniqueConstraints = {@UniqueConstraint(columnNames = {"email"})})
public class User {

 

테스트 코드

    @Autowired
    private UserHistoryRepository userHistoryRepository;
    
    @Test
    void userHistoryTest(){
        User user = new User();
        user.setEmail("martin-new@naver.com");
        user.setName("martin-new");

        userRepository.save(user);

        // 업데이트 발생
        user.setName("martin-new-new");
        userRepository.save(user);
        userHistoryRepository.findAll().forEach(System.out::println);
    }

 

처리 결과(정상적으로 history에 값이 입력되는 것을 확인 할 수 있다.)

 

'Spring > JPA' 카테고리의 다른 글

연관 관계 설정 -1  (0) 2021.07.24
연관관계, ERD  (0) 2021.07.24
Entity Listener  (0) 2021.07.18
Enum 사용 예제  (0) 2021.07.18
Entity 기본 속성  (0) 2021.07.18

Listener: 어떠한 동작하는 것을 기다리다가 동작하게 되면 이벤트를 동작 해주는 것

Entity Listener: Entity 객체가 어떠한 동작을 하는지 기다리다가 이벤트를 동작 시키는 것

 

참고 사이트: https://erim1005.tistory.com/entry/%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%B3%80%EA%B2%BD-%EC%95%8C%EB%A6%BC-EntityListeners

 

데이터 변경 알림 - @EntityListeners

spring 의 data-jpa 사용시 데이터 변경시 알림을 받는 방법이 있다. EntityListener 클래스를 만들고 public class DataDtoListener { @PostLoad public void postLoad(DataDto dto) { log.info("post load: {}",..

erim1005.tistory.com

 

테스트 진행을 위한 코드 입력

user 객체에 어노테이션을 적용한 메소드 추가

@PrePersist
public void prePersist(){
    System.out.println(">>> prePersist");
    // 사용자가 입력을 안해주어도 자동으로 현재 날짜를 입력하도록 설정
    this.createdAt = LocalDateTime.now();
    this.updatedAt = LocalDateTime.now();
}

@PostPersist
public void postPersist(){
    System.out.println(">>> postPersist");
}

@PreUpdate
public void preUpdate(){
    System.out.println(">>> preUpdate");
    // Update를 진행할 때 자동으로 update를 진행한 날짜를 입력하도록 설정
    this.updatedAt = LocalDateTime.now();
}

@PostUpdate
public void postUpdate(){
    System.out.println(">>> postUpdate");
}

@PreRemove
public void preRemove(){
    System.out.println(">>> preRemove");
}

@PostRemove
public void postRemove(){
    System.out.println(">>> postRemove");
}

@PostLoad
public void postLoad(){
    System.out.println(">>> postLoad");
}

테스트 코드 작성

    @Test
    void listenerTest(){
        Dto();
        User user = new User();
        user.setEmail("martin2@fastcapmus.com");
        user.setName("martin");

        userRepository.save(user);

        User user2 = userRepository.findById(1L).orElseThrow(RuntimeException::new);
        user2.setName("marrrrrrrtin");;
        userRepository.save(user2);
        userRepository.deleteById(4L);
    }

확인

저장하기 전과 저장한 후에 호출

조회 후에 호출

 

업데이트 전과 후에 호출

삭제 전 후에 호출

 

주로 사용자가 프로그램상에서 새로 데이터를 생성하거나 수정을 하였을때 날짜를 자동으로 입력해줄 때 사용할 수 있다.

테스트 코드

    @Test
    void prePersistTest() {
        User user = new User();
        user.setEmail("martin2@fastcampus.com");
        user.setName("martin");
//        user.setCreatedAt(LocalDateTime.now());
//        user.setUpdatedAt(LocalDateTime.now());

        userRepository.save(user);
        System.out.println(userRepository.findByEmail("martin2@fastcampus.com"));
    }

    @Test
    void preUpdateTest() {
        Dto();
        User user = userRepository.findById(1L).orElseThrow(RuntimeException::new);

        System.out.println("as-is: " + user);

        user.setName("martin22");
        userRepository.save(user);

        System.out.println("to-be: " + userRepository.findAll().get(0));
    }

 

여러군데에서 적용하기 위해서는 아래와 같이 인터페이스를 따로 만들어서 적용 시킬수 있다.

인터페이스 생성

package com.example.bookmanager.domain;

import java.time.LocalDateTime;

public interface Auditable {
    LocalDateTime getCreatedAt();
    LocalDateTime getUpdatedAt();

    void setCreatedAt(LocalDateTime createdAt);
    void setUpdatedAt(LocalDateTime updatedAt);
}

Entity Listener를 선언한 클래스 작성

package com.example.bookmanager.domain;

import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import java.time.LocalDateTime;

public class MyEntityListener {
    @PrePersist
    public void prePersist(Object o) {
        // 전달받은 매개변수가 Auditable 클래스로 형변환해도 괜찮은지 확인
        if (o instanceof Auditable){
            ((Auditable) o).setCreatedAt(LocalDateTime.now());
            ((Auditable) o).setUpdatedAt(LocalDateTime.now());
        }
    }

    @PreUpdate
    public void preUpdated(Object o){
        // 전달받은 매개변수가 Auditable 클래스로 형변환해도 괜찮은지 확인
        if (o instanceof Auditable){
            ((Auditable) o).setUpdatedAt(LocalDateTime.now());
        }
    }
}

사용할 Listener를 적용할 객체  클래스 작성

@EntityListeners(value = MyEntityListener.class) 를 선언하여 작성한 클래스가 적용될 수 있도록 설정한다.

package com.example.bookmanager.domain;

import lombok.*;

import javax.persistence.*;
import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;

@NoArgsConstructor
@AllArgsConstructor
@RequiredArgsConstructor
@Data
@Builder
@Entity
@EntityListeners(value = MyEntityListener.class)
//@Table(name = "user", indexes = {@Index(columnList = "name")}, uniqueConstraints = {@UniqueConstraint(columnNames = {"email"})})
public class User {
    @Id
    @GeneratedValue
//    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NonNull
    private String name;

    @NonNull
    private String email;

    // update 할 경우 값을 제외하도록 설정
    @Column(updatable = false)
    private LocalDateTime createdAt;

    private LocalDateTime updatedAt;

    @Enumerated(value = EnumType.STRING)
    private Gender gender;
//    @Transient
//    private String testData;

    public User(String name, String email, LocalDateTime creDate, LocalDateTime upDate) {
        this.name = name;
        this.email = email;
        this.createdAt = creDate;
        this.updatedAt = upDate;
    }

    //@OneToMany(fetch = FetchType.EAGER)
    //private List<Address> address;

    // Entity Listener 이벤트 정의 및 동작 시점 확인
    @PrePersist
    public void prePersist(){
        System.out.println(">>> prePersist");
        // 사용자가 입력을 안해주어도 자동으로 현재 날짜를 입력하도록 설정
        this.createdAt = LocalDateTime.now();
        this.updatedAt = LocalDateTime.now();
    }

    @PostPersist
    public void postPersist(){
        System.out.println(">>> postPersist");
    }

    @PreUpdate
    public void preUpdate(){
        System.out.println(">>> preUpdate");
        // Update를 진행할 때 자동으로 update를 진행한 날짜를 입력하도록 설정
        this.updatedAt = LocalDateTime.now();
    }

    @PostUpdate
    public void postUpdate(){
        System.out.println(">>> postUpdate");
    }

    @PreRemove
    public void preRemove(){
        System.out.println(">>> preRemove");
    }

    @PostRemove
    public void postRemove(){
        System.out.println(">>> postRemove");
    }

    @PostLoad
    public void postLoad(){
        System.out.println(">>> postLoad");
    }
}

 

테스트 코드 작성

package com.example.bookmanager.repository;

import com.example.bookmanager.domain.Book;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;

import static org.junit.jupiter.api.Assertions.*;

@SpringBootTest
class BookRepositoryTest {

    @Autowired
    private BookRepository bookRepository;

    @Test
    void bookTst(){
        Book book = new Book();
        book.setName("Jpa 공부");
        book.setAuthor("여기저기");

        bookRepository.save(book);
        System.out.println(bookRepository.findAll());
    }
}

확인

 

수정한 날짜와 생성날짜를 따로 입력하지 않았지만 자동으로 입력된 것을 확인할 수 있다.

 

'Spring > JPA' 카테고리의 다른 글

연관관계, ERD  (0) 2021.07.24
Entity Listener 사용 예제 2  (0) 2021.07.18
Enum 사용 예제  (0) 2021.07.18
Entity 기본 속성  (0) 2021.07.18
Query Method 예제 - 5(페이징 처리)  (0) 2021.07.17

Enum: 열거형으로 객체를 새로 생성할 때 사용한다.

 

코드

package com.example.bookmanager.domain;

public enum Gender {
    MALE,
    FEMALE
}

User 객체에 추가

private Gender gender;

enum 값을 확인하기 위해 쿼리 작성 (JpaRepository를 상속받은 위치에서 작성한다.)

    // enum test
    // 사용자가 쿼리를 직접 입력하고 반영할 수 있도록 설정
    @Query(value = "select * from user limit 1;", nativeQuery = true)
    Map<String, Object> findRawRecord();

테스트 코드

@Test
void enumTest(){
    Dto();
    User user = userRepository.findById(1L).orElseThrow(RuntimeException::new);
    user.setGender(Gender.MALE);

    userRepository.save(user);
    userRepository.findAll().forEach(System.out::println);
    System.out.println(userRepository.findRawRecord().get("gender"));
}

 

확인

선언한 값이 정확하게 들어간 것을 확인할 수 있다.
쿼리 확인
원하는 값이 아닌 0이라는 값이 찍히는 것을 확인

 

이러한 문제가 발생하면 나중에 열거를 선언한 객체에 값을 추가하게 되면 DB에 입력한 데이터의 매핑된 데이터가 조회되는 데이터가 달라지는 문제가 발생할 수 있다.

따라서 객체 변수에 @Enumerated(value = EnumType.STRING) 어노테이션을 선언해 준다.

    @Enumerated(value = EnumType.STRING)
    private Gender gender;

문자로 나타나는 것을 확인할 수 있다.

참고 사이트

https://lng1982.tistory.com/280

 

9. [JPA] @Enumerated

자바 enum 타입을 엔티티 클래스의 속성으로 사용할 수 있다. @Enumerated 애노테이션에는 두 가지 EnumType이 존재한다. EnumType.ORDINAL : enum 순서 값을 DB에 저장 EnumType.STRING : enum 이름을 ..

lng1982.tistory.com

 

'Spring > JPA' 카테고리의 다른 글

Entity Listener 사용 예제 2  (0) 2021.07.18
Entity Listener  (0) 2021.07.18
Entity 기본 속성  (0) 2021.07.18
Query Method 예제 - 5(페이징 처리)  (0) 2021.07.17
Query Method 예제 - 4 (정렬)  (0) 2021.07.17

@Entity: DTO에 명시해주는 어노테이션으로 해당 객체가 JPA에서 관리되어지는 Entity객체임을 나타낸다. 반드시 구분할 수 있는 기본키가 존재해야한다.(@ID 어노테이션으로 정의 해준다.)

 

@GeneratedValue: 기본키를 자동적으로 생성할 수있도록 도와주는 어노테이션

 

@Table: DTO에 명시해주는 어노테이션으로 선언해주게 되면 해당 클래스의 매핑되는 테이블을 생성할 때 테이블의 해당하는 속성을 정의해 줄 수 있다.

 

테이블명 선언 예제

@Table(name = "user_aa")

확인

인덱스와 키 설정도 함께 만들어 줄 수 있지만 실제 DB에서는 사용이 불가하다.

@Table(name = "user_aa", indexes = {@Index(columnList = "name")}, uniqueConstraints = {@UniqueConstraint(columnNames = {"email"})})

 

@Column: Dto내 변수에 선언해주게 되면 해당 컬럼에 대한 속성을 정의해 줄 수 있다.

변수명 선언 예제

@Column(name = "createA")
private LocalDateTime createdAt;

 

nullable 속성을 정의해 주어 null값을 가능하게 할지 설정 가능하다

@Column(name = "createA", nullable = false)
private LocalDateTime createdAt;

updatable, insertable 속성을 정의해주어 insert, update 할때 값을 제외시킬수도 있다.

예제

@Column(updatable = false)
private LocalDateTime createdAt;

@Column(insertable = false)
private LocalDateTime updatedAt;

테스트 코드

    @Test
    void insertAndUpdateTest(){
        User user = new User();
        user.setName("martin");
        user.setEmail("martin2@fastcampus.com");
        userRepository.save(user);

        User user2 = userRepository.findById(1L).orElseThrow(RuntimeException::new);
        user2.setName("marrrrrrrrrtin");
        userRepository.save(user2);
    }

insert 처리 할경우 false 처리했던 updatedAt 컬럼을 제외시킨다

update 처리 할경우 false 처리했던 createdAt 컬럼을 제외시킨다

그 외에도 다른 속성들을 지정해 줄 수 있다.

 

@Transient: 변수명에 선언하며 선언하게 되면 해당 컬럼은 DB에 반영되지 않고 프로그램 실행시에만 생성되었다가 프로그램 종료시 함께 종료되는 컬럼으로 선언 된다.

@Transient
private String testData;

테이블 생성할 경우 제외되는 것을 확인할 수 있다.

 

참고 사이트

https://velog.io/@leyuri/Spring-boot-JPA-%EC%96%B4%EB%85%B8%ED%85%8C%EC%9D%B4%EC%85%98-Entity-Table-Column-Id-Lombok

 

Spring-boot JPA 어노테이션 @Entity, @Table, @Column, @Id @Lombok

객체 - 테이블 맵핑 : @Entity, @Table필드 - 컬럼의 팹핑 : @Column기본키의 맵핑 : @Id조인 맵핑 : @ManyToOne, @JoinColumn@Entity 어노테이션을 클래스에 선언하면 그 클래스는 JPA가 관리한다. 그러므로 DB의 테

velog.io

 

'Spring > JPA' 카테고리의 다른 글

Entity Listener  (0) 2021.07.18
Enum 사용 예제  (0) 2021.07.18
Query Method 예제 - 5(페이징 처리)  (0) 2021.07.17
Query Method 예제 - 4 (정렬)  (0) 2021.07.17
Query Method 예제 - 3  (0) 2021.07.17

페이징 처리 코드

// Page: 응답 값, Pageable: 요청 값
Page<User> findByName(String name, Pageable pageable);

테스트 코드 

// 페이징 처리
System.out.println("findByName: " + userRepository.findByName("martin", PageRequest.of(0, 1, Sort.by(Sort.Order.desc("id")))));

확인

 

페이징 처리된 데이터를 확인하는 방법

테스트 코드

// 내부의 값을 가져오는 방법
System.out.println("findByName2: " + userRepository.findByName("martin", PageRequest.of(0, 1, Sort.by(Sort.Order.desc("id")))).getContent());

확인

 

총 페이징 처리된 갯수를 확인하는 방법

// 총 페이지 갯수를 확인하는 방법
System.out.println("findByName3: " + userRepository.findByName("martin", PageRequest.of(0, 1, Sort.by(Sort.Order.desc("id")))).getTotalElements());

확인

'Spring > JPA' 카테고리의 다른 글

Enum 사용 예제  (0) 2021.07.18
Entity 기본 속성  (0) 2021.07.18
Query Method 예제 - 4 (정렬)  (0) 2021.07.17
Query Method 예제 - 3  (0) 2021.07.17
QueryMethod 예제 2  (0) 2021.07.17

+ Recent posts