본문 바로가기
IT/오픈소스

JPA - 기본 개념

by 모띠 2024. 1. 30.

 

 

 

본 포스팅은 총 4편으로 구성되어 있습니다.

2024.01.30 - [IT/오픈소스] - JPA - 기본 개념

2024.12.23 - [IT/오픈소스] - JPA - 고급 매핑

2024.12.23 - [IT/오픈소스] - JPA - 다양한 쿼리방법

2024.12.24 - [IT/오픈소스] - JPA - 스프링 연동

 

 

JPA란 무엇인가?

 

ORM이란?

  • Ojbect-relational mapping(객체 관계 매핑)
  • 객체는 객체대로 설계 / 관계형 db는 관계형 db대로 설계
  • ORM 프레임워크가 중간에서 매핑
  • 대중적인 언어는 대부분 ORM기술이 존재

 

JAVA와 RDB사이에서 JPA가 다 해결해준다.

예를들어 DTO를 넘겼을때 JPA가 Entitiy를 분석해서 insert / select 쿼리를 생성해주고 결과를 알려주고

패러다임 불일치를 해결해준다.

 

mybatis는 매핑은 해주지만 쿼리는 내가 짜야한다. 그러나, JPA는 쿼리까지 짜줌.

EJB(자바표준)라고 초창기가 있었고 → 그게 너무 별로라서 다시 개발한게 하이버네이트(오픈소스) → JPA(자바표준) (하이버네이트 거의 복붙수준)

JPA는 인터페이스고 구현은 알아서 하는것. 여러가지가 있지만 .. 그냥 Hibernate로 구현한다고 생각하면 된다.

 

 


 

 

JPA를 왜 쓰는가?

1. 지금 시대는 객체를 관계형DB에 관리 - RDB는 SQL문을 짜야한다. → SQL에 의존적인 개발을 할 수 밖에없다.

 

예를들어, 테이블을 생성할때 member테이블에 memberId, name컬럼이있으면 그거에 맞춰서 sql를 짜야한다.

근데 member테이블에 tel컬럼이 추가되면 CRUD에 전부 다 수정해줘야하고, 하다가 중간에 빠질수도있음.. 이런 번거로움이 존재.

 

또한, 이러한 NPE문제점도 존재 <- 보장을 해줄수가없기때문

public void process(String id){
	Member m = dao.find(id);
	
    // 객체지향적으로는 ok이지만, 실제로 해당하는 id에 team값이 입력되어있어야만 에러가 없는코드. null이 나올수도있음
    m.getTeam(); 
}

 

 

2. JAVA와 RDB 의 패러다임의 불일치

  • 객체(어떻게해서 잘 추상화할까) vs RDB(어떻게해서 데이터를 잘 저장할까) ← 이렇게 나온 사상자체가 다른데 우리가 억지로 껴맞추고있는것. 객체 —> sql —> RDB 그래서 SQL를 열심히 짜고 있는 실정.
  • Object를 저장하는곳은 file, rdb, nosql등 여러가지가 있지만 현실적으로 지금은 rdb를 쓴다.
  • 상속관계 : 객체는 상속관계가 되지만, DB에 저장할때 상속관계를 쓰면 JOIN을 테이블마다 다해줘야하므로, 한DTO에 모든 컬럼을 때려박는식으로 상속관계를 안쓴다.
  • 연관관계 : 객체를 참조를 사용 m.getTeam() / 테이블을 외래키를 사용 join ← 테이블은 방향성이없고 객체는 방향성이 있다.

객체답게 모델링할수록 매핑작업만 늘어난다.. 객체를 자바 컬렉션에 저장하듯이 db에 저장할 수 없을까?

→ 이렇게 해서 나온게 자바진영에서는 JPA

 


 

JPA장점

1. 일단 작성이 편하다. (JPA의 CRUD)

  • 저장 : em.persist(member)
  • 조회 : Member member = em.find(memberId)
  • 수정 : member.setName("변경할이름")
  • 삭제 : em.remove(member)

 

2. 신뢰할 수 있다.

public void process(String id){
	Member m = dao.find(id);
	m.getTeam(); // 이상황에서 team이 없으면 team만 조회하는 쿼리를 알아서 한번 더 날림 / 한번에 관련된걸 다 꺼내올수있음
}

 

 

3. 성능

  • 같은 트랜잭션 안에서는 같은 엔티티를 반환
  • 트랜잭션을 지원하는 쓰기 지연.  
    • 트랜잭션을 커밋할때까지 INSERT SQL문을 모은다. JDBC BATCH SQL기능을 이용해서 한번에 할수있지만, 이건 순수 JDBC라서 ROW레벨의 코드가 엄청 길어진다.
    • JPA를 쓰면 BATCH로 한꺼번에 해준다. (옵션으로 가능, 기본적으로는 하나씩 insert)
  • 지연로딩 - 객체가 실제 사용될 때 로딩 (fetch=FetchType.LAZY) ← 권장
Member m = dao.find(memberId);   <- select * from member;
Team t = m.getTeam();             <- 여기서 team을 불러오는게 아니라 실제 사용될때 불러옴. 최대한 미룬다는뜻
String teamName = t.getName();   <- select * from team;
  • 즉시로딩 - JOIN SQL로 한번에 연관된 객체까지 미리 조회 (항상 같이쓰는 테이블이라면 그냥 미리 조회하도록 할수있다) (fetch=FetchType.EAGER)

 


 

 

JPA 사용법

스프링부트를 이용하면 전부다 알아서해주긴 하지만, 정공법으로 설명.

 

1. pom.xml 추가

<dependency>
	    <groupId>org.hibernate</groupId>
	    <artifactId>hibernate-entitymanager</artifactId>
	    <version>5.3.7.Final</version>
</dependency

 

 

2. persistence.xml 작성

JPA는 기본적으로 persistence.xml파일이 필요하다.

  • /META-INF/persistence.xml 위치
  • javax.persistence로 시작 - jpa표준속성
  • hibernate로 시작 - 하이버네이트 전용 속성
  • MySQLDialect, OracleDialect, H2Dialect 중에서 선택..

jpa는 특정 db에 종속적이지 않는 기술이지만, 각각의 db가 제공하는 sql문법과 함수가 조금씩 다르다보니 통일이 안된다. 그래서 어떤걸 쓸지 선택해야한다.

  MYSQL ORACLE
가변문자  varchar varchar2
문자열자름 substring() substr()
페이징 limit rownum

 

<?xml version="1.0" encoding="UTF-8"?>
<persistence xmlns="http://xmlns.jcp.org/xml/ns/persistence" version="2.2">
    
    <persistence-unit name="hello">
        <properties>
            <!-- 필수 속성 -->
            <property name="javax.persistence.jdbc.driver" value="org.h2.Driver"/>
            <property name="javax.persistence.jdbc.user" value="sa"/>
            <property name="javax.persistence.jdbc.password" value=""/>
            <property name="javax.persistence.jdbc.url" value="jdbc:h2:tcp://localhost/~/test"/>
            <property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
        
        	<!-- 옵션 -->
            <property name="hibernate.show_sql" value="true"/>
            <property name="hibernate.format_sql" value="true"/>
            <property name="hibernate.use_sql_comments" value="true"/>
            <property name="hibernate.hbm2ddl.auto" value="create" /> <!-- ddl 옵션 -->

        </properties>
    </persistence-unit>
</persistence>

 

 

3. 객체매핑

@Entity // JPA가 관리할 객체라고 표시, 이게 붙어있는 클래스만 JPA가 인식함
public class Member{
	@Id // PK를 나타냄
	private Long id;
	pirvate String name;
}

 

 

4. 코드작성

public static void main(String args[]) {
		EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello"); // xml에서 지정한 name을 넣어주면됨, EntityManager를 생산하는 팩토리
		
		EntityManager em = emf.createEntityManager(); // 실제로 쓸때마다 생성
		EntityTransaction tx = em.getTransaction();
		tx.begin();
		
		try {
		Member member = new Member();
		member.setId(101L);
		member.setName("안녕하세요");
		
		em.persist(member);
		tx.commit();
		} catch (Exception e) {
			tx.rollback();
		} finally {
			em.close(); // 리소스를 물고있으므로 반드시 닫아줘야함
		}
		
		emf.close(); // 리소스를 물고있으므로 반드시 닫아줘야함
	}

 

 

주의

  • EntityManagerFactory 하나만 생성해서 애플리케이션 전체에서 공유해야한다.
  • EntityManager는 쓰레드간에 공유하면안된다.(사용하고 버려야한다)
  • jpa의 모든 데이터 변경은 트랜잭션 안에서 실행.

 


 

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

JPA는 DDL을 애플리케이션 실행 시점에서 자동생성해줄수있다

→ 기존에는 DDL을 직접 넣어줬는데  JPA를 사용하면 매핑 & 쿼리문작성은 DDL까지 JPA에게 맡길수있다는 의미

대신 이렇게 생성된 DDL은 개발장비에서만 사용

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

 

  • create : 기존테이블 삭제 후 다시 생성 (drop + create)
  • create-drop : create와 같으나 종료시점에 테이블 drop
  • update : 변경분만 반영(운영db에는 사용x)
  • validate : 엔티티와 테이블이 정상 매핑되었는지만 확인 - 다르면 오류냄
  • none : 사용하지않음

* 운영에는 절대 create, create-drop, update를 사용하면안됨 *

 

어노테이션

@Entity
public class Member{
	@Id @GeneratedValue(strategy=GenerationType.AUTO) // PK키인데, 알아서 생성해줌 AUTO_INCREMENT처럼..
	private Long id;
	
	@Column(name="USERNAME", nullable=false, length=20) // 실제 디비컬럼이 USERNAME, 근데 자바 name과 매칭하겠다는뜻
	private String name;
	
	private int age;
	
	@Temporal(TemporalType.TIMESTAMP) // 날짜타입
	private Date regDate;

	@Enumerated(EnumType.STRING) // 자바 Enum타입 매핑, 항상 STRING으로 할것! ORDINAL이 기본값인데 그럼 0,1,2 순으로 들어감
	private MemberType memberType;

	@Lob
	private String lobString;

	@Lob
	private byte[] lobByte;	
}

 

 

@Id GeneratedValue(strategy=GenerationType.AUTO)

  • SEQUENCE : 데이터베이스 시퀀스 오브젝트 사용. ORACLE @SequenceGenerator 필요
  • IDENTITY : 데이터베이스에 위임, MYSQL
  • TABLE : 키생성용 테이블사용 모든 DB에서 사용 @TableGenerator 필요
  • AUTO : 방언에 따라 자동 지정, 기본값 - 위 3가지 방식중에 하나가 자동으로 선택된다.

 

기본키 매핑에는 2가지 방식이 존재.

  • 직접 할당: 기본키를 직접 할당 (@Id)
  • 자동 생성: 대리 키 사용 (@GeneratedValue)
    • IDENTITY : 데이터베이스에 위임, MYSQL (AUTO_INCREMENT)
      @Id
      @GeneratedValue(strategy=GenerationType.IDENTITY)
      private Long id;
    • SEQUENCE : 데이터베이스 시퀀스 오브젝트 사용, ORACLE @SequenceGenerator 필요
      // 오라클 시퀀스를 사용하여 기본키를 생성하므로 시퀀스를 생성해야한다.
      CREATE SEQUENCE BOARD_SEQ START WITH 1 INCREMENT BY 1;
      
      @Entity
      @SequenceGenerator(
      	name = "BOARD_SEQ_GENERATOR",
      	sequenceName = "BOARD_SEQ", // 매핑할 시퀀스 이름
      	initialValue = 1, allocationSize = 1)
      public class Board{
      	@Id
      	@GeneratedValue(strategy=GenerationType.SEQUENCE, generator = "BOARD_SEQ_GENERATOR")
      	private Long id;
      }
    • TABLE : 키생성용 별도의 테이블사용. 모든 DB에서 사용 @TableGenerator 필요 
    • // 키생성용 테이블을 별도로 생성해야한다.
      create table MY_SEQUNECES ( .... )
      
      @Entity
      @TableGenerator(
      	name = "BOARD_SEQ_GENERATOR",
      	sequenceName = "MY_SEQUNECES ", // 매핑할 키 테이블 이름
      	pkColumnValue= "BOARD_SEQ", allocationSize = 1)
      public class Board{
      	@Id
      	@GeneratedValue(strategy=GenerationType.TABLE, generator = "BOARD_SEQ_GENERATOR")
      	private Long id;
      }
    • AUTO : 방언에 따라 자동 지정, 기본값 - 위 3가지 방식중에 하나가 자동으로 선택 (디폴트)

 

@Column

  • name : 필드와 매핑할 테이블의 컬럼명
  • insertable, updatable : 읽기전용 insertable=false 면 insert가 안된다.
  • nullable : false면, ddl생성할때 그 컬럼은 not null로 만든다.
  • unique : 유니크 제약조건, ddl생성시 사용
  • length : 길이

 

@Lob

  • 컨텐츠의 파일이 너무 길때는 바이너리로 밀어넣어야한다. 그때 사용
  • CLOB : 캐릭터 긴단어를 저장, String, char[], java.sql.CLOB
  • BLOB : 바이트를 저장, byte[], java.sql.BLOB

 

@Transient

  • 이 필드는 매핑하지않음.
  • 디비에는 안넣고싶은데 객체에는 필요할때 사용

 

@Enumerated

  • 자바 enum 타입을 매핑할 때 사용.
  • ORDINAL (디폴트) / STRING 타입 존재
  • @Enumerated(EnumType.String)으로 사용하는 것을 권장
enum RoleType { ADMIN, USER }


@Enumerated(EnumType.ORDINAL)
private RoleType roleType;

member.setRoleType(RoleType.ADMIN) // DB에 0이 저장됨. 순서가 바뀌면 안됨.


@Enumerated(EnumType.STRING)
private RoleType roleType;

member.setRoleType(RoleType.ADMIN) // DB에 ADMIN이 저장됨. 순서가 바뀌어도 안전

 

 

@Temporal

  • 날짜 타입을 매핑할 때 사용.
  • 방언에 따라 알아서 ddl생성. mysql : datetime / oralce, h2 : timestamp
@Temporal(TemporalType.DATE)
private Date date;

@Temporal(TemporalType.TIME)
private Date time;

@Temporal(TemporalType.TIMESTAMP)
private Date time;

 

 

@Access

  • JPA가 Entity 데이터에 접근하는 방식 지정.
  • FILED : 필드에 직접 접근/ PROPERTY : getter로 접근
@Entity
@Access(AccessType.FIELD) // 생략가능
public class Member {
	@Id
	private String id;
}
@Entity
@Access(AccessType.PROPERTY) // 생략가능
public class Member {
	private String id;
	
	@Id
	public String getId(){return id;}
}

 

 


 

 

연관관계

JPA를 이해함에 있어서 가장 중요한 부분. 이 개념을 이해하지못하면 JPA를 활용할 수 없다.

 

 

데이터지향 모델링

 

@Entity
public void Member(
	@Id @GeneratedValue
	long member_id;
	@Column(name="TEAM_ID")
	long teamId; // Team 타입이 아님 <- 객체지향적이 아님
	String username; 
);

public void Team(
	long team_id;
	String name;
);

 

 

객체를 테이블에 맞추어 모델링하게되면, 조회를 2번해야한다. 서로 협력관계가 아니게 되므로 ←데이터 지향적

// 팀저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);

// 회원저장
Member member = new Member();
member.setName("member1");
member.setTeamId(team.getId()); // id를 꺼내서 넣음
em.persist(member);


Member member1 = em.find(Member.class, member.getId());
String teamId = member.getTeamId();
// member에 연관된 team을 찾고싶을때도 연관관계가 없으므로 한번 더 조회해야함
Team team1 = em.find(Team.class, teamId);

 

  • 테이블은 외래키로 조인을 사용해서 연관된 테이블을 찾고
  • 객체는 참조를 사용해서 연관된 객체를 찾는다.

 

객체지향 모델링 (단방향 매핑)

@Entity
public void Member(
	@Id @GeneratedValue
	long member_id;

	@ManyToOne  // member입장에서는 다대일 .. 1개의 팀은 여러명의 멤버를 가질수있으므로
	@JoinColumn(name="TEAM_ID")
	private Team team; // Team객체로 넣음
	
	String username; 
);

public void Team(
	long member_id;
	String name;
);

 

// 팀저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);

// 회원저장
Member member = new Member();
member.setName("member1");
member.setTeam(team); // team객체 자체를 넣음
em.persist(member);

Member member1 = em.find(Member.class, member.getId());
Team team1 = member.getTeam(); // member객체를 찾았으니 team객체는 그 안에 있으므로 바로 조회가능

 

 

객체지향 모델링 (양방향 매핑)

 

// 나는 매핑관계에서 주인이 아니는 뜻
// team이라는 이름의 객체한테 묶여있다.
@OneToMany(mappedBy = "team") 									
private List<Member> members = new ArrayList<>();

 

객체는 서로 관계를 맺고 왔다갔다 하려면 ← 결국 단방향2개로 구성해야 한다.

Member → Team 연관관계 1개(단방향)

Team → Member 연관관계 1개(단방향)

객체를 양방향으로 참조하려면 단방향 연관관계 2개를 만들수밖에없다.

테이블은 외래키 하나로 두테이블의 연관관계를 관리 ← 여기서 차이점이 발생

회원 ←→ 팀 연관관계 1개(양방향)

 

 

// 팀저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);

// 회원저장
Member member = new Member();
member.setName("member1");
member.setTeam(team); // team객체 자체를 넣음
em.persist(member);

em.flush(); // sql문 날리기
em.clear(); // 캐시 비우기

Member findMember = em.find(Member.class, member.getId());
Team findTeam = findMember.getTeam();

List<Member> members = findTeam.getMembers();  // team에서 member를 접근할수있음
for(Member member1 : members) {
    System.out.println("@!!!!!"+member1);
}

 

 

만약 두 테이블 다 @JoinColumn를 쓴다면 디비는 Member, Team테이블 중에 어느 관계를 믿어야할지 모른다.

그래서 두 객체의 관계중 하나만 연관관계의 주인으로 지정해야한다.

  • 연관관계의 주인만이 외래키를 관리(등록, 수정) ← 주인이 아닌사람한테 백날 등록해봤자 db에 반영이 안된다는 의미.  제일 실수많이하는부분.
  • 주인이 아닌쪽은 조회만 가능함
  • 주인은 mappedBy 속성사용 x
  • 주인이 아니면 mappedBy 속성으로 주인 지정

둘중 누가 주인으로 하면 좋을지는 → 테이블에 외래키가 있는 곳을 주인으로 설정 (N쪽)

위 예시에서 Member.team이 주인. Member테이블에 외래키가 존재하므로.

 

// 팀저장
Team team = new Team();
team.setName("TeamA");
em.persist(team);

// 회원저장
Member member = new Member();
member.setName("member1");
member.setTeam(team); // team객체 자체를 넣음
// 얘는 데이터관점으로봤을때 넣어도 어차피 db에서는 주인관계가 아니라 무시당하지만, 객체관점에서는 넣어야함.
// 결국 주인, 하인 현업에서는 둘다 넣어줌	
team.getMembers().add(member) 

em.persist(member);

 

단방향 매핑만으로도 이미 db연관관계 매핑은 완료.

양방향매핑은 반대방향으로 조회하는 기능이 추가된것뿐.

단방향매핑으로 일단 다 박아놓고 필요할때 양방향매핑으로 바꾸는것을 추천. 자바코드 몇줄만 추가하면되고 실제로 db에 영향을 주지않는다.

 

 

 


 

 

JPA 내부구조

영속성 컨텍스트

  • JPA를 이해하는데 가장 중요한 용어
  • 엔티티를 영구 저장하는 환경이라는 의미
  • EntityManager.persist(entity);
  • 영속성컨텍스트 = EntityManager

그냥 엔티티매니저를 생성하면 내부에 영속성 컨텍스트가 같이 생긴다 라고 생각하면 편함.

// 1:1매핑이다. (스프링에서는 N:1로해줌)

영속성컨텍스트 = 엔티티매니저

 

엔티티의 생명주기

  • 비영속 : 영속성 컨텍스트와 전혀 관련이 없는상태 ← 생성만해놓고 jpa에 안넣었을때
  • 영속 : 영속성 컨텍스트 저장된 상태 // jpa 넣었을때 em.persist(member);
  • 준영속 : 영속성 컨텍스트에 저장되었다가 분리된 상태 // em.detach(member);
  • 삭제 : 삭제된상태 // em.remove(member);

 

그냥 em.persist()할때 디비에 넣으면되는데 왜 영속성컨텍스트라는 상태를 가지고있는걸까?

아래의 것들이 가능하려면 영속성컨텍스트라는 개념이 있어야하기때문

  • 1차 캐시
  • 동일성(identity)보장
  • 트랜잭션을 지원하는 쓰기 지연
  • 변경 감지
  • 지연 로딩

 

1차 캐시

em.persist(member)할때 실제로는 1차캐시에 저장된다. (잠깐쓰다 버리는 내부캐시)

Member finMember = em.find(Member.class, "member1"); 로 조회를 할때 db에 접근하는것이 아니라 먼저 1차캐시부터 뒤져서 1차캐시에 있으면 그냥 그값을 조회한다.

만약 조회했는데 없는값이라면 그때서야 db로 가서 조회함. 조회된 값을 1차캐시에 먼저 저장하고 그다음에 결과를 반환

 

같은 1차 캐시를 보때문에 동일성을 보장해줌

Member a = em.find(Member.class, "member1");
Member b = em.find(Member.class, "member1");
System.out.println(a == b); //동일성 비교 true

 

 

트랜잭션을 지원하는 쓰기 지연

em.persist(memberA);

em.persist(memberB); 가 입력되면,

실제로 db에는 저장하지않고 1차캐시에만 저장하고 각각의 sql문만 말아놓고있다.

tx.commit()되는 시점에 말아놨던 sql들을 전부 db에 넣는다. 이때 기본적으로는 하나씩 insert하지만 동시에 실행할수도있다(옵션 - 배치)

쓰기지연 sql저장소에 있던 sql문들을 db에 보내는 과정을 flush() (db와 싱크를 맞춘다고 생각하면 편함, 1차캐시도 안비움)

그래서 em.flush(); 하면 tx.commit();을 하지않아도 db에 값이 저장된다.

em.clear() 는 1차캐시를 싹 비우는 것.

 

엔티티 수정 변경감지

영속성컨텍스트가 존재하므로 엔티티수정을 알아서 감지해준다.

 

1차캐시는 스냅샷을 항상 뜨고있다.

flush() 할때 들어온엔티티와 내가 지니고있는 스냅샷을 비교

commit()할때는 flush()가 자동으로 실행되니까 commit()도 해당

변화가생겼으면 sql update문을 만들어서 insert문이랑 같이보내준다.

1차 캐시와 비교하는것이기 때문에 1차캐시에 없다던가하면 (clear해서) update문이 생성되지않는다.

 

Member member = new Member();
member.setName("member1");

em.persist(member); // 저장완료 <- 1차캐시에 저장되었으므로 영속성을 가짐

member.setName("member3"); // 영속 엔티티 데이터를 수정

//em.update(member); 수정되었다는걸 알려줘야하지않을까? <-필요없음 영속성을 가진녀석은 스냅샷을 다 찍어놨기때문에 변화했는지 알수있으므로
//em.persist(member); 이것도 필요없음. 자바의 객체처럼 생각하면됨.

tx.commit();

 

 

flush할때 등록,수성,삭제쿼리가 일괄적(옵션)으로 db에 전송됨

  • em.flush() 직접호출
  • 트랜잭션 커밋 - flush 자동호출
  • JPQL쿼리 실행 - flush 자동호출
em.persist(memberA);
em.persist(memberB);
em.persist(memberC);

// 중간에 JPQL 실행

// 원래대로라면 이시점에서 1차캐시에서 db로 보내지지않았기때문에 값을 가져올수없음.
// flush를 강제로한것도아니고 commit을 한것도 아니니까
// 근데 그런것을 방지하기 위해서 JPQL을 실행하면 flush가 자동호출되서 db로 데이터가 들어간다.
// 하도 개발자들이 실수를 많이하니까, 아예 넣어버림
// JPQL은 제공하지만, mybatis나 스프링jdbc를 사용하면 반드시 flush해주고 읽어야함.
query = em.createQuery("select * from Member", Member.class); 

List<Member> members = query.getResultList();

 

  • flush()는 영속성컨텍스트(1차캐시)를 비우지않는다 / 비우는건 clear()
  • 영속성 컨텍스트의 변경내용을 db에 동기화 하는게 목적
  • 커밋직전에만 동기화 해주기위함. 그전까지 최대한 미룬다

 

준영속

 

영속상태가 이제 아니라는 의미

  • em.detach(entity) - 특정 엔티티만 준영속상태로 전환
  • em.clear() - 영속성컨텍스트를 완전히 초기화
  • em.close() - 영속성 컨텍스트 종료
@Entity
public void Member(
	@Id @GeneratedValue
	long member_id;

	@Column(name="USERNAME")
	private String name;
	
	@ManToOne(fetch=FetchType.LAZY) // 이러면 한꺼번에 조회하지않음
	//@ManToOne(fetch=FetchType.EAGER) // 이러면 한꺼번에 조회
	@JoinColumn(name="TEAM_ID")
	private Team team; 
);


Member findMember = em.find(Member.class, member.getId());
Team findTeam = findMember.getTeam(); // 이시점에서는 LAYZ이므로 Team은 프록시객체가있음.
findTeam.getName(); // 이시점에서 실제로 db에서 Team을 조회


Member findMember = em.find(Member.class, member.getId());
Team findTeam = findMember.getTeam(); // 이시점에서는 LAYZ이므로 Team은 프록시객체가있음.
em.clear(); // 캐시를 비워서 준영속상태로 바꿈
findTeam.getName(); // 이시점에서 조회하려고하면 영속성이 사라졌기때문에 LazyInitial 예외 발생

 

준영속상태가되면 지연로딩이 불가능 - 프록시객체가 필요하므로..

Member를 조회할때 Team도 함께 조회해야하는걸까? -> 단순히 member만 사용하면 Team을 조회할필요가없다.

LAZY 를 쓰면 Team에 null이 아니라 가짜 객체(프록시)를 채우고 실제로 team이 사용되는 시점에 db를 조회한다.

LAZY(지연로딩)가 협업에 권장

영속성이 없는상태에서 지연로딩을하려면 LazyInitial.. 예외발생

스프링프레임워크는 controller가 끝나면 준영속상태로 만들어버린다.

 

즉시로딩을 적용하면 예상치못한 SQL이 발생할수있음

  • @ManyToOne, @OneToOne은 EAGER가 디폴트
  • @OneToMany, @ManyToMany은 LAZY가 디폴트

 

 

 

 

쿼리 작성관련해서는 JPA의 다양한 쿼리방법에서 설명한다.

'IT > 오픈소스' 카테고리의 다른 글

프로메테우스(Prometheus) 기본 사용법  (0) 2024.05.05
프로메테우스(Prometheus) 오픈소스란?  (0) 2024.05.05
카프카(Kafka) - 활용정리  (0) 2023.09.12
카프카(Kakfa) 개념정리  (1) 2023.09.11
Docker란?  (1) 2023.03.26

댓글