
스프링 데이터의 공통적인 기능에서 JPA의 유용한 기술이 추가된 기술이다. (Spring Date + JPA) 비즈니스 로직에 더 집중할 수 있게 데이터베이스 사용 기능을 클래스 레벨에서 추상화하여 스프링 데이터에서 제공하는 인터페이스를 통해서 스프링 데이터를 사용할 수 있다. CRUD를 포함한 여러 메서드가 포함되어있으며, 알아서 쿼리를 만들어주는 것 뿐 아니라 페이징 처리, 메소드 이름으로 자동으로 쿼리 빌딩 기능, 데이터베이스의 특성에 맞춰 기능을 확장해 제공하는 기술 등이 제공된다.
스프링 데이터 JPA에서는 스프링 데이터의 인터페이스인 PagingAndSortingRepository를 상속받아 JpaRepository 인터페이스를 만들었으며, JPA를 더 편리하게 사용하는 메소드를 제공한다.
// 메소드 호출로 엔티티 상태 변경
@PersistenceContext
EntityManager em;
public void join() {
// 기존에 엔티티 상태를 바꾸는 방법
Member member = new Member(1L, "홍길동");
em.persist(member);
}
스프링 데이터 JPA를 사용하면 레파지토리 역할을 하는 인터페이스를 만들어 데이터베이스의 테이블 조회, 수정, 생성, 삭제 같은 작업을 간단히할 수 있다.
기본 CRUD 메소드를 사용 : JpaRepository 인터페이스를 작업자가 만든 인터페이스에서 상속받고, 제네릭에는 관리할 <엔티티 이름, 엔티티 기본키의 타입>을 입력
public interface MemberRepository extends JpaRepository<Member, Long> {
}
@Service
public class MemberService {
@Autowired
private MemberRepository memberRepository;
public void test() {
// 1. 생성(Create)
memberRepository.save(new Member(1L, "길동"));
// 2. 조회(Read)
Optional<Member> member = memberRepository.findById(1L); // 단건 조회
List<Member> memberList = memberRepository.findAll(); // 전체 조회
// member
// 3. 삭제(Delete)
memberRepository.deleteById(1L);
}
}
save() : 메소드를 호출해 데이터 객체를 저장, 전달 인수로 엔티티 Member를 넘기면 반환값으로 저장한 엔티티를 반환받을 수 있다.findById() : 메소드에 id를 지정해 엔티티를 하나 조회할 수 있다.findAll() : 전체 엔티티 조회deleteById() : 메소드에 id를 지정하면 해당 엔티티를 삭제할 수 있다.@Getter
@Entity // 1. 엔티티로 지정
@NoArgsConstructor(access = AccessLevel.PROTECTED) // 2. 기본 생성자
@AllArgsConstructor
public class Member {
@Id // 3. id 필드를 기본 키로 지정
@GeneratedValue(strategy = GenerationType.IDENTITY) // 4. 기본키를 자동으로 1씩 증가
@Column(name = "id", updatable = false)
private Long id;
@Column(name = "name", nullable = false) // 5. name이라는 not null 컬럼과 매핑
private String name;
}
@Repository // 엔티티에 있는 데이터들을 조회하거나, 저장, 변경, 삭제를 할 때 사용하는 인터페이
public interface MemberRepository extends JpaRepository<Member, Long> {
}
@Entity 어노테이션이 붙은 클래스는 JPA가 관리하는 테이블이 되고, JPA는 이 어노테이션 정보를 바탕으로 데이터베이스 스키마를 자동으로 생성한다.
@Table 어노테이션은 엔티티와 매핑할 테이블을 지정
// 회원 엔티티 클래스
import javax.persistence.*;
import java.util.Date;
@Entity
@Table(name = "member")
public class Member {
@Id
@Column(name = "id")
private String id;
@Column(name = "name" columnDefinition="varchar(255)")
private String userName;
@Column(name = "description", columnDefinition="text")
private String description;
private Integer age;
@Column(name = "is_delete", columnDefinition="tinyint(1)")
private Boolean isDelete; // true, false, tinyint(1) 0, 1
}
@Id 어노테이션으로 지정하며 데이터베이스에서의 Primary Key와 동일한 의미로 사용한다. JPA는 여러가지 방법으로 기본키를 매핑할 수 있도록 지원한다.
@Entity
public class Member {
@Id
@Column(name = "ID")
private String id;
...
}
애플리케이션에서 직접 할당하는 대신에 데이터베이스가 생성해주는 값을 사용하기 위한 매핑 방법으로는
가장 많이 사용하는 전략으로 기본 키 생성을 데이터베이스에 위임하는 전략이다.
// DDL
CREATE TABLE board (
id INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
data VARCHAR(255)
);
// DML
INSERT INTO board (data) VALUES ('A');
INSERT INTO board (data) VALUES ('B');
| id | data |
|---|---|
| 1 | A |
| 2 | B |
id컬럼을 비워두면 데이터베이스가 순서대로 값을 채워준다.
@Entity
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
}
// Identity 사용 코드
private static void logic(EntityManager em) {
Member member = new Member();
em.persist(board);
System.out.println("board.id = " + board.getId());
}
데이터베이스 기본 키(Primary Key) 조건 - 모두 만족해야 한다.
기본키 선택 전략
비즈니스 요구사항은 계속 변화하지만 테이블은 한 번 정의하면 변경하기 어렵기 때문에 외부의 요인에 영향(비즈니스 요구사항 변경 등)을 받지 않는 대리 키의 사용을 권장한다.
| 매핑 어노테이션 | 설명 |
|---|---|
| @Column | 컬럼 매핑 |
| @Enumerated | 자바의 enum 타입 매핑 |
| @Temporal | 날짜 타입 매핑 |
객체 필드를 테이블 컬럼에 매핑하는 어노테이션으로 가장 많이 사용되며 기능도 많지만 속성 중에 name, nullable이 주로 사용되고 나머지는 잘 사용되지 않는 편이다.
@Entity
public class Member {
@Id
@Column(name = "ID")
private String id;
@Column(name = "NAME", nullable = false, length = 10)
private String name;
...
}
@Column 어노테이션의 속성
자바 enum 타입의 상수값을 테이블 컬럼에 매핑하는 어노테이
public enum RoleType {
ADMIN, USER
}
// 회원 엔티티 클래스
import javax.persistence.*;
import java.util.Date;
@Entity
@Table(name = "member")
public class Member {
@Id
@Column(name = "id")
private String id;
@Column(name = "name")
private String userName;
@Column(name = "name")
private Integer age;
// Enumerated 어노테이션 추가
@Enumerated(EnumType.STRING)
private RoleType roleType;
@Temporal(TemporalType.TIMESTAMP)
private Date createAt;
}
EnumType.STRING : enum 이름 그대로 ADMIN은 ‘ADMIN’, USER는 ‘USER’ 라는 문자로 데이터베이스에 저장EnumType.ORDINAL : enum에 정의된 순서대로 ADMIN은 0, USER는 1 값이 데이터베이스에 저장날짜 타입(java.util.Date, java.util.Calandar)을 매핑할 때 사용한다.
TemporalType.DATE : 날짜, 데이터베이스 date 타입과 매핑 (예: 2024-06-01)TemporalType.TIME : 시간, 데이터베이스 time 타입과 매핑 (예: 01:55:11)TemporalType.TIMESTAMP : 날짜와 시간, 데이터베이스 timestamp 타입과 매핑(예: 2024-06-01 01:55:11)어노테이션의 속성으로 위 세가지 값 중 하나를 설정해야 한다.
연관 관계란 두 개 이상의 사물, 사건, 개체들 간에 서로 영향을 미치거나 서로 관련되어 있는 것을 의미한다. 객체 지향 프로그래밍에서의 연관 관계는 객체 간의 참조가 될 것이고, 관계형 데이터베이스에서의 연관 관계는 테이블 조인이 될 것이다.
게시판(Board)과 게시글(Post)로 예를 들었을 때, 하나의 게시판에 여러 개의 게시글을 작성할 수 있다고 가정하면 게시판 1개에 게시글 N개가 작성될 수 있는 구조이므로 둘 사이는 1:N (일대다)관계로 볼 수 있다.

게시글(post) 테이블의 외래키인 board_id와 게시판(board) 테이블의 기본키인 board_id로 두 테이블을 조인하는 구조
SELECT *
FROM board b INNER JOIN post p ON b.board_id = p.board_id

JPA는 자바의 객체를 이용하여 관계형 데이터베이스를 다루기 위해 만들어졌다. 자바의 객체를 SQL문으로 바꿔주고, 데이터베이스의 데이터를 객체로 만들어주는 매개체 역할을 하고 있다. JPA는 개발자가 SQL문을 작성하지 않아도 데이터를 다룰 수 있게 해주는데, 이는 데이터베이스의 테이블을 객체로 표현하는 객체와 테이블 매핑 덕분에 가능한 일이다. 매핑이란 어떤 값을 다른 값에 대응시키는 과정이다.
@Entity, @Column, @Id 등의 어노테이션을 이용해서 테이블과 객체를 1:1로 매핑하는 것으로 엔티티 매핑을 말한다.
@Entity
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Member {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "id")
private Long id;
...
}
JOIN을 통해 두 개 이상의 테이블을 묶을 수 있는 것을 테이블 간에 연관 관계라고 한다. JPA에서 관계형 데이터베이스의 JOIN을 자바 객체로 표현하려면 @OneToOne, @ManyToOne 등의 어노테이션을 사용하여 표현 가능하다.
테이블을 조인하면 양쪽 테이블에 있는 데이터 모두 접근이 가능하기 때문에 데이터베이스에는 방향이라는 개념이 없지만, 객체는 연관된 객체만 참조할 수 있다. 객체 관계에서 한 쪽만 참조하는 것을 단방향, 양쪽이 서로 참조하는 것을 양방향이라고 한다.
게시판(Board), 게시글(Post) 객체가 있을
모든 객체를 양방향 참조로 하게 되면 설정이 복잡해지기 때문에 기본적으로 단방향 참조를 사용하고 필요시 양방향을 추가하는 방향으로 작업하는 것이 좋다.
외래키를 관리하는 주체, 양방향 관계일 때 고려가 필요하며, 데이터베이스는 외래키 하나로 두 테이블이 연관 관계를 맺기 때문에 테이블의 연관 관계를 관리하는 포인트는 외래키 하나이다.
객체를 양방향으로 매핑하면 A→B, B→A 두 곳에서 서로를 참조합니다. 따라서 객체의 연관 관계를 관리하는 포인트는 두 곳이다.
관리 포인트가 서로 다른 객체와 테이블을 매핑하기 위해 두 개의 단방향 관계 중 외래키를 관리할 주체를 정해야 하는데, 보통 외래 키가 있는 곳을 연관 관계의 주인으로 정한다. 연관 관계의 주인으로 선택되면 데이터를 조회, 저장, 수정, 삭제를 할 수 있지만, 연관 관계의 주인이 아니면 조회만 가능하다.
회원(Member)과 팀(Team)으로 예를 들었을 때, 하나의 Team에는 여러 명의 Member가 올 수 있으므로 1:N 관계

@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은 Member를 참조할 필드가 없다. Member와 Team은 일대다 단방향 연관 관계
일대다 단방향에서는 “다”쪽에 해당하는 Member에만 @ManyToOne을 추가했으며, @JoinColumn(name = "TEAM_ID")어노테이션을 이용하여 Member의 team 필드를 TEAM_ID 외래키와 매핑하였는데, 해당 필드로 TEAM_ID 외래키를 관리한다는 의미이다.

@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;
@OneToMany(mappedBy = "team")
private List<Member> members = new ArrayList<Member>();
}
일대다 양방향 관계인 경우 “다”쪽에 항상 외래키가 있다. Member가 “다”에 해당하고 연관 관계의 주인이 된다.
일대다 관계에서 “일”에 해당하는 Team.members 필드에 @OneToMany 어노테이션을 추가하고 mappedBy로 연관 관계의 주인을 지정해준다. mappedBy의 값은 변수명을 적으면 된다. Member 객체의 “team”이라는 변수가 연관 관계의 주인이므로 변수명인 “team”으로 설정
양쪽 테이블 중 어느 곳이나 외래키를 가질 수 있다. 테이블 A, B가 있을 때 A가 주 테이블이면 B가 대상 테이블이고, B가 주 테이블이면 A가 대상 테이블

@Entity
public class Member {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
}
@Entity
public class Locker {
@Id
@GeneratedValue
@Column(name = "LOCKER_ID")
private Long id;
private String name;
}
@OneToOne 어노테이션을 추가하였고, @JoinColumn에는 외래키인 LOCKER_ID를 명시

@Entity
public class Member {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private Long id;
private String username;
@OneToOne
@JoinColumn(name = "LOCKER_ID")
private Locker locker;
}
@Entity
public class Locker {
@Id
@GeneratedValue
@Column(name = "LOCKER_ID")
private Long id;
private String name;
@OneToOne(mappedBy = "locker")
private Member member;
}
일대일 단방향과의 차이점은 Locker 클래스에 Member를 참조할 수 있게 되었다는 점이다. 양방향 관계이므로 연관 관계의 주인을 정해야하고, 여기서는 외래키를 가진 테이블이 MEMBER이므로 Member.locker를 주인으로 정했기 때문에 Locker.member에는 mappedBy를 통해 주인이 누구인지 명시했다.
기본적으로 관계형 데이터베이스에서는 2개의 테이블로 다대다 관계를 표현할 수 없고, JPA에서는 다대다 관계를 설정하기 위해 두 개의 테이블 사이에 중간 테이블이라는 가상의 테이블을 만드는데, 중간 테이블은 자동으로 생성되기 때문에 개발자가 의도하지않은 복잡한 쿼리가 발생할 수도 있기 때문에 실무에서 사용을 지양하며, 다대다 관계를 여러 개의 일대다 관계로 풀어내는 방식으로 해결한다.
@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;
}
@Entity
public class Member {
@Id
@GeneratedValue
@Column(name = "MEMBER_ID")
private String id;
private String username;
@OneToMany(mappedBy = "member")
private List<Order> orders;
}
@Entity
public class Product {
@Id
@GeneratedValue
@Column(name = "PRODUCT_ID")
private String id;
private String name;
}
회원(Member)과 상품(Product) 사이에 주문(Order)테이블을 두는 방식으로 해결한다.
⭐ 선택 주의

스프링 기능을 사용할 것임으로 org.springframework로 !
테스트 데이터 삽입 -> 엔티티 선언 -> 레포지토리(인터페이스) 추가 -> 서비스 작성 -> 컨트롤러 추가 -> 모델 추가(RequestBody 처리, 리퀘스트 파라미터가 아닌 객체에 실어서 보낼 것)