ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • 스프링 부트 14일차 - EntityManager, @Entity, @Table, @Column
    Portfolio/Spring Boot 2019. 5. 27. 00:12
    728x90

    1. Spring JPA에서 EntityManager는 가장 핵심적인 요소

    entityManager.persist(??); 로 ??를 DB에 저장할 수 있음.


    2. PostgreSQL을 쓰면 경고메시지가 뜨는데 없애려면

    spring.jpa.properties.hibernate.jdbc.lob.non_contextual_creation=true

    를 추가해줘야함.

    이유 : createClob()가 implement 되지 않았기 때문 -_-


    3. 트랜젝션 처리를 위해서는 클래스나 메소드에 @Transactional 을 붙여주면 됨.


    4. Hibernate의 가장 핵심적인 요소는 Session임.

    session.save(??)를 하면 ??가 DB에 저장 Session을 가져오는 곳은 EntityManager를 통해서 가져올 수 있다.

    Session session = entityManager.unwrap(Session.class);
    session.save(account);


    엥? 어떻게 Session이 EntityManager에서 튀어나오는거지? 할 수 있으나,

    잘 생각해보면 JPA를 구현한 것이 Hibernate이고, JPA 는 추상적인 기술 명세이기 때문이다.

    이해가 어렵다면 '추상적인것=상위의 것(더 큰것)' 이라고 생각하고 아래에 있는 것은 당연히 가져올 수 있다고 생각하면 이해가 쉬울 것 같다.


    5. spring.jpa.hibernate.ddl-auto 에서 update를 사용했을 때는 다음과 같은 특징이 있다.


     - 기존 스키마에서 추가시킬 수 있다.

    - 기존에 들어가 있는 레코드들을 건드리지 않는다. (create 일 때와 차이점)

    - 기존에 있던 컬럼이 사라졌다고 해서 그 컬럼을 실제 DB에서 없애지 않는다.


    세번째 있는 기존 컬럼이 사라졌을 때 실제 DB에서 없애지 않는 것은 컬럼이 자주 바뀌다 보면 그 컬럼이 실제 DB에 계속 쌓일 수 있다는 말이다. 개발 환경이라면 가급적 중간중간 create를 사용해서 스키마를 새로 만들어주자.


    6. @Entity, @Table

    다음과 같은 코드가 있다면 DB에 생성되는 테이블 이름은 클래스를 따라간다.


    @Entity

    public class Account {

    .....


    테이블 이름은 account가 될 것이다.

    근데 만약 클래스 이름이 예약어라면? 당연히 그걸로 테이블 이름을 사용할 수 없음. 직접 SQL로 DDL을 작성한다면 `(백쿼터?)로 감싸서 작성하는 방법도 있겠으나 권장되는 방법은 아닌 것 같다.


    그럴때 사용하는 것이 @Entity에 이름을 지정하는 것임.


    @Entity(name = "users")

    public class User {

    ...


    User은 여러 DB에서 예약어로 사용된 경우가 많다. 그래서 사용할 수 없기 때문에 users로 Entity의 이름을 지정해주면 테이블 이름이 따라간다. (클래스 이름을 Users로 바꾸면 되지 않나?에 대한 것은 너무 나쁜 네이밍 이니까 하지말자 ㅎ)


    근데 사실 Entity는 도메인 모델을 객체 세상에서 부르는 이름이다.

    DB들의 세상, 릴레이션에서는 Table로 바꿔 불러야한다.

    결국 @Table 애너테이션을 쓰는게 더 자연스러울듯


    그래서 이런 코드가 더 나을 것 같다.


    @Entity

    @Table(name = "users")

    public class User {

    ...


    아니면 아예 예약어가 아닌 것으로 쓰거나.. (백기선 선생님 취향이랍니다. 코드가 더 깔끔해지니까)


    최초의 코드 처럼.

    @Entity

    public class Account {

    .....




    7. @Id의 Long을 레퍼런스 타입의 Long로 쓸 것인가, 프리미티브 타입의 long으로 쓸 것인가?

    long으로 사용할 경우 초기화되지 않으면 Id(보통 primary key가 될텐데)가 0으로 들어갈 것이다.

    그런 것을 방지해주기 위해서는 Long을 레퍼런스타입으로 지정해주는게 더 안전한 것 같다.

    레퍼런스 타입은 기본값이 null이니까


    8. @GeneratedValue는 자동 생성된 값을 넣어줌. 보통 auto increment 같은 것으로 넣어줌.

    DB마다 좀 다를 수 있다.

    @GeneratedValue(strategy = ???) 로 지정해줄 수도 있음.


    9. @Entity의 밑에 있는 멤버 변수들은 모두 @Column을 가지고 있는 것과 마찬가지임 그럴 경우

    Not Null이나 Unique 속성을 주기 위해서는 다음과 같이 지정해주면 됨.


    @Column(nullable = false, unique = true)



    10. 컬럼으로 만들지 않고 싶다. 그냥 이 객체에서만 사용하고 싶다.

    할때는 @Transient 를 달아주면 됨.


    11. application.properties에 다음과 같이 넣어주면 Hibernate를 사용할 때 좋음

    • spring.jpa.show-sql=true

    • spring.jpa.properties.hibernate.format_sql=true

    이렇게 뜬다.

    Hibernate: 

        select

            nextval ('hibernate_sequence')

    Hibernate: 

        insert 

        into

            account

            (email, password, username, id) 

        values

            (?, ?, ?, ?)


    여기서 값이 들어가는 곳에는 ? 로 표시되어 있는데, 마치 prepared query를 작성하는 것처럼 나온다.

    이것도 어떤 값이 들어가는지 알기 위해서는 Logger 쪽 설정을 해야한다고 한다.



    12. 1대다 맵핑(일방향, 양방향 관계)

    https://docs.google.com/document/d/1IjSKwMEsLdNXhRLvFk576VTR03AKTED_3jMsk0bHANg/edit# 에서 발췌

    관계에는 항상 두 엔티티가 존재 합니다.

    • 둘 중 하나는 그 관계의 주인(owning)이고

    • 다른 쪽은 종속된(non-owning) 쪽입니다.

    • 해당 관계의 반대쪽 레퍼런스를 가지고 있는 쪽이 주인.


    단방향에서의 관계의 주인은 명확합니다.

    • 관계를 정의한 쪽이 그 관계의 주인입니다.


    단방향 @ManyToOne

    • 기본값은 FK 생성


    단방향 @OneToMany

    • 기본값은 조인 테이블 생성


    양방향

    • FK 가지고 있는 쪽이 오너 따라서 기본값은 @ManyToOne 가지고 있는 쪽이 주인.

    • 주인이 아닌쪽(@OneToMany쪽)에서 mappedBy 사용해서 관계를 맺고 있는 필드를 설정해야 합니다.


    양방향

    • @ManyToOne (이쪽이 주인)

    • @OneToMany(mappedBy)

    • 주인한테 관계를 설정해야 DB에 반영이 됩니다.


    특히 양방향 관계에서 주의.

    양방향 관계를 추가/제거 할 때에는 반드시 양쪽의 값을 모두 넣어주고 제거해야한다.

    ex)

    account.getStudies().add(study);

    study.setOwner(account);


    위 코드에서 account.getStudies().add(study); 같은 경우 DB에는 반영되지 않을 수 있으나, DB에서 값을 가져오지 않고 getStudies를 할 경우 값을 가져오지 못할 수 있기 때문에 꼭 해주는 것이 좋다.


    13. Cascade는 연관된 Entity 사이에서 상태의 변화를 전이 시키고 싶을 때 사용

    다음과 같이 4가지 상태가 있음.

    • Transient: JPA가 모르는 상태

    • Persistent: JPA가 관리중인 상태 (1차 캐시, Dirty Checking, Write Behind, ...)

    • Detached: JPA가 더이상 관리하지 않는 상태.

    • Removed: JPA가 관리하긴 하지만 삭제하기로 한 상태.

    이런식으로 사용가능

    ex) 만약 게시글을 의미하는 post와 댓글을 의미하는 comment가 있다고 가정하면 post는 parent, comment는 child가 될 수 있다.

    post가 없어질 경우 comment는 의미가 없어진다. 그럴경우 다음과 같이 Cascade를 주면 된다.


    @OneToMany(mappedBy = "post", cascade = CascadeType.REMOVE)

    private Set<Comment> comments = new HashSet<>();


    위와 같이 해주면 post의 remove되는 상태가 comment에게로 전이된다.



    14. @OneToMany는 기본값으로 Lazy Fetch(나중에 가져오기)를 하고, @ManyToOne는 기본값으로 Eager Fetch(지금 가져오기)를 한다.

    당연히 이유는 ManyToOne 같은 경우 매우매우매우매우매우매우 높은 확률로 One에 해당하는 엔티티에 접근하게 되기 때문이고(+ 가져와 봤자 1개만 가져오면 되니까 별 부하가 없다.), 그 만대로 OneToMany는 비교적 Many 쪽에 접근하게 될 가능성이 낮기 때문(물론 어떤 데이터냐에 따라 꼭 접근해야 될 수도 있겠지만) 기본값으로 위와 같이 지정되어 있는 것이다.

    당연히 위에 있는 기본값 말고 다른 값을 사용하고 싶다면 명시적으로 적어주면 된다.

    @OneToMany(mappedBy = "post", fetch = FetchType.Eager)




Designed by Tistory.