- 객체와 관계형 데이터베이스의 데이터를 자동으로 매핑해주는 것을 말한다.
- 객체 지향 프로그래밍은 클래스를 사용하고, 관계형 데이터베이스는 테이블을 사용
- 객체 모델과 관계형 모델 간에 불일치가 존재
- ORM을 통해 객체 간의 관계를 바탕으로 SQL을 자동으로 생성하여 불일치를 해결
- 객체 지향적인 코드로 인해 더 직관적으고 비즈니스 로직에 더 집중할 수 있다.
- 재사용 및 유지보수의 편리성이 증가한다.
- DBMS에 대한 종속성이 줄어든다.
- 완벽한 ORM으로만 서비스를 구현하기가 어렵다.
- 잘못 구현된 경우 속도 저하 및 일관성이 무너질 수 있다.
- 어플리케이션과 데이터베이스는 ORM이라는 개념이다.
- JPA는 자바 ORM의 표준 스택으로 인터페이스 형태로 제공하여 준다.
- JPA의 실제 구현 클래스를 모아 놓은 것이 Hibernate 그중 스프링에서 자주 사용되는 것을 모아놓은 것이 Spring data jpa이다.
dependency추가하기
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
-
yaml수정하기spring: jpa: show-sql: true # SQL 사용 로그 출력 properties: hibernate: format_sql: true # SQL 로그 출력을 이쁘게 generate-ddl: false hibernate: ddl-auto: create # Spring 실행시 TABLE 생성 규칙 defer-datasource-initialization: true # Spring 2.5 부터 resource의 SQL을 Spring 생성시 초기화 하지 않기 때문에 초기화 하기 위해서 `true`로 설정
show-sql을true로 설정하면 속도가 느려질 수 있으나, JPA에서 사용하는 쿼리를 확인하기 쉽다.ddl-auto는 실제 운영시에는none,validate옵션으로만 사용해야 한다.- create : 기존 테이블 삭제 후 다시 생성 (drop + create)
- create-drop : create와 같으나 종료 시점에 테이블 drop
- update : 변경분만 반영
- validate : 엔티티와 테이블이 정상 매핑 되었는지만 확인
- none : 사용하지 않음
- 주의 사항
- 운영 장비에서는 create, create-drop, update 를 사용해서는 않된다.
- 개발 초기 : create, create-drop
- 테스트 서버 : update, validate
- 스테이징과 운영 서버 : validate, none
- 로컬 환경을 제외하고는 직접 쿼리 명령을 하는 것이 좋다.
-
JpaRepository 생성하기 Spring에서 JPA는 Entity를 생성하고 JpaRepository 인터페이스를 생성하는 것으로 사용할 수 있다.
public interface MemberRepository extends JpaRepository<Member, Long> { }
JpaRepository<T, ID>: T는 Entity class, ID에는 Entity 클래스의 ID Type을 지정하면 된다.
List<Menu> findByRestaurant_Name(String name);AUTO 전략 / 기본키 매핑 정리 매핑 전략GenerationType.AUTO는 선택한 데이터베이스 방언에 따라 IDENTITY, SEQUENCE, TABLE 전략 중 하나를 자동으로 선택한다.
- SEQUENCE: 데이터베이스 시퀸스에서 식별자 값을 획득한 수 영속성 컨텍스트에 저장 (Oracle)
- IDENTITY: 데이터베이스에 엔티티를 저장해서 식별자 값을 획득한 후 영속성 컨텍스트에 저장한다. (IDENTITY 전략은 테이블에 데이터를 저장해야 식별자 값을 획득할 수 있다.) (MySQL)
- TABLE: 데이터베이스 시퀸스 생성용 테이블에서 식별자 값을 획득한 후 영속성 컨텍스트에 저장
@GeneratedValue(strategy = GenerationType.UUID) // 사용 (springboot 3.0 & hibernate6 에서만 지원)
private UUID id; // uuid로 생성
@GeneratedValue(strategy = GenerationType.UUID)
private String id; // varchar로 생성
@Id
@GeneratedValue(generator = "uuid2")
@GenericGenerator(name = "uuid2", strategy = "uuid2")
private String id- name: 데이터베이스의 Column과 Object name을 별도로 매핑하기 위해서 사용
- nullable: default는 null 이지만 false로 두면 not null로 테이블을 생성하게 된다.
- unique: column 하나만 unique 키로 둘때 사용
- length: default=255 Varchar 등의 크기를 결정
- insertable / updatable: DDL 뿐만 아니라 DML에도 영향을 끼친다. 값이 false이면 insert / update 시에 반영되지 않는다.
- columnDefinition(DDL): 데이터베이스 컬럼 정보를 직접 줄 수 있다
Enum type을 컬럼으로 생성함으로써 에러를 방지할 수 있다.
- Entity에서 컬럼시에 Enum column을 생성하기 위해서는
@Enumerated어노테이션을 이용할 수 있다. @Enumerated어노테이션을 이용해서 컬럼생성시EnumType은String으로 해주는 것이 좋다.EnumType을String으로 하지 않았을때의 문제점EnumType을String으로 하지 않았을때 컬럼은 Enum 파라미터의 index 번호 ex)0, 1, 2...으로 생성된다.- 만약 중간에 Enum의 순서가 변경되거나 새로운 값이 추가되었을때 저장된 데이터베이스 값의 의미가 달라지는 일이 발생할 수 있다.
@Enumerated(EnumType.STRING)
private Category category;- 임베디드 타입은 새로운 값 타입을 직접 정의해서 사용한는 JPA의 방법이다.
- 임베디드 타입을 사용하여 더욱 객체지향적인 코드를 만들 수 있다.
- 코드의 재사용성을 향상 시킬 수 있다.
Member Entity의 Address Embedded class
@Data @AllArgsConstructor @NoArgsConstructor @Embeddable public class Address { private String city; // 시 private String district; // 구 @Column(name = "address_detail") private String detail; // 상세주소 private String zipcode; // 우편번호 }
@Embeddable어노테이션을 붙인다.Member Entity Column
@Embedded private Address address // @Embedded 어노테이션 사용
- Embedded 클래스의 재사용성을 높이기 위해서
@AttributeOverride어노테이션을 사용할 수도 있다.
@AttributeOverrides({
@AttributeOverride(name = "city", column = @Column(name = "home_city")),
@AttributeOverride(name = "district", column = @Column(name = "home_district")),
@AttributeOverride(name = "detail", column = @Column(name = "home_address_detail")),
@AttributeOverride(name = "zipCode", column = @Column(name = "home_zip_code"))
})
@Embedded
private Address address;해당 어노테이션을 붙인 Column은 영속성 처리에서 제외되기 때문에 DB에 반영되지 않는다.
@Transient
private String column;@PrePersist:Insert메소드가 호출되기 전에 실행@PrePersist public void prePersist() { this.createdDate = LocalDateTime.now(); this.updatedDate = LocalDateTime.now(); }
@CreatedDate@LastModifiedDate
Entity클래스가 상속 받을 BaseEntity클래스를 생성하여 공통된 컬럼에 대한 작업을 쉽게 처리할 수 있다.
@MappedSuperclass: 해당 클래스의 필드를 상속받는 클래스의 필드로 추가EntityListeners(AuditingEntityListener.class):Entity의 변화를Listener를BaseEntity에 부착
@Getter
@Setter
@Entity
public class Member {
@Id
@GeneratedValue
@Column(name = "member_id")
private Long id;
private String username;
@OneToOne
@JoinColumn(name = "locker_id")
private Locker locker;
}
@Getter
@Setter
@Entity
public class Locker {
@Id
@GeneratedValue
@Column(name = "locker_id")
private Long id;
private String name;
}@Getter
@Setter
@Entity
public class Member {
@Id
@GeneratedValue
@Column(name = "member_id")
private Long id;
private String username;
@OneToOne
@JoinColumn(name = "locker_id")
private Locker locker;
}
@Getter
@Setter
@Entity
public class Locker {
@Id
@GeneratedValue
@Column(name = "locker_id")
private Long id;
private String name;
@OneToOne(mappedBy = "locker")
private Member member;
}@Entity
public class Member {
@Id @GeneratedValue
@Column(name = "member_id")
private Long id;
private String username;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
}
@Entity
public class Team {
@Id
@GeneratedValue
@Column(name = "team_id")
private Long id;
private String name;
}회원은 으로 팀 엔티티를 참조할 수 있지만 반대로 팀에는 회원을 참조하는 필드가 없다.
Member.team 필드로 회원 테이블의 TEAM_ID 외래키를 관리한다.
@Getter
@Setter
@Entity
public class Member {
@Id
@GeneratedValue
@Column(name = "member_id")
private Long id;
private String username;
@ManyToOne
@JoinColumn(name = "team_id")
private Team team;
public void setTeam(Team team) {
this.team = team;
// 무한 루프 방지
if (!team.getMembers().contains(this)) {
team.getMembers().add(this);
}
}
}
@Getter
@Setter
@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<>();
public void addMember(Member member) {
this.members.add(member);
if (member.getTeam() != this) { // 무한 루프 방지
member.setTeam(this);
}
}
}1:N 연관관계 단점
- 연관관계 관리를 위해 추가로 UPDATE SQL을 실행
- 연관 관계의 주인이 FK를 관리
- 본인 테이블에 FK가 있으면 엔티티의 저장과 연관관계 처리를 INSERT SQL 한 번으로 끝낼 수 있지만, 다른 테이블에 외래 키가 있으면 연관관계 처리를 위한 UPDATE SQL을 추가로 실행해야 한다. 위와 같은 이유로 잘 사용되지 않는다.
@ManyToMany는 실무에서 사용되지 않는다. 대신 1:N N:1의 관계를 이용해서 N:N 관계를 구현한다.
@Getter
@Setter
@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;
private int orderAmount;
}@Getter
@Setter
@Entity
public class Member {
@Id
@GeneratedValue
@Column(name = "member_id")
private Long id;
private String username;
@OneToMany(mappedBy = "member")
private List<Order> orders = new ArrayList<>();
}
@Getter
@Setter
@Entity
public class Product {
@Id @Column(name = "product_id")
private String id;
private String name;
@OneToMany(mappedBy = "product")
private List<Order> orders = new ArrayList<>();
}




