본문 바로가기
Blog/TIL

[240602] JPA 영속성 컨텍스트

by 코젼 2024. 6. 2.
728x90
반응형

🔶JPA

🔶Hibernate

🔶Stack


목차

    / 오늘의 TIL /


    JPA

    리플렉션 같은 동적 객체를 생성해야 하기 때문에 기본 생성자가 필요하다.

    JPA의 모든 작업은 트랜잭션 내에서 진행되어야 한다.

    EntityTransaction tx = em.getTransaction();
    tx.begin();
    
    ...
    
    tx.commit();

     

    * Maven 기준으로 작성됨.

     

    1. Persistence 클래스를 통해 /resources/META-INF/persistence.xml 파일을 조회한다.

    2. 파일 내에 있는 persistence-unit 태그의 name을 통해 EntityManagerFactory를 생성한다.

    EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

     

    3. EntityManager를 생성한다. 쓰레드 간에 공유하지 말아야 한다. 사용하고 버릴 것!

    EntityManager em = emf.createEntityManager();

    애플리케이션이 종료하면 EntityManagerFactory와 EntityManager를 close 한다.


    CRUD

    JPQL(엔티티 객체를 대상으로 쿼리 -> 객체 지향 쿼리(sql)) 사용

    createQuery를 통해 직접 쿼리를 작성할 수도 있다.

    List<Member> result = em.createQuery("select m from Member as m", Member.class).getResultList();

    등록

    em.persist(member);

    조회

    Member findMember = em.find(Member.class, 1L);

    삭제

    em.remove(findMember);

    수정

    findMember.setName("HelloJPA");
    //변경 사항이 있으면 persist를 안 해도 자동으로 저장된다.

    영속성 컨텍스트

    EntityManager를 통해 접속한다. 

    EntityManager -> PersistenceContext

    객체를 생성한 상태는 비영속 상태라고 말한다.

    persist로 데이터를 저장하려고 할 때, 실제로 db에 저장되지 않고 영속성 컨텍스트 내에 영속 상태로 저장된다.

     

    영속성 컨텍스트 이점

    1. 1차 캐시

    객체를 생성 후 persist를 통해 1차 캐시에 저장해두면, 동일한 id의 객체를 가져올 때 db를 조회하지 않아도 되는 이점이 있다.

    그러나 1차 캐시는 트랜잭션 내에서만 남아있기 때문에 대폭적인 성능 향상은 기대하기 어렵다.

     

    1-1. 영속 엔티티의 동일성 보장

    1차 캐시 덕분에 ==로 두 객체를 비교하면 동일한 레퍼런스를 참조하는 객체를 비교하는 것과 같다.

     

    2. 트랜잭션을 통한 쓰기 지연

    트랜잭션 덕분에 query들을 commit하기 전까지 jpa가 가지고 있다가, commit 후에 db로 flush한다.

    플러쉬(flush): 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영한다.

    플러쉬를 하더라도, 1차 캐시에 있는 데이터들이 삭제되지는 않는다.

    JPQL을 사용하는 경우는 db에 있는 데이터를 조회하기 때문에 자동으로 flush가 호출된다.

     

    3. 변경 감지(Dirty Checking)

    update query를 따로 보내지 않아도 1차 캐시에 저장된 스냅샷을 통해 값을 비교한다. 값이 변경되면 jpa가 알아서 update query를 세팅해준다.

     

    4. 지연 로딩(Lazy Loading)

     

    <-> 준영속 상태

    영속성 컨텍스트 내에 있는 영속 상태의 객체를 제거해내는 것을 준영속 상태라고 한다.

    1. em.detach()

    2. em.clear()

    영속성 컨텍스트를 모두 지우고 싶은 경우는 clear()를 사용한다.

    -> 1차 캐시 모두 사라짐

    3. em.close()


    필드와 컬럼 매핑

    @Column(name = "컬럼명 지정") //name 속성
    @Transient //db에 반영 X 메모리에서만 사용
    
    //enum
    @Enumerated(EnumType.STRING) //String형 Enum 타입 - enum에 작성되어있는 문자열 형태로 저장됨
    //enum 순서가 변경될 수 있기 때문에 운영 상에서 사용할 경우 위험하다.
    @Enumerated(EnumType.ORDINAL) //enum 순서 저장 (default) - integer 타입으로 생성 0, 1, 2...
    
    //날짜(이전 사용)
    @Temporal(TemporalType.TIMESTAMP) //날짜 타입. DATE, TIME, TIMESTAMP
    //최신 버전 Hibernate
    //객체를 직접 사용하면 db에서 date 타입으로 인식해서 저장한다.
    private LocalDate localDate;
    private LocalDateTime localDateTime;
    
    //매핑하는 필드 타입이 문자면 clob, 나머지는 blob 매핑 (ex. byte[])
    @Lob //굉장히 큰 데이터 (ex. 설명) - 데이터베이스에서 clob 타입

    @Column 속성

    //기본 true값 설정. JPA 사용 Application 내 등록, 수정 가능 여부 설정
    insertable = true/false
    updateable = true/false
    
    //not null
    nullable = false
    
    //unique 제약 조건. 그러나 unique key 이름 랜덤 값이기 때문에 반영 어려움
    unique = true
    //-> @Table의 uniqueConstraints 속성으로 자주 사용함
    @Table(uniqueConstraints = {@UniqueConstraint(columnNames = {"id", "user"})})
    
    //데이터베이스 컬럼 정보 직접 주기
    columnDefinition = "varchar(100) default 'EMPTY'"
    
    //BigDecimal 타입 처리할 때 percision 속성 사용
    @Column(precision = 0)
    private BigDecimal a;

    기본 키 매핑

    @GeneratedValue

    • strategy
      • AUTO (default) - dialect에 맞춰서 자동으로 설정됨. 버전마다 다르므로 주의해서 사용 필요
      • IDENTITY - 기본 키 생성을 데이터베이스에 위임. insert 시 생성됨. mysql은 auto_increment 실행됨
        • JPA는 보통 트랜잭션 커밋 시점에 INSERT SQL 실행되기 때문에 persist하면 바로 sql을 날린다.
        • 따라서 모아서 insert하는 것을 할 수 없다.
        • jdbc driver 덕분에 insert하고 id값을 얻어서 영속성 컨텍스트의 PK 값으로 사용하게 된다.
      • SEQUENCE - 시퀀스 오브젝트 생성 후 값 설정. @SequenceGenerator를 통해 매핑 가능(generator 속성 필요)
        • allocationSize 덕분에 성능 최적화를 할 수 있다. (기본 50) - 시퀀스 한 번에 호출에 증가하는 수
        • 처음 호출할 때 seq 값이 1이고, 추가적으로 호출할 때마다 db에 있는 seq를 가져오지 않고 메모리 seq를 가져와서 2, 3, ... 세팅한다. (allocationSize는 51로 세팅되어 있음)
      • TABLE - 키 생성 전용 테이블을 만들어서 데이터베이스 시퀀스를 흉내냄. 성능 이슈 있음(generator 속성 필요)

    Hibernate

    데이터베이스 스키마 자동 생성

    * 운영 서버에는 create, create-drop, update 적용하지 말 것

    create, create-drop, update, validate, none 속성이 있다.

    <property name="hibernate.hbm2ddl.auto" value="create" />

    개발 초기 단계: create, update

    테스트 서버: update, validate

    스테이징과 운영 서버: validate, none

     


    Stack

    https://school.programmers.co.kr/learn/courses/30/lessons/64061

     

    프로그래머스

    코드 중심의 개발자 채용. 스택 기반의 포지션 매칭. 프로그래머스의 개발자 맞춤형 프로필을 등록하고, 나와 기술 궁합이 잘 맞는 기업들을 매칭 받으세요.

    programmers.co.kr

    정답

    더보기

    [접근법]

    격자 배열을 stack 형태로 변환한다.

    격자 배열은 2차원 배열이므로, 각 열에 대한 stack 배열로 만든다.

     

    격자 배열을 역순으로 탐색하면서 열의 인형을 stack에 push한다.

    0일 경우는 공백이므로 스택에 푸시하지 않는다.

     

    인형을 담을 bucket stack 하나를 생성한다.

    크레인 배열을 순회하면서, 매핑하는 stack 열이 비어있지 않은 경우에 인형을 뽑는다.

    bucket이 비어있지 않고, 인형이 동일한 경우 뽑는다.

    그 외는 인형을 stack에 push한다.

     

    import java.util.*;
    
    public class Solution {
    
        /**
         * @param board 격자
         * @param moves 크레인 작동 위치
         * @return 터트려져 사라진 인형의 개수
         */
        public static int solution(int[][] board, int[] moves) {
            ArrayDeque<Integer>[] stack = new ArrayDeque[board.length];
    
            //stack 초기화
            for (int i = 0; i < stack.length; i++) {
                stack[i] = new ArrayDeque<>();
            }
    
            //board 순환
            for (int i = board.length-1; i >= 0; i--) {
                for (int j = 0; j < board[i].length; j++) {
                    //인형이 있는 경우만 stack 열에 추가한다.
                    if (board[i][j] > 0) {
                        stack[j].push(board[i][j]);
                    }
                }
            }
    
            //인형 상자
            ArrayDeque<Integer> bucket = new ArrayDeque<>();
            int answer = 0;
    
            for (int move : moves) {
                //크레인이 뽑는 위치에 인형이 있는 경우
                if (!stack[move - 1].isEmpty()) {
                    //인형 뽑기
                    int doll = stack[move - 1].pop();
    
                    //인형 상자가 비어있지 않고, 인형 상자에 있는 인형과 뽑은 인형이 동일할 경우 
                    if (!bucket.isEmpty() && bucket.peek() == doll) {
                        //인형 폭발!
                        bucket.pop();
                        answer += 2;
                    } else {
                        //인형이 뽑은 인형과 다른 경우
                        bucket.push(doll);
                    }
                }
            }
            return answer;
        }
    
    
        public static void main(String[] args) {
            int[][] a = {{0,0,0,0,0},{0,0,1,0,3}, {0,2,5,0,1}, {4,2,4,4,2}, {3,5,1,3,1}};
            int[] b = {1,5,3,5,1,2,1,4};
            solution(a, b);
        }
    }
    

    실패

    더보기

    [접근법]

    O(N^2) 시간 복잡도를 생각 안 해도 될만큼 작은 범위기 때문에 이중 for문을 사용해서 크레인 배열과 격자 배열에 각각 접근했다.

    격자 배열의 가장 최상단에 있는 인형을 선택한 후 빼내야하기 때문에 0이 아닌 인형을 찾고,

    check 변수를 통해 다른 인형을 선택하지 못하도록 lock을 건다.

    스택이 비어있는 경우는 짝을 맞출 수 없으므로 push하고, 있는 경우는 stack.peek을 통해 동일한 인형인지 확인한다.

    동일하다면 stack에 있던 인형을 제거하고 answer를 +2 한다.

     
    채점 결과
    정확성: 81.8
    합계: 81.8 / 100.0
    import java.util.*;
    
    public class Solution {
    
        /**
         * @param board 격자
         * @param moves 크레인 작동 위치
         * @return 터트려져 사라진 인형의 개수
         */
        public static int solution(int[][] board, int[] moves) {
            ArrayDeque<Integer> stack = new ArrayDeque<>();
    
            int answer = 0;
    
            //크레인 작동 동안 동작
            A: for (int i = 0; i < moves.length; i++) {
                //격자의 열 기준, 가장 위쪽에 있는 인형을 선택해야 한다.
                int doll = 0;
                boolean check = false;
                for (int[] arr : board) {
                    int tmp = arr[moves[i] - 1];
                    if (tmp != 0 && !check) {
                        doll = tmp;
                        arr[moves[i] - 1] = 0;
                        check = true;
                    }
                }
                //스택이 비어있다면 인형을 추가한다.
                if (stack.isEmpty()) {
                    stack.push(doll);
                    continue A;
                } else {
                    //stack 마지막 값과 동일한 인형이 아닌 경우
                    if (stack.peek() != doll) {
                        stack.push(doll);
                    } else { //동일한 인형인 경우
                        stack.pop();
                        answer += 2;
                    }
                }
            }
            return answer;
        }
    
        public static void main(String[] args) {
            int[][] a = {{0,0,0,0,0},{0,0,1,0,3}, {0,2,5,0,1}, {4,2,4,4,2}, {3,5,1,3,1}};
            int[] b = {1,5,3,5,1,2,1,4};
            solution(a, b);
        }
    }

    회고

    2차원 배열 -> stack 배열로 만드는 방법을 사용하면 경우 check 할 필요도 없고, 좀 더 쉽게 접근해서 풀 수 있는 문제였다 ㅠ.ㅠ

    비교하는 로직은 동일하게 작성했다.

    728x90
    반응형

    댓글