728x90

깃허브 바로가기
이번 발표는 내가 진행하였다. 그래서 이해하기가 더욱 더 쉬웠다.
JPA가 제공하는 기능은 크게 엔티티와 테이블을 매핑하는 설계부분과 매핑한 엔티티를 실제 사용하는 두가지 부분으로 나눌 수 있다.
여기서는 매핑한 엔티티를 엔티티 매니저를 통해 사용하는 것을 알아보자.

영속성 관리

엔티티 매니저는 엔티티를 저장, 수정, 삭제, 조회 등등 엔티티와 관련된 모든 일을 처리한다.
말그대로 엔티티를 관리하는 것이다.

엔티티 매니저 팩토리와 엔티티 매니저

DB를 하나만 사용하는 애플리케이션은 일반적으로 EntityManagerFactory를 하나만 생성한다.
팩토리를 만드는 것으로 생성할 때 비용이 아주 많이 든다.

EntityManagerFactory emf = Persistence.createEntityManagerFactory("");

교재에서는 이것을 통해 META-INF/persistence.xml에 persistence-unit name에 매핑된 값을createEntityManager에 넣어준다.

필요할때마다 엔티티 매니저 팩토리에서 엔티티 매니저를 생성하면 된다.
팩토리에서 매니저를 생성하므로, 비용이 작다.

EntityManager em = emf.createEntityManager();

엔티티 매니저 팩토리는 이름 그대로 엔티티 매니저를 만드는 공장이다.
공장을 만드는 것은 상당한 비용이 든다.
그래서 하나만 만들어서 어플리케이션 전체에서 공유하도록 설계가 되어있다.
그렇기 때문에 공장에서 엔티티 매니저를 생성하는 비용은 작다고 하는 것이다.

엔티티 매니저 팩토리는 여러 스레드가 동시에 접근해도 안전하므로 서로 다른 스레드간에 공유를 해도 되지만,
엔티티 매니저는 여러 스레드가 동시에 접근하면 동시성문제가 발생하므로 스레드 간에 절대 공유해서는 안된다.
일반적인 웹 어플리케이션

그림에서 하나의 EntityManagerFactory에서 다수의 엔티티 매니저를 생성했다. EntityManager1은 아직 DB connection을 하지 않는다. 엔티티 매니저는 DB 연결이 꼭 필요한 시점까지 connection을 얻지 않는다.
보통 트랜잭션을 시작할 때 커넥션을 획득한다.
Hibernate를 포함한 JPA 구현체들은 EntityManagerFactory를 생성할 때 커넥션 풀도 만드는데
xml설정은 J2SE에서 사용한 방식이다.

영속성 컨텍스트란?

JPA를 이해하는데에 있어서 가장 중요한 용어가 바로 영속성 컨텍스트 다. 해석하자면 엔티티를 영구 저장하는 환경이다.
EntityManager로 엔티티를 저장하거나 조회하면 EntityManager는 영속성 컨텍스트에 엔티티를 보관하고 관리한다.

em.persist(member);

persist()를 단순히 회원 엔티티를 저장한다고 표현했지만 사실 persist()는 엔티티 매니저를 이용하여 회원 엔티티를 영속성 컨텍스트에 저장한다는 말이다.

영속성 컨텍스트는 물리적인 것이 아니라 논리적인 개념에 가깝기 때문에 볼 수 없다. 영속성 컨텍스트는 엔티티 매니저를 생성할 때 하나 만들어진다. 그리고 생성된 매니저를 통해 영속성 컨텍스트에 접근하고 관리할 수가 있다.

엔티티의 생명주기

  • 비영속(new/transient) : 영속성 컨텍스트와 전혀 관계가 없는 상태
  • 영속(managed) : 영속성 컨텍스트에 저장된 상태
  • 준영속(detached) : 영속성 컨텍스트에 저장되었다가 분리된 상태
  • 삭제(removed) : 삭제된 상태

엔티티 생명주기

비영속

Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

엔티티 객체를 생성한 상태. 이 때는, 순수한 객체 상태이며 아직 저장하지 않은 것이다.
따라서 영속성 컨텍스트나 DB와 전혀 관련이 없다. 이것을 비영속 상태라고 한다.

영속

em.persist(member);

엔티티 매니저를 통해 엔티티를 영속성 컨텍스트에 저장한다. 영속성 컨텍스트가 관리하는 엔티티를 영속 상태라고 한다. persist()를 사용하는 순간부터 영속상태에 들어가게 되었다.

결국 영속상태 라는 것은 영속성 컨텍스트에 의해 관리된다 라는 뜻이다.

더불어 em.find()나 JPQL을 사용해서 조회한 엔티티도 영속성 컨텍스트가 관리하는 영속 상태이다.

준영속

영속성 컨텍스트가 관리하던 영속 상태의 엔티티를 영속성 컨텍스트가 관리하지 않으면 준영속 상태가 된다.
특정 엔티티를 준영속 상태로 만들려면 detach()를 호출하면 된다.
close()를 호출해서 영속성 컨텍스트를 닫거나 clear()를 호출해서 영속성 컨텍스트를 초기화해도 영속성 컨텍스트가 관리하던 영속 상태의 엔티티는 준영속 상태가 된다.

삭제

엔티티를 영속성 컨텍스트와 데이터베이스에서 삭제한다.
remove()를 사용한다.

영속성 컨텍스트의 특징

영속성 컨텍스트와 식별자 값

엔티티를 식별자 값(@Id로 테이블의 PK와 매핑한 값)으로 구분한다.
따라서 영속 상태는 식별자 값이 반드시 있어야 한다. 없으면 예외가 발생한다.

영속성 컨텍스트와 DB저장

JPA는 보통 트랜잭션을 커밋하는 순간 영속성 컨텍스트에 새로 저장된 엔티티를 데이터베이스에 반영하는데
이를 flush라 한다.

영속성 컨텍스트가 엔티티를 관리할때의 장점

  • 1차 캐시
  • 동일성 보장
  • 트랜잭션을 지원하는 쓰기 지연
  • 변경 감지
  • 지연 로딩

엔티티 조회

영속성 컨텍스트는 내부에 캐시를 가지고 있는데 이것을 1차 캐시라고 한다.
영속 상태의 엔티티는 모두 1차캐시에 저장된다. 내부에 Map이 하나 있는데 키는 @Id로 매핑한 식별자고
값은 엔티티의 인스턴스이다.

// 엔티티 생성(비영속)
Member member = new Member();
member.setId("member1");
member.setUsername("회원1");

//엔티티를 영속
em.persist(member);

이코드를 실행하면 1차 캐시에 회원 엔티티를 저장한다. 회원 엔티티는 아직 데이터베이스에 저장되지 않았다.

1차 캐시의 키는 식별자 값이라고 설명했다. 식별자 값은 데이터베이스 기본키와 매핑이 되었다고도 언급했다.
그렇기 때문에 영속성 컨텍스트에 데이터를 저장하고 조회하는 모든 기준은 데이터베이스의 기본키 값이다.

Member member = em.find(Member.class, "member1");

find()메소드를 보면 첫번째 매개변수는 엔티티 클래스 타입이고, 두번째 매개변수는 엔티티의 식별자 값이다. find()를 호출하면 1차에서 엔티티를 찾고 만약 찾는 엔티티가 1차 캐시에 없으면 데이터베이스에서 조회한다.

데이터베이스에서 조회

find()를 호출했는데 엔티티가 1차 캐시에 없으면 DB에서 조회하여 엔티티를 생성한다.
생성한 다음 1차 캐시에 저장한 후, 영속 상태의 엔티티를 반환해준다.

영속 엔티티의 동일성 보장

객체를 두개 생성하는데 식별자가 같은 인스턴스를 조회하면 1차 캐시에 있는 같은 엔티티 인스턴스를 반환한다.

영속성 컨텍스트는 성능상 이점과 엔티티의 동일성을 보장한다.

  • 동일성
    • 실제 인스턴스가 같다. 참조 값을 비교하는 == 비교의 값이 같다.
  • 동등성
    • 실제 인스턴스가 다를 수 있지만, 인스턴스가 가지고 있는 값이 같다. equals() 메소드로 확인한다.

엔티티 등록

//엔티티 매니저는 데이터 변경시 트랜잭션을 시작해야 한다.
transaction.begin(); // 트랜잭션 시작
em.persist(memberA);
em.persist(memberB);

//커밋하는 순간 데이터베이스에 INSERT SQL을 보냄
transaction.commit();

persist()만 계속 진행하였을 경우 커밋 직전까지 데이터베이스에 엔티티를 저장하지 않고 내부 쿼리 저장소에 모아둔다. 이후 트랜잭션을 커밋할 때 쿼리를 데이터베이스에 보내는데 이것을 트랜잭션을 지원하는 쓰기 지연이라고 한다.

엔티티 수정

엔티티의 데이터만 변경했는데 변경사항을 데이터베이스에 자동으로 반영하는 기능을 변경감지라고 한다.
스냅샷 : JPA는 엔티티를 영속성 컨텍스트에 보관할 때, 최초 상태를 복사하여 저장해둔다.

  1. 커밋하면 엔티티 매니저 내부에서 플러시가 먼저 호출됨
  2. 엔티티와 스냅샷을 비교하여 변경된 엔티티 탐색
  3. 변경된 엔티티가 있으면 수정쿼리를 생성하여 쓰기 지연 sql저장소에 보낸다
  4. 쓰기지연 저장소의 sql을 데이터베이스에 보낸다.
  5. 데이터베이스 트랜잭션을 커밋한다.

변경 감지는 영속성 컨텍스트가 관리하는 영속 상태의 엔티티에만 적용
JPA의 기본 전략은 엔티티의 모든 필드를 업데이트한다.

  • 모든필드를 사용하면 수정 쿼리가 항상 같다.
  • 데이터베이스에 동일한 쿼리를 보내면 데이터베이스는 이전에 한 번 파싱된 쿼리를 재사용할 수 있다.

저장되는 내용이 너무 크면 수정된 데이터만 사용하는
Hibernate의 확장 기능인 @DynamicUpdate를 사용하면 된다.

삭제도 마찬가지로 remove()를 호출해서 사용하는데 쓰기지연 SQL에 모아둔 후에
트랜잭션을 커밋하여 플러시를 호출하면 삭제 쿼리를 DB에 전달한다. remove는 호출하는 순간
영속성 컨텍스트에서 제거시킨다.

플러시

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

실행했을 때 일어나는 일

  • 변경 감지가 동작하여 영속성 컨텍스트에 있는 모든 엔티티를 스냅샷과 비교해 수정된 엔티티를 탐색
    • 여기서 수정된 엔티티는 수정쿼리를 쓰기 지연 SQL저장소에 등록함
  • 쓰기 지연 SQL 저장소의 쿼리를 DB에 전송

플러시 하는 방법

  • em.flush() 직접 호출
    • 강제로 하는 것이기 때문에 테스트나 다른 프레임워크와 JPA를 사용할때 사용
  • 트랜잭션 커밋시 자동으로 호출
    • 트랜잭션만 커밋하면 DB에 반영되지 않음. 반영해야 하기 때문에 JPA는 자동으로 호출해준다.
  • JPQL 쿼리 실행 시 플러시가 자동 호출
    • JPA와 같이 사용했을 때, 엔티티로 조회하던 객체들은 쓰기지연에 남아있으므로 결과가 조회되지 않는다.
      그래서 플러시를 하여 내용을 DB에 반영해야 한다.

플러시 모드 옵션

  • FlushModeType.AUTO : 커밋이나 쿼리를 실행할때 (기본값)
  • FlushModeType.COMMIT : 커밋할 때만 플러시

준영속

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

  • em.detach(entity) : 특정엔티티만 준영속 상태로 전환
  • em.clear() : 영속성 컨텍스트를 완전히 초기화
  • em.close() : 영속성 컨텍스트를 종료

detach를 호출하는 순간 1차 캐시부터 쓰기지연 SQL저장소까지 해당 엔티티를 관리하기 위한 모든 정보가 제거된다.

준영속 상태의 특징

  • 거의 비영속 상태에 가까움
    • 영속성 컨텍스트가 관리하지 않으므로 1차캐시, 쓰기지연, 변경 감지, 지연 로딩 등 어떠한 기능도 동작하지 않는다.
  • 식별자 값을 가지고 있다.
    • 비영속은 식별자 값이 없을수 있지만 이미 한번 영속되었던 상태이므로 반드시 식별자 값을 가지고 있음.
  • 지연 로딩을 할 수 없다.
    • 실제 객체 대신 프록시 객체를 로딩해두고 해당 객체를 실제 사용할때 영속성 컨텍스트를 통해 불러오는 방법이 지연로딩인데 준영속은 컨텍스트가 더는 관리하지 않기 때문에 지연로딩에 문제가 발생한다.

병합

준영속 상태의 엔티티를 다시 영속상태로 변경할 때 사용
merge()메소드는 준영속 상태의 엔티티를 받아 그 정보로 새로운 영속 상태의 엔티티를 반환 한다.

병합은 비영속 엔티티도 영속 상태로 만들 수 있다.
병합은 준영속, 비영속을 신경 쓰지 않는다. 식별자 값으로 엔티티를 조회할 수 있으면 불러서 병합, 없으면 새로 생성해서 병합. 그래서 병합은 save or update 기능 수행

정리

  • 엔티티 매니저는 엔티티 매니저 팩토리에서 생성.
    • 영속성 컨텍스트는 엔티티 매니저를 통해 접근이 가능
  • 영속성 컨텍스트는 애플리케이션과 DB사이에 객체를 보관하는 가상DB같은 역할
    • 이로 인해 1차 캐시, 동일성 보장, 트랜잭션을 지원하는 쓰기 지연, 변경 감지, 지연 로딩 기능을 사용할 수 있다.
  • 영속성 컨텍스트에 저장한 엔티티는 플러시 시점에 DB에 반영되는데 일반적으로 트랜잭션을 커밋할 때 영속성 컨텍스트가 플러시 된다.
  • 영속성 컨텍스트가 관리하는 엔티티는 영속 상태의 엔티티인데, 컨텍스트가 더이상 관리하지 못하면 그 엔티티는 준영속 상태의 엔티티가 된다. 이 엔티티는 컨텍스트의 관리를 더는 받지 못하므로 영속성 컨텍스트가 제공하는 1차 캐시, 동일성 보장, 트랜잭션을 지원하는 쓰기 지연, 변경 감지, 지연 로딩 기능 등을 사용할 수 없다.
728x90

'JPA' 카테고리의 다른 글

[JPA] 연관관계 매핑  (0) 2022.08.04
[JPA] 엔티티 매핑  (0) 2022.08.04
[JPA] JPA 스터디 2장  (0) 2022.08.04
[JPA] JPA 스터디 1장  (0) 2022.08.04

728x90

2장 요약정리
처음 2장은 바로 설치부터 maven 의존성, 라이브러리 설정을 진행한다.
테이블도 생성하는데 이부분은 생략하도록 하겠다.

어노테이션 정리

  • @Entity
    • 이 클래스를 테이블과 매핑한다고 JPA에게 알려줌.
    • 이렇게 @Entity가 사용된 클래스를 엔티티 클래스라고 함
  • @Table
    • 엔티티 클래스에 매핑할 테이블 정보를 알려줌
    • @Table(name = "테이블명") 으로 매핑가능함
    • name속성을 생략하면 클래스 이름을 테이블 이름으로 매핑한다
  • @Id
    • 엔티티 클래스의 필드를 테이블의 기본 키에 매핑한다.
    • @Id가 사용된 필드를 식별자 필드라고 한다.
  • @Column
    • 필드를 칼럼에 매핑한다
    • 매핑 어노테이션이 없는 경우 생략하면 필드명을 사용하여 컬럼명으로 매핑
    • 대소문자를 구분하는 DB를 사용한다면 @Column(name = '칼럼명')으로 명시적 매핑을 해야함

JPA표준 속성

  • JDBC드라이버 : javax.persistence.jdbc.driver
  • 데이터베이스 접속 ID : javax.persistence.jdbc.user
  • 데이터베이스 접속 비밀번호 : javax.persistence.jdbc.password
  • 데이터베이스 접속 url : javax.persistence.jdbc.url

hibernate 속성

  • hibernate.dialect: 데이터베이스 방언 설정 (가장 중요)

이름이 javax.persistence로 시작하는 속성은 JPA 표준 속성으로 특정 구현체에 종속되지 않음
hibernate로 시작하는 속성은 하이버네이트 전용 속성으로 하이버네이트에서만 사용가능

데이터베이스 방언

JPA는 특정 데이터베이스에 종속적이지 않은 기술이다. 그래서 데이터베이스를 쉽게 교체할 수 있다.
그런데 DB마다 sql문법, 함수가 조금씩 다르다.

  • 데이터 타입 - Mysql : VARCHAR, Oracle : VARCHAR2
  • 다른함수 - 문자열 자르는 함수:SUBSTRING Oracle : SUBSTR()
  • 페이징처리 - Mysql : LIMIT, Oracle : ROWNUM

위와 같이 sql 표준을 지키지 않거나 특정 데이터베이스만의 고유한 기능을 JPA에서는 방언이라고 한다
이런 종속되는 기능을 사용하면 나중에 데이터베이스 교체가 어렵다.

JPA가 제공하는 표준 문법에 맞춰 JPA를 사용하고, 특정 데이터베이스에 의존적인 SQL은 데이터베이스 방언이 처리해준다.

  • H2: org.hibernate.dialect.H2Dialect
  • Oracle 10g : org.hibernate.dialect.Oracle10gDialect
  • MySQL : org.hibernate.dialect.MYSQL5InnoDBDialect

JPA의 코드는 크게 3가지 부분으로 나뉜다

  • 엔티티 매니저 설정
  • 트랜잭션 관리
  • 비즈니스 로직

build.gradle 설정

implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-validation'

JPA의존성과 null체크 유효성검사에 대한 의존성을 추가했다.

엔티티 매니저 설정

EntityManagerFactory emf = Persistence.createEntityManagerFactory("test");

이 엔티티 매니저 팩토리는 데이터베이스 커넥션 풀도 생성할수 있으므로 엔티티 매니저 팩토리를 생성하는 비용이 아주 크다.
엔티티 매니저 팩토리는 어플리케이션 전체에서 딱 한번만 생성하고 공유하여 사용해야 한다.

EntityManager em = emf.createEntityManager();

JPA의 기능 대부분은 이 엔티티 매니저가 제공한다. 대표적으로 이 엔티티 매니저를 사용해서 엔티티를 데이터베이스에 CRUD가 가능하다. 엔티티 매니저는 데이터베이스 커넥션과 밀접한 관계가 있으므로
스레드 간에 공유하거나 재사용해서는 안된다.

사용이 끝난 엔티티 매니저는 반드시 종료하여야 한다.

em.close();
emf.close(); //엔티티 매니저 팩토리도 종료하여야 한다.

CRUD

  • 등록 : em.persist('등록할객체');
  • 수정 : 객체.set메소드
  • 삭제 : em.remove(객체)
  • 조회 : 객체 변수명 = em.find(객체.class, 객체에 해당하는 pk)

JPQL

기본적인 CRUD는 JPA를 사용하여 엔티티 객체 중심으로 개발하고 데이터베이스에 대한 처리는 JPA에 맡긴다
그래서 SQL문을 전혀 개발자 자신이 사용하지 않았다.
문제는 검색하는 쿼리이다. JPA는 객체중심으로 개발하므로 검색할 때에도 테이블이 아닌 엔티티 객체를 대상으로 검색해야 한다.
JPA는 JPQL(Java Persistence Query Language)라는 쿼리 언어로 문제를 해결해준다.

JPQL은 SQL을 추상화한 JPA의 객체지향 쿼리 언어이다. SQL문법과 많이 유사해서 select, from, where, group by, having, join 등을 사용할 수 있다.

  • JPQL은 엔티티 객체를 대상으로 쿼리한다. 클래스와 필드를 대상으로 쿼리
  • SQL은 DB 테이블을 대상으로 쿼리한다.

em.createQuery("query String")를 통하여 JPQL 쿼리를 날리면 된다.

여기서 Query String은 엔티티 객체를 다루는 것이지 절대 테이블이 아니다!!!!!

JPQL은 DB 테이블을 전혀 알지 못한다.

  • 순서
    • em.createQuery("QueryString")
    • 쿼리객체 생성된 후 쿼리 객체의 getResultList() 메소드를 호출

정리

JPA를 사용하기 위하여 개발 환경 설정부터 진행하였다. 지금까지의 장점은
내가 지금 진행하고있는 게시판 api 프로젝트에서 사용한 CRUD가 이 JPA로 인해 엄청 편하다는 사실
덕분에 SQL에 의존적인 개발을 피할 수 있고 좀 더 객체지향 관점에서 코드를 볼 수있게 된 것같다.

다음 3주차에서는 영속성 관리에 대해 알아볼 것이다.

728x90

'JPA' 카테고리의 다른 글

[JPA] 엔티티 매핑  (0) 2022.08.04
[JPA] 영속성 관리  (0) 2022.08.04
[JPA] JPA 스터디 1장  (0) 2022.08.04
[JPA] MariaDB Charset 오류 해결  (0) 2022.08.04
728x90

1주차 학습내용

깃허브 바로가기

1주차 스터디에 대해 간략하게 정리하는 내용이다.

JPA란?

Java Persistence API 의 줄임말로써, 자바 진영의 표준 ORM 기술이다.
JPA는 다른 JDBCTemplate 나 Mybatis 등등 객체와 관계형 데이터베이스의 차이를 메우기 위해 SQL을 작성을 해서 개발을 진행해야했었는데, 반복적인 CRUD 뿐만아니라 객체 모델링과 관계형 데이터베이스의 차이를 해결해주었다.
JPA의 핵심은 바로 SQL이 아닌 객체 중심으로 개발을 진행하기 때문에 생산성과 유지보수에서 용이하다.
이렇게 되면 테스트를 작성하기에도 편리하다.

SQL을 직접 다룰 때 발생하는 문제점

지금까지 실무에서 Mybatis나 iBatis를 사용하였다. 그래서 소스코드를 보거나 내가 구현해야 할 때 상당 시간을 데이터베이스를 매핑시킨 xml에 너무 의존하여 개발했다.

  • 왜? - 오타에 의한 , 띄어쓰기에 의한 하나라도 틀리게 되면 무수한 에러를 발생시킨다.

기존의 DTO파일에 뭔가 한 칼럼을 추가해야된다고 하면 추가할 클래스들과 sql을 수정해서 작성해야 하는데,
이 때 또 다시 아까처럼 오타가 발생하면 안되기에 꼼꼼하게 개발해야 한다.

또 연관관계에 있어서 Member라는 객체가 Team을 갖고 있는다고 가정을 한다면, Team에 대한 SQL문도 직접 매핑을 시킨다음 묶어주어야 했다.

JPA와 문제 해결

  • 저장기능persist()
  • 조회기능 find()
  • 수정 : 메소드는 별도로 없지만 객체를 변경하게 되면 적절한 update sql쿼리가 실행
  • 연관된 객체 조회기능 : 연관된 객체를 사용하는 시점에 적절한 select sql 실행

상속

객체는 상속이라는 기능을 가지고 있지만 테이블은 상속이라는 기능을 지원하기는 하지만 객체의 상속과 다르다.
JPA는 상속과 관련된 패러다임의 불일치 문제를 개발자 대신 해결해준다. 개발자는 마치 자바 컬렉션에 객체를 저장하듯이 JPA에게 객체를 저장하면 된다.

연관관계

객체는 참조를 사용하여 다른 객체와 연관관계를 가지고 참조에 접근해서 연관된 객체를 조회한다.
반면에 테이블은 외래 키를 사용해서 다른 테이블과 연관관계를 가지고 조인을 사용해서 연관된 테이블을
조회한다.

→ 객체를 테이블에 맞춰 모델링 한다 이렇게 객체지향 모델링을 해도 무척 번거롭고 어려움
→ 이런 패러다임 불일치는 객체지향 모델링을 포기하게 만들정도로 극복하기에 어려움

반면 JPA는 이런 문제를 손쉽게 해결해준다.

객체 그래프 탐색

참조를 사용하여 연관된 객체를 탐색하는 것이다.
SQL을 직접 다루면 처음 실행하는 SQL에 따라 객체 그래프를 어디까지 탐색할 수 있는지 정해짐
반면 JPA는 자유롭게 마음껏 탐색이 가능함. 연관된 객체를 사용하는 시점에 적절한 SELECT SQL을 실행하기 때문에 자유로운 것.

  • 지연로딩 : 실제 객체를 사용하는 시점까지 데이터베이스 조회를 미루는 것
  • JPA는 연관된 객체를 즉시 함께 조회할지 아니면 실제 사용되는 시점에 지연해서 조회할지 간단한 설정으로 정의할 수 있다.

비교

DB의 경우 같은 로우를 조회할 때마다 같은 인스턴스를 반환하도록 구현하는것은 쉽지 않다.
반면에 JPA는 같은 트랜잭션일 때 같은 객체가 조회되는 것을 보장한다.

정리

객체 모델과 관계형 데이터베이스 모델은 지향하는 패러다임이 다르기 때문에 차이를 극복하려고 개발자가 너무 많은 시간과 코드를 소비한다. 그래서 정교한 객체 모델링을 할수록 불일치 문제는 더 커진다. 이러다보니 데이터 중심의 모델로 변하는 과정이 되버린다.
그리고 이것을 해결해주는 것이 바로 JPA다. 불일치 문제를 해결해주고 정교한 객체 모델링을 유지하게 도와준다.

📕 ORM(Object Relational Mapping)

ORM(Object Relational Mapping)은 이름 그대로 객체와 관계형 데이터베이스를 매핑한다는 뜻.
이 프레임워크는 객체와 테이블을 매핑하여 패러다임 불일치 문제를 개발자 대신 해결해준다.

ORM이란 것은 애플리케이션과 데이터베이스 사이에 가상의 데이터베이스(JPA에서 영속성 컨텍스트라고 불리는)라는 인디렉션을 만드는 것이다.

즉, OOP(Object-Oriented Programming)를 하기위한 프로그래밍 기법이다.

원래는 데이터베이스에 종속적이었던 코드를 역으로 데이터베이스를 추상화하여 코드레벨에 맞춰주며

그렇게 함으로써 데이터에 종속적인 프로그래밍이 아닌 OOP를 할 수 있게 된다.

내가 실무를 보며 느끼기에 ORM이란 것은 결국 사전적인 정의대로 OOP를 위한 기술이다.

SQL에 종속적이다, 똑같은 코드가 반복된다 등의 잡다한 이유가 있지만

이러한 문제들의 본질은 OOP를 하지 못하기 때문에 발생하는 것이다.

애플리케이션이 데이터베이스와 연동되면 모든 코드가 자연스럽게 데이터 기반으로 돌아가기 때문에

자연스럽게 OOP 5대 원칙, SOLID도 대부분 위반하게 된다.

약어 개념
SRP 단일 책임 원칙 (Single responsibility principle)
한 클래스는 하나의 책임만 가져야 한다.
OCP 개방-폐쇄 원칙 (Open/closed principle)
“소프트웨어 요소는 확장에는 열려 있으나 변경에는 닫혀 있어야 한다.”
LSP 리스코프 치환 원칙 (Liskov substitution principle)
“프로그램의 객체는 프로그램의 정확성을 깨뜨리지 않으면서 하위 타입의 인스턴스로 바꿀 수 있어야 한다.” 계약에 의한 설계를 참고하라.
ISP 인터페이스 분리 원칙 (Interface segregation principle)
“특정 클라이언트를 위한 인터페이스 여러 개가 범용 인터페이스 하나보다 낫다.”
DIP 의존관계 역전 원칙 (Dependency inversion principle)
프로그래머는 “추상화에 의존해야지, 구체화에 의존하면 안된다.” 의존성 주입은 이 원칙을 따르는 방법 중 하나다.

간단한 예제를 보자면

Member 객체를 DB에 저장할것이라고 했을 때,

Member 클래스를 Java Beans 규약에 맞추어 작성하고, SQL을 작성한다.

class Member{
    private String name;
    private int age;

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return this.age;
    }

    public void setAge(int age) {
        this.age = age;
    }  
}
INSERT INTO MEMBER
    (NAME, AGE)
VALUES
    (#name, #age);

이제 접근자를 이용해 nameage를 추출하여 SQL에 넣어줘야한다.

class InsertService {
    public void insert(String name, int age);
}

...
service.insert(member.getName, member.getAge);
...

이 데이터를 뽑아내고 입력하는 과정 자체가 데이터에 종속적인 과정이며, 결국 이 과정으로 인해 모든 문제가 발생한다.

만약 이 상황에서 address라는 필드가 하나 더 필요해진다면?

Memberaddress 필드를 추가하고, SQLService 클래스도 변경되어야만 한다.

실무에서는 절대 써먹을수조차 없는 매우 간단한 예제임에도 불구하고 즉시 OCP가 위배된다.

이처럼 코드와 SQL 사이에 매우 강한 결합도(=내용 결합도)가 생기기 때문에 SOLID를 지키기가 정말 어려워진다.

나는 개인적으로 불가능하다고 생각하기도 한다.

그렇다면 ORM을 도입하면 어떻게 변할까?


@Entity
class Member{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private int age;

    public Long getId() {
        return this.id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return this.name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public int getAge() {
        return this.age;
    }

    public void setAge(int age) {
        this.age = age;
    }  
}

SQL은 따로 작성하지 않는다.

추상화된 가상의 데이터베이스가 알아서 처리해주기 때문이다.

이 상태에서 address 필드를 추가해야 한다면 그냥 추가하면 끝이다. 더 손댈 게 없다. 즉, OCP를 위반하지 않게된다.

이렇게 간단한 예제만으로도 SOLID를 지키기가 압도적으로 수월해지며, 결과적으로 OOP의 장점을 모두 누릴 수 있게된다.

SQL에 종속적인 문제, 재사용성 증가, 유지보수성 증가, 리팩토링 용이성 등등. 결국 모두 OOP를 하게 됨으로써 얻는 이득이다.

자바진영에 다양한 ORM프레임워크가 존재하는데 그중 Hibernate 프레임워크가 가장 많이 사용된다.

728x90

'JPA' 카테고리의 다른 글

[JPA] 영속성 관리  (0) 2022.08.04
[JPA] JPA 스터디 2장  (0) 2022.08.04
[JPA] MariaDB Charset 오류 해결  (0) 2022.08.04
[JPA] JPA Auditing  (0) 2022.08.03
728x90

요즘 계속 뭔가 하나씩 연결이 되는 상황이 많다. 그래서 기분이 좀 좋다. 몰랐던 것을 조사하면 이전의 학습했던 것들이 쭉 이어진다. 오늘도 마찬가지이다.

라우터란?

라우터는 패킷의 위치를 추출하여, 그 위치에 대한 최적의 경로를 지정하며, 이 경로를 따라 데이터 패킷을 다음 장치로 전향시키는 장치이다. 즉, 라우터는 이름 그대로 네트워크와 네트워크 간의 경로를 설정하고 가장 빠른 길로 트래픽을 이끌어주는 네트워크 장비이다. 그냥 공유기라 생각하면 된다.

이 라우터에는 단자를 꼽는게 크게 두가지로 볼 수 있는데
그것이 바로 WAN과 LAN이다.

WAN

WAN은 Wide Area Network의 줄임말로 한 집마다 인터넷 회선이 있는데 그것을 WAN으로 생각하면 되겠다. 광범위한 지역 단위로 구성하는 네트워크이다.
이 회선을 가지고 집이나 사무실의 네트워크를 구성한다.
public IP이다.

LAN

LAN은 Local Area Network로 사용자가 포함된 지역 네트워크이다. 랜랜 거리는 것이 바로 이 LAN이다.
사무실에는 예를 들면 내선전화가 있고 집에서는 공유기를 통해 Wi-Fi나 이더넷으로 연결한 기기들이 다 LAN으로 구성이 되어있다. 그래서 구성할 때 드는 비용과 전기세를 빼면 유지보수비가 들지 않는다. private IP이다.

라우터를 쓸 때에는 이 자체에도 IP주소가 붙게된다.

인터넷을 실행시키는 순서는 다음과 같다.
컴퓨터에서 웹 브라우저를 킨다. 그리고 도메인명을 입력해서 그 주소에 해당하는 자료를 요청하면 화면이 나오게 되는데
이것을 우리가 배운것으로 설명하자면...

private IP를 라우터가 받는다. 여기서 NAT가 등장하는데 이 뒤에서 설명하도록 하겠다. 공유기가 하는 일은 여기서 두 가지 이다.

  • 어떤 IP가 도메인명을 요청하는지 내부에서 자체적으로 확인 후 기억
  • NAT가 요청한 데이터를 변경 ex) 192.168.0.xx 를WAN(public IP)으로 주소를 변경해서 요청(request)한다.

이렇게 되면 그 도메인은 WAN주소에 응답(response)하고 데이터를 전달해준다. 그러면 WAN에서 다시 공유기로 받은 데이터를 가져온다.
여기서 공유기가 아까 했던 두번째를 다시 바꿔주는데
이제 WAN을 LAN으로 변경해준다. 그렇게 한 뒤에
첫번째로 했던 일인 private IP를 기억해내서 해당 IP에 WAN으로 부터 넘어온 데이터를 넘겨준다.
이런식으로 인터넷으로 소통을 하는것이다.

NAT

NAT는 Network Address Translation이다. IP 패킷에 있는 출발지 및 목적지의 IP주소와 TCP/UDP 포트 숫자 등을 바꿔 재기록하며 네트워크 트래픽을 주고 받게하는 기술이다.

NAT가 있어서 내가 가지고 있는 하나의 공인 IP주소(private IP)를 사용하여 여러 대의 호스트가 인터넷에 접속할 수 있다.
그래서 LAN으로 묶여있는 기기들은 전부 같은 공인 IP주소를 사용한다.

포트 포워딩

0~ 65535 포트까지 있음
22 - SSH
80 - http
1023까지 well-known port

127.0.0.1:8080 ==> 저 ip주소에 8080포트로 서버를 연결해달라는 의미

근데 여기서 문제는 192 사설포트가 여러개일 경우에 해당하는 서버로 매칭시켜주는 것이 포트가 필요한 이유이다.
공인ip:포트 접속요청이 오면 내부에서 192.168.0.x:포트 로 보내는것

여기서 라우터의 nat설정을 바꿔야한다.(라우터가 없을때는 그냥 localhost = 128.0.0.1)

외부포트 -> 공인ip:요청포트
내부포트 -> 설정한 사설ip:요청포트

동적 IP - 계속 값이 변화하기 때문에 지속적인 연결을 하려는데에 제한이 있다.
대신 보안에는 좋을 듯?
고정 IP - 지속적인 연결에는 좋을것, 하지만 보안에는 상대적으로 취약할 수 있다.

네트워크의 기초 개념들을 학습중인데 다시 보니까 이해가 더 잘되는 것같다. 이렇게 공부하고 그냥 아니까 지나가자 가 아니라 다시 한번 와서 개념들을 복습하는게 훨씬 좋은 것이라고 생각한다.🤣🤣

mybatis나 ibatis의 db 매핑정보에서 url주소를 192.168인 내부IP, 그리고 127.0.0.1(localhost) 종류를 많이 사용했고 실제 배포할 때에는 외부 IP를 사용하여 데이터베이스 정보를 잘 매핑시켜야 배포도 원활하게 진행이 되는 것이다. 다음에 서버를 구매하여 배포를 할 때에는 이점을 명시하고 잘 배포해보아야 겠다.

728x90
728x90

XSS : Cross Site Scripting

사이트를 교차해서 스크립트를 발생시킴.
게시판을 포함한 웹에서 자바스크립트같은 스크립트 언어를 삽입해 개발자가 의도하지 않은 기능을 작동시키는 것
클라이언트 측을 대상으로 한 공격이다.

게시판에서 글을 쓰는곳에 작성자가 HTML 코드를 삽입하여 글을 읽는사람의 브라우저에서 실행되게 하는 원리이다.

이게 단순하게 alert()만 띄운다고 되는것이 아니라 그 스크립트 안에 엄청 긴 코드를 주입해서 무한정 창을 띄우게 하거나 악성코드를 직접적으로 받게 할수는 없지만, URL을 클릭하도록 유도하여 악성 프로그램을 다운받는 사이트로 Redirect시킨다.

위험성

  1. 쿠키 정보 및 세션 획득
    공격자는 XSS에 취약한 페이지 및 게시판에 XSS공격을 수행함으로써 해당 페이지를 이용하는 사용자의 쿠키 정보나 세션 ID를 획득할 수 있다.
    이렇게 되면 XSS공격을 통해 페이지를 사용하는 사용자의 세션ID를 얻어서 공격자가 불법적으로 사용자인 척 하면서 활보할 수 있다.
  2. 시스템 관리자 권한 획득
    XSS취약점이 있는 웹서버에 다양한 악성 데이터를 포함시킨 후, 사용자의 브라우저가 악성 데이터를 실행하게 할 수 있다.
    만약 이렇게 해킹이 될 경우, 내부로 악성코드가 이동해 중요 정보가 탈취될 수 있다.
  3. 거짓페이지 노출
    <script> 뿐만이 아니라 <img>와 같은 그림을 표시하는 태그를 사용해 원래 페이지와 전혀 관련 없는 페이지를 표시할 수가 있다.

XSS 방지법

XSS 공격은 IPS, IDS, 방화벽 등으로도 방지할 수가 없다.
그래서 문자를 필터링 해주는 방법이 있다.

Script 문자 필터링

XSS 공격은 입력값에 대한 검증이 제대로 이루어지지 않아 발생하는 취약점이다. 그래서 입력값에 대해 서버는 필터링을 해야 한다. < > " ' 와 같은 문자열들을 정규표현식으로 필터링하여 태그를 제거해준다.

예시

게시판에서 글 제목을 클릭하면 다른 페이지로 넘어가서 글 상세보기를 해준다고 가정을 했을 때, 제목을 <a href="hi">test</a> 로 한다면 원래 보여주려던 '상세보기'는 구현할 수 없고 href에 있는 hi 주소로 http 통신을 보내기 때문에 생뚱맞는 페이지를 호출하여 redirect 해준다.

이 점을 항상 생각하고 유의하여 코드를 작성하여야 한다.

728x90

+ Recent posts