프로그래밍/- Java,Spring 초격차 교육

loombok , JPA , Builder 패턴

즐겁게 하하하 2022. 2. 13. 20:00
728x90

1. Loombok

 
loombok
   - build.gradle > dependencies 추가
     compileOnly 'org.projectlombok:lombok'
     annotationProcessor 'org.projectlombok:lombok'
    
     configurations {
       compileOnly {
          extendsFrom annotationProcessor
       }
     }

@Getter  @Setter  @ToString 

@Data 
  @Getter, @Setter, @RequiredArgsConstructor, @ToString, 
  @EqualsAndHashCode을 한꺼번에 설정

@NoArgsConstructor  파라미터가 없는 기본 생성자를 만듬
@AllArgsConstructor  모든 필드 값을 파라미터로 받는 생성자를 만듬
@RequiredArgsConstructor final이나 @NonNull인 필드 값만 파라미터로 받는 생성자를 만듬

@EqualsAndHashCode 자바 빈을 만들 때 equals와 hashCode 메소드를 자주 오버라이딩 
   callSuper 속성을 통해 equals와 hashCode 메소드 자동 생성 시 
   부모 클래스의 필드까지 감안할지 안 할지에 대해서 설정할 수 있습니다. 
   즉, callSuper = true로 설정하면 부모 클래스 필드 값들도 동일한지 체크하며,
   callSuper = false로 설정(기본값)하면 자신 클래스의 필드 값들만 고려합니다.
 

 

 

 

2. Builder 패턴

@Builder  
    - Builder.Default : private List<String> defaultItem = new ArrayList<String>();  
  
   불필요한 생성자의 제거
   데이터의 순서에 상관없이 객체생성 가능
   명시적 선언으로 이해하기가 쉽고
   각 인자가 어떤 의미인지 알기 쉽다.
   setter메서드가 없으므로 변경 불가능한 객체를 만들수있다.
   한번에 객체를 생성하므로 객체일관성이 깨지지 않는다.
   build()함수가 null인지 체크해주므로 검증이 가능한다.
   안그러면 set하지않은 객체에대해 get을 하게되는경우 nullPointerExcetpion발생 등등의 문제

public class PersonInfo { 
    private String name;         //필수적으로 받야할 정보 
    private int age;             //선택적으로 받아도 되는 정보 
    private int phonNumber;      //선택적으로 받아도 되는 정보 
    private PersonInfo() { }

    public static class Builder { 
        private String name; 
        private int age; 
        private int phonNumber;
 
        public Builder(String name) { // 필수변수는 생성자로 값을 넣는다. 
            this.name = name; 
        }

        // 멤버변수별 메소드 - 빌더클래스의 필드값을 set하고 빌더객체를 리턴한다  
        public Builder setAge(int age) { 
            this.age = age; 
            return this; 
        }

        public Builder setPhonNumber(int phonNumber) { 
            this.phonNumber = phonNumber; 
            return this; 
        }

	   // 빌더메소드
        public PersonInfo build() { 
            PersonInfo personInfo = new PersonInfo(); 
            personInfo.name = name; 
            personInfo.age = age; 
            personInfo.phonNumber = phonNumber; 
            return personInfo; 
        }
    }
}

PersonInfo personinfo = new PersonInfo
    .Builder("SeungJin")    // 필수값 입력 ( 빌더클래스 생성자로 빌더객체 생성)
    .setAge(25)  			// 값 set
    .setPhoneNumber(1234)
    .build() 
____________________________________________________
@Builder
public class Person {
    private final String name;
    private final int age;
    private final int phone;
}

Person person = Person.builder() // 빌더어노테이션으로 생성된 빌더클래스 생성자
    .name("seungjin")
    .age(25)
    .phone(1234)
    .build();
 

 

 

 


 

3. JPA

       
     
 
1. application.yml
   - spring.h2.console.enabled: 
      true h2 console을 사용하는지 여부 설정
   - spring.jpa.defer-datasource-initialization: 
      truedata.sql을 시스템이 올라온 후 사용할지 여부 설정
   - spring.jpa.show-sql: 
      truejpa 쿼리를 볼 것인지 여부 설정
   - spring.jpa.properties.hibernate.format_sql: 
      truejpa 쿼리를 정렬된 상태로 볼 수 있는 설정
   - logging.level.org.hibernate.type: 
      trace파라미터 값이 어떻게 매핑되는지 확인하는 설정

spring:

  h2:
    console:
      enabled: true

  jpa:
    properties:
      hibernate:
        show-sql : true
        format_sql: true

server:
  port: 8070
__________________________________
2. build.gradle
dependencies{
    implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
    implementation 'org.springframework.boot:spring-boot-starter-web'
    compileOnly 'org.projectlombok:lombok'
    runtimeOnly 'com.h2database:h2'
    annotationProcessor 'org.projectlombok:lombok'
    testImplementation 'org.springframework.boot:spring-boot-starter-test'
}
__________________________________
 
객체와 테이블 매핑 
  @Entity
   - @Entity가 붙은 클래스는 JPA가 관리하는 것으로, 엔티티라고 불림 
   - 기본 생성자는 필수 (JPA가 엔티티 객체 생성 시 기본 생성자를 사용) 
   - final 클래스, enum, interface, inner class 에는 사용할 수 없음 
   - 저장할 필드에 final 사용 불가 

  @Table
   - @Table(name="user",
        indexes = {@Index(columnList="name")},
        uniqueConstraints = {@UniqueConstraint(columnNames = {"email"})})
   - 엔티티와 매핑할 테이블을 지정 
   - 생략 시 매핑한 엔티티 이름이 테이블 이름 
   > 속성  
     Name : 매핑할 테이블 이름 (default. 엔티티 이름 사용) 
     Catalog : catalog 기능이 있는 DB에서 catalog 를 매핑 (default. DB 명) 
     Schema : schema 기능이 있는 DB에서 schema를 매핑 
     uniqueConstraints : DDL 생성 시 유니크 제약조건을 만듦 
     스키마 자동 생성 기능을 사용해서 DDL을 만들 때만 사용

   @Column : 컬럼 속성 지정
   @Column( updatable = false)
   @Column( insertable = false)
   @Column(nullable = false)

   @Transient :  해당 데이터를 테이블의 컬럼과 매핑 시키지 않는다. 
                 컬럼를 제외하기 위해 사용

   
_____________________________________________________________________________
- JPA는 데이터베이스 스키마를 자동으로 생성하는 기능을 지원 
- 클래스의 매핑 정보와 데이터베이스 방언을 사용하여 데이터베이스 스키마 생성  
- 애플리케이션 실행 시점에 데이터베이스 테이블을 자동으로 생성 
- 스키마 자동 생성 기능 사용을 위해 persistence.xml에 속성 추가
  <property name="hibernate.hbm2ddl.auto" value="create" />

  hibernate.hbm2ddl.auto 속성
    create : 기존 테이블을 삭제하고 새로 생성(DROP + CREATE)
    create-drop : CREATE 속성에 추가로 애플리케이션을 종료할 때 
                생성한 DDL을 제거 (DROP + CREATE + DROP) 
    update : DB 테이블과 엔티티 매핑 정보를 비교해서 변경 사항만 수정 
    validate : DB 테이블과 엔티티 매핑정보를 비교해서 차이가 있으면 
               경고를 남기고 애플리케이션을 실행하지 않음. DDL을 수행하지 않음
    none : 자동 생성 기능을 사용하지 않음

> 주의사항 
- 개발 초기 단계는 create 또는 update 
- 초기화 상태로 자동화된 테스트를 진행하는 개발자 환경과 CI서버는 create 또는 create-drop 
- 테스트 서버는 update 또는 validate 
- 스테이징과 운영 서버는 validate 또는 none
 
persistence.xml에 속성 추가
<property name="hibernate.ejb.naming_strategy" value="org.hibernate.cfg.ImprovedNamingStrategy" />
_____________________________________________________________________________

기본 키 생성 및 자동매핑 : @Id  , @GeneratedValue
필드와 컬럼 매핑 : @Column
연관관계 매핑 : @ManyToOne, @JoinColumn
_____________________________________________________________________________
 
 
 
@SpringBootTest
class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Test 
    void crud(){ 
       ___________________________________________________________
        List<User> users = userRepository
            .findAllById(Lists.newArrayList(1L,3L,5L));
            //.findAll(Sort.by(Sort.Direction.DESC , "name")); //order by name desc
        users.forEach(System.out::println);
       ___________________________________________________________
        User user1 = new User("jack", "aa@naver.com");
        User user2 = new User("kone", "bb@naver.com"); 

        userRepository.saveAll(Lists.newArrayList(user1,user2));
        List<User> users = userRepository.findAll(); 
        users.forEach(System.out::println); 
       ___________________________________________________________
        User user = userRepository.findById(1L).orElse(null);
        System.out.println(user);
       ___________________________________________________________
        userRepository.save(new User("new marin" , "aa@naver.com" ));
        userRepository.flush();
        userRepository.findAll().forEach(System.out::println);
       ___________________________________________________________
        boolean exists = userRepository.existsById(1L);
        System.out.println(exists);
       ___________________________________________________________
       //entity
        userRepository.delete(
                userRepository.findById(1L)
                        .orElseThrow(RuntimeException::new)
        );

       //단일
       userRepository.deleteById(1L);

        //delete 전에 select 후 delete
        //1000개면 쿼리만 1000개..
        userRepository.deleteAll();

        userRepository.deleteAll(
                userRepository.findAllById(
                        Lists.newArrayList(1L,3L)
                )
        );

        // select in && delete query 1번
        userRepository.deleteInBatch(
                userRepository.findAllById(
                        Lists.newArrayList(1L,3L)
                )
        );

        //delete 1번에 모두 지움
        userRepository.deleteAllInBatch();
       ___________________________________________________________
        userRepository.save(new User("david" , "fffs@naver.com"));  ::insert
        User user = userRepository.findById(1L).orElseThrow(RuntimeException::new);
        user.setEmail("mortain-update.com");
        userRepository.save(user); ::update
       ___________________________________________________________
        paging
        Page<User> users = userRepository.findAll( PageRequest.of(1,3) );
        System.out.println("page : "+ users);
        System.out.println("total Elements : "+ users.getTotalElements());
        System.out.println("total Pages : "+ users.getTotalPages());
        System.out.println("number of Elements : "+ users.getNumberOfElements());
        System.out.println("sort : "+ users.getSort());
        System.out.println("size : "+ users.getSize()); 
        users.getContent().forEach(System.out::println);
       ___________________________________________________________
       ExampleMatcher

        :: name 매치 안하고 email로 매치
        ExampleMatcher matcher = ExampleMatcher.matching()
                .withIgnorePaths("name")
                .withMatcher("email" , endsWith());

       :: 이메일은 매치 안함으로 하여 na는 의미없고 slowcampus.com 으로 매치하여 조회
        Example<User> example = Example.of(new User("ma" , "slowcampus.com"), matcher  );
        userRepository.findAll(example).forEach(System.out::println);
       ___________________________________________________________
        User user = new User();
        user.setEmail("slow");

        ExampleMatcher matcher = ExampleMatcher.matching().withMatcher("email", contains() );
        Example<User> example = Example.of(user, matcher);
        userRepository.findAll(example).forEach(System.out::println);
       ___________________________________________________________
    }
}
 
 
 

Spring Data JPA 에서 getOne 과 findById 차이점

 

 

쿼리 메소드 네이밍 규칙

쿼리 방법의 결과는 first 또는 top 키워드를 통해 제한(limit) 될 수 있으며,

이 키워드는 서로 바꿔 사용할 수 있습니다.

Top/first에 선택적 숫자 값을 추가하여 반환 할

최대 결과 크기를 지정할 수 있습니다. 숫자를 생략하면 결과 크기는 1로 가정됩니다.

public interface UserRepository extends JpaRepository<User, Long> { 

    @Query(value="select * from user limit 1;" , nativeQuery = true)
    Map<String , Object> finRawRecord();

    Set<User> findUserByNameIs(String name);
    Set<User> findUserByName(String name);
    Set<User> findUserByNameEquals(String name);
    ___________________________________________
    Optional<User> findByName(String name);
    List<User> findFirst2ByName(String name);
    List<User> findTop1ByName(String name);
    User findLsat1ByName(String name);
    List<User> findByEmailAndName(String emaail, String name);
    List<User> findByEmailOrName(String emaail, String name);
    List<User> findByCreatedAtAfter(LocalDateTime localDateTime);
    List<User> findByIdAfter(Long id);
    List<User> findByCreatedAtGreaterThan(LocalDateTime yesterday);
    List<User> findByCreatedAtGreaterThanEqual(LocalDateTime yesterday);
    List<User> findByCreatedAtBetween(LocalDateTime yesterday , LocalDateTime romorrow);
    List<User> findByIdBetween(Long id1 , Long id2);
    List<User> findByIdIsNotNull();
    List<User> findByAddressIsNotEmpty();
    List<User> findByNameIn(List<String> names);
    List<User> findByNameStartingWith(String names);
    List<User> findByNameEndingWith(String names);
    List<User> findByNameContains(String names);
    List<User> findByNameLike(String names);
    Page<User> findByName(String name , Pageable pageable);
}
__________________________________________
@SpringBootTest
class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    void select(){
        System.out.println(userRepository.findByName("marin"));
        System.out.println("findFirst2ByName : " + userRepository.findFirst2ByName("marin"));
        System.out.println("findTopBy1Name : " + userRepository.findTop1ByName("marin"));
        System.out.println("findTopBy1Name : " + userRepository.findLsat1ByName("marin"));
        System.out.println("=>>>findByEmailAndName : " + userRepository.findByEmailAndName("ddd@naver.com","margin"));
        System.out.println("=>>>findByEmailOrName : " + userRepository.findByEmailOrName("ddd@naver.com","margin"));
        System.out.println("=>>>findByCreatedAtAfter : " + userRepository.findByCreatedAtAfter(LocalDateTime.now().minusDays(1L)));
        System.out.println("=>>>findByIdAtAfter : " + userRepository.findByIdAfter(3L));
        System.out.println("=>>>findByCreatedAtGreaterThan : " + userRepository.findByCreatedAtGreaterThan(LocalDateTime.now().minusDays(1L)));
        System.out.println("=>>>findByCreatedAtGreaterThanEqual : " + userRepository.findByCreatedAtGreaterThanEqual(LocalDateTime.now().minusDays(1L)));
        System.out.println("=>>>findByCreatedAtBetween : " + userRepository.findByCreatedAtBetween(LocalDateTime.now().minusDays(1L) , LocalDateTime.now().plusDays(1L)));
        System.out.println("=>>>findByIdBetween : " + userRepository.findByIdBetween( 1L, 3L));
        System.out.println("=>>>findByIdIsNotNull : " + userRepository.findByIdIsNotNull());
        System.out.println("=>>>findByAddressIsNotEmpty : " + userRepository.findByAddressIsNotEmpty());
        System.out.println("=>>>findByNameIn : " + userRepository.findByNameIn(Lists.newArrayList("margin", "dennis")));
        System.out.println("=>>>findByNameStartingWith : " + userRepository.findByNameStartingWith("mar"));
        System.out.println("=>>>findByNameEndingWith : " + userRepository.findByNameEndingWith("mar"));
        System.out.println("=>>>findByNameContains : " + userRepository.findByNameContains("mar"));
        System.out.println("=>>>findByNameLike : " + userRepository.findByNameLike("%art%"));
        System.out.println("findByName-paging :" + userRepository.findByName("martin", PageRequest.of(0, 1, Sort.by(Sort.Order.desc("id")))).getContent() );
        System.out.println("findByName-paging :" + userRepository.findByName("martin", PageRequest.of(0, 1, Sort.by(Sort.Order.desc("id")))).getTotalElements() );
   }
}
 
2. 쿼리 메소드로 정렬 처리하기
__________________________________________
public interface UserRepository extends JpaRepository<User, Long> {

      List<User> findTop1ByName(String name);
      List<User> findTopByNameOrderByIdDesc(String name);
      List<User> findFirstByNameOrderByIdDescEmailAsc(String name);
      List<User> findFirstByName(String name , Sort sort);
}
__________________________________________
@SpringBootTest
class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Test
    void pagingAndSortingTest(){
        System.out.println("findTop1ByName :" + userRepository.findTop1ByName("martin"));
        System.out.println("findTopByNameOrderByIdDesc :" + userRepository.findTopByNameOrderByIdDesc("martin"));
        System.out.println("findFirstByNameOrderByIdDescEmailAsc :" + userRepository.findFirstByNameOrderByIdDescEmailAsc("martin"));
        System.out.println("findFirstByNameWithSortParams :" + userRepository.findFirstByName("martin" , getSort() ));

    }
    
    private Sort getSort(){
        return Sort.by(
           Sort.Order.desc("id"),
           Sort.Order.asc("email")
        );
    }
}
 
 

 


 

Entity Listener

@PrePersist :: insert 메서드 호출전 실행

@PreUpdate :: mergy 메서드 호출전 실행

@PreRemove :: delete 메서드 호출전 실행

@PostPersist :: persist 메서드 실행이후

@PostUpdate :: mergy 메서드 호출이후 실행

@PostRemove :: delete 메서드 호출이후 실행

@PostLoad :: select 조회 일어난 직후 실행

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

    void setCreatedAt(LocalDateTime createdAt);
    void setUpdatedAt(LocalDateTime updatedAt);
}
_______________________________________ 
@NoArgsConstructor
@Data
@Entity
@EntityListeners(value = MyEntityListener.class)
public class Book implements Auditable {

    @Id
    @GeneratedValue
    private Long id;

    private String name; 
    private String author; 
    private LocalDateTime createdAt; 
    private LocalDateTime updatedAt;
}
_______________________________________
public class MyEntityListener {

    @PrePersist
    public void prePersist(Object o){
        if(o instanceof Auditable){
           ((Auditable) o).setCreatedAt(LocalDateTime.now());
           ((Auditable) o).setUpdatedAt(LocalDateTime.now());
        }
    }

    @PreUpdate
    public void preUdate(Object o){
        if( o instanceof Auditable){
            ((Auditable) o).setUpdatedAt(LocalDateTime.now());
        }
    }
}
_______________________________________
@SpringBootTest
public class BookRepositoryTest {

    @Autowired
    private BookRepository bookRepository;

    @Test
    void bookTest(){
        Book book = new Book();
        book.setName("Jap 초격차 패키지");
        book.setAuthor("book");

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

 

EnableJpaAuditing & History 

 
EntityListeners는 JPA Entity에 Persist, Remove, Update, Load에 대한
event 전과 후에 대한 콜백 메서드를 제공한다. 
_____________________________
@EnableJpaAuditing :: ★
@SpringBootApplication
public class BookmanagerApplication {
    public static void main(String[] args) {
        SpringApplication.run(BookmanagerApplication.class, args);
    }
}
_____________________________
public interface Auditable {
    LocalDateTime getCreatedAt();
    LocalDateTime getUpdatedAt();

    void setCreatedAt(LocalDateTime createdAt);
    void setUpdatedAt(LocalDateTime updatedAt);
}
_____________________________
@Data
@MappedSuperclass // 해당 클래스를 상속받는 클래스에 현재 변수를 사용할수 있도록 해줌
@EntityListeners(value = AuditingEntityListener.class)
public class BaseEntity implements Auditable {

    @CreatedDate  :: ★
    private LocalDateTime createdAt;

    @LastModifiedDate  :: ★
    private LocalDateTime updatedAt;
}
_____________________________
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
@Entity
@EntityListeners(value = {UserEntityListener.class})
public class User extends  BaseEntity {

    @Id
    @GeneratedValue
    private Long id;

    @NonNull
    private String name;

    @NonNull
    private String email;
}
_____________________________
@Data
@ToString(callSuper = true)            ::  상속받은 것도 처리
@EqualsAndHashCode(callSuper = true)   ::  상속받은 것도 처리
@Entity
@NoArgsConstructor
@AllArgsConstructor
public class UserHistory extends BaseEntity {
    @Id
    @GeneratedValue
    private Long id;

    private Long userId;
    private String name;
    private String email;
}
_____________________________
public class UserEntityListener {

    @PreUpdate
    @PrePersist
    public void preListener(Object o){
        UserHistoryRepository userHistoryRepository =
                BeanUtils.getBean(UserHistoryRepository.class);

        User user = (User) o;
        UserHistory userHistory = new UserHistory();
        userHistory.setUserId(user.getId());
        userHistory.setName(user.getName());
        userHistory.setEmail(user.getEmail());
        userHistoryRepository.save(userHistory);
    }
}
_____________________________
@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);
    }
}
_____________________________
public interface UserRepository extends JpaRepository<User, Long> {}
public interface UserHistoryRepository extends JpaRepository<UserHistory , Long> {}
_____________________________
================= TEST =================
@SpringBootTest
class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private UserHistoryRepository userHistoryRepository;

    @Test
    void userPersistAndHistoryTest(){
        User user = new User();
        user.setEmail("hahaha@naver.com");
        user.setName("mamama");

        userRepository.save(user);
        user.setName("margin update");
        userRepository.save(user);
        System.out.println();
        System.out.println("===>>");
        userHistoryRepository.findAll().forEach(System.out::println);
        System.out.println();
    }
}
 

1:1 관계

@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
@Entity
@EntityListeners(value = {UserEntityListener.class})
public class Book extends  BaseEntity { 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id; 

    private String name; 
    private String category; 
    private Long authorId; 
    private Long publisherId;

    @ToString.Exclude
    @OneToOne(mappedBy = "book")
    private BookReviewInfo bookReviewInfo;
}
_____________________________
@Entity
@NoArgsConstructor
@Data
@ToString(callSuper = true) //상속받은 것도 처리
@EqualsAndHashCode(callSuper = true)//상속받은 것도 처리
public class BookReviewInfo extends BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    // 1:1 관계 , optional = false : null 비허용
    // mappedBy = "bookReviewInfo" : 연관키를 해당 테이블에서는 갖지 않는다.
    @OneToOne(optional = false)
    private Book book;

    private float averageReviewScore;

    private int reviewCount;
}
_____________________________
public interface BookRepository extends JpaRepository<Book, Long> {}
public interface BookReviewInfoRepository extends JpaRepository<BookReviewInfo, Long> {}
_____________________________
@SpringBootTest
class BookReviewInfoTest {

    @Autowired
    private BookReviewInfoRepository bookReviewInfoRepository;

    @Autowired
    private BookRepository bookRepository;

    private Book givenBook(){
        Book book = new Book();
        book.setName("JPA 1:1 테스트");
        book.setAuthorId(1L);
        book.setPublisherId(1L);
        return bookRepository.save(book);
    }

    private void givenBookReviewInfo(){
        BookReviewInfo bookReviewInfo = new BookReviewInfo();
        bookReviewInfo.setBook( givenBook() );
        bookReviewInfo.setAverageReviewScore(4.5f);
        bookReviewInfo.setReviewCount(2);
        bookReviewInfoRepository.save(bookReviewInfo);
    }

    @Test
    void crudTest2(){ //RDB 1:1관계
        givenBookReviewInfo();

        Book result = bookReviewInfoRepository
                    .findById(1L)
                    .orElseThrow(RuntimeException::new)
                    .getBook();
        System.out.println(">> " + result);

        BookReviewInfo result2 = bookRepository
                .findById(1L)
                .orElseThrow(RuntimeException::new)
                .getBookReviewInfo();
        System.out.println(">>" + result2);
    }
}
 

1:N 관계

@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@Entity
@NoArgsConstructor
@AllArgsConstructor
public class UserHistory extends BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name="user_id")
    private Long userId;
    private String name;
    private String email;
}
_____________________________
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
@Entity
@EntityListeners(value = {UserEntityListener.class})
public class User extends  BaseEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NonNull
    private String name;

    @NonNull
    private String email;

//    create table user_user_histories (
//            user_id bigint not null,
//            user_histories_id bigint not null
//    )  이상한 user_user_histories  매핑 테이블이 추가로 생성되어 @JoinColumn
    @JoinColumn(name = "user_id" , insertable = false , updatable = false)
    @OneToMany(fetch = FetchType.EAGER)
    private List<UserHistory> userHistories = new ArrayList<>();

}
_____________________________
public interface UserHistoryRepository extends JpaRepository<UserHistory , Long> {
    // 데이터가 많은경우 모든것을 가져올수 없어서
    // findById를 이용하여 특정값만 조회
    List<UserHistory> findByUserId(Long userId); 
}
_____________________________
public interface UserRepository extends JpaRepository<User, Long> {
    Optional<User> findByEmail(String email);
}
_____________________________
@SpringBootTest
class UserRepositoryTest {

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private UserHistoryRepository userHistoryRepository;

    @Test
    void userRelationTest(){
        User user = new User();
        user.setName("david");
        user.setEmail("hoho@naver.com");
        userRepository.save(user);

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

        user.setEmail("daniel@naver.com");
        userRepository.save(user);

        List<UserHistory> result =
                userRepository.findByEmail("daniel@naver.com").get().getUserHistories();

        result.forEach(System.out::println);
    }
}
 

N :1 관계

@Entity
@NoArgsConstructor
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class Review extends BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String title;
    private String content;
    private float score;

    @ManyToOne
    private User user;

    @ManyToOne
    private Book book;
}
_____________________________
@Entity
@NoArgsConstructor
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class Publisher extends BaseEntity{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @OneToMany
    @JoinColumn(name = "publisher_id")
    private List<Book> books = new ArrayList<>();
}
_____________________________
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
@Entity
@EntityListeners(value = {UserEntityListener.class})
public class User extends  BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @NonNull
    private String name;

    @NonNull
    private String email;

//    create table user_user_histories (
//            user_id bigint not null,
//            user_histories_id bigint not null
//    ) 라는 이상한 매핑 테이블이 추가로 생성되어 @JoinColumn
    @JoinColumn(name = "user_id" , insertable = false , updatable = false)
    @OneToMany(fetch = FetchType.EAGER)
    @ToString.Exclude
    private List<UserHistory> userHistories = new ArrayList<>();

    @OneToMany
    @JoinColumn(name = "user_id")
    @ToString.Exclude
    private List<Review> reviews = new ArrayList<>();
}
_____________________________
@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@NoArgsConstructor
@AllArgsConstructor
@Entity
@EntityListeners(value = {UserEntityListener.class})
public class Book extends  BaseEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;
    private String category;
    private Long authorId;

    @ToString.Exclude
    @OneToOne(mappedBy = "book")
    private BookReviewInfo bookReviewInfo;

    @OneToMany
    @ToString.Exclude
    @JoinColumn(name = "book_id")
    private List<Review> reviewList = new ArrayList<>();

    @ManyToOne
    @ToString.Exclude
    private Publisher publisher;
}
_____________________________
public interface BookRepository extends JpaRepository<Book, Long> {}
public interface BookReviewInfoRepository extends JpaRepository<BookReviewInfo, Long> {}
public interface PublisherRepository extends JpaRepository<Publisher , Long> {}
public interface ReviewRepository extends JpaRepository<Review, Long> {}
public interface UserRepository extends JpaRepository<User, Long> {
    User findByEmail(String email);
}
_____________________________
@SpringBootTest
class BookRepositoryTest {

    @Autowired
    private BookRepository bookRepository;

    @Autowired
    private PublisherRepository publisherRepository;

    @Autowired
    private UserRepository userRepository;

    @Autowired
    private ReviewRepository reviewRepository;

    @Test
    @Transactional
    void bookRelationTest(){
        givenBookAndReview();

        User user = userRepository.findByEmail("martin@fastcampus.com");
        System.out.println("Review : "+ user.getReviews());
        System.out.println("Book : " + user.getReviews().get(0).getBook());
        System.out.println("Publisher : " + user.getReviews().get(0).getBook().getPublisher());
    }

    private void givenBookAndReview(){
        givenReview(givenUser() , givenBookAndReview( gibenPublisher()) );
    }

    private Book givenBookAndReview(Publisher publisher){
        Book book = new Book();
        book.setName("Jap Book");
        book.setPublisher(publisher);
        return bookRepository.save(book);
    }

    private User givenUser(){
        return userRepository.findByEmail("martin@fastcampus.com");
    }

    private void givenReview( User user , Book book){
        Review review = new Review();
        review.setTitle("JPa N :1");
        review.setContent("아자자");
        review.setScore(5.0f);
        review.setUser(user);
        review.setBook(book);
        reviewRepository.save(review);
    }

    private Publisher gibenPublisher(){
        Publisher publisher = new Publisher();
        publisher.setName("퍼블리셔~~");
        return publisherRepository.save(publisher);
    }
}
 

 

 

 

728x90