class Member{
@Id
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@ManyToOne
@JoinColumn(name="TEAM_ID")
private Team team; //Team 객체와 연관관계를 맺음
}
class Team{
@Id
@Column(name = "TEAM_ID")
private Long id;
private String name;
//팀은 회원을 알 수 없다.
}
}
회원은 하나의 팀에만 소속될 수 있다.
회원과 팀은 다대일 관계이다.
member -> team: member.getTeam() 은 가능하지만
team -> member : 불가능하다.
만약 @JoinColumn을 생략하게 된다면 외래 키를 찾을 때 기본 전략을 사용한다.
기본 전략: 필드명 + _ + 참조하는 테이블의 컬럼 명
필드명 ( team) + _ + 참조하는 테이블의 컬럼명(TEAM_ID)
team_TEAM_ID 외래키를 사용한다.
인스턴스 관계
public static void main(String[] args) {
//생성자(id, 이름)
Member member1 = new Member("member1", "회원1");
Member member2 = new Member("member2", "회원2");
Team team1 = new Team("team1", "팀1");
//member1과 member2를 team1에 소속
member1.setTeam(team1);
member2.setTeam(team1);
//객체 그래프 탐색을 통한 연관관계 탐색
Team findTeam = member1.getTeam();
}
양방향 연관 관계
양방향 연관관계: 엔티티의 관계가 서로를 참조하는 것
class Member{
//연관관계 주인 클래스
@Id
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@ManyToOne
@JoinColumn(name="TEAM_ID")
private Team team; //Team 객체와 연관관계를 맺음
}
class Team{
@Id
@Column(name = "TEAM_ID")
private Long id;
private String name;
@OneToMany(mappedBy = "team")//양방향 연관관계 설정
private List<Member> memberList = new ArrayList<>();
}
Team 엔티티는 컬렉션을 추가하면 된다.
Team의 입장에서 바라보는 일대다, @OneToMany를 설정한다
mappedBy로 team과 연관이 있는 것을 알려준다
컬렉션을 매핑한다. 관례로 ArrayList로 초기화하여 NPE방지한다.
NPE: Null Pointer Exception
반대 방향으로 객체 그래프 탐색이 가능하다.
//팀 조회
Team findTeam = em.find(Team.class, team.getId());
// 역방향으로 멤버들 조회
int memberSize = findTeam.getMembers().size();
@OneToMany의 mappedBy가 필요한 이유
객체에서는 양방향 연관관계가 없고 서로 다른 단방향 연관관계를 2개 묶어 양방향인것 처럼 보이게한다.
양방향으로 매핑하면 서로를 참조하게 되는데 이때 객체의 연관관계를 관리하는 포인트가 2곳으로 늘어난다.
객체의 참조는 둘인데 외래키는 하나이므로 둘 사이의 차이가 발생한다.
그래서 JPA에서는 두 객체 연관관계 중 하나를 정해서 테이블의 외래키를 관리해야하는데 이것이
연관관계의 주인(Owner)이다.
주인은 mappedBy를 사용하지 않는다
주인이 아닐 경우 mappedBy를 사용하여 주인을 지정한다.
외래키가 있는 곳을 주인으로 설정한다.
객체에 둘다 정보를 업데이트해도 연관관계 주인것만 DB에 영향을 준다
주인이 아닌쪽은 읽기만 가능하다.
객체지향적, 순수 객체 상태를 고려해서 항상 양쪽에 값을 설정하자!
양방향 매핑시 무한루프를 주의한다.( Lombok이 자동으로 만드는 toString()사용하지말자)
JPA:자바에서 ORM(Object-Relational Mapping) 기술 표준으로 사용하는 인터페이스 모음이다.
@Entity
해당클래스를 테이블과 매핑한다고 JPA에게 알린다.
@Table
엔티티 클래스에 매핑할 테이블 정보를 지정한다.
만약 생략하게 되면 클래스 이름을 테이블 이름으로 매핑한다.
@Table 옵션
name
테이블 명
catalog
테이블 카테고리
schema
테이블 스키마
uniqueConstraints
컬럼값 유니크 제약조건
indexes
인덱스 생성
@Access
JPA가 엔티티 데이터에 접근하는 방식을 지정합니다.
@Entity
@Access(AccessType.FIELD)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
@Entity
@Access(AccessType.PROPERTY)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
@Access 옵션
AccessType.Field
필드에 직접 접근
AccessType.PROPERTY
getter을 통해 접근
@Id
엔티티 클래스의 필드를 테이블의 기본키(PK)에 매핑합니다.
사용된 필드를 식별자 필드라 하며 주로 @GenerateValue와 같이 이용해서 식별키를 어떤 전략으로 생성하는지 명시한다.
@Column
엔티티 클래스의 필드를 컬럼에 매핑한다.
name
컬럼 이름
unique
유니크 여부(DDL)
nullable
null 허용 여부(DDL)
insertable
추가 가능 여부(DDL, DML)
updateable
수정 가능 여부(DDL, DML)
table
테이블 이름(하나의 엔티티를 두개 이상의 테이블에 매핑할 때 사용
columnDefinition
데이터베이스 컬럼 정보 직접 부여(DDL)
length
컬럼 사이즈(default:225) (DDL)
percision
소수 정밀도(default:0) (DDL)
scale
소수점 이하 자리수(default:0) (DDL)
* DDL: 테이블, 뷰, 인덱스 및 프로시저와 같은 데이터베이스 및 개체의 구조를 정의
* DML: 데이터베이스 내의 데이터를 조작하는 데 사용
@GenerateValue
데이터베이스에 의해 자동으로 생성될 값에 대한 전략을 지정
보통 식별키 생성 전략으로 많이 사용
@GenerateValue 옵션AUTO: 특정 데이터베이스에 맞게 자동으로 생성되는 방식
@Id //primary key 설정
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
IDENTITY: 기본키 생성 방식 자체를 데이터베이스에 위임하는 방식 데이터베이스에 의존 방식(MySQL MariaDB 등)
@Id //primary key 설정
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
TABLE: 데이터베이스 종류 상관없이 별도의 키를 생성해주는 채번 테이블을 이용하는 방법
성능이 좋지않아 권장하진않는다.
@TableGenerator가 필요하다
@Entity
@TableGenerator(name = "MEMBER_SEQ_GENERATOR",
table = "MY_SEQUENCES",
pkColumnValue = "MEMBER_SEQ",
allocationSize = 1)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.TABLE,
generator = "MEMBER_SEQ_GENERATOR")
private Long id;
}
속성
설명
기본
name
식별자 생성기 이름
필수
table
키생성 테이블명
hibernate_sequence
pkColumnName
시퀸스 컬럼명
sequence_name
valueColumnName
시퀸스 값 컬럼명
next_val
pkColumnValue
키로 사용할 값 이름
엔티티이름
initialValue
초기 값, 마지막으로 생성된 값이 기준
0
allocationSize
시퀸스 한 번 호출에 증가하는 수(성능 최적화에 사용)
50
catalog, schema
DB catalog, schema 이름
uniqueConstraints(DDL)
유니크 제약 조건을 지정할 수 있음
SEQUENCE: 데이터베이스의 시퀸스를 이용해서 식별키 생성(ORACLE 등 시퀸스 제공 DB)
@SequenceGenerator가 필요하다
@Entity
@SequenceGenerator(name = “MEMBER_SEQ_GENERATOR",
sequenceName = “MEMBER_SEQ", //매핑할 데이터베이스 시퀀스 이름
initialValue = 1,
allocationSize = 1)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.SEQUENCE,
generator = "MEMBER_SEQ_GENERATOR")
private Long id;
}
name
식별자 생성기 이름(필수)
sequenceName
DB에 등록되어 있는 시퀸스 이
initialValue
DDL 생성 시에만 사용됨, 시퀀스 DDL을 생성할 때 처음 시작하는 수를 지정(Default:1)
allocationSize
시퀀스 한 번 호출에 증가하는 수(성능 최적화에 사용됨) 데이터베이스 시퀀스 값이 하나씩 증가하도록 설정되어 있으면 이 값 을 반드시 1로 설정해야 한다(Defaule: 50)
catalog, schema
데이터베이스 catalog, schema 이름
@Index
인덱스를 지정합니다
@Entity
@Table(name = "MEMBER", indexes={
@Index(name = "idx__name__age", columnList = "name, age", unique = true)
})
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
}
스프링 부트는 spring-boot-starter-validation 라이브러리를 넣으면 자동으로 Bean Validator를 인지하고 스프링에 통합
스프링 부트는 자동으로 글로벌 Validator로 등록한다.
LocalValidatorFactoryBean 을 글로벌 Validator로 등록한다. 이 Validator는 @NotNull 같은 애노테이션을 보고 검증을 수행한다. 이렇게 글로벌 Validator가 적용되어 있기 때문에, @Valid , @Validated 만 적용하면 된다.
검증 오류가 발생하면, FieldError , ObjectError 를 생성해서 BindingResult 에 담아준다.
하지만 직접 글로벌 Validator를 직접 등록하면 스프링 부트는 Bean Validator를 글로벌 Validator 로 등록하지 않는다.
@Bean Validation은 등록할때와 수정할때 요구사항이 다를수있어 이를 해결하기 위해 groups, Form 전송 객체 분리 를 사용한다.
BeanValidation groups 기능
인터페이스를 생성하고 어노테이션에 groups를 추가한다.
groups는 실무에서 잘 사용하지 않는다고 한다.
등록시 폼에서 전달하는 데이터가 Item 도메인 객체와 딱 맞지 않기 때문이다.
SaveCheck 인터페이스
package hello.itemservice.domain.item;
public interface SaveCheck {
}