반응형
반응형

 

📝 기본값 타입

int a = 4;
long b = 5L;
Integer c = 4;
String d = "hello world";

자바 기본타입(int, double), 래퍼 클래스 (Integer, Long), String이 이에 해당하며 primitive type의 경우는 항상 값을 복사하고 래퍼클래스나 String같은 특수 클래스의 경우는 공유 가능한 객체이지만 불변이여서 기존 값을 변경할 수 없다.

 

 

📝 임베디드 타입 

@Embeddable
public class Address {

    private String city;
    private String street;
    private String zipcode;

    protected Address() {
    }

    public Address(String city, String street, String zipcode) {
        this.city = city;
        this.street = street;
        this.zipcode = zipcode;
    }
}

 

@Entity
public class Member {

    @Id
    @GeneratedValue
    private Long id;

    private String name;

    @Embedded
    private Address address;

    protected Member() {
    }

    public Member(String name, Address address) {
        this.name = name;
        this.address = address;
    }
}

일반적인 클래스를 의미하며 여러 값 타입들이 모여서 만들어진 타입이다.

@Embedded로 @Embeddable을 사용한 클래스를 주입할 수 있으며 Address는 테이블이 따로 만들어지지 않는다.

Member테이블에 필드로 들어가게 된다.

임베디드 타입을 사용하는 경우 재사용이 가능해지며 주소를 확인하는 등 의미 있는 메서드를 만들어서 재사용이 더욱 좋아진다.

 

여담으로 임베디드는 Entity는 아니지만 JPA와 관련있는 테이블형식의 느낌이기 때문에 protected를 사용해서 Entity를 만드는 방향과 동일하게 가는게 철학에 맞다. (기본생성자는 필수)

 

 

📝 불변 객체

String a = "hello";
a = a + " world";

한 번 만들어지면 내부 상태를 바꿀 수 없는 객체입니다. 위 코드는 변경하는 것이 아닌 새로운 객체가 만들어지는 것입니다.

String는 불변 객체로 되어있어서 공유 참조가 일어나도 문제가 없습니다.

 

int a = 10;
int b = a;//기본 타입은 값을 복사
b = 4;

기본 타입(primitive type)의 경우는 기본 타입 값을 복사하고 변경해도 주소를 참조하는 형태가 아니기 때문에 문제가 없다.

 

Address a = new Address(“Old”);
Address b = a; //객체 타입은 참조를 전달
b. setCity(“New”)

객체 타입의 경우는 얕은 참조가 일어나기 때문에 값 변경시 같이 영향을 받게 된다. 이걸 공유 참조라고 하는데 부작용 원천 차단을 위해 생성자로 생성만 할 수 있게하고 setter를 만들지 않게 해서 차단해야한다

 

 

 

📝 컬렉션 값 타입

@Entity
@Table(name = "member")
public class Member {

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

    private String name;

    // 값 타입 컬렉션
    @ElementCollection
    @CollectionTable(
        name = "favorite_food",
        joinColumns = @JoinColumn(name = "member_id")
    )
    @Column(name = "food_name")
    private Set<String> favoriteFoods = new HashSet<>();
}

값타입 컬렉션이란 컬렉션 자료구조를 이용한 방법이라 하나의 테이블로 되지 않고 컬렉션을 저장하기 위한 별도 테이블이 필요하다.

 

@ElementCollection, @CollectionTable을 이용해 사용할 수 있지만 값타입 컬렉션에 변경 사항이 발생시 주인 엔티티와 관련된 모든 데이터를 삭제하고 값타입 컬렉션에 있는 현재값을 모두 다시 저장하기 때문에 사용을 지양하며 일대다 관계를 사용하는 게 좋다.

 

값타입 컬렉션의 경우는 영속성 전이(CASCADE) + 고아 객체 기능을 필수로 가지게 되기 때문에 일대다 관계를 사용 한 이후 같이 설정해줘서 동일하게 동작하도록 유도한다.

 

 


🔗 참고 및 출처

https://www.inflearn.com/course/ORM-JPA-Basic

 

 

반응형
반응형



 

📝 프록시(Proxy), em.find vs em.getReference

Member와 Team이 연관관계로 묶여있는 경우 Member 정보만 필요한데 Team까지 연관관계 묶여있다는 이유로 가져와야할까? 성능 문제도 있기 때문에 필요한 것만 조회하는게 좋다.

 

Member member = em.find(Member.class, memberId);
Member proxyMember = em.getReference(Member.class, memberId);

System.out.println("member usename : " + member.getUserName());
System.out.println("proxyMember usename : " + proxyMember.getUserName());

 

기존 조회는 find()를 통해 조회했는데 이럴 경우 find()를 사용하자마자 Member정보와 묶여있는 Team정보도 동시에 가져오게 된다. 하지만 proxy인 getReference()를 사용하는 경우 바로 실행되는게 아닌 아래 getUserName()을 할 때 조회하게 된다. 이때 team정보는 호출 안 했기 때문에 team정보는 조회하지 않는다.

 

 

Proxy 동작구조

프록시 동작 구조는 위와 같은데 먼저 getReference()를 통해 조회한 이후에 getName()을 호출한 경우 Proxy는 영속성 컨텍스트에 조회요청을 하고 실제 Entity를 통해 값이 들어가며 Proxy는 Member의 target으로 주소를 참조하게 된다.

 

📝 즉시 로딩(Eager Loading), 지연 로딩(Lazy Loading)

Member와 Team이 연관관계로 있을 때 즉시 로딩의 경우 Member조회시 Team이 같이 조회됩니다.

Default로 Eager Loading이 되어있습니다.

지연 로딩의 경우는 위에 프록시와 같이 필요할때 필요한 것만 조회하게 됩니다.

 

@Entity
 public class Member {
 @Id
 @GeneratedValue
 private Long id;
 @Column(name = "USERNAME")
 private String name;
 @ManyToOne(fetch = FetchType.LAZY) //**
 @JoinColumn(name = "TEAM_ID")
 private Team team;
 ..
}

지연 로딩을 적용하는 방법은 연관관계에서 fetch type을 LAZY로 주면 됩니다.

 

실무에서는 무조건 지연 로딩만 사용해야합니다.

즉시로딩을 사용할 경우 예상하지 못한 SQL이 발생하게 되며 JPQL에서 N+1문제가 일어나게 됩니다.

 

 

📝 영속성 전이 (CASCADE)

@OneToMany(mappedBy = "parent", cascade = CascadeType.ALL)
private List<Child> children = new ArrayList<>();
Parent parent = new Parent();
parent.setName("부모1");

Child child1 = new Child("자식1");
Child child2 = new Child("자식2");

parent.addChild(child1);
parent.addChild(child2);

em.persist(parent);

특정 엔티티를 영속 상태로 만들 때 그와 연관된 엔티티도 함께 영속 상태로 만들고 싶을 때 사용합니다.

부모에 연관관계를 가진 자식을 넣고 부모를 persist하게 되면 자식들도 같이 저장됩니다.

ALL, PERSIST, REMOVE 등 다양한 옵션이 있지만 ALL을 많이 씁니다.

 

Parent와 Child 관계가 1개인 경우에만 사용합니다. 만약 다른곳에서도 Child를 사용하는 경우 문제가 생길 수 있습니다.

사용 예제로는 게시판의 첨부파일 개념으로 게시판의 자식인 첨부파일이 있고 해당 첨부파일은 게시판 외에는 쓰는 곳이 없는 경우입니다.

 

 

📝 고아 객체

@OneToMany(mappedBy = "parent", orphanRemoval = true)
private List<Child> children = new ArrayList<>();
Parent parent = em.find(Parent.class, 1L);

Child child = parent.getChildren().get(0);
parent.removeChild(child);

부모 엔티티와 연관관계가 끊어진 자식 엔티티를 자동적으로 삭제해줍니다.

CASCADE와 마찬가지로 Parent와 Child 관계가 1개인 경우에만 사용합니다. 

 

보통 고아 객체와 CASCADE를 같이 쓰게 되는데 이럴 경우 부모에서 자식의 라이프사이클을 모두 통제할 수 있습니다.

 

 

 

🔗 참고 및 출처

https://www.inflearn.com/course/ORM-JPA-Basic

 

반응형
반응형

 

📝 상속관계 매핑

RDBMS는 상속관계라는게 없지만 자바에서는 클래스 상속이 있다.

 

 

조인 전략

@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@DiscriminatorColumn(name = "dtype")
public abstract class Item {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "item_id")
    private Long id;

    private String name;

    private Integer price;
}
@Entity
@DiscriminatorValue("ALBUM")
public class Album extends Item {

    private String artist;
}

@Entity
@DiscriminatorValue("MOVIE")
public class Movie extends Item {

    private String director;

    private String actor;
}

@Entity
@DiscriminatorValue("BOOK")
public class Book extends Item {

    private String author;

    private String isbn;
}

각각 테이블로 변환시키는 전략으로 테이블이 정규화 되며 외래키 참조 무결성이나 제약조건 활용이 가능하다

단점으로는 조회시 조인을 많이 사용하게 되어 성능 저하조회 쿼리가 복잡해지며 데이터 저장시 INSERT SQL 2번 호출하게 된다.

 

  • DiscriminationValue
    • 하위 클래스 DType 이름 명시
  • DiscriminationColumn
    • DType필드명 명시
  • Inheritance
    • 상속 관계 전략 명시 (Default 단일 테이블 사용)

 

 

 

 

단일 테이블로 사용하는 전략

조인이 필요 없으므로 일반적으로 조회 성능이 빠르며 조회 쿼리가 단순하다.

단점으로 NULL이 많아지게 되며 단일 테이블에 저장해서 테이블이 커지며 상황에 따라 조회 성능이 오히려 느려질 수 있다.

 

자식 엔티티가 매핑한 컬럼은 모두 NULL 허용

 

 

결론

진짜 단순한 것 아닌 이상은 조인전략을 기본적으로 가져가며 DType을 가져가 어디에서 INSERT되었는지 확인하는게 좋다

 

 

📝 MappedSuperclass

@Entity
public class Product extends BaseTimeEntity {

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

    private String productName;
}

@MappedSuperclass
public abstract class BaseEntity {

    @Column(name = "created_at")
    private LocalDateTime createdAt;

    @Column(name = "updated_at")
    private LocalDateTime updatedAt;

    @Column(name = "created_by")
    private String createdBy;

    @Column(name = "updated_by")
    private String updatedBy;
}

공통 매핑 정보 사용할 때 사용 (등록자, 등록일, 수정자, 수정일)하기 때문에 BaseEntity는 엔티티로 생성 안 된다.

추상클래스로 만들어 직접 이 객체를 만들어서 사용 못하게 하고 상속으로만 사용 가능하게 한다.

 

 

🔗 참고 및 출처

https://www.inflearn.com/course/ORM-JPA-Basic

반응형
반응형

 

📝 테이블과 객체 연관관계

//조회
 Member findMember = em.find(Member.class, member.getId());
 //연관관계가 없음
 Team findTeam = em.find(Team.class, team.getId())

객체를 테이블에 맞추어 모델링할 때 FK를 이용해 다시 다른 테이블을 재조회하기 때문에 객체 지향적인 방법이 아니다.

 

 

//조회
 Member findMember = em.find(Member.class, member.getId());
//참조를 사용해서 연관관계 조회
 Team findTeam = findMember.getTeam();

객체지향적으로 모델링을 한 경우 member만 조회해도 관련된 team 정보까지 가져올 수 있다.

 

 

📝 단방향

한쪽 방향으로만 연결된 형태로 위 도식을 보면 Member를 조회했을 때 Team정보를 가져올 수 있지만 Team에서는 Memeber정보를 가져올 수 없다. Member → Team으로 연결된 단방향 형태라고 한다.

 

📝 양방향

양쪽 방향으로 연결된 형태Member를 조회했을 때 Team정보를 얻을 수 있고 Team을 조회했을 때 Member정보를 조회할 수 있다. Member → Team, Team → Member으로 연결된 양방향 형태라고 한다. (서로 다른 단방향 관계가 2개인 형태)

 

 

양방향 매핑 규칙

  • 두 관계중 하나를 연관관계 주인으로 지정한다.
  • 주인만이 외래키를 관리한다. (등록, 수정)
  • 주인이 아닌쪽읽기만 가능하다.
  • 주인mappedBy 속성을 사용하지 않는다.

 

양방향 매핑 주의점

// Member (주인)
public void setTeam(Team team){
	this.team = team;
	team.getMembers().add(this);
}

양방향인 경우 주인쪽에만 관계값을 set하는게 아닌 주인 아닌쪽에도 관계값을 set하는게 객체지향적으로 자연스럽다.

좋은 방법으로는 setter를 선언할 때 team을 세팅한 이후에 team의 member에 거기에 member를  add하면 깔끔하게 코드를 입력해 실수와 보일러 플레이트도 줄일 수 있다.

 

 

📝 연관관계 주인은 누구로 해야하는가?

테이블상에서 외래키가 있는 곳을 주인으로 잡는다. (TEAM_ID가 있는 MEMBER를 주인)

 

 

📝 다대일 (N:1)

 

다대일 (N:1) 단방향

Member(N)[주인]에서 Team(1)로 연결된 형태로 테이블하고 형상도 맞는 가장 많이 사용하는 연관관계이다.

  1. Team을 저장한다. (INSERT)
  2. Member에 저장한 Team 객체를 넣는다.
  3. Member가 저장되면서 Team_Id가 들어간다. (INSERT)

 

 

다대일 (N:1) 양방향

다대일 단방향에서 양쪽을 참조하는 형태이다.

 

 

📝 일대다 (1:N)

일대다(1:N) 단방향

일대다의 경우 Team(1)이 연관관계 주인으로 있지만 테이블에서는 외래키가 Member에 있기 때문에 위에 설명한 연관관계주인 설정이 맞지 않는다.

  1. Member값을 저장할때 Team_Id값이 없어서 Null이 된다. (INSERT)
  2. Team을 저장하면서 Member를 Setter로 넣으면 Team이 만들어진다. (INSERT)
  3. Team이 저장되면서 Member에 Team_Id값을 넣을수 있기 때문에 이미 들어간 것에 Update문이 실행된다. (UPDATE)

 

지금은 테이블이 한개라서 그렇지 여러개인 경우 로그로 파악이 불가능해진다.

일대다의 단반향 매핑보다는 다대일 양방향 매핑을 사용하자

 

 

일대다(1:N) 양방향

단방향에서도 불안정한 상태이기 때문에 양방향의 경우 더더욱 사용하면 안 좋다. (없는 셈 쳐라)

 

📝 일대일 (1:1)

일대일(1:1) 단방향 (주 테이블)

주 테이블이라 함은 많이 조회되는 테이블을 연관관계 주인으로 잡은 형태이다.

 

 

일대일(1:1) 양방향 (주 테이블)

양방향으로 연결한 형태로 문제될 것 없다.

 

 

일대일(1:1) 단방향 (대상 테이블)

대상테이블을 연관관계 주인으로 잡는 일대일 관계는 지원하지 않는다.

일대다와 다르게 제약조건이 걸려있기 때문에 Locker를 먼저 만들고 Member를 만든 이후에 Locker를 업데이트하는 행위가 불가능하다.

필요시에는 양방향 관계를 사용해야한다.

 

 

일대일(1:1) 양방향 (대상 테이블)

Member가 주인이 되는 연관관계가 아니라 Locker가 주인이 되어버리는 연관관계가 되어버린다.

결국 방법이 없어서 일대일(1:1) 양방향 (주 테이블)과 동일하게 가게 된다.

 

 

결론

주 테이블

  • 주 테이블에 외래키가 있는 경우 단방향이든 양방향이든 상관없기 때문에 개발자 입장에선 편하다.
  • 주 테이블만 조회해도 대상 테이블 데이터가 있는지 확인이 가능하기 때문에 좋다

대상 테이블

  • 일대일에서 일대다 관계로 추후 변경할 일이 있을 때 테이블 구조가 유지되고 제약조건만 제거하면 된다.
    주 테이블의 경우는 Member가 Locker를 여러개 가지게 될 경우 Member에 있는 Locker_Id가 필요없기 때문에 제거해야하며 Locker쪽으로 FK가 옮겨가게 되면서 수정이 많아지게 된다.

 

 

📝 다대다 (N:M)

다대다의 경우 테이블 관점에서는 중간테이블이 필요하며 JPA에서는 객체지향적으로 바로 연결시킬수 있지만 불안정하며 쿼리도 이상하게 나가기 때문에 실무에서 사용하면 안 되고 사용하려면 중간 테이블이 있어서 그 테이블을 엔터티로 해서 일대다와 다대일로 분리해야한다.

 

 

📝 예제

다대일 단방향 연결

// 일(1)
@Entity
public class Team {

    @Id @GeneratedValue
    @Column(name = "team_id")
    private Long id;

    private String name;
}

// 다(N)
@Entity
public class Member {

    @Id @GeneratedValue
    @Column(name = "member_id")
    private Long id;

    private String username;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "team_id")
    private Team team;
}

연관관계 주인에 JoinColumn을 붙인다.

 

 

다대일 양방향 연결

// 일(1)
@Entity
public class Team {

    @Id @GeneratedValue
    @Column(name = "team_id")
    private Long id;

    private String name;

    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<>();
}

// 다(N) 주인
@Entity
public class Member {

    @Id @GeneratedValue
    @Column(name = "member_id")
    private Long id;

    private String username;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "team_id")
    private Team team;
}

연관관계 주인에 JoinColumn을 붙이고 그 반대편에는 mappedBy로 연결한다.

 

 

 

🔗 참고 및 출처

https://www.inflearn.com/course/ORM-JPA-Basic

반응형
반응형

📝엔티티 매핑 관련 어노테이션

@Entity
@Table(name = "MBR")
public class Member {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // IDENTITY 전략
    // @GeneratedValue(strategy = GenerationType.SEQUENCE) // SEQUENCE 전략
    @Column(name="member_id")
    private Long id;

    private String name;
    private int age;
    private String address; // 필드만 추가해주면 끝

    // @Enumerated(EnumType.ORDINAL) // enum 타입 매핑 [숫자] (금지)
    @Enumerated(EnumType.STRING) // enum 타입 매핑 [문자] (사용 OK)
    private CategoryEnum name;

    // 날짜 타입 매핑 [아래 LocalDateTime을 쓰면 알아서 YYYYMMDDHHMMSS 설정]
    @Temporal(TemporalType.DATE) 
    private Date date;
    
    private LocalDateTime createDate
    
    @Lob // BLOB, CLOB 매핑
    private String description;
    
    @Transient
    private int temp;
}
  • @Entity
    • JPA에서 영속성 컨텍스트가 관리할 테이블이라는 의미이다.
    • 기본생성자 필수 (JPA에서 내부적으로 리플렉션 등을 사용하기 때문에)
  • @Table
    • DB 테이블명하고 엔티티 이름이 다를 때 매핑 가능
  • @Column
    • DB 필드에 사용할 이름이나 제약조건 가능 (nullable, length 등...)
  • @Id
    • 해당 필드를 PK로 지정하겠다는 의미이다.
  • @GeneratedValue
    • PK AutoIncrement설정이다. IDENTITY로 설정할지 SEQUENCE로 지정할지 정할 수 있다.
      MySQL AutoIncrement로 설정한 경우 INSERT하기 전까지 PK의 값을 모르기 때문에 예외적으로 persist시 바로 flush가 되어 DB에 저장되며 PK값을 가져온다. 그렇기 때문에 벌크 INSERT가 JPA로는 힘들다. (JDBC 네이티브 쿼리로 가능)
  • @Enumerate
    • DB에서는 varchar형태로 만들어지는 Enum Type
    • ORDINAL 사용 X → 순서를 DB에 저장해서 0, 1 이렇게 저장되기 때문에 맨앞에 0에 해당하는 PineApple이란 걸 추가하면 기존 0에 해당하는 Apple로 저장되어있던 데이터들이 같이 변경되지도 않기 때문에 Apple로 저장되어있는 0이 PineApple인 0이 되어버린다.
  • @Temporal
    • 날짜 필드라는 걸 알리는 의미이다.
    • LocalDate, LocalDateTime을 쓰는 경우 Temporal이 알아서 적용된다.
  • @Lob
    • BLOB, CLOB타입이라는 걸 알리는 의미이다.
  • @Transient
    • DB 필드에 매핑시키지 않고 메모리에서만 사용

 

📝DDL 설정

hibernate설정으로 자동적으로 엔티티에 매핑되는 테이블을 생성하거나 할 수 있고 방언 설정으로 다양한 RDBMS에 맞는 SQL로 변환되어서 실행된다. [운영 장비에는 절대 create, create-drop, update 사용하면 안된다.]

 

 

hibernate.hbm2ddl.auto

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

 

 

 

🔗 참고 및 출처

https://www.inflearn.com/course/ORM-JPA-Basic

반응형
반응형

📝ORM(Object-Relational Mapping)

객체와 관계형 데이터베이스를 매핑하는 기술 또는 방식입니다.

 

 

📝JPA(Java Persistence API)

자바에서 ORM을 사용하기 위한 표준 명세(스펙)입니다.

 

 

📝엔티티 (Entity)

데이터베이스의 테이블과 1:1로 매핑되는 자바 클래스로 모델이라고 생각하면 편하다

 

📝영속성 컨텍스트

엔티티를 영구 저장하는 환경으로 JPA에 있는 persist라는 영속화시키는 함수를 사용했을 때 이게 실제로 바로 DB에 저장되는 게 아닌 영속성 컨텍스트라는 공간에 저장한 이후에 트랜잭션이 끝나서 Commit이 되었을 때 변경내용이 DB에 저장되게 됩니다.

 

 

📝엔티티 생명주기

비영속

  • 영속성 컨텍스트와 전혀 관계 없는 새로운 상태 → persist 안 한 상태

영속

  • 영속성 컨텍스트에 관리되는 상태 → persist 한 상태

준영속

  • 영속성 컨텍스트에 저장되었다가 분리된 상태

삭제

  • 영속성 컨텍스트에서 삭제된 상태

 

📝영속성 컨텍스트 특징

1차 캐시 조회

Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

//1차에 캐시
em.persist(member);
//1차 캐시에서 조회
Member findMember = em.find(Member.class, "member1");

영속성 컨텍스트에서 먼저 저장되어있는 걸 조회하기 때문에 성능의 이점이 있다.

(트랜잭션 단위로 사용하기 때문에 캐시 내용이 남아있는게 아니라서 다른곳에서 조회하더라도 없기 때문에 DB에서 재조회해서 크게 이점이 되지 않는 경우가 많다.)

 

조회 과정

  1. 1차캐시에서 조회
  2. 없을 때 DB조회
  3. DB조회한 경우 1차캐시에 저장
  4. 반환

 

 

트랜잭션 쓰기 지연

트랜잭션 범위에서는 1차 캐시에 쌓아둔 이후에 transaction.commit()을 할 때 DB에 INSERT문이 날아가게 된다. (버퍼 기능을 이용한 속도 및 성능 이점)

 

 

변경 감지 (엔티티 수정) Dirty Check

EntityManager em = emf.createEntityManager();
EntityTransaction transaction = em.getTransaction();
transaction.begin();

// 영속 엔터티 조회
Member memberA = em.find(Member.class, "memberA");

// 영속 엔터티 수정
memberA.setUsername("hi");
memberA.setAge(10);

transaction.commit();

조회해온 엔터티를 setter로 수정만 해도 조회해온 실제 데이터도 커밋시 수정된다. (update문 필요 없음)

 

엔티티 삭제

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

 

 

플러시 (flush)

영속성 컨텍스트 변경 내용을 DB에 반영하는 과정으로 트랜잭션이 걸려있는 경우 Commit단계를 해야 최종 반영이 된다.

flush를 해도 영속성컨텍스트에 있는 1차 캐시는 계속 존재한다.

 

 

플러시 동작 시키는 법

  1. em.flush() - 직접 호출
  2. 트랜잭션 커밋 - 플러시 자동 호출
  3. JPQL 쿼리 실행 - 플러시 자동 호출

 

굳이 중간에 flush를 해야하나?

  1. ID가 지금 당장 필요할 때
    → IDENTITIY같은 전략에서는 INSERT가 나가야 PK가 생기기 때문에 바로 확보하려는 목적
  2. 대량 UPSERT의 경우
    → flush로 SQL을 보내고 clear로 1차 캐시 비워 메모리가 터지는 걸 막습니다.
  3. 변경사항이 이미 DB에 반영되어야 다음 쿼리가 진행 가능할 때
    → 동시성 문제 등

 

준영속 상태

JPA가 더이상 해당 엔티티를 관리하지 않아 변경감지, 1차캐시등을 이용할 수 없습니다.

 

 

준영속 상태 만들기

  1. em.detach()
    → 특정 엔터티만 준영속 상태로 변경
  2. em.clear()
    → 영속성 컨텍스트 모두 초기화
  3. em.close()
    → 영속성 컨텍스트 종료

 

 

🔗 참고 및 출처

https://www.inflearn.com/course/ORM-JPA-Basic

반응형
반응형

 

📝객제지향 개발 vs SQL 중심적인 개발

SQL에서의 쓰는 방식을 객체지향적인 개발에 딱 1:1로 매칭이 되지 않는다.

상속, 연관관계, 그래프 탐색등에서의 차이가 있다.

 

 

상속

기본적으로 DB에는 상속개념이 없기 때문에 객체지향적인 언어와 DB와는 완전이 호환이 되지않는다. 

 

 

연관관계

// DB
class Member{
    String id;
    Long teamId;
    String username;
}

// 객체
class Member{
    String id;
    Team team;
    String username;
}

DB의 경우 teamId가 같은 테이블에 있어서 한번에 조회가 가능하다. (연관 테이블 한번에 조회)

객체지향적으로 되어있는 경우는 참조가 기반이기 때문에 Team이라는 테이블도 조회해야 teamId값을 가져올 수 있다. (연관 테이블 각각 조회)

 

 

그래프탐색

DB에서 Member, Team을 Join한 경우 Member, Team은 가져올 수 있지만 Order는 가져올 수 없다. (특정한 모델만 가져옴)

객체지향처럼 Member.getOrder()이런식으로 처리하면 Member와 연관된 테이블을 다 가져오면 어마무시한 쿼리가 나가는 문제가 있다. (유연적인 모델 가져오기 가능)

 

 

위와 같은 문제를 해결하기 위해 ORM이라는 기술이 만들어졌습니다.

 

🔗 참고 및 출처

https://www.inflearn.com/course/ORM-JPA-Basic

반응형
반응형

📝Serializable

public class User implements Serializable {
    private String id;
    private int age;
    ...
}

자바 객체를 바이트 스트림으로 변환(직렬화) 하여 파일 저장, 네트워크 전송, 캐시 저장 등이 가능하게 하는 인터페이스입니다.

Kafka나 Redis에도 Key, Value설정을 하게 되는데 요즘에는 String, Json형태를 많이 쓰고 JdkSerializable은 잘 안 쓰게 됩니다.

 

 

📝@Serial

@Serial
private static final long serialVersionUID = 1L;

@Serial은 이 메서드나 필드가 직렬화와 관련된 것임을 컴파일러에게 알려주는 표시용 어노테이션입니다.

 

📝SerialVersionUid

@Serial
private static final long serialVersionUID = 1L;

직렬화된 객체의 버전 관리용 고유 ID입니다. 클래스 구조가 바뀌면(필드 추가/삭제 등) 역직렬화 시 오류가 날 수 있습니다.

그래서 내부 구조가 바뀐 경우 Generate해서 다른 값으러 바꿔줘야합니다.

(SerialVersionUID가 같은 경우는 호환이 되지만 다르면 InvalidClassException이 발생)

 

반응형
반응형
Spring Data  (상위 프로젝트)
   │
   └── Spring Data JPA  (JPA 전용 모듈)
           │
           ├── JPA (표준 ORM 스펙)
           │       │
           │       └── JPQL (JPA의 쿼리 언어)
           │
           └── QueryDSL (JPA 위에서 동작하는 쿼리 빌더)

 

 

📝 Spring Data

public interface UserRepository extends JpaRepository<User, Long> {
    User findByEmail(String email);
}

DB 접근 코드를 자동 생성해주는 스프링 프로젝트 묶음으로 Repository 인터페이스만 만들면 구현은 자동으로 해준다.

Spring Data JPA, Spring Data MongoDB, Spring Data Redis, Spring Data Elasticsearch 등이 있다

 

 

📝 Spring Data Jpa

findByNameAndAgeGreaterThan(String name, int age)
public interface UserRepository extends JpaRepository<User, Long> {

    @Query("select u from User u where u.age > :age and u.status = :status")
    List<User> findAdultsByStatus(@Param("age") int age,
                                  @Param("status") String status);
}

JPA를 쉽게 쓰게 해주는 Spring Data 모듈 Spring Data + JPA이 포함되어있다

 

 

📝 JPA

자바 객체와 DB 테이블을 매핑하는 표준 스펙

 

 

📝 JPQL

@Query("select u from User u where u.age > 20")
List<User> findAdult();

엔티티 기준으로 쓰는 SQL 같은 언어입니다.

 

 

📝 QueryDSL

QUser user = QUser.user;

List<User> result = queryFactory
    .selectFrom(user)
    .where(user.age.gt(20))
    .fetch();

JPQL을 자바 코드로 짜는 것

반응형