나를 기록하다
article thumbnail
Published 2023. 4. 7. 15:44
[JPA] JPA 모르는 개념 정리 Java/JPA
반응형

getter and setter

  • getter는 클래스의 멤버 변수의 값을 반환하는 메소드입니다. 즉, 객체가 가지고 있는 값을 외부에서 읽을 수 있게 해줍니다.
  • 반면에, setter는 멤버 변수의 값을 설정하는 메소드입니다. 이 메소드를 사용하여 외부에서 객체 내부의 값을 변경할 수 있습니다.

이러한 gettersetter를 통해 클래스 내부의 멤버 변수를 안전하게 관리하고, 캡슐화를 구현할 수 있습니다. 또한, 이를 이용하여 객체의 상태를 제어하고 유효성 검사 등을 수행할 수 있습니다.

따라서 gettersetter는 자바 프로그래밍에서 매우 중요한 역할을 수행하며, 객체지향 프로그래밍에서 캡슐화와 정보 은닉을 구현하는 데 필수적입니다.

  • 예를 들어, 학생 클래스를 만든다고 가정해봅시다. 이 클래스는 학생의 이름, 나이, 학번 등의 정보를 저장할 수 있습니다. 이 때, 멤버 변수들은 private으로 설정하여 클래스 외부에서 직접 접근하지 못하도록 합니다. 그리고, 이 멤버 변수들을 조작하기 위한 gettersetter 메소드를 구현합니다. 예를 들어, 학생 클래스에는 다음과 같은 gettersetter 메소드가 있을 수 있습니다.
public class Student {
    private String name;
    private int age;
    private String studentId;

    // Getter 메소드
    public String getName() {
        return this.name;
    }

    public int getAge() {
        return this.age;
    }

    public String getStudentId() {
        return this.studentId;
    }

    // Setter 메소드
    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    public void setStudentId(String studentId) {
        this.studentId = studentId;
    }
}

이제, 클래스 외부에서는 직접 학생의 이름, 나이, 학번을 변경할 수 없고, gettersetter 메소드를 통해 접근해야 합니다. 예를 들어, 학생 객체의 이름을 변경하려면 다음과 같이 setName() 메소드를 호출합니다.

Student student = new Student();
student.setName("홍길동");

이러한 gettersetter를 통해 클래스의 멤버 변수를 캡슐화하고, 외부에서의 접근을 제한하여 객체지향 프로그래밍의 장점인 정보 은닉과 보안을 유지할 수 있습니다.


DAO(Data Acess Object)

DAO (데이터 액세스 오브젝트)는 데이터베이스와 애플리케이션 간의 정보 교환을 담당하는 소프트웨어 패턴 중 하나입니다. 이 패턴은 데이터베이스에 저장된 데이터를 Java 객체로 변환하거나 Java 객체를 데이터베이스에 저장하기 위한 메서드를 제공합니다. DAO는 데이터베이스에 대한 접근 로직을 캡슐화하므로, 데이터베이스 변경 시 애플리케이션의 다른 부분에 영향을 미치지 않습니다.

일반적으로 DAO는 다음과 같은 기능을 제공합니다.

  1. 생성 (Create): 데이터베이스에 새로운 레코드를 생성하는 메서드를 제공합니다.
  1. 조회 (Read): 데이터베이스에서 데이터를 검색하고 해당 데이터를 Java 객체로 변환하는 메서드를 제공합니다.
  1. 업데이트 (Update): 데이터베이스의 기존 레코드를 수정하는 메서드를 제공합니다.
  1. 삭제 (Delete): 데이터베이스에서 레코드를 삭제하는 메서드를 제공합니다.

DAO를 사용하면 데이터베이스와의 상호 작용을 단순화하고, 코드 재사용성을 높이며, 유지 보수를 쉽게 할 수 있습니다. 이 패턴은 데이터베이스 구현에 독립적이므로, 애플리케이션 로직을 수정하지 않고도 다른 데이터베이스로 전환할 수 있습니다.

DAO 패턴을 사용하는 간단한 예제로, 사용자 정보를 저장하는 데이터베이스와 상호 작용하는 Java 애플리케이션을 만들어 봅시다. 사용자 정보에는 ID, 이름, 이메일이 포함됩니다.

먼저, 사용자 정보를 나타내는 Java 클래스를 만듭니다.

public class User {
    private int id;
    private String name;
    private String email;

    public User(int id, String name, String email) {
        this.id = id;
        this.name = name;
        this.email = email;
    }

    // getter와 setter 메서드 생략
}

다음으로, User 객체와 데이터베이스를 연결하는 UserDAO 인터페이스를 정의합니다.

public interface UserDAO {
    void createUser(User user);
    User getUserById(int id);
    List<User> getAllUsers();
    void updateUser(User user);
    void deleteUser(int id);
}

이제 UserDAO 인터페이스를 구현하는 클래스를 작성합니다. 여기서는 MySQL 데이터베이스를 사용한다고 가정하겠습니다.

public class MySQLUserDAO implements UserDAO {
    // 데이터베이스 연결 및 초기화 코드 생략

    @Override
    public void createUser(User user) {
        // 사용자를 데이터베이스에 저장하는 코드
    }

    @Override
    public User getUserById(int id) {
        // id를 사용하여 데이터베이스에서 사용자를 검색하고 User 객체로 변환하는 코드
    }

    @Override
    public List<User> getAllUsers() {
        // 데이터베이스의 모든 사용자를 검색하고 User 객체 목록으로 변환하는 코드
    }

    @Override
    public void updateUser(User user) {
        // 사용자 정보를 데이터베이스에 업데이트하는 코드
    }

    @Override
    public void deleteUser(int id) {
        // id를 사용하여 데이터베이스에서 사용자를 삭제하는 코드
    }
}

이제 애플리케이션에서 UserDAO를 사용하여 데이터베이스와 상호 작용할 수 있습니다.

public class UserService {
    private UserDAO userDAO;

    public UserService(UserDAO userDAO) {
        this.userDAO = userDAO;
    }

    public void addUser(String name, String email) {
        User user = new User(0, name, email);
        userDAO.createUser(user);
    }

    public User findUser(int id) {
        return userDAO.getUserById(id);
    }

    // 다른 메서드 생략
}

위 예제에서 UserDAO 인터페이스를 사용하면 데이터베이스 구현을 변경하더라도 UserService 클래스를 수정할 필요가 없습니다. 이렇게 DAO 패턴을 사용하면 코드의 유지 관리가 쉬워지고, 데이터베이스와의 상호 작용을 캡슐화할 수 있습니다.


개발 환경 관련 도구

maven

Maven은 Apache Foundation에서 개발한 자바 기반의 프로젝트 관리 및 빌드 도구입니다. Maven은 프로젝트 구조를 표준화하고, 라이브러리 종속성을 관리하며, 빌드, 테스트, 배포 등의 프로세스를 자동화합니다.

Maven은 pom.xml 파일을 사용하여 프로젝트 구성 및 의존성을 정의합니다. 이 파일에는 프로젝트 정보, 빌드 설정, 라이브러리 의존성 등이 포함됩니다.

Maven의 주요 특징

  1. 프로젝트 구조 표준화 Maven은 프로젝트의 소스 코드, 테스트 코드, 리소스 파일 등을 저장하는 폴더 구조를 표준화합니다. 이로 인해 개발자가 쉽게 프로젝트를 이해하고 유지보수할 수 있습니다.
  1. 라이브러리 의존성 관리 Maven은 프로젝트에서 사용하는 외부 라이브러리의 버전을 관리하고, 필요한 라이브러리를 자동으로 다운로드 및 설치합니다. 이를 통해 개발자가 직접 라이브러리를 관리할 필요가 없어집니다.
  1. 빌드 및 테스트 자동화 Maven은 프로젝트의 소스 코드를 컴파일, 테스트, 패키징, 배포 등의 작업을 자동으로 수행할 수 있습니다. 이를 통해 개발자는 빌드 및 테스트 과정에 대한 부담을 줄일 수 있습니다.
  1. 플러그인 시스템 Maven은 다양한 플러그인을 지원하여 개발자가 필요한 기능을 쉽게 추가할 수 있습니다. 이를 통해 빌드 및 배포 과정을 개발자의 요구에 맞게 커스터마이징할 수 있습니다.
  1. 저장소 시스템 Maven은 중앙 저장소를 통해 라이브러리를 관리하며, 필요한 라이브러리를 자동으로 다운로드 및 설치합니다. 개발자는 라이브러리를 수동으로 관리할 필요 없이, 프로젝트에 필요한 라이브러리를 사용할 수 있습니다.

hibernate

Hibernate는 자바 기반의 오픈 소스 객체 관계 매핑 (ORM) 프레임워크입니다. ORM은 객체 지향 프로그래밍 언어에서 사용되는 객체와 관계형 데이터베이스 시스템 간의 데이터를 매핑하는 기술입니다. Hibernate는 자바 객체와 데이터베이스 테이블 간의 매핑을 처리하여, 개발자가 SQL 쿼리를 직접 작성할 필요 없이 데이터베이스와 상호 작용할 수 있게 합니다.

Hibernate의 주요 특징은 다음과 같습니다:

  1. 객체 관계 매핑 Hibernate는 자바 객체와 데이터베이스 테이블 간의 매핑을 처리하며, 이를 통해 객체 지향 프로그래밍의 장점을 데이터베이스 작업에 적용할 수 있습니다.
  1. 데이터베이스 독립성 Hibernate는 다양한 데이터베이스 시스템과 호환됩니다. 개발자는 데이터베이스 시스템을 변경하더라도 Hibernate의 설정만 수정하면 되므로, 코드의 변경 없이 데이터베이스를 전환할 수 있습니다.
  1. 캐싱 Hibernate는 1차 캐시와 2차 캐시를 지원합니다. 1차 캐시는 세션 범위의 캐시로, 각각의 데이터베이스 세션에 대해 생성됩니다. 2차 캐시는 프로세스 범위의 캐시로, 여러 세션 간에 공유됩니다. 이를 통해 Hibernate는 데이터베이스 작업의 성능을 향상시킬 수 있습니다.
  1. 지연 로딩 Hibernate는 지연 로딩을 지원하여, 필요한 시점에만 관련 데이터를 로드할 수 있습니다. 이를 통해 애플리케이션의 성능을 향상시킬 수 있습니다.
  1. HQL (Hibernate Query Language) Hibernate는 SQL과 유사한 쿼리 언어인 HQL을 제공합니다. HQL을 사용하면 개발자는 객체 지향적인 방식으로 데이터베이스 작업을 수행할 수 있습니다.

최근에는 Hibernate 외에도 JPA (Java Persistence API)라는 자바 표준 ORM 기술이 있으며, Hibernate는 JPA의 구현체 중 하나로 사용되곤 합니다. 이를 통해 개발자는 다른 ORM 프레임워크로 전환할 때 일관된 방식으로 코드를 작성할 수 있습니다.

캐싱(caching)이란?

캐싱(caching)은 컴퓨터 과학에서 자주 사용되는 데이터나 결과를 빠르게 검색할 수 있도록 일시적으로 저장하는 기술입니다. 캐시(cache)고속의 데이터 스토리지로, 요청에 대한 결과를 저장하고, 이후 동일한 요청이 발생하면 빠르게 결과를 제공하기 위해 사용됩니다. 캐싱을 통해 시스템의 성능을 향상시키고, 응답 시간을 줄이며, 서버나 데이터베이스에 대한 부하를 줄일 수 있습니다.

캐싱은 다양한 분야에서 활용되며, 웹, 애플리케이션, 데이터베이스 등에서 사용됩니다. 웹 캐싱의 경우, 웹 페이지나 이미지 등의 자원을 사용자의 브라우저나 프록시 서버에 저장하여, 이후 동일한 요청이 발생할 때 빠르게 자원을 제공할 수 있습니다. 애플리케이션 캐싱의 경우, 자주 사용되는 데이터나 계산 결과를 메모리에 저장하여, 이후 동일한 요청이 발생할 때 빠르게 결과를 제공할 수 있습니다. 데이터베이스 캐싱의 경우, 자주 사용되는 쿼리 결과를 메모리에 저장하여, 이후 동일한 쿼리가 발생할 때 빠르게 결과를 제공할 수 있습니다.

캐싱에는 몇 가지 주의할 점이 있습니다. 먼저, 캐싱된 데이터가 원본 데이터와 동기화되지 않으면 오래된 결과를 제공할 수 있습니다. 따라서 캐시 만료 시간을 설정하거나 데이터가 변경될 때 캐시를 갱신하는 방법을 사용해야 합니다. 또한, 캐싱은 추가적인 메모리를 사용하므로, 저장 공간과 성능 간의 균형을 고려하여 적절한 캐싱 전략을 선택해야 합니다.


라이브러리(library)

라이브러리(library)는 프로그래밍에서 재사용 가능한 코드 조각이나 기능들을 모아놓은 모음집입니다. 라이브러리는 개발자들이 흔히 사용하는 기능들을 쉽게 활용할 수 있도록 미리 구현된 코드를 제공하므로, 개발자는 해당 기능을 처음부터 직접 구현할 필요 없이 라이브러리를 가져와 사용할 수 있습니다. 이로 인해 개발 시간을 절약하고, 코드의 안정성과 효율성을 높일 수 있습니다.

라이브러리는 다양한 프로그래밍 언어에서 사용되며, 특정 언어로 작성된 라이브러리는 해당 언어의 프로젝트에서 사용할 수 있습니다.

라이브러리의 형태

  1. 정적 라이브러리 정적 라이브러리는 프로그램이 컴파일되는 동안 프로그램에 포함되는 코드입니다. 이 경우, 라이브러리의 코드가 프로그램의 실행 파일에 직접 포함되므로, 별도의 라이브러리 파일이 필요하지 않습니다.

    예시)

    C 언어에서 사용되는 표준 수학 라이브러리인 'libm'은 정적 라이브러리로 사용될 수 있습니다. 이 라이브러리는 수학 관련 함수들을 제공하며, 사용자가 프로그램에서 해당 함수들을 사용하려면 컴파일 시 libm 라이브러리를 링크해야 합니다. 정적 라이브러리는 일반적으로 '.a' 확장자를 가진 파일로 제공됩니다. 예를 들어, 'libm.a' 파일이 정적 라이브러리입니다.

  1. 동적 라이브러리 동적 라이브러리는 프로그램이 실행되는 동안 로드되어 사용되는 코드입니다. 이 경우, 라이브러리의 코드가 별도의 파일로 존재하며, 프로그램 실행 시 필요한 라이브러리를 시스템에서 찾아 로드합니다. 동적 라이브러리는 공유 라이브러리라고도 불리며, 여러 프로그램이 동일한 라이브러리를 공유하여 메모리 사용량을 줄일 수 있습니다.

    예시)

    C 언어에서 널리 사용되는 문자열 처리 라이브러리인 'libpcre'는 동적 라이브러리로 사용될 수 있습니다. 이 라이브러리는 정규 표현식을 사용한 문자열 처리 기능을 제공하며, 사용자가 프로그램에서 해당 기능을 사용하려면 런타임에 libpcre 라이브러리를 로드해야 합니다. 동적 라이브러리는 일반적으로 '.so' (리눅스), '.dll' (윈도우) 또는 '.dylib' (맥OS) 확장자를 가진 파일로 제공됩니다. 예를 들어, 'libpcre.so' 파일이 동적 라이브러리입니다.

라이브러리의 기능

또한, 라이브러리는 다양한 영역에 대한 기능을 제공합니다. 예를 들어, 네트워크 통신, 데이터베이스 연결, 그래픽 처리, 암호화, 파일 입출력 등 다양한 기능에 대한 라이브러리가 존재합니다. 개발자는 필요한 기능에 맞는 라이브러리를 선택하여 프로젝트에 포함시켜 사용할 수 있습니다.

라이브러리의 장단점

  1. 정적 라이브러리 장점 : 프로그램 실행 파일에 직접 포함되므로, 별도의 라이브러리 파일이 필요하지 않고, 프로그램 실행에 필요한 모든 코드가 하나의 파일에 포함되어 배포가 간편합니다. 단점 : 하지만 정적 라이브러리를 사용하면 프로그램의 크기가 커지고, 여러 프로그램이 동일한 라이브러리를 사용할 때 메모리 사용량이 증가할 수 있습니다.
  1. 동적 라이브러리

    장점 : 런타임에 로드되므로, 프로그램 실행 파일의 크기가 작아지고, 여러 프로그램이 동일한 라이브러리를 공유하여 메모리 사용량을 줄일 수 있습니다. 또한, 동적 라이브러리를 업데이트하면 사용 중인 모든 프로그램이 동일한 업데이트된 라이브러리를 사용할 수 있습니다. 단점 : 런타임에 로드되므로, 라이브러리 파일이 없거나 호환되지 않는 경우 프로그램이 실행되지 않을 수 있습니다. 동적 라이브러리를 사용하면 프로그램 실행에 필요한 라이브러리 파일을 함께 배포해야 하며, 여러 버전의 라이브러리가 공존하는 경우 버전 충돌 문제가 발생할 수 있습니다.

개발자는 정적 라이브러리와 동적 라이브러리의 장단점을 고려하여 프로젝트의 요구 사항과 상황에 맞는 라이브러리 유형을 선택할 수 있습니다. 예를 들어, 프로그램의 크기와 메모리 사용량이 크게 중요하지 않은 경우 정적 라이브러리를 사용할 수 있으며, 여러 프로그램이 동일한 라이브러리를 공유하거나 라이브러리를 쉽게 업데이트해야 하는 경우 동적 라이브러리를 사용할 수 있습니다.

Java의 라이브러리

Java에서는 정적 라이브러리와 동적 라이브러리의 개념이 약간 다릅니다. Java는 JVM(Java Virtual Machine) 위에서 실행되기 때문에, Java 라이브러리는 JAR(Java Archive) 파일 형태로 제공됩니다. JAR 파일은 Java 클래스 파일과 관련 리소스를 압축하여 패키징한 것입니다. Java에서 JAR 파일은 정적 라이브러리와 동적 라이브러리의 역할을 모두 수행할 수 있습니다.

예를 들어, Java에서 널리 사용되는 라이브러리들은 다음과 같습니다.

  1. Apache Commons: Apache Commons는 여러 하위 프로젝트로 구성된 Java 라이브러리 모음으로, 다양한 일반적인 작업을 처리하는 데 사용되는 코드 조각과 유틸리티 클래스를 제공합니다. Apache Commons 라이브러리는 JAR 파일로 제공되며, 프로젝트에 필요한 라이브러리를 가져와 사용할 수 있습니다.
  1. Gson: Gson은 Google에서 개발한 JSON 처리를 위한 Java 라이브러리입니다. Gson을 사용하면 Java 객체를 JSON으로 변환하거나, JSON을 Java 객체로 변환하는 작업을 간단하게 수행할 수 있습니다. Gson 라이브러리는 JAR 파일로 제공되며, 프로젝트에 추가하여 사용할 수 있습니다.

Java에서 JAR 파일을 사용하려면 프로젝트의 클래스 경로에 추가해야 합니다. 클래스 경로에 추가된 JAR 파일은 프로젝트에서 필요한 클래스와 리소스를 로드하는 데 사용됩니다. Java에서는 클래스 로더가 런타임에 필요한 클래스를 찾아 로드하므로, JAR 파일은 동적 라이브러리와 유사한 역할을 수행합니다. 그러나 JAR 파일은 정적 라이브러리처럼 컴파일 시간에 프로젝트에 포함되므로, 둘 다의 특성을 갖고 있다고 볼 수 있습니다.


JPA 개발 시작

클래스와 테이블 매핑

어노테이션

  1. @Entity

    이 클래스를 테이블과 매핑한다고 JPA에게 알림. @Entity가 사용된 클래스를 엔티티 클래스라고 함.

  1. @Table

    엔티티 클래스에 매핑할 테이블 정보를 알림. 이 어노테이션을 생략 시 클래스 이름(엔티티 이름)을 테이블 이름으로 매핑함.

  1. @Id

    엔티티 클래스의 필드를 테이블의 기본 키(Primary key)에 매핑. @Id가 사용된 필드를 식별자 필드라 한다.

  1. @Column

    필드를 컬럼에 매핑.

  1. 매핑 정보가 없는 필드

    필드명을 사용해서 컬럼명으로 매핑.

📝
참고 JPA 어노테이션의 패키지는 javax.persistence이다.

데이터베이스 방언

JPA는 특정 데이터베이스에 종속적이지 않은 기술이기에 다른 데이터베이스로 손쉽게 교체 가능

각 데이터베이스가 제공하는 SQL 문법과 함수가 조금씩 다르다는 문제점 존재

  • 예시
    1. 데이터 타입 : 가변 문자 타입으로 MySQL은 VARCHAR, 오라클은 VARCHAR2
    1. 다른 함수명 : 문자열을 자르는 함수로 SQL 표준은 SUBSTRING(), 오라클은 SUBSTR()
    1. 페이징 처리 : MySQL은 LIMIT, 오라클은 ROWNUM

이처럼 SQL 표준을 지키지 않거나 특정 데이터베이스만의 고유한 기능을 JPA에서는 방언(Dialect)이라 부름

하이버네이트를 포함한 대부분의 JPA 구현체들은 이런 문제를 해결하려고 다양한 데이터베이스 방언 클래스 제공

하이버네이트가 제공하는 데이터베이스 방언

  • H2 : org.hibernate.dialect.H2Dialect
  • 오라클 10g : org.hibernate.dialect.Oracle10gDialect
  • MySQL : org.hibernate.dialect.MySQL5InnoDBDialect


애플리케이션 개발

엔티티 매니저, 엔티티 매니저 팩토리

  • Persistence 클래스를 사용해서 엔티티 매니저 팩토리 생성
EnitityMangerFactory emf = Persistence.createEntityManagerFactory(”jpabook”);

이렇게 하면 META-INF/persistence.xml에서 이름이 jpabook인 영속성 유닛(persistence-unit)을 찾아서 엔티티 매니저 팩토리를 생성한다. persistence.xml의 설정 정보를 읽어서 JPA를 동작시키기 위한 기반 객체 생성 및 JPA 구현체에 따라서 데이터베이스 커넥션 풀도 생성 → 엔티티 매니저 팩토리르 생성하는 비용이 매우 큼

엔티티 매니저 팩토리는 애플리케이션 전체에서 딱 한 번만 생성하고 공유해서 사용해야 함

  • 엔티티 매니저 생성
EntityManger em = emf.createEntityManager();

JPA의 기능 대부분은 엔티티 매니저가 제공한다. 엔티티 매니저로 엔티티를 데이터베이스에 등록(Create)/조회(Read)/수정(Update)/삭제(Delete) 가능

엔티티 매니저는 데이터베이스 커넥션과 밀접한 관계가 있으므로 스레드간에 공유하거나 재사용하면 안된다.

  • 종료

사용이 끝난 엔티티 매니저는 반드시 종료

em.close(); // 엔티티 매니저 종료

애플리케이션을 종료할 때 엔티티 매니저 팩토리도 반드시 종료

emf.close(); // 엔티티 매니저 팩토리 종료

트랜잭션 관리

JPA를 사용하면 항상 트랜잭션 안에서 데이터를 변경해야 함. 트랜잭션 없이 데이터를 변경하면 예외 발생.

트랜잭션을 시작하려면 em에서 트랜잭션 API를 받아와야 함

EntityTransaction tx = em.getTransaction(); // 트랜잭션API
try {

		tx.begin(); // 트랜잭션 시작
		login(em);  // 비즈니스 로직 실행
		tx.commit();// 트랜잭션 커밋

} catch (Exception e) {
		tx.rollback(); // 예외 발생 시 트랜잭션 롤백
}

트랜잭션 API를 사용해서 비즈니스 로직이 정상 동작하면 트랜잭션을 커밋하고 예외가 발생하면 트랜잭션을 롤백

비즈니스 로직

public static void login(EntityManger em) {

		String id = "id1";
		Member member = new Member();
		member.setId(id);
		member.setUsername("창호");
		member.setAge(2);

		// 등록
		em.persist(member);

		// 수정
		member.setAge(20);

		// 한 건 조회
		Member findMember = em.find(Member.class, id);
		System.out.println("findMember = " + findmember.getUsername() +
                       ", age = " + findMember.getAge());

		// 목록 조회
		List<Member> members =
         em.createQuery("select m from Member m", Member.class).getResultList();
		System.out.println("member.size = " + members.size());

		// 삭제
		em.remove(member);
// 결과
findMember = 창호, age = 20
members.size = 1

  • 등록
    		String id = "id1";
    		Member member = new Member();
    		member.setId(id);
    		member.setUsername("창호");
    		member.setAge(2);
    
    		// 등록
    		em.persist(member);

    엔티티를 저장하려면 empersist() 메소드에 저장할 엔티티를 넘겨주면 된다.

    예제에서 JPA는 회원 엔티티의 매핑 정보(어노테이션)를 분석해서 다음과 같은 SQL을 만들어 DB에 전달함

    INSERT INTO MEMBER (ID, NAME, AGE) VALUES ('id1', '창호', 2)

  • 수정
    		// 수정
    		member.setAge(20);

    JPA는 어떤 엔티티가 변경되었는지 추적하는 기능을 갖추고 있다.

    member.setAge(20)처럼 엔티티의 값만 변경하면 UPDATE SQL을 생성해서 데이터베이스 값을 변경

    UPDATE MEMBER
    		SET AGE=20, NAME='창호'
    WHERE ID='id1'

  • 삭제
    		// 삭제
    		em.remove(member);

    JPA는 다음 DELETE SQL을 생성해서 실행함

    DELETE FROM MEMBER WHERE ID = 'id1'

  • 한 건 조회
    		// 한 건 조회
    		Member findMember = em.find(Member.class, id);

    find() 메소드는 조회할 엔티티 타입과 @Id로 데이터베이스 테이블의 기본 키와 매핑한 식별자 값으로 엔티티 하나를 조회하는 가장 단순한 조회 메소드이다. 메소드 호출 시 다음 SELECT SQL을 생성해서 데이터베이스에 결과를 조회하고 조회한 결과 값으로 엔티티를 생성해서 반환한다.

    SELECT * FROM MEMBER WHERE ID='id1'

JPQL(Java Persistence Query Language)

		// 목록 조회
TypedQuery<Member> query =
		em.createQuery("select m from Member m", Member.class);
List<Member> members = query.getResultList();

JPA를 사용하면 개발자는 엔티티 객체를 중심으로 개발하고 데이터베이스에 대한 처리는 JPA에 맡겨야 함.

등록, 수정, 삭제, 한건 조회는 SQL 사용하지 않음. 문제는 검색 쿼리

JPA는 엔터티 객체 중심으로 개발하으몰 검색을 할 때도 테이블이 아닌 엔티티 객체를 대상으로 검색해야 함.

→ 테이블이 아닌 객체를 대상으로 검색하려면 데이터베이스의 모든 데이터를 애플리케이션으로 불러와서 엔티티 객체로 변경한 다음 검색해야 함 → 사실상 불가능 → JPQL이라는 언어로 이 문제를 해결

  • JPQL은 SQL을 추상화한 객체지향 쿼리 언어로 SQL과 문법이 거의 유사 SELECT, FROM, WHERE, GROUP BY, HAVING, JOIN 등 사용 가능
  • 차이점 JPQL : 엔티티 객체를 대상으로 쿼리 → 클래스필드를 대상 SQL : 데이터베이스 테이블을 대상으로 쿼리
select m from Member m

→ 이것이 JPQL이다. from Member은 회원 엔티티 객체를 뜻함(MEMBER 테이블 아님)

JPQL은 데이터베이스 테이블을 전혀 알지 못함.

JPQL을 사용하려면 em.createQuery(JPQL, 반환 타입) 메소드를 실행시켜서 쿼리 객체를 생성한 후 쿼리 객체의 getResultList() 메소드를 호출하면 된다.

JPA는 JPQL을 분석해서 적절한 SQL을 만들어 데이터베이스에서 데이터를 조회한다.

SELECT M.ID, M.NAME, M.AGE FROM MEMBER M
📝
JPQL은 대소문자를 명확히 구분, SQL은 관례상 대소문자 구분 X


영속성 관리

엔티티 매니저 팩토리와 엔티티 매니저

엔티티 매니저 팩토리 → 여러 스레드가 동시에 접근해도 안전 → 다른 스레드 간에 공유 가능

엔티티 매니저 → 여러 스레드가 동시에 접근하면 동시성 문제 발생 → 스레드 간에 공유 X

  • 하나의 EntityMangerFactory에서 다수의 엔티티 매니저를 생성했다.
  • EntityManager1은 아직 데이터베이스 커넥션을 사용하지 않는다.

    → 엔티티 매니저는 데이터베이스 연결이 꼭 필요한 시점까지 커넥션을 얻지 않는다.

    ex) 트랜잭션을 시작할 때 커넥션을 획득한다.

  • EntityManager2는 커넥션 사용 중 → 보통 트랜잭션을 시작할 때 커넥션 획득
    • 하이버네이트를 포함한 JPA 구현체들은 EntityManagerFactory를 생성할 때 커넥션 풀도 만듬 → J2SE 환경에서 사용하는 방법, JPA를 J2EE 환경(스프링 프레임워크 포함)에서 사용하면 해당 컨테이너가 제공하는 데이터소스를 사용

영속성 컨텍스트(persistence context)란?

엔티티를 영구 저장하는 환경, 엔티티 매니저로 엔티티를 저장하거나 조회하면 엔티티 매니저는 영속성 컨텍스트에 엔티티를 보관하고 관리한다.

em.persist(memeber);

persist() 메소드는 엔티티 매니저를 사용해서 회원 엔티티를 영속성 컨텍스트에 저장

영속성 컨텍스트는 엔티티 매니저를 생성할 때 하나 만들어짐. 그리고 엔티티 매니저를 통해서 영속성 컨텍스트에 접근할 수 있고, 영속성 컨텍스트를 관리할 수 있음.

📝
여러 엔티티 매니저가 같은 영속성 컨텍스트에 접근할 수도 있음


엔티티의 생명주기

엔티티의 4가지 상태

  1. 비영속(new/transient) : 영속성 컨텍스트와 전혀 관계가 없는 상태
  1. 영속(managed) : 영속성 컨텍스트에 저장된 상태
  1. 준영속(detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태
  1. 삭제(removed) : 삭제된 상태

비영속

엔티티 객체를 생성했으나 저장하지 않은 상태. 영속성 컨텍스트나 데이터베이스와는 전혀 관련이 없음.

// 객체를 생성한 상태(비영속)
Member mmember = new Member();
member.setId("member1");
member.setUsername("회원1");

em.persist() 호출 전, 비영속 상태

영속

엔티티 매니저를 통해 엔티티를 영속성 컨텍스트에 저장 → 영속성 컨텍스트가 관리하는 엔티티를 영속 상태라 함.

em.find()나 JPQL을 사용해서 조회한 엔티티도 영속성 컨텍스트가 관리하는 영속 상태

// 객체를 저장한 상태(영속)
em.persist(member);

em.persist() 호출 후, 영속 상태

준영속

영속성 컨텍스트가 관리하던 영속 상태의 엔티티를 영속성 컨텍스트가 관리하지 않으면 준영속 상태가 된다.

특정 엔티티를 준영속 상태로 만들려면 em.detach()를 호출하면 된다.

em.close()를 호출해서 영속성 컨텍스트를 닫거나 em.clear()를 호출해서 영속성 컨텍스트를 초기화해도 영속성 컨텍스트가 관리하던 영속 상태의 엔티티는 준영속 상태가 됨.

// 회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
em.detach(member);

삭제

엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제

// 객체를 삭제한 상태(삭제)
em.remove(member);


영속성 컨텍스트의 특징

  1. 영속성 컨텍스트와 식별자 값 영속 상태는 식별자 값이 반드시 있어야 한다. 없으면 예외 발생
  1. 영속성 컨텍스트와 데이터베이스 저장 커밋하는 순간 영속성 컨텍스트에 새로 저장된 엔티티를 데이터베이스에 반영한다 - 플러시(flush)
  1. 영속성 컨텍스트가 엔티티를 관리할 때의 장점
    1. 1차 캐시
    1. 동일성 보장
    1. 트랜잭션을 지원하는 쓰기 지연
    1. 변경 감지
    1. 지연 로딩

엔티티 조회

영속성 컨텍스트는 내부에 캐시를 가지고 있는데 이것을 1차 캐시라 한다.

영속 상태의 엔티티는 모두 이곳에 저장 → 영속성 컨텍스트 내부에 Map이 하나 있음, 키는 @Id로 매핑한 식별자값은 엔티티 인스턴스

// 엔티티를 생성한 상태(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

// 엔티티를 영속
em.persist(member);

영속성 컨텍스트 1차 캐시

1차 캐시의 키식별자 값, 식별자 값은 데이터베이스 기본 키와 매핑되어있음. → 영속성 컨텍스트에 데이터를 저장하고 조회하는 모든 기준은 데이터베이스 기본 키 값이다.

  • 1차 캐시에 대하여

    1차 캐시는 JPA에서 매우 중요한 개념입니다.1차 캐시는 영속성 컨텍스트에 속한 데이터를 임시 저장하는 캐시로, 트랜잭션 범위 내에서 데이터를 일시적으로 저장합니다. 이러한 1차 캐시는 데이터베이스의 기본 키와 연결되어 있으며, 이 키를 사용하여 엔티티를 참조하거나 검색할 수 있습니다.

    예를 들어, 데이터베이스에 '사용자'라는 테이블이 있고, 이 테이블에는 각 사용자에 대한 고유한 기본 키가 있습니다. JPA를 사용하여 사용자 정보를 조회하려면 다음과 같이 코드를 작성할 수 있습니다.

    EntityManager em = entityManagerFactory.createEntityManager();
    EntityTransaction tx = em.getTransaction();
    
    tx.begin();
    User user = em.find(User.class, 1L); // 1L은 기본 키 값입니다.
    tx.commit();

    위 코드에서 em.find() 메소드는 영속성 컨텍스트에서 기본 키 값(여기서는 1L)을 사용하여 해당 사용자를 찾습니다. 만약 이 사용자가 영속성 컨텍스트의 1차 캐시에 이미 존재하면, 캐시에서 해당 사용자를 반환합니다. 만약 캐시에 없다면 데이터베이스에서 사용자를 조회하고, 조회한 사용자를 1차 캐시에 저장한 뒤 반환합니다.

    이처럼 1차 캐시는 데이터베이스 기본 키를 사용하여 엔티티를 저장하고 검색하는데 사용되며, 영속성 컨텍스트 범위 내에서 일시적으로 데이터를 저장해두어 성능 향상 및 데이터 일관성을 제공합니다.

  • 엔티티 조회
    Member member = em.find(Member.class, "member1");

    find() 메소드 첫 번째 파라미터 → 엔티티 클래스의 타입

    find() 메소드 두 번째 파라미터 → 조회할 엔티티의 식별자값

    // EntityManager.find() 메소드 정의
    public <T> T find(class<T> entityClass, Object primaryKey);

    em.find()를 호출하면 먼저 1차 캐시에서 엔티티를 찾고 만약 찾는 엔티티가 1차 캐시에 없으면 데이터베이스에서 조회함.

  • 1차 캐시에서 조회

    em.find()를 호출하면 우선 1차 캐시에서 식별자 값으로 엔티티를 찾음. 만약 찾는 엔티티가 있으면 데이터베이스에서 조회하지 않고 메모리에 있는 1차 캐시에서 엔티티 조회

    Member member = new Member();
    member.setId("member1");
    member.setUsername("회원1");
    
    // 1차 캐시에 저장됨
    em.persist(member);
    
    // 1차 캐시에서 조회
    Member findMember = em.find(Member.class, "member1");

  • 데이터베이스에서 조회

    em.find()를 호출했는데 엔티티가 1차 캐시에 없으면 엔티티 매니저는 데이터베이스를 조회하여 엔티티 생성. 1차 캐시에 저장 후 영속 상태의 엔티티를 반환

    Member findMember2 = em.find(Member.class, "member2");
    1. em.find(Member.class, “member2”)를 실행
    1. member2가 1차 캐시에 없으므로 데이터베이스에서 조회
    1. 조회한 데이터로 member2 엔티티를 생성해서 1차 캐시에 저장(영속 상태)
    1. 조회한 엔티티를 반환

  • 영속 엔티티의 동일성 보장
    Member a = em.find(Member.class, "member1");
    Member b = em.find(Member.class, "member1");
    
    System.out.println(a == b); // 동일성 비교

    둘은 같은 인스턴스, 결과는 참

    영속성 컨텍스트는 성능상 이점과 엔티티의 동일성 보장

📝
동일성과 동등성 동일성(identity) : 실제 인스턴스가 같다. 따라서 참조 값을 비교하는 == 비교의 값이 같다. 동등성(equality) : 실제 인스턴스는 다를 수 있지만 인스턴스가 가지고 있는 값이 같다. 자바에서 동등성 비교는 equals() 메소드를 구현해야 함.

  • 엔티티 등록
    EntityManager em = emf.createEntityManager();
    EntityTransaction transaction = em.getTransaction();
    // 엔티티 매니저는 데이터 변경 시 트랜잭션을 시작해야 함.
    transaction.begin(); // 트랜잭션 시작
    
    em.persist(memberA);
    em.persist(memberB);
    // 여기까지 INSERT SQL을 데이터베이스에 보내지 않음
    
    // 커밋하는 순간에 데이터베이스에 INSERT SQL을 보냄
    transaction.commit(); // [트랜잭션] 커밋

  • 쓰기 지연(transactional write-behind)

    INSERT SQL을 모아두었다가 트랜잭션을 커밋할 때 모아둔 쿼리를 데이터베이스에 보내는 것

  • 변경감지
    EntityManager em = emf.createEntityManager();
    EntityTransaction transaction = em.getTransaction();
    transaction.begin(); // 트랜잭션 시작
    
    // 영속 엔티티 조회
    Member memberA = em.find(Member.class, "memberA");
    
    // 영속 엔티티 데이터 수정
    memberA.setUsername("hi");
    memberA.setAge(10);
    
    //em.update(member);
    
    transaction.commit(); // 트랜잭션 커밋
    1. 트랜잭션을 커밋 → 엔티티 매니저 내부에서 먼저 플러시(flush()) 호출
    1. 엔티티와 스냅샷을 비교해서 변경된 엔티티 찾음 스냅샷 : JPA는 엔티티를 영속성 컨텍스트에 보관할 때 최초 상태를 복사해서 저장함
    1. 변경된 엔티티가 있으면 수정 쿼리 생성해서 쓰기 지연 SQL 저장소에 보냄
    1. 쓰기 지연 저장소의 SQL을 데이터베이스에 보냄
    1. 데이터베이스 트랜잭션을 커밋

    → 변경 감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용됨.

  • JPA의 기본 전략은 엔티티의 모든 필드를 업데이트한다. 이유?
    1. 모든 필드 사용 시 수정 쿼리 항상 같음 → 애플리케이션 로딩 시점에 수정 쿼리 미리 생성 후 재사용 가능
    1. 데이터베이스에 동일한 쿼리를 보내면 데이터베이스는 이전에 한 번 파싱된 쿼리를 재사용 가능
    • 파싱이란?

      파싱이란, 주어진 텍스트나 데이터를 구문 분석하여 의미있는 정보로 변환하는 과정을 말합니다. 컴퓨터 프로그래밍에서 이 과정은 주로 프로그램 코드, 데이터 파일, 웹 페이지 등에서 구조화된 정보를 추출하고 이해하는 데 사용됩니다. 파싱을 통해 프로그램은 데이터를 처리하고, 분석하며, 저장할 수 있게 됩니다. 자바 언어에서는 java.util.Scanner, java.io.BufferedReader, javax.xml.parsers 등의 내장 라이브러리를 사용하여 파싱 작업을 수행할 수 있습니다.

  • 필드가 많거나 저장되는 내용이 너무 클 때
    @Entity
    @org.hibernate.annotations.DynamicUpdate
    @Table(name = "Member")
    public class Member {...}

    @org.hibernate.annotations.DynamicUpdate → 수정된 데이터만 동적으로 UPDATE SQL 생성

    (컬럼이 30개가 넘으면 DynamicUpdate를 사용한 동적 수정 쿼리가 더 빠를 가능성 높음 → 한 테이블에 컬럼이 30개 이상 된다는 것은 테이블 설계상 책임이 적절히 분리되지 않았을 가능성이 높음)

엔티티 삭제

Member memberA = em.find(Member.class, "memberA"); // 삭제 대상 엔티티 조회
em.remove(memberA); // 엔티티 삭제

em.remove()에 삭제 대상 엔티티를 넘겨주면 엔티티 삭제 → 엔티티를 즉시 삭제하는 것이 아닌 지연 SQL 저장소에 등록 → 트랜잭션을 커밋해서 플러시를 호출하면 실제 데이터베이스에 삭제 쿼리를 전달

em.remove(memberA)를 호출하는 순간 memberA는 영속성 컨텍스트에서 제거됨. → 삭제된 엔티티는 재사용하지 말고 자연스럽게 가비지 컬레션의 대상이 되도록 하는 것이 좋음

플러시

플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영한다.

  • 영속성 컨텍스트를 플러시하는 방법
    1. em.flush() 직접 호출 테스트나 다른 프레임워크와 JPA를 함께 사용할 때 외에 거의 사용 X
    1. 트랜잭션 커밋 시 플러시가 자동 호출 JPA에서는 트랜잭션을 커밋할 때 플러시를 자동으로 호출
    1. JPQL 쿼리 실행 시 플러시가 자동 호출 예시)
      em.persist(memberA);
      em.persist(memberB);
      em.persist(memberC);
      
      // 중간에 JPQL 실행
      query = em.createQuery("select m from Member m", Member.class);
      List<Member> members = query.getResultList();
      1. em.persist() 호출하여 엔티티들을 영속 상태로 만들었음
      1. 엔티티들은 영속성 컨텍스트엔 존재, 데이터베이스에는 반영 X 상태
      1. 이때 JPQL을 실행하면? → SQL로 변환되어 데이터베이스에서 엔티티 조회
      1. But 엔티티들은 아직 데이터베이스에 없으므로 쿼리 결과로 조회 X
      1. 따라서 쿼리 실행 직전에 영속성 컨텍스트를 플러시해서 변경 내용을 데이터베이스에 반영해야 함.
      1. JPA는 이런 문제 예방하기 위해 JPQL 실행 시 플러시 자동 호출

        (식별자 기준으로 조회하는 find() 메소드를 호출할 때는 플러시 실행 X)

플러시 모드 옵션

  • FlushModeType.AUTO : 커밋이나 쿼리를 실행할 때 플러시(기본값)
  • FlushModeType.COMMIT: 커밋할 때만 플러시

📝
플러시라는 이름으로 인해 영속성 컨텍스트에 보관된 엔티티를 지운다고 생각하면 안 된다. 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 것이 플러시이다. 데이터 베이스와 동기화를 최대한 늦추는 것이 가능한 이유는 트랜잭션이라는 작업 단위가 있기 때문! 트랜잭션 커밋 직전에만 변경 내용을 데이터베이스에 보내 동기화하면 된다.

준영속

영속 상태의 엔티티가 영속성 컨텍스트에서 분리(detached)된 것을 준영속 상태라 함.

준영속 상태의 엔티티는 영속성 컨텍스트가 제공하는 기능 사용 불가

  • 준영속 상태로 만드는 방법 3가지
    1. em.detach(entity) : 특정 엔티티만 준영속 상태로 전환
    public void testDetached() {
    		...
    		// 회원 엔티티 생성, 비영속 상태
    		Member member = new Member();
    		member.setId("memberA");
    		member.setUsername("회원A");
    
    		// 회원 엔티티 영속 상태
    		em.persist(member);
    
    		// 회원 엔티티를 영속성 컨텍스트에서 분리, 준영속 상태
    		em.detach(member);
    
    		transaction.commit(); // 트랜잭션 커밋
    }

    1. em.clear() : 영속성 컨텍스트를 완전히 초기화
    //엔티티 조회, 영속 상태
    Member member = em.find(Member.class, "memberA");
    
    em.clear(); // 영속성 컨텍스트 초기화
    
    // 준영속 상태
    member.setUsername("changeName"); // 준영속 상태 -> 이름 변경해도 데이터베이스에 반영 X

    1. em.close() : 영속성 컨텍스트를 종료
    public void closeEntityManager() {
    
    		EntityManagerFactory emf =
    				Persistence.createEntityManagerFactory("jpabook");
    
    		EntityManager em = emf.createEntityManager();
    		EntityTransaction transaction = em.getTransaction;
    
    		transaction.begin(); // 트랜잭션 시작
    
    		Member memberA = em.find(Member.class, "memberA");
    		Member memberB = em.find(Member.class, "memberB");
    
    		transaction.commit(); // 트랜잭션 커밋
    
    		em.close(); // 영속성 컨텍스트 닫기(종료)

📝
영속 상태의 엔티티는 주로 영속성 컨텍스트가 종료되면서 준영속 상태가 된다. 개발자가 직접 준영속 상태로 만드는 일은 드물다.

준영속 상태의 특징

  1. 거의 비영속 상태에 가깝다 1차 캐시, 쓰기 지연, 변경 감지, 지연 로딩을 포함한 영속성 컨텍스트가 제공하는 기능 동작 X
  1. 식별자 값을 가지고 있다 비영속 상태는 식별자 값이 없을 수도 있지만 준영속 상태는 이미 한 번 영속 상태였으므로 반드시 식별자 값을 가지고 있다.
  1. 지연 로딩을 할 수 없다 지연 로딩은 실제 객체 대신 프록시 객체를 로딩해두고 해당 객체를 실제 사용할 때 영속성 컨텍스트를 통해 데이터를 불러오는 방법 → 준영속 상태에서는 지연 로딩 시 문제 발생

병합 : merge()

merge() 메소드는 준영속 상태의 엔티티를 받아서 그 정보로 새로운 영속 상태의 엔티티를 반환

public <T> T merge(T entity); // merge() 메소드 정의
Member mergeMember = em.merge(member); // merge() 사용 예

  • 준영속 병합
    public class ExamMergeMain {
    
    		static EntityManagerFactory emf =
    				Persistence.createEntityManagerFactory("jpabook");
    
    		public static void main(String args[]) {
    
    				Member member = createMember("memberA", "회원1"); // 1번
    
    				member.setUsername("회원명변경"); // 2번. 준영속 상태에서 변경
    
    				mergeMember(member); // 3번
    		}
    
    		static Member createMember(String id, String username) {
    				//== 영속성 컨텍스트1 시작 ==//
    				EntityManager em1 = emf.createEntityManager();
    				EntityTransaction tx1 = em1.getTransaction();
    				tx1.begin();
    
    				Member member = new Member(); 
    				member.setId(id);
    				member.setUsername(username);
    
    				em1.persist(member);
    				tx1.commit();
    
    				em1.close(); // 영속성 컨텍스트1 종료,
    										 // member 엔티티는 준영속 상태가 된다.
    				// == 영속성 컨텍스트1 종료 == //
    
    				return member;
    }
    
    		static void mergeMember(Member member) {
    				// == 영속성 컨텍스트2 시작 == //
    				EntityManager em2 = emf.createEntityManager();
    				EntityTransaction tx2 = em2.getTransaction();
    
    				tx2.begin();
    				Member mergeMember = em2.merge(member);
    				tx2.commit();
    
    				// 준영속 상태
    				System.out.println("member = " + member.getUsername());
    
    				// 영속 상태
    				System.out.println("mergeMember = " + mergeMember.getUsername());
    				
    				System.out.println("em2 contains member = " + em2.contains(member));
    				System.out.println("em2 contains mergeMember = "
    													+ em2.contains(mergeMember));
    
    				em2.close();
    				// == 영속성 컨텍스트2 종료 == //
    

    1번 - member 엔티티는 createMember() 메소드의 영속성 컨텍스트1에서 영속 상태였다가 영속성 컨텍스트1이 종료되면서 준영속 상태 → createMember() 메소드는 준영속 상태의 member 엔티티 반환

    2번 - main() 메소드에서 member.setUsername(”회원명변경”)을 호출해서 회원 이름을 변경했지만 준영속 상태인 member 엔티티를 관리하는 영속성 컨텍스트가 더는 존재하지 않으므로 수정 사항을 데이터베이스에 반영 불가

    3번 - mergeMember() 메소드에서 새로운 영속성 컨텍스트2를 시작, em2.merge(member)을 호출해서 준영속 상태의 member 엔티티를 영속성 컨텍스트2가 관리하는 영속 상태로 변경했다. 영속 상태이므로 트랜잭션을 커밋할 때 수정했던 회원명이 데이터베이스에 반영된다.(정확히는 member 엔티티가 준영속 상태에서 영속 상태로 변경되는 것이 아니고 mergeMember라는 새로운 영속 상태의 엔티티가 반환된다.

    • 병합이 끝난 후 tx.commit()을 호출해서 트랜잭션 커밋
    • mergeMember의 이름이 “회원1”에서 “회원명변경”으로 변경되었으므로 변경 감지 기능 동작하여 변경 내용을 데이터베이스에 반영
    • merge()는 파라미터로 넘어온 준영속 엔티티를 사용해서 새롭게 병합된 영속 상태의 엔티티를 반환, 이때 파라미터로 넘어온 엔티티는 병합 후에도 준영속 상태로 남아있음

  • 비영속 병합
    Member member = new Member();
    Member newMember = em.merge(member);// 비영속 병합
    tx.commit();

    병합은 준영속, 비영속을 신경쓰지 않고 식별자 값으로 엔티티를 조회할 수 있으면 불러서 병합하고 조회할 수 없으면 새로 생성해서 병합한다. 따라서 병합은 save or update 기능을 수행한다.


Uploaded by N2T

반응형

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

[JPA] Getter and Setter  (0) 2023.04.02
[JPA] 객체와 테이블, 조인 전략  (0) 2023.04.02
[JPA] Flush  (0) 2023.03.19
[JPA] JPQL의 기초  (0) 2023.03.16
[JPA] JPA 기초설정과 H2 DB 연동  (0) 2023.03.16
profile

나를 기록하다

@prao

포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!

profile on loading

Loading...