728x90

ManyToOne

다대일 관계의 반대 방향은 항상 일대다 관계이며 일대다 관계의 반대 방향도 다대일 관계다.

 

데이터베이스에서 일대다 관계에서 외래 키는 항상 다쪽이 가지고 있기 때문에

객체의 양방향 관계에서의 주인도 항상 다쪽이다.

 

양방향 연관관계는 항상 서로를 참조해야 한다.

 

setTeam, addMember 같은 편의 메서드를 작성하는 것이 좋지만

양쪽에 모두 작성하는 경우에는 무한루프에 빠질 수 있으니 검사해줘야 한다.

 

OneToMany

일대다 관계는 엔티티를 하나 이상 참조할 수 있어서 컬렉션 타입을 사용한다.

 

일대다 단방향 관계를 매핑할 때 @JoinColumn 어노테이션을 명시하지 않으면

연결 테이블을 중간에 두고 연관관계를 관리하는 조인 테이블 전략을 사용하여

반드시 명시해주는게 좋다.

 

일대다 단방향 매핑은 결국 외래키를 직접 가지고 있는 것이 아니기 때문에

연관관계 처리를 위한 UPDATE SQL을 추가로 실행해야 한다.

 

그래서 일대다 단방향 매핑은 반대 테이블의 외래 키를 관리해야 하기 때문에

성능과 관리적인 부분에서 좋지 않다.

 

일대다 단방향 매핑보다는 관리해야 하는 외래 키가 본인 테이블에 있는

다대일 양방향 매핑을 사용하는 것이 좋다.

 

OneToMany : ManyToOne (일대다 양방향)

다대일 양방향 매핑에서 연관관계의 주인은 항상 다쪽인 ManyToOne이기에

@ManyToOne 속성에는 mappedBy가 없는 것이다.

 

일대다 양방향 매핑을 사용할 일이 없지만 아래와 같이 매핑 할 수는 있다.

public class Team {
	// 기존 코드 생략
    @OneToMany
    @JoinColumn(name = "TEAM_ID")
    private List<Member> members = new ArrayList<Member>(); 
}

public class Member {
	// 기존 코드 생략
    @ManyToOne
    @JoinColumn(name = "TEAM_ID", insertable = false, updatable = false)
    private Team team;
}

위의 코드처럼 양쪽 모두에 같은 외래키를 사용하게 매핑을 하는데

이때 다쪽의 외래키를 읽기 전용으로 만들어 양쪽에서 같은 외래키를 관리하지 못하게 한다.

 

일대다 양방향이라기 보단 반대쪽은 읽기 전용으로 만들기 때문에

일대다 단방향 매핑이 가지는 단점을 그대로 가셔서 다대일 양방향 매핑을 사용하는 것이 좋다.

 

OneToOne

한 명의 회원이 하나의 장바구니만 가지는 것과 같은 관계로

아래와 같은 특징을 가지고 있다.

  • 일대일 관계에서는 반대 쪽도 항상 일대일 관계다.
  • 둘 중 어느 곳이나 외래 키를 가질 수 있다.

일대일 관계는 둘 다 외래키를 가질 수 있기 때문에 누가 외래키를 가질지 지정해야한다.

주 테이블에 외래 키를 두는 방법

주 객체가 대상 테이블을 참조하는 것처럼 주 테이블(회원)이 장바구니를 참조한다.

 

외래키를 객체 참조와 비슷하게 사용할 수 있어서 객체지향 개발자들이 선호하며

주 테이블만 확인해도 연관관계를 확인 할 수 있다.

@Entity
public class Member {
	// 기본 필드 생략
    @OneToOne
    @JoinColumn(name = "LOCKER_ID")
    private Locker locker;
}

@Entity
public class Locker {
    @Id
    @GeneratedValue
    @Column(name = "LOCKER_ID")
    private Long id;
}

위의 코드처럼 단방향으로 매핑하거나

@Entity
public class Locker {
    // 기존코드 생략
    @OneToOne(mappedBy = "locker")
    private Member member;
}

위의 코드처럼 양방향으로 매핑할 수 있다.

양방향 매핑이니 당연히 연관관계의 주인을 설정해줘야하는데

주 테이블인 멤버 엔티티의 외래키 컬럼을 주인으로 설정한다.

대상 테이블에 외래 키를 두는 방법

JPA에서는 일대일 관계에서 대상 테이블에 외래 키가 있는 단방향 관계를 지원하지 않기 때문에

대상 테이블에서 주 테이블로 단방향 관계를 수정하거나

양방향 관계로 만들고 대상 테이블을 연관관계의 주인으로 설정해야 한다.

@Entity
public class Member {
	// 기본 필드 생략
    @OneToOne(mappedBy = "member")
    private Locker locker;
}

@Entity
public class Locker {
    // 기본 필드 생략
    @OneToOne
    @JoinColumn(name = "MEMBER_ID")
    private Member member;
}

위와 같이 일대일 매핑에서 대상 테이블에 외래키를 두기 위해선

양방향으로 설정한 후에 연관관계의 주인을 주 테이블이 아닌

대상 테이블로 설정한다.

 

ManyToMany

관계형 데이터베이스에서는 테이블 2개로 다대다 관계를 표현할 수 없기 때문에

다대다 관계를 다대일 관계로 풀어내는 연결 테이블을 사용하지만

객체는 테이블과 다르게 객체 2개로 다대다 관계를 만들 수 있다.

단방향

@Entity
public class Member {
	// 기본 필드 생략
	@ManyToMany
    @JoinTable(name = "MEMBER_PRODUCT", joinColumns = @JoinColumn(name = "MEMBER_ID"),
    			inverseJoinColumns = @JoinColumn(name = "PRODUCT_ID"))
	private List<Product> products = new ArrayList<Product>();
}

위와 같이 @ManyToMany 어노테이션을 사용하여 다대다 단방향 관계를 설정할 수 있으며

@JoinTable 어노테이션을 사용하여 연결 테이블을 바로 매핑할 수 있다.

 

@JoinTable의 속성은 아래와 같다.

  • name : 연결 테이블을 지정
  • joinColumns : 현재 방향인 회원과 매핑할 조인 컬럼 정보를 지정 (회원 테이블의 기본키)
  • inverseJoinColumns : 반대 방향인 상품 테이블과 매핑할 조인 컬럼 정보를 지정 (상품 테이블의 기본키)

양방향

@Entity
public class Product {
	// 기본 필드 생략
	@ManyToMany(mappedBy = "products")
	private List<Member> members;
}

반대쪽 엔티티에도 매핑을 해주면 양방향 관계로 설정할 수 있다.

양방향 연관관계 편의 메서드

member.getProducts().add(product);
product.getMembers().add(member);

위와 같이 연관관계를 설정해주는 반복적인 작업들을

아래 코드와 같이 주 테이블에 메서드로 만들어 편리하게 사용하는 것이 좋다.

@Entity
public class Member {
	// 기존 코드 생략
    public void addProduct(Product product) {
    	products.add(product);
        product.getMembers().add(this);
    }
}

member.addProduct(product);

@ManyToMany 어노테이션을 사용한 매핑의 단점

어노테이션을 사용하여 연결 테이블을 자동으로 매핑하면 편리하지만

관계를 맺는 양쪽 테이블의 기본키만 외래키로 가지고 있기 때문에

추가적인 컬럼이 필요한 경우 추가할 수가 없다.

 

그래서 N:M 관계를 직접 연결 테이블을 추가하여 매핑하는 것이 좋다.

 

만약 회원과 주문이 N:M 관계라면

회원N : M주문 >> 회원1 : N:연결 테이블M : 1주문 형태로 바꿀 수 있다.

@Entity
public class Member {
	@OneToMany(mappedBy = "member")
    private List<MemberProducts> memberProducts;
}

@Entity
@IdClass(MemberProductId.class)
public class MemberProduct {
	@Id
	@ManyToOne
    @JoinColumn(name = "MEMBER_ID")
    private Member member;
    
    @Id
	@ManyToOne
    @JoinColumn(name = "PRODUCT_ID")
    private Product product;
}

@Entity
public class Product {
	@OneToMany(mappedBy = "product")
    private List<MemberProducts> memberProducts;
}

위의 코드는 다대다 양방향 관계를 코드로 표현한 예시다.

 

만약 회원상품 엔티티처럼 복합 키를 사용하는 경우에는

식별자 클래스가 추가로 필요하다.

public class MemberProductId implements Serializable {
	// 회원멤버 엔티티의 member / product와 연결
	private String member;
    private String product;
    
    // hashCode, equals 메서드 오버라이딩 하기
}

위의 코드처럼 복합 키는 별도의 식별자 클래스를 사용해서 만들어야 하며

Serializable을 구현하여야 하며 hashCode, equals 메서드를 구현해야 하고

기본 생성자가 있어야 하며 해당 클래스는 public이어야 한다.

@Entity
public class Order {
	@Id
    @GeneratedValue
    @Column(name = "ORDER_ID")
    private Long id;

	@ManyToOne
    @JoinColumn(name = "MEMBER_ID")
    private Member member;
    
	@ManyToOne
    @JoinColumn(name = "PRODUCT_ID")
    private Product product;
}

복합 키를 사용하는 방법은 번거롭기 때문에 위의 코드처럼

별도의 기본 키를 사용하는 것이 편하다.

더보기

실습

@Entity
public class Member {
	@Id
    @GeneratedValue
    @Column(name = "MEMBER_ID")
    private Long id;

	@Column(name = "MEMBER_NAME")
    private String name;
    
    @Column(name = "MEMBER_CITY")
    private String city;
    
    @Column(name = "MEMBER_STREET")
    private String street;
    
    @Column(name = "MEMBER_ZIPCODE")
    private String zipcode;
    
    @OneToMany(mappedBy = "member")
    private List<Order> orders = new ArrayList<Order>();
}

@Entity
@Table(name = "ORDERS")
public class Order {
	@Id
    @GeneratedValue
    @Column(name = "ORDERS_ID")
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "MEMBER_ID")
    private Member member;
    
    @OneToOne
    @JoinColumn(name = "DELIVERY_ID")
    private Delivery delivery;
    
    @Column(name = "ORDERDATE")
    private LocalDateTime orderDate = LocalDateTime.now();
    
    @Enumerated
    @Column(name = "ORDERS_STATUS")
    private OrderStatus status = OrderStatus.ACTIVE;
    
    public enum OrderStatus {
    	ACTIVE,
        STOP
    }
}

@Entity
public class Delivery {
	@Id
    @GeneratedValue
    @Column(name = "DELIVERY_ID")
    private Long id;
    
    @Column(name = "DELIVERY_CITY")
    private String city;
    
    @Column(name = "DELIVERY_STREET")
    private String street;
    
    @Column(name = "DELIVERY_ZIPCODE")
    private String zipcode;
    
    @Enumerated(EnumType.STRING)
    @Column(name = "DELIVERY_STATUS")
    private DeliveryStatus status = DeliveryStatus.START;
    
    public enum DeliveryStatus {
    	START,
        END
    }
}

@Entity
public class OrderItem {
	@Id
    @GeneratedValue
    @Column(name = "ORDER_ITEM_ID")
    private Long id;
    
    @ManyToOne
    @JoinColumn(name = "ORDER_ID")
    private Order order;
    
    @ManyToOne
    @JoinColumn(name = "ITEM_ID")
    private Item item;
    
    @Column(name = "ORDERPRICE")
    private int orderPrice;
    
    private int count;
}

@Entity
public class Item {
	@Id
    @GeneratedValue
    @Column(name = "ITEM_ID")
    private Long id;
    
    @Column(name = "ITEM_NMAE")
    private String name;
    
    @Column(name = "ITEMPRICE")
    private int itemPrice;
    
    private int stockQuantity;
}

@Entity
@IdClass(CategoryItemId.class)
@Table(name = "CATEGORY_ITEM")
public class CategoryItem {
	@Id
    @ManyToOne
    @JoinColumn(name = "ITEM_ID")
    private Item item;
    
    @Id
    @ManyToOne
    @JoinColumn(name = "CATEGORY_ID")
    private Category category;
}

public class CategoryItemId implements serializable {
	private String item;
    private String category;
    
    @Override
    // equals 및 hashCode 오버라이딩
}

@Entity
public class Category {
	@Id
    @GeneratedValue
    @Column(name = "CATEGORY_ID")
    private Long id;
}

'Back-End > JPA' 카테고리의 다른 글

연관관계 매핑 기초  (0) 2023.06.20
엔티티 매핑 : 학습을 위한 스키마 자동 생성하기  (0) 2023.06.19
엔티티 매핑 : 매핑 어노테이션  (0) 2023.06.19
영속성과 엔티티  (0) 2023.06.01
JPA 시작하기  (0) 2023.06.01
728x90

단방향 연관관계

 

 

만약 위와 같은 연관관계의 테이블이 있다고 했을 때

멤버는 하나의 팀에만 소속될 수 있지만

팀은 여러 명의 회원을 가질 수 있는 다대일(멤버:팀) 관계이다.

 

테이블과의 연관관계는 한 쪽에서만 외래키를 참조하면

어느 쪽에서든 조인할 수 있는 양방향 관계이다.

@Entity
@Table
@Getter @Setter
public class Member {
	@Id
    @Column(name = "member_id")
	private int id;
    
    @Column(name = "member_name")
    private String memberName;
    
    @ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;
}

@Entity
@Table
@Getter @Setter
public class Team {
	@Id
    @Column(name = "team_id")
	private int Id;
    
    @Column(name = "team_name")
    private String teamName;
}

하지만 객체에서는 참조하고자 하는 필드에서

팀 객체를 필드로 선언하여 연관관계를 맺는다.

 

테이블과는 다르게 객체에서의 연관관계의 방향성은

객체를 참조한 쪽에서만 접근할 수 있는 단방향 관계다.

 

참조를 통한 연관관계는 언제나 단방향이기에 양방향으로 만들려면

반대쪽에서도 객체 참조를 해줘야한다.

 

하지만 이건 엄밀히 따진 서로 다른 단방향 관계 두 개라고 보는게 맞다.

Member member1 = new Member(1,"고길동");
Member member2 = new Member(2,"신짱구");
Team team1 = new Team(1,"팀1");

member1.setTeam(team1); // Member 객체의 Setter를 통해 연관관계 설정
member2.setTeam(team1);

Team findTeam = member1.getTeam(); // Member 객체의 Getter를 통해 객체 참조 탐색

객체는 위와 같이 참조를 사용해 연관관계를 탐색하며

이러한 탐색을 객체 그래프 탐색이라고 한다.

다대일 관계 매핑

@ManyToOne
@JoinColumn(name = "team_id")
private Team team;

참조하고자 하는 객체의 필드에서 참조하려는 객체를 필드에 선언한다.

해당 필드에는 @ManyToOne 어노테이션과 @JoinColumn 어노테이션을 추가한다.

@ManyToOne

다대일 관계를 정의할 때 사용하는 어노테이션으로

하나의 팀(One)에 여러 멤버(Many)가 속할 수 있으니

위의 코드에서는 Many(Member) To One(Team)이라고 볼 수 있다.

 

아래와 같은 속성들을 사용할 수 있다.

  • optional : 기본값은 true로 false로 설정하면 연관된 엔티티가 항상 있어야 함
  • fetch : 글로벌 패치 전략을 설정한다. 기본값은 FetchType.Eager
  • cascade : 영속성 전이 기능을 사용한다
  • targetEntity : 연관된 엔티티의 타입 정보를 설정한다. 거의 사용하지 않음

@JoinColumn

외래키 를 매핑할 때 사용하는 어노테이션

 

아래와 같은 속성들을 사용할 수 있다.

  • name : 매핑할 외래 키 이름을 지정하며 미지정시 필드명_참조하는 기본 키 컬럼명
  • referencedColumnName : 외래 키가 참조하는 대상 테이블의 컬럼명
  • foreignKey(DDL) : 외래 키 제약조건을 직접 지정하며 테이블을 생성할 때만 사용
  • 추가로 @Column 어노테이션의 속성도 사용가능

 

연관관계 사용

저장

Team team1 = new Team(1, "팀1"); // 비영속
em.persist(team1); // 영속

Member member1 = new Member(1, "홍길동"); // 비영속
member1.setTeam(team1); // 비영속 연간관계 설정
em.persist(member1); // 영속

JPA에서 엔티티를 저장하려면 연관된 모든 엔티티까지 영속 상태여야 하기 때문에

팀과 멤버 엔티티를 모두 영속 상태로 만든다.

조회

연간관계가 있는 엔티티의 조회 방법은 두 가지가 있다.

Team findTeam = member1.getTeam();

첫 번째 방법은 이전에 살펴보았던 객체 그래프 탐색이 있다.

필드에 참조하고 있는 객체를 사용하여 조회한다.

String jpql = "select m from Member m join m.team t where t.name = :teamName"

List<Member> resultList = em.createQuery(jpql, Member.class)
	.setParameter("teamName", "팀1");
    .getResultList();

두 번째 방법은 객체지향 쿼리인 JPQL을 사용하는 방법이다.

 

객체 참조 방식으로는 데이터베이스의 조인을 사용할 수 없었지만

이 방식을 사용하면 조인을 사용하여 조회할 수 있다.

String jpql = "select m from Member m join m.team t where t.name = :teamName"

위의 코드를 살펴보면 변수 jpql에 쿼리문을 담아두는데

여기서 사용되는 m과 t는 각각 멤버와 팀 테이블의 별칭을 지정한 것이며

:teamName 같은 부분은 이후에 파라미터로 값을 받아올 부분이다.

 

쿼리문으로 변환하면 아래와 같다.

 

SELECT * FROM MEMBER AS M JOIN TEAM ON M.TEAM_ID = T.TEAM_ID

WHERE TEAM_NAME = "파라미터로 받아올 팀명";

List<Member> resultList = em.createQuery(jpql, Member.class)
	.setParameter("teamName", "팀1");
    .getResultList();

해당 팀에 속한 멤버 엔티티를 가져오는 쿼리문이니

멤버 엔티티를 담기 위한 멤버 타입의 리스트를 선언한다.

 

해당 리스트에 엔티티 매니저createQuery 메서드를 사용하여

기존에 만들어둔 쿼리문을 넘겨주고 반환 타입을 지정해준다.

 

쿼리문의 :teamName 파라미터에 값을 넘겨주기 위해

setParameter 메서드를 사용하여

어느 파라미터에 어떤 값을 넘겨줄지 지정해준다.

 

getResultList 메서드를 사용하여 쿼리문의 결과를

리스트 형태로 가져온다.

수정

Team team2 = new Team(2, "팀2");
em.persist(team2);

Member member = em.find(Member.class, 1);
member.setTeam(team2);

새로운 팀을 만들고 기존에 팀1에 속해있던 member_id가 1인 멤버를 팀2로 수정한다.

 

영속성 컨텍스트에서 Setter를 통해 엔티티의 값을 수정하면

변경 감지 기능이 자동으로 update 쿼리문을 생성해주기 때문에

위와 같이 간단한 작업만으로 수정할 수 있다.

제거

Member member = em.find(Member.class, 1);
member.setTeam(null);

연관관계 제거도 수정과 마찬가지로 기존의 연관관계를 null로 바꿔주기만 하면 된다.

연관된 엔티티 삭제

member1.setTeam(null);
member2.setTeam(null);
em.remove(team);

연관된 엔티티를 삭제하려면 기존에 있던 연관관계들을 모두 제거해야 한다.

 

만약 팀1을 삭제하려면 위의 코드처럼

팀1에 속한 멤버와의 연간관계를 모두 제거한 후에

해당 팀을 삭제해야 외래 키 제약조건에 걸리지 않고

연관된 엔티티를 삭제할 수 있다.

 

양방향 연관관계

양방향 연관관계 매핑

게시글 초반에 언급했던 것처럼 양방향 연관관계를 맺으려면

다른 반대 쪽에도 단방향 연관관계를 추가해주면 된다.

@Entity
@Table
@Getter @Setter
public class Team {
	@Id
    @Column(name = "team_id")
	private int Id;
    
    @Column(name = "team_name")
    private String teamName;
    
    @OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<Member>();
}

멤버는 여러 멤버가 하나의 팀에 속하는 것이니 @ManyToOne 어노테이션을 사용했다면

팀은 하나의 팀이 여러 멤버를 가지니 @OneToMany 어노테이션을 사용한다.

 

또한 멤버는 하나의 팀만 가지니 팀 객체를 하나만 가지는 필드를 선언했다면

팀은 여러 멤버를 가지는 리스트 필드를 선언한다.

일대다 컬렉션 조회

Team team = em.find(Team.class, 1);

List<Member> members = team.getMembers();

일대다 컬렉션도 마찬가지로 객체 그래프 탐색을 이용해서 조회한다.

 

연관관계의 주인

데이터베이스에서는 외래 키 하나 만으로 양방향 관계를 맺을 수 있지만

객체는 두 개의 단방향 관계를 사용하여 양방향 관계를 흉내내는 것만 가능하다.

 

하지만 객체를 양방향으로 만들면 참조는 둘인데 외래 키가 하나인 경우가 되기에

둘 사이에 차이가 발생하는 문제가 생긴다.

 

이를 해결하기 위해 두 객체의 연관관계 중 하나를 정해서

테이블의 연관관계를 관리하게 하고 이러한 역할을 연관관계의 주인이라 한다.

양방향 매핑 규칙

양방향 매핑을 하는 경우 두 연관관계 중 하나를 무조건 연관관계의 주인으로 정해야 한다.

 

연관관계의 주인만 데이터베이스 연관관계와 매핑되어 외래 키를 관리할 수 있게 만들고

나머지 반대 쪽은 읽기만 할 수 있게 만들어 위에서 언급한 차이를 없앤다.

public class Member {
	// 기존 코드 생략
	@ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;
}

public class Team {
	// 기존 코드 생략
	@OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<Member>();
}

위의 코드처럼 연관관계의 주인인 Member 엔티티의 필드에는

mappedBy 속성을 사용하지 않고

 

주인이 아닌 Team 엔티티의 필드에는

mappedBy 속성을 사용하여 연관관계의 주인을 Member 엔티티의 team 필드로 지정한다.

 

즉, 연관관계의 주인은 테이블에 외래 키가 있는 곳으로 지정해야 한다.

 

멤버는 팀을 옮길 수 있으니 수정권한이 필요하지만

팀은 멤버를 옮길 수 없으니 수정권한이 필요 없다고 볼 수 있다.

 

다대일과 일대다 관계에서는 항상 다 쪽이 외래키를 가진다.

 

양방향 연관관계 저장

Team team1 = new Team(1, "팀1");
em.persist(team1);

Member member1 = new Member(1, "홍길동");
member1.setTeam(team1);
em.persist(member1);

위의 코드를 보면 단방향 연관관계에서의 코드와 완전히 일치하지만

이 코드가 양방향 연관관계를 저장하는 방식이다.

 

연관관계의 주인만 연관관계의 작업이 가능하기에

팀 엔티티가 아닌 멤버 엔티티를 통해 작업하는 것이다.

 

양방향 연관관계의 주의점

Member member1 = new Member(1, "홍길동");
em.persist(member1);

Team team1 = new Team(1, "팀1");
team1.getMembers().add(member1);
em.persist(team1);

위의 코드처럼 연관관계의 주인이 아닌 곳에서 값을 입력하는 경우에는

읽기전용이기 때문에 값이 추가되지 않는다.

public class Member {
	// 기존 코드 생략
	@ManyToOne
    @JoinColumn(name = "team_id")
    private Team team;
}

public class Team {
	// 기존 코드 생략
	@OneToMany(mappedBy = "team")
    private List<Member> members = new ArrayList<Member>();
}

객체까지 고려해서 주인이 아닌 곳에도 값을 입력하는 것이 좋고,

객체의 양방향 연관관계는 반드시 양쪽 모두 관계를 맺어줘야 한다.

연관관계 편의 메서드

기존의 연관관계의 주인인 쪽의 Setter 메서드는 자신 쪽의 객체의 값만 수정하여

반대 쪽의 객체에는 값이 입력되지 않는다.

 

이러한 문제를 해결하기 위해 Setter를 수정하는데

이렇게 양쪽에 값을 추가하게 수정된 Setter를 편의 메서드라고 부른다.

 

수정된 코드는 아래와 같다.

public void SetTeam(Team team) {
	// 기존의 팀이 있다면 연관관계를 제거
	if(this.team != null) {
    	this.team.getMembers().remove(this);
    }
    // 주인뿐만 아니라 반대 쪽의 객체에도 값을 추가
    this.team = team;
    team.getMembers().add(this);
}

기존에 어떤 팀과 연관관계가 존재한다면 연관관계를 끊고

다른 팀과 연결해야하니 연관관계가 존재하는지 확인 후에

주인 쪽 객체에만 값을 추가하는 것이 아니라 양쪽 모두에 값을 추가한다.

 

정리

단방향 매핑만으로도 테이블과 객체의 연관관계 매핑은 완료되었기에

무조건 양방향 매핑을 할 필요는 없다.

 

양방향 매핑은 그저 반대 쪽에서도 객체 그래프 탐색 기능을

사용할 수 있다는 점을 빼면 장점이 없기 때문에

상황을 봐가면서 추가 하는 것이 좋다.

'Back-End > JPA' 카테고리의 다른 글

다양한 연관관계 매핑  (0) 2023.06.21
엔티티 매핑 : 학습을 위한 스키마 자동 생성하기  (0) 2023.06.19
엔티티 매핑 : 매핑 어노테이션  (0) 2023.06.19
영속성과 엔티티  (0) 2023.06.01
JPA 시작하기  (0) 2023.06.01
728x90

JPA는 클래스의 매핑정보와 데이터베이스 방언을 사용해

스키마를 자동으로 생성하는 기능을 지원한다.

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

해당 속성을 추가하면 애플리케이션 실행 시

데이터베이스에 테이블을 자동으로 생성해준다.

 

실제 서버 운영에서 사용할 만한 기능은 아니기 때문에

개발 환경이나 학습용으로만 참고하는 것이 좋다.

<property name="hibernate.show_sql" value="true" />

해당 속성을 추가하여 sql이 어떻게 작성되는지 볼 수 있다.

속성

create

기존 테이블 삭제 후 새로 생성

create-drop

create + 애플리케이션 종료 시 테이블 삭제

update

데이터베이스와 매핑정보를 비교해 변경 사항만 수정

validate

데이터베이스와 매핑정보를 비교해 차이가 있으면 경고 후 실행하지 않음

none

자동 생성 기능을 사용하지 않고 싶을 때 사용

 

'Back-End > JPA' 카테고리의 다른 글

다양한 연관관계 매핑  (0) 2023.06.21
연관관계 매핑 기초  (0) 2023.06.20
엔티티 매핑 : 매핑 어노테이션  (0) 2023.06.19
영속성과 엔티티  (0) 2023.06.01
JPA 시작하기  (0) 2023.06.01
728x90

@Entity

@Entity
public class Member {  }

테이블과 매핑할 클래스는 해당 어노테이션을 필수로 붙여야 한다.

속성

name

기본값은 클래스명을 사용하며, 직접 지정도 가능하다.

주의사항

기본 생성자는 필수다.

final 클래스, enum/interface/inner 클래스에는 사용할 수 없다.

필드에도 final을 사용하면 안 된다.

 

@Table

@Entity
@Table(name = "Member", uniqueConstraints = {@UniqueConstraint (name = "NAME_AGE_UNIQUE", columnNames = {"NAME", "AGE"})})
public class Member {  }

엔티티와 매핑할 테이블을 지정하는 어노테이션이다.

생략 시 엔티티 이름을 테이블로 사용한다.

속성

name

매핑할 테이블의 이름을 지정하는 속성으로 기본값은 엔티티 이름을 사용

catalog

카탈로그 기능이 있는 데이터베이스에서 카탈로그를 매핑

schema

스키마 기능이 있는 데이터베이스에서 스키마를 매핑

uniqueConstraints

DDL 생성 시에 유니크 제약조건을 만든다. 2개 이상의 복합 유니크 제약 조건도 생성 가능

스키마 자동 생성 기능으로 DDL을 만들 때만 사용

@ID

기본 키를 매핑할 때 사용하는 어노테이션

기본 키 생성 전략

직접 할당

기본 키를 애플레이케션에서 직접 할당

@Id 어노테이션만 사용

@Id
@Column(name = "ID")
private String id;

자동 생성

대리 키를 사용하는 방식

@Id 어노테이션과 @GeneratedValue 어노테이션을 사용

  • IDENTITY : 기본 키 생성을 데이터베이스에 위임(MySQL, PostgreSQL, SQL Server에서 사용)
    • 만약 MySQL의 기본키 속성에 AUTO_INCREMENT가 할당되어 있다면 해당 속성을 사용
    • 데이터베이스에 값을 저장한 후에 기본 키를 알 수 있음
    • 기본 키 값을 얻어오기 위해 데이터베이스의 추가적인 조회가 필요
    • 영속 상태가 되기 위해선 식별자 값이 필요하기 때문에 쓰기 지연이 적용되지 않음
    • 기본적으로 통신 횟수가 2회
    • 하이버네이트는 JDBC3에 추가된 Statement.getGeneratedKey 메서드를 사용하여 한 번만 통신
@Id
@Column(name = "ID")
@GeneratedValue(startegy = GenerationType.IDENTITY)
private String id;
  • SEQUENCE : 데이터베이스 시퀀스를 사용해서 할당 (Oracle, PostgreSQL, DB2, H2)
    • 유일한 값을 순서대로 생성
    • 사용하기 위해 시퀀스를 매핑해야 함
    • IDENTITY와 비슷하지만 식별자를 조회한 후에 엔티티에 할당한다.
    • 그렇기 때문에 영속성 컨텍스트에 저장이 가능하다.
    • 영속성 컨텍스트를 사용할 수 있기 때문에 쓰기 지연 기능 사용 가능
    • 데이터베이스의 시퀀스를 조회하는 작업이 필요해서 통신 횟수가 2회다.
//SEQUENCE 전략 사용
@Entity
@SequenceGenerator ( //시퀀스 생성기 등록
	name = "BOARD_SEQ_GENERATOR", // 시퀀스 생성기 이름
    sequenceName = "BOARD_SEQ", // DB의 시퀀스 이름
    initialValue = 1, //시작값
    allocationSize = 1 //증가하는 범위, 기본값 50
    //catalog //카탈로그명
    //schema //스키마명
)
public class Board {
	@Id
    @GeneratedValue(startegy = GenerationType.SEQUENCE,
    				generator = "BOARD_SEQ_GENERATOR") //시퀀스 생성기 사용
    private long Id;
}
  • TABLE : 키 생성 테이블을 사용
    • 기본키의 이름과 값 컬럼을 가진 키 생성 전용 테이블을 생성하여 사용
    • 테이블을 사용하여 모든 데이터베이스에서 사용할 수 있음
    • 테이블 전략 조회와 값을 증가시키기 위한 작업 때문에 통신 횟수가 1회 더 많다.
sequence_name == pkColumnName next_val == valueColumnName
BOARD_SEQ == pkColumnValue 2 == valueColumnValue
MEMBER_SEQ 10
PRODUCT_SEQ 50
//TABLE 전략 사용
@Entity
@SequenceGenerator ( //테이블 시퀀스 생성기 등록
	name = "BOARD_SEQ_GENERATOR", //테이블 시퀀스 생성기 이름
    sequenceName = "MY_SEQUENCE", //DB의 시퀀스 테이블 이름
    pkColumnValue = "BOARD_SEQ", //키로 사용할 값 이름
    allocationSize = 1 //증가하는 범위, 기본값 50
)
public class Board {
	@Id
    @GeneratedValue(startegy = GenerationType.TABLE,
    				generator = "BOARD_SEQ_GENERATOR") //테이블 시퀀스 생성기 사용
    private long Id;
}
  • AUTO : 선택한 데이터베이스에 따라 위의 3가지 전략 중 하나를 자동으로 선택
    • 오라클이면 SEQUENCE, MySQL이면 IDENTITY
    • 데이터베이스를 변경해도 코드를 수정하지 않아도 상관 없음
    • 키 생성 전략이 확정되지 않은 개발 초기 및 프로토타입 개발 시 유용
    • 스키마 자동 생성 기능을 사용하면 시퀀스나 키 생성용 테이블을 하이버네이트가 만들어 줌

기본키의 조건

  • null 허용하지 않음
  • 유일해야 함
  • 변하지 않아야 함

자연 키

비즈니스에 의미가 있는

사용자의 ID, 주민등록번호, 이메일 등

 

대리 키 (대체 키)

비스니스와 관련이 없는

오라클 시퀀스, auto_increment, 키 생성 테이블 등

 

비즈니스 규칙은 생각보다 쉽게 변하기 때문

자연 키는 기본키의 조건을 만족하지 않게될 수도 있어서

대리 키를 권장

@Column

@Column(name = "NAME", nullable = false, length = 10)
private String username;

컬럼을 매핑할 때 사용하는 어노테이션으로

옵션을 사용해 DDL을 생성할 수 있다.

속성

name

매핑할 컬럼명을 지정

기본값은 변수명을 따라감

nullable

null 값을 허용할지 지정

기본값은 true(허용)

length

문자의 최대 길이를 지정

insertable

해당 필드를 데이터베이스에 저장할지 지정

false면 데이터베이스에 저장하지 않으며 읽기 전용일 때 사용

기본값은 true

updatable

수정 내용을 데이터베이스에 반영할지 지정

insertable과 마찬가지로 읽기 전용일 때 사용

기본값은 true

table

하나의 엔티티를 두 개 이상의 테이블에 매핑할 때 사용

unique

하나의 컬럼에 유니크 제약조건을 걸 때 사용

두 개 이상의 컬럼에 유니크 제약조건을 걸려면

@Table 어노테이션의 uniqueConstraints 옵션을 사용

columnDefinition

데이터베이스의 컬럼 정보에 맞게 자동으로 설정

precision, scale

BigDecimal 타입에서 사용하며

각각 소수점 포함 전체 자릿수, 소수의 자릿수를 지정

double, float 타입에는 적용되지 않음

아주 큰 숫자나 정밀한 소수를 다루는 경우에만 사용

 

주의사항

@Column 어노테이션을 생략 시에

자바 기본 타입(int, char 등)은 null을 허용하지 않게 설정되고

객체 타입(Integer, String 등)은 null을 허용한다.

@Enumerated

enum RoleType {
	ADMIN, USER
}
@Enumerated(EnumType.STRING)
private RoleType roleType;

enum 타입을 매핑할 때 사용하는 어노테이션

속성

value

EnumType.ORDINAL : enum 순서를 데이터베이스에 저장  (기본값)

EnumType.STRING : enum 이름을 데이터베이스에 저장  (권장)

순서와 이름

위의 enum 클래스에는 순서대로 ADMIN, USER가 정의되어 있다.

 

ORDINAL 속성을 사용하면 각각 0, 1번이 되는데

데이터베이스에 저장되는 크기는 작지만,

이미 저장된 ENUM의 순서를 변경할 수 없다.

 

STRING 속성을 사용하면 각각의 이름을 그대로 사용한다.

저장된 ENUM의 순서가 바뀌거나 추가되어도 상관 없지만,

데이터베이스에 저장되는 크기가 크다.

@Temporal

@Temporal(Temporal.DATE)
private Date date;

@Temporal(Temporal.TIME)
private Date time;

@Temporal(Temporal.TIMESTAMP)
private Date timestamp;

Date, Calendar 같은 자바의 날짜 타입을 매핑할 때 사용하는 어노테이션

해당 어노테이션 생략 시 timestamp로 매핑한다.

속성

value(필수)

Temporal.DATE : 날짜를 데이터베이스의 date 타입과 매핑

Temporal.TIME : 시간을 데이터베이스의 time 타입과 매핑

Temporal.TIMESTAMP : 날짜와 시간을 데이터베이스의 timestamp 타입과 매핑

@Lob

@Lob
private String lobString //CLOB

@Lob
private byte[] lobByte //BLOB

문자열의 길이에 제한이 없는 경우 사용하는 BLOB, CLOB 타입으로 매핑할 때 사용하는 어노테이션

속성

지정할 수 있는 속성은 없지만

매핑하는 필드 타입에 따라 문자면 CLOB으로 매핑되고

나머지는 BLOB으로 매핑한다.

@Transient

@Transient
private Integer temp;

특정 필드를 매핑하지 않고 싶은 경우에 사용하는 어노테이션

 

데이터베이스에 저장되지도 않고 조회되지도 않으므로

객체에 임시로 데이터를 보관하고 싶을 때 사용

@Access

@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가 엔티티에 접근하는 방식을 지정할 때 사용하는 어노테이션

속성

AccessType

FIELD : 필드에 직접 접근하는 방식으로 접근 권한이 private이여도 가능

PROPERTY : 접근자(Getter)를 사용하여 접근하는 방식

 

기본키의 위치에 따라서 AccessType 속성을 적용한 것과 같기 때문에 생략해도 상관없다.

주의사항

nullable, length, uniqueConstraints rkxdms 속성은

DDL을 자동 생성할 때만 사용되고 JPA의 실행 로직에는 영향을 주지 않음

 

'Back-End > JPA' 카테고리의 다른 글

연관관계 매핑 기초  (0) 2023.06.20
엔티티 매핑 : 학습을 위한 스키마 자동 생성하기  (0) 2023.06.19
영속성과 엔티티  (0) 2023.06.01
JPA 시작하기  (0) 2023.06.01
엔티티 매핑 기본  (0) 2023.05.31
728x90

영속성 컨텍스트

엔티티 매니저로 엔티티를 저장하거나 조회 시 엔티티를 영구적으로 보관하고 관리하는 저장소 같은 개념

 

엔티티 매니저를 생성할 때 한 개의 영속성 컨텍스트가 만들어지고

엔티티 매니저는 이런 영속성 컨텍스트에 접근하고 관리하는 도구라고 볼 수 있음

em.persist(member);

persist 메서드는 정확하게는 엔티티를 저장하는 메서드가 아닌 영속성 컨텍스트에 저장하는 메서드인 것

 

엔티티의 생명주기

비영속

영속성 컨텍스트와 아무런 관계가 아닌 상태

엔티티 객체를 생성만 한 상태

 

영속

영속성 컨텍스트에 저장된 상태

엔티티 매니저를 통해 영속성 컨텍스트에 저장된 상태

즉, persist 메서드를 사용한 상태

 

영속성 컨텍스트에 의해 관리가 되고 있는 상태

 

준영속

영속 상태였다가 분리된 상태

// 영속성 컨텍스트에서 분리
em.detach(member);

// 자동으로 영속성 컨텍스트에서 분리되는 경우
em.close(); // 영속성 컨텍스트 닫기
em.clear(); // 영속성 컨텍스트 초기화

영속성 컨텍스트에 의해 관리가 되고 있지 않는 상태

 

삭제

엔티티가 영속성 컨텍스트와 데이터베이스에서 삭제된 상태

em.remove(member);

 

영속성 컨텍스트의 특징

  • 엔티티식별자 값(@Id 어노테이션으로 매핑된 기본키 컬럼)으로 구분하기 때문에 영속 상태는 식별자 값이 반드시 있어야 하고 없을시 예외 발생
  • 트랜잭션이 커밋되는 순간 영속성 컨텍스트의 엔티티들이 데이터베이스에 반영 (flush)
  • 영속 상태의 엔티티가 저장되는 1차 캐시라는 내부의 캐시를 가지고 있음
  • 엔티티의 동일성을 보장 (같은 엔티티(식별자가 같은)인 경우 주소가 같음)

영속성 컨텍스트의 엔티티 조회

비영속 상태의 엔티티를 영속성 컨텍스트에 영속 시키면

해당 엔티티의 식별자 값과 엔티티1차 캐시에 저장

 

즉, 1차 캐시의 키는 식별자 값이므로 영속성 컨텍스트의 모든 CRUD 작업의 기준은 기본키 값이다.

1차 캐시에서 식별자 값으로 엔티티를 조회한 후에 찾는 엔티티가 없는 경우 데이터베이스에서 조회

find(엔티티클래스, 기본키)

 

데이터베이스에서 엔티티를 조회한 경우

조회한 엔티티를 생성한 후 1차 캐시에 저장하여 영속 상태의 엔티티를 반환

 

엔티티 등록 - 쓰기 지연

엔티티 매니저는 트랜잭셕이 커밋되기 전까지 SQL을 내부 쿼리 저장소에 저장해 두고

트랜잭션이 커밋되는 순간 저장해 둔 SQL을 데이터베이스에 전달

  1. 트랜잭션 시작
  2. SQL 작업 수행
  3. SQL 작업 저장
  4. 트랜잭션 커밋
  5. 영속성 컨텍스트의 작업 내용을 DB와 동기화하는 flush 실행
  6. 실제 데이터베이스의 트랜잭션을 커밋

엔티티 수정 - 변경 감지

UPDATE 쿼리문을 일일히 작성할 필요 없이

변경 사항을 데이터베이스에 자동반영해주는 변경 감지 기능이 있기 때문에

수정하고자 하는 엔티티를 조회해서 Setter 메서드 등을 통해 값을 수정하기만 하면 됨

 

JPA의 변경 감지 기능은 엔티티의 영속성 컨텍스트 보관 시의 상태를 저장해두는 스냅샷

플러시 시점의  엔티티를 비교해주는 기능이다.

  1. 트랜잭션 커밋 시 엔티티 매니저 내부에서 플러시 호출
  2. 현재 엔티티와 스냅샷을 비교하여 변경 사항 조회
  3. 변경된 엔티티 존재 시 수정 쿼리를 생성해서 쓰기 지연 저장소에 전달
  4. 쓰기 지연 저장소의 SQL문을 데이터베이스에 전달
  5. 실제 데이터베이스의 트랜잭션을 커밋

영속 상태의 엔티티에만 적용되는 기능

 

엔티티 삭제

  1. 삭제할 엔티티 조회
  2. remove 메서드를 통해 엔티티 삭제 SQL 저장

플러시(flush)

플러시는 영속성 컨텍스트의 변경 내용을 데이터베이스에 반영하는 역할을 수행하는

즉, 작업한 쿼리 내용을 데이터베이스에 최종적으로 저장하는 역할이다.

  1. 변경 감지를 통해 영속성 컨텍스트에 있는 모든 엔티티를 기존 스냅샷과 현재 상태와 비교
  2. 수정 된 엔티티들은 쓰기 지연 저장소에 쿼리를 저장
  3. 데이터베이스에 저장소의 쿼리를 전달

플러시는 위의 과정을 통해 이루어진다.

 

영속성 컨텍스트의 플러시는 트랜잭션을 커밋하거나 객체지향 쿼리 실행자동으로 호출되며

엔티티 매니저의 flush 메서드를 통해 직접 호출할 수도 있다.

tx.commit();

트랜잭션을 커밋할 때 변경사항(SQL)을 데이터베이스에 전달하지 않으면

데이터베이스는 어떤 변경사항이 있는지 모르기 때문에 반영이 되지 않으므로

커밋이 실행되기 전에 자동으로 플러시를 호출한 후 커밋이 진행된다.

TypedQuery<Member> query = em.createQuery("select m from Member m", Member.class);

JQPL 같은 객체지향 쿼리는 SQL로 변환되어 영속성 컨텍스트가 아닌 데이터베이스에서

작업을 수행하는데 이 때 변경된 내용을 플러시하지 않으면 데이터베이스는

변경된 내용이 반영되지 않은 상태기 때문에 원활한 SQL 작업을 할 수 없기 때문에

객체지향 쿼리를 사용할 때도 자동으로 플러시를 호출

em.flush();

플러시를 직접 호출해서 사용하는 것은 영속성 컨텍스트를 강제로 플러시하는 것이기 때문에

테스트 같은 경우를 제외하고 거의 사용하지 않음

FlushModeType.AUTO
FlushModeType.COMMIT

기본값은 AUTO로 커밋과 쿼리를 실행할 때 자동으로 플러시를 호출하는 상태이고

COMMIT으로 설정 시 커밋할 때만 자동으로 플러시를 호출한다.

 

플러시 후영속성 컨텍스트가 비워지는 것은 아니고

영속성 컨텐스트와 데이터베이스 간의 동기화가 이루어지는 것 뿐이다.

 

준영속

영속성 컨텍스트의 관리에서 벗어난 엔티티

영속성 컨텍스트의 기능을 사용할 수 없는 상태의 엔티티다.

em.detach(entity);

엔티티 매니저의 detach 메서드를 사용해 수동으로 엔티티를 준영속 상태로 만들 수 있고

해당 엔티티와 관련된 1차 캐시와 쓰기 지연 저장소의 SQL 같은 모든 정보가 삭제

 

당연히 영속성 컨텍스트에서 분리된 상태니 데이터베이스에도 반영되지 않는다.

em.clear();

엔티티 매니저의 clear 메서드는 영속성 컨텍스트 자체를 초기화하는 메서드로

detach 메서드를 영속성 컨텍스트의 모든 엔티티에 적용한다고 볼 수 있다.

em.close();

영속성 컨텍스트를 종료하는 메서드로 영속성 컨텍스트가 사라지니

당연히 영속성 컨텍스트에 영속되어 있던 엔티티들이 준영속 상태로 만들어진다.

 

detach와 clear를 통해 개발자가 직접적으로 준영속 상태로 만드는 경우는 드물고

대부분 영속성 컨텍스트의 종료를 통해 자동으로 준영속 상태로 만든다.

 

영속성 컨텍스트에 속하지 않고 기능들도 사용할 수 없으니 비영속 상태와 가깝다고 볼 수 있지만

식별자 값이 없을 수도 있는 비영속 상태와는 다르게 영속된 상태였으므로

반드시 식별자 값을 갖고 있다는 차이점이 있다.

em.detach(entity);
Member newMember = em.merge(entity);

// 준영속 영속 상태인 엔티티를 햇갈리지 않기 위해
// 아래와 같은 코드로 짜는 것이 좋음
entity = em.merge(entity);

준영속 상태의 엔티티를 다시 영속 상태로 만들기 위해서는 병합(merge)을 사용하면 되는데

merge 메서드는 준영속 상태의 엔티티로 새로운 영속 상태의 엔티티를 반환해준다.

 

즉, newMember라는 엔티티의 값이 수정되고 데이터베이스에 반영되는 것이지

준영속 상태인 entity 엔티티는 수정은 가능하지만 데이터베이스에 반영되지 않는다.

// 비영속 엔티티 생성
Entity entity = new Entity();

// 비영속 병합
entity = em.merge(entity);

병합은 준영속 뿐만 아니라 비영속 상태의 엔티티도 영속 상태로 만들 수 있다.

 

merge 메서드의 파라미터로 전달 받은 엔티티의 식별자 값으로 영속성 컨텍스트를 우선적으로 조회 후

없는 경우 데이터베이스에서 조회하고 그럼에도 없으면 새로운 엔티티를 생성해서 반환한다.

 

병합하려는 엔티티가 있든 없든 상관 없이 무조건 적으로 엔티티를 반환하기 때문에

준영속, 비영속 상태와 상관 없이 모두 영속 상태로 만들 수 있는 것

'Back-End > JPA' 카테고리의 다른 글

엔티티 매핑 : 학습을 위한 스키마 자동 생성하기  (0) 2023.06.19
엔티티 매핑 : 매핑 어노테이션  (0) 2023.06.19
JPA 시작하기  (0) 2023.06.01
엔티티 매핑 기본  (0) 2023.05.31
JPA  (0) 2023.05.31
728x90

1. 엔티티 매니저 팩토리 생성하기

JPA를 시작하기 위해선 엔티티 매니저 팩토리를 생성해야 하므로

설정 정보(persistence.xml)를 통해 엔티티 매니저 팩토리를 생성

EntityManagerFactory emf = Persistence.createEntityManagerFactory("영속성 유닛명");

EntityManagerFactory는 인터페이스이기 때문에 이를 구현한

Persistence 클래스의 createEntityManagerFactory 메서드를 사용해 엔티티 매니저 팩토리 생성

 

하이버네이트 같은 JPA 구현체들은 팩토리 생성 시 커넥션풀도 생성

 

엔티티 매니저 팩토리는 생성 비용이 크기 때문에 한 번만 생성하고 공유해서 사용해야 함

 

2. 엔티티 매니저 생성하기

엔티티 매니저 팩토리를 통해 엔티티 매니저를 생성

JPA의 기능 대부분을 엔티니 매니저가 제공 (CRUD)

EntityManager em = emf.createEntityManager();

내부에 데이터소스를 유지하며 데이터베이스와 통신하는 역할로 가상의 데이터베이스라고 볼 수 있음

데이터베이스 연결이 필요한 시점에만 커넥션을 얻음 (보통 트랜잭션 시작 시 커넥션 획득)

 

동시성 문제가 생기기 때문에 엔티티 매니저를 스레드 간에 공유/재사용하면 안됨

 

3. 트랜잭션 생성 및 사용

데이터의 변경은 항상 트랜잭션 안에서 이루어져야 함

트랜잭션은 엔티티 매니저를 통해 받아올 수 있음

EntityTransaction tx = em.getTransaction();

트랜잭션과 예외 처리를 통해 효율적인 데이터 처리를 할 수 있음

try {
    tx.begin(); //트랜잭션 시작
    crud(엔티티매니저);  //CRUD 작업수행
    tx.commit();//트랜잭션 커밋
} catch (Exception e) { //작업 실패 시
    e.printStackTrace();
    tx.rollback(); //트랜잭션 롤백
} finally {
    em.close(); //엔티티 매니저 종료
}

begin 메서드를 통해 트랜잭션을 시작한 후, 필요한 CRUD 작업을 수행한다.

작업이 정상적으로 수행되지 못한 경우에는 예외가 발생하여 rollback 메서드를 통해 작업 내용이 롤백되고,

작업이 정상적으로 수행된다면 commit 메서드를 통해 작업 내용을 반영한다.

 

엔티티 매니저는 사용이 끝났으면 항상 종료시켜야 하기 때문에

반드시 실행되는 finally 영역에서 종료시켜준다.

 

4. CRUD

새로운 엔티티를 추가(INSERT) 하려는 경우

추가하고자 하는 테이블의 객체(엔티티)를 생성

Member member = new Member(); // 회원 엔티티 생성

해당 엔티티의 Setter 메서드를 통해 엔티티의 값을 수정

member.setId("id1");
member.setUsername("이름");
member.setAge(2);

엔티티 매니저의 persist 메서드에 엔티티를 전달하여 INSERT

//엔티티를 등록(CREATE - INSERT)
//inser into member(id, name, age) values(id1, 이름, 2)
em.persist(member);

기존 엔티티를 수정(UPDATE)하려는 경우

수정하고자 하는 엔티티 객체의 Setter 메서드를 통해 값을 변경

//UPDATE MEMBER SET AGE = 20, NAME = "이름" WHERE ID = 'ID1'
member.setAge(20);

테이블을 조회(READ)하려는 경우

조회한 테이블을 담을 동일 엔티티 타입의 참조변수를 선언

엔티티 매니저의 find 메서드에 조회하고자 하는 테이블과 검색조건을 매개변수로 전달

참조변수를 통해 해당 엔티티의 Getter를 통해 값을 조회

//조회(메서드 사용)(READ)
//select name, age from member
Member findMember = em.find(Member.class, "id1");
System.out.println("findMember=" + findMember.getUsername() + ", age=" + findMember.getAge());

createQuery 메서드를 통해 쿼리문을 사용하여 조회 할 수도 있으며,

getResultList 메서드로 리스트 타입으로 변환 가능

리스트에는 조회한 엔티티의 주소값이 담겨 있으므로 주소값을 통해 Getter, Setter 사용가능

//조회(쿼리 사용)(READ)
//SELECT * FROM MEMBER WHERE ID = 'ID1'
List<Member> members = em.createQuery("select m from Member m", Member.class).getResultList();
System.out.println("members.size=" + members.size());
System.out.println(members.get(0).getUsername());

엔티티를 삭제하려는 경우

엔티티 매니저의 remove 메서드에 삭제하고자 하는 엔티티 객체를 전달

//DELETE FROM MEMBER WHERE ID = 'ID1'
em.remove(member);

객체지향 쿼리 언어 JPQL을 통해 조회하는 경우

JPQL은 엔티티 객체를 대상으로 쿼리를 수행

TypedQuery<Member> query = em.createQuery("select m from Member m", Member.class);
List<Member> memberList = query.getResultList();

TypedQuery 타입 참조 변수를 선언 후에

createQuery 메서드의 매개변수로 수행할 쿼리문과 반환타입을 전달하고

사용하기 편하게 getResultList 메서드를 통해 List에 담고

마찬가지로 주소값을 통해 Getter, Setter를 사용한다.

 

5. 종료

사용이 끝난 엔티티 매니저와 팩토리는 반드시 종료해야 함

em.close();
emf.close();

 

'Back-End > JPA' 카테고리의 다른 글

엔티티 매핑 : 학습을 위한 스키마 자동 생성하기  (0) 2023.06.19
엔티티 매핑 : 매핑 어노테이션  (0) 2023.06.19
영속성과 엔티티  (0) 2023.06.01
엔티티 매핑 기본  (0) 2023.05.31
JPA  (0) 2023.05.31
728x90

객체 매핑

MEMBER 테이블이 존재하고 ID(기본키), NAME, AGE 컬럼이 존재한다는 가정하에 매핑을 해보자

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

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

    @Column(name = "NAME")
    private String username;

    private Integer age;

    @Column(nullable = false, name = "LAST_MODIFIED_AT")
    private LocalDateTime modifiedAt = LocalDateTime.now();
    
    @Transient
    private String email;
}

매핑 어노테이션

@Entity

지정 클래스를 테이블과 매핑하겠다고 JPA에 전달하는 어노테이션

 

@Table(name = "테이블명")

매핑할 테이블이 어떤 것인지 알려주는 어노테이션

생략 시 클래스 이름(엔티티명)을 테이블명으로 자동 매핑

 

@Id

엔티티 클래스의 기본키를 매핑하는 어노테이션

 

@GeneratedValue(strategy = 사용할 전략 타입)

기본키의 생성전략을 지정해주는 어노테이션이다.

 

GenerationType.AUTO

GenerationType.SEQUENCE

GenerationType.IDENTITY

 

@Column(name = "컬럼명")

엔티티 클래스의 컬럼을 매핑하는 어노테이션

생략 시 필드명을 컬럼명으로 자동 매핑

 

name 속성 외에도

unique, nullable, updatable 등의 속성도 지원한다.

 

@Temporal

java.util.Date / java.util.Calendar 타입으로 매핑하기 위해서 필요한 어노테이션

LocalDateTime을 사용하는 경우에는 생략 가능

LocalDateTime은 TIMESTAMP와 매핑된다.

 

@Transient

테이블과 매핑하지 않는 열을 설정할 때 사용하는 어노테이션

매핑되지 않았기에 데이터베이스에 저장도 되지 않고, 조회도 되지 않는다.

보통 임시 데이터를 사용하기 위한 용도로 사용한다.

 

@Enumerated(EnumType.STRING)

enum 타입과 매핑할 때 사용하는 어노테이션

ORDINAL과 STRING타입을 가질 수 있으며

각각 enum의 순서를 나타내는 숫자 테이블을 저장하거나

enum의 이름을 테이블에 저장한다.

 

STRING 타입을 순서유지를 위해 권장한다.

'Back-End > JPA' 카테고리의 다른 글

엔티티 매핑 : 학습을 위한 스키마 자동 생성하기  (0) 2023.06.19
엔티티 매핑 : 매핑 어노테이션  (0) 2023.06.19
영속성과 엔티티  (0) 2023.06.01
JPA 시작하기  (0) 2023.06.01
JPA  (0) 2023.05.31
728x90

JPA

자바 진영의 ORM 기술 표준 프레임워크

애플리케이션과 JDBC 사이에서 동작

애플리케이션을 SQL 중심이 아닌 객체 중심으로 개발할 수 있게 도와줌

대표적인 JPA의 구현체는 하이버네이트가 있음

ORM

Object - Relational Mapping

객체와 관계형 데이터베이스 간의 차이를 중간에서 해결해주는 프레임워크

객체와 관계형 데이터베이스를 매핑한다는 뜻

 

직접적인 SQL 사용의 단점

  • 데이터베이스는 데이터 중심의 구조를 가지기 때문에 객체 구조와는 다름
  • 구조가 다르기 때문에 객체를 데이터베이스에 직접적으로 저장 및 조회 불가능
  • 개발자가 직접 변환 작업을 해줘야 함
  • 위와 같은 작업을 위해서 반복적으로 작성해야 할 코드가 너무 많음
  • SQL 중심의 개발로 인해 코드의 수정이 필요한 경우 변경해야 할 코드가 많음
  • 객체와 엔티티가 강한 의존 관계를 가지고 있음 (엔티티를 신뢰할 수 없음)
  • 같은 로우를 조회할 때마다 같은 인스턴스를 반환하도록 구현하기 쉽지 않음

JPA 사용의 장점

  • SQL 중심의 개발이 아닌 JPA  API를 사용하여 개발
  • 매핑 정보를 바탕으로 메서드 호출로만 적절한 SQL문을 수행해줌
  • 객체의 저장, 조회, 수정 등도 간단하게 가능
  • 객체와 데이터베이스 간의 구조 불일치를 중간에서 처리해 줌(패러다임 불일치)
  • 즉, 데이터베이스 구조에는 없는 자바의 상속, 참조 같은 객체지향 개념을 자동으로 처리해준다는 것
  • 연관된 객체를 사용하는 시점에 적절한 SQL문을 사용하여 연관된 객체를 신뢰할 수 있음
  • 같은 트랜잭션인 경우 같은 객체가 조회되도록 보장
  • 반복적인 코드와 SQL문을 개발자가 직접 작성할 필요가 없어 생산성을 향상 시킴
  • SQL과 JDBC와의 의존성이 떨어져 유지보수가 편함
  • 애플리케이션과 DB 사이에서 동작하여 성능 최적화 기회가 있음
  • 마찬가지로 사이에서 추상화된 데이터 접근 계층을 통해 사용하는 DB가 바껴도 DB만 변경하면 됨
  • 즉, 특정 DB 기술에 종속되지 않음

더 알아보면 좋은 내용

MyBatis

Spring JdbcTemplate

Hibernate

JDBC

'Back-End > JPA' 카테고리의 다른 글

엔티티 매핑 : 학습을 위한 스키마 자동 생성하기  (0) 2023.06.19
엔티티 매핑 : 매핑 어노테이션  (0) 2023.06.19
영속성과 엔티티  (0) 2023.06.01
JPA 시작하기  (0) 2023.06.01
엔티티 매핑 기본  (0) 2023.05.31

+ Recent posts