Object Relational Mapping '객체로 연결을 해준다' 라는 의미로
어플리케이션과 데이터베이스 연결 시 SQL 언어가 아닌,
어플리케이션 개발 언어로 데이터베이스를 접근할 수 있게 해주는 툴이다.
객체(Object)와 DB의 테이블을 Mapping 시켜,
RDB 테이블을 객체지향적으로 사용하게 해주는 기술
RDB 테이블은 객체지향적 특성(상속, 다형성 등)을 가지지 않는다.
그러나, 객체지향적 언어인 Java는 ORM을 사용하면
보다 객체지향적으로 RDB를 사용할 수 있게 된다.
직관적인 코드 (가독성) + 비즈니스 로직 집중 가능 (생산성)
ORM을 이용하면 SQL문이 아닌 클래스의 메서드를 통해 DB를 조작할 수 있어,
개발자가 객체 모델만 이용해서 프로그래밍을 하는 데 집중할 수 있게 한다.
결과적으로,
1) SQL문을 사용하면서 같이 필요한 선언문, 할당, 종료같은 부수적인 코드가 사라지거나 감소
2)각종 객체에 대한 코드를 별도로 작성하여, 코드의 가독성이 증가
3) 오직 객체지향적 접근만 고려하면 되므로, 생산성 증가 (객체지향적 접근과 SQL의 절차적 / 순차적 접근이 혼재되어 있던 기존 방식과는 다름)
재사용, 유지보수, 리팩토링 용이성
1) ORM은 기존 객체와 독립적으로 작성이 되어있고, 객체로 작성되어 있기 때문에 재활용이 가능
2) 매핑하는 정보가 명확하기 때문에, ERD를 보는 의존도가 낮다.
DBMS(DataBase Management System) 중속성 하락
1) 객체 간의 관계를 바탕으로, SQL문 자동 생성과 객체의 자료형 타입까지 사용할 수 있기 때문에, RDBMS의 데이터 구조와 객체지향 모델 사이의 간격을 좁힐 수 있다.
2) 객체에만 집중할 수 있기 때문에, DBMS를 교체하는 큰 작업에도 리스크가 적고 드는 시간도 적다
DBMS 예시 )
자바에서 가공할 경우equals,hashCode의 오버라이드 같은 자바의 기능을 이용 가능, 간결하고 빠르게 가공 가능
프로젝트가 복잡해질 수록, ORM 사용의 난이도가 증가
미흡한 설계로 인해 잘못 구현될 경우, 속도가 감소, 일관성이 없음
(일부) 자주 사용되는 대형 SQL문은 속도를 위해 별도의 튜닝이 필요하므로, 결국 SQL문을 사용해야함
이미 프로시저가 많은 시스템에서는 다시 객체로 바꿔야하며, 그 과정에서 생산정 저하나 리스크가 많이 발생
객체 지향 프로그램밍은 클래스를 사용,
관계형 데이터베이스는 테이블을 사용한다.
다음과 같은 경우에 객체 모델과 관계형 모델 간의 불일치가 생성
예시 )
사용자의 세부 사항에 대해 데이터를 저장할 경우
객체 지향 프로그래밍 : 코드 재사용과 유지보수를 위해,Person과Address라는 2개의 클래스로 나누어 관리
DB : Person 테이블 1개에 사용자의 세부사항을 모두 저장
Object는 2개, Table은 1개가 되어 개수가 달라진다
RDBMS는 객체지향 프로그래밍 언어의 특징인 상속 개념이 없음
(sameness)의 개념을 정의(reference) 를 사용해 연관성을 나타냄public class Employee {
private int id;
privatae String first_name;
...
private Department department; // Employee -> Department
...
}
Java에서의 객체 참조는 아래처럼 방향성이 있기 때문에
Employee → Department
양방향 관계가 필요한 경우 연관을 두 번 정의해야 한다.
즉, 서로 Reference를 가지고 있어야 한다.

INSERT INTO
EMPLOYEE(id, first_name, _ ,department_id) // FK
VALUES ...
→ foriegn key와 Join으로 자연스럽게 방향성이 없는 연결이 이루어졌다.
Java와 RDBMS에서 객체를 접근하는 방법이 근본적으로 다르다.
user.getBillingDatails().getAccountNumber()
예시 )
쿼리 수를 최소화하고,
JOIN을 통해 여러 엔티티를 로드,
원하는 대상 엔티티를 선택(SELECT)하는 방식으로 탐색
@Data
@Entity
public class Question {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
// Primary Key(id)
private Integer id;
// 제목 (최대 200자)
@Column(length = 200)
private String subject;
// 본문 (TEXT 타입으로 저장)
@Column(columnDefinition = "TEXT")
private String content;
// 생성일
private LocalDateTime createDate;
}
@Getter , @Setter , @ToString , @EqualsAndHashCode , @RequiredArgsConstructor 를 한꺼번에 설정합니다.
@Data 사용으로 반복 코드 제거
요 이노테이션은 클래스를 스프링부트의 테스트 클래스로 지정하는 것
@Autowired 로 의존성 주입(DI)이라는 기능을 사용
DI : 스프링이 객체를 대신 생성하여 주입하는 기법
@Autowired 애너테이션을 변수에 적용하면 객체를 자동으로 만들어 주입한다.
객체를 주입하는 방식에는
@Autowired 사용
Setter 메서드 사용
순환 참조문제와 같은 이유로 개발 시 @Autowired 보다는 생성자를 통한 객체 주입 방식을 권장
테스트 코드의 경우 JUnit이 생성자를 통한 객체 주입을 지원하지 않으므로 테스트 코드 작성 시에만 @Autowired 를 사용
실제 코드 작성 시에는 생성자를 통한 객체 주입 방식을 사용
이 애너테이션을 붙이면 붙인 메서드가 테스트 메서드임을 나타낸다.
@SpringBootTest 클래스를 JUnit으로 실행하면
@Test 애너테이션이 붙은 메서드가 실행된다.
@SpringBootTest
class SbbApplicationTests {
@Autowired
private QuestionRepository questionRepository;
@Test
void testJpa() {
Question q1 = new Question();
q1.setSubject("sbb가 무엇인가요?");
q1.setContent("sbb에 대해 알고 싶습니다.");
q1.setCreateDate(LocalDateTime.now());
// 1번 질문 저장
this.questionRepository.save(q1);
Question q2 = new Question();
q2.setSubject("스프링부트 모델 질문입니다.");
q2.setContent("id는 자동으로 생성되나요?");
q2.setCreateDate(LocalDateTime.now());
// 2번 질문 저장
this.questionRepository.save(q2);
}
}
실행하면 실행할 수록 question 데이터베이스에 추가된다.
question 테이블에 저장된 모든 데이터를 조회하기 위해 리포지터리의 findAll 메서드를 사용
데이터 사이즈가 얼마인지 확인하고 싶다면,
JUnit의 assertEquals 메서드를 사용하기
이 메서드는 테스트에서 예상한 결과와 실제 결과가 같은지 확인하는 목적으로 사용한다.
JPA 또는 데이터베이스에서 올바르게 가져오는지 확인 용도
예시 )
@Test
void testJpa() {
List<Question> all = this.questionRepository.findAll();
assertEquals(2, all.size());
Question q = all.get(0);
assertEqauls("sbb가 무엇인가요?", q.getSubject());
assertEqauls(기댓값, 실젯값)
assertEqauls(2, all.size());
id 값으로 데이터를 조회하기 위해서는 리포지터리의 findById 메서드를 사용해야 한다.
findById 의 리턴 타입은 Qustion이 아닌 Optional
Optional은 그 값을 처리하기 위한 (null 값을 유연하게 처리하기 위한) 클래스로
isPresent() 메서드로 값이 존재하는지 확인이 가능
만약 isPresent()로 값이 존재한다는 것을 확인했다면,
get() 메서드를 통해 실제 Question 객체의 값을 얻는다.
@SpringBootTest
class SbbApplicationTests {
@Autowired
private QuestionRepository questionRepository;
@Test
void testJpa() {
Optional<Question> oq = this.questionRepository.findById(1);
if (oq.isPresent()) {
Question q = oq.get();
assertEquals("sbb가 무엇인가요?", q.getSubject());
}
}
}
데이터베이스에서 ID가 1인 질문을 검색,
이에 해당하는 질문의 제목이 "sbb가 무엇인가요?" 인 경우
JUnit 테스트를 통과한다.
리포지터리는 findBySubject 메서드를 기본적으로 제공하지 않음
그래서 어떻게 사용하느냐
바로 QuestionRepository 인터페이스를 변경해야 한다
package com.mysite.sbb;
import org.springframework.data.jpa.repository.JpaRepository;
public interface QuestionRepository extends JpaRepository<Question, Integer> {
Question findBySubject(String subject);
}
Question 엔티티와 관련된 데이터베이스 작업을 처리하기 위한 리포지토리 인터페이스
JpaRepository를 확장하므로 Spring Data JPA의 기본적인 CRUD 기능을 사용할 수 있음
객체 : Question
메서드 : findBySubject(String subject);
Spring Data JPA의 쿼리 메서드(query method) 중 하나
Question 엔티티의 subject 라는 필드를 조건으로 하여,
해당 값을 가진 데이터를 검색하는 메서드
SQL로 표현하면 다음과 비슷하다.
SELECT * FROM QUESTION WHERE subject = ?;
Question 엔티티의 속성(Property)
@Entity
public class Question {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String subject; // 질문 제목 (subject)
private String content; // 질문 내용
private LocalDateTime createDate;
// Getter, Setter 등
따라서 subject는 질문의 "제목"을 나타낸다.
예를 들어,
데이터베이스에 저장된 질문 중에서 제목이 "What is Java?" 인 데이터를 검색하려면
findBySubject("What is Java?") 를 호출한다.
반환값은 제목이 "What is Java?" 인 Question 객체
메서드 이름에 따라 자동으로 쿼리를 생성한다.
findBy[필드명]:
findBySubject() 는 Question 엔티티의 subject 필드를 조건으로 검색예시
@Autowired private QuestionRepository questionRepository; // public void testFindBySubject() { Question question = questionRepository.findBySubject("What is Java?"); System.out.println(question.getContent()); // 해당 질문의 내용을 출력 }
인터페이스에 메서드를 선언만 하고 구현하지 않았는데 실행되는 이유는?
JPA에 리포지터리의 메서드명을 분석하여 쿼리를 만들고 실행하는 기능이 있기 때문에 가능한 것
즉, findBy + 엔티티의 속성명 (예를 들면 findBySubject)과 같은 리포지터리의 메서드를 작성하면
입력한 속성의 값으로 데이터를 조회할 수 있다.