@Entity 애노테이션이 붙은 클래스는 JPA가 관리하는 엔티티로 JPA를 사용해서 테이블과 매핑할거면 필수로 입력해야 한다.

파라미터가 없는 public 또는 protected 생성자가 필수이며 final 클래스나 ENUM, 인터페이스, Inner 클래스는 엔티티로 등록할 수가 없다. (저장할 필드에 final 사용 x)

 

@Table 애노테이션은 Entity와 매핑할 테이블을 지정할 수 있다. (기본값 엔티티명)

 

ddl-auto 기능을 사용하면 DDL(Database Definition Language)을 애플리케이션 실행 시점에 자동으로 적절하게 생성해주는데 개발 단계에서만 사용하고 운영 서버에서는 사용하지 않는 것이 좋다.

 

  • create : 기존 테이블 삭제 후 재생성 (drop + create)
  • create-drop : 종료 시점에 테이블 (drop)
  • update : 변경부분만 반영
  • validate : 엔티티와 테이블 정상 매핑 확인
  • none : 사용 x(default)

 

개발 초기에는 create, update를 사용하고 테스트 서버는 update, validate, 스테이징과 운영 서버는 validate 또는 none을 주로 사용한다. DDL 생성 기능은 DDL을 자동 생성할 때만 사용되고 JPA의 실행 로직에는 영향을 주지 않는다.

 

 

필드 매핑 애노테이션

  • @Column : 기본 Column
  • @Temporal : 날짜 타입 (LocalDate, LocalDateTime 사용 시 생략 가능)
  • @Enumerated : ENUM 타입 (기본 설정인 ORDINAL 대신 STRING 사용)
  • @Lob : 데이터베이스 BLOB, CLOB과 매핑, 문자면 CLOB 나머지는 BLOB 매핑
  • @Transient : 매핑 무시 (메모리상에 임시로 값 보관할 때 사용)

 

기본 키 매핑 애노테이션은 @Id(Private Key), @GeneratedValue가 있는데 GeneratedValue는 다음과 같이 설정을 할 수있다.

 

IDENTITY

  • 데이터베이스에 위임 (MySQL의 AUTO_INCREMENT, PostgreSQL ..)
  • AUTO_INCREMENT는 INSERT를 한 이후에 ID 값을 알 수 있어서 em.persist() 시점에 즉시 INSERT SQL이 실행된다.

 

SEQUENCE

데이터베이스 시퀀스는 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트로 @SequenceGenerator 선언이 추가로 필요하다. (ORACLE, PostgreSQL, H2..) 

 

@SecuenceGenerator

  • name : 식별자 생성기 이름
  • sequenceName : 데이터베이스에 등록되어 있는 시퀀스 이름
  • initialValue : DDL 생성시에만 사용, 시퀀스 DDL 생성할 때 처음 시작하는 수를 지정 (기본값 1)
  • allocationSize : 시퀀스 한번 호출시 증가하는 수, 이 값이 1이면 매번 DB에 접근해서 시퀀스 값을 받아온다. (기본값 50)

기타)

TABLE : 키 생성용 테이블, 모든 DB에서 사용, @TableGenerator 필요 (성능상 잘 쓰진 않음)

AUTO : 데이터베이스에 따라 자동 지정

 

 

 

[참고] 인프런 김영한님 강의를 공부한 내용입니다.

 

ORM(Object Relatinal Mapping)이란 객체는 객체대로, 관계형 데이터베이스는 관계형 데이터베이스대로 설계를 하고 둘 사이의 관계를 중간에서 매핑시켜 관계형 데이터베이스를 객체지향적으로 사용하게 해주는 기술이다. JPA는 자바 애플리케이션과 JDBC 사이에서 JDBC API를 이용해 DB에 SQL 전달하여 동작하는 자바 ORM 기술 표준 인터페이스로 대표적인 구현체로는 하이버네이트 JPA가 있다.

 

먼저 JPA를 사용하려면 EntityManagerFactory, EntityManager, 영속성 컨텍스트, 트랜잭션에 대해 알아야 한다.

 

EntityManagerFactory는 웹서버가 올라오는 시점에 DB당 하나만 생성되어 애플리케이션 전체에서 공유되며 멀티 스레드 환경에서 사용할 수 있으며 createEntityManager()를 통해 EntityManager를 생성할 수 있다.

엔티티 매니저트랜잭션을 수행하고 엔티티, SQL을 관리하며 커넥션을 통해 DB에 접근하는 등 핵심 동작을 담당하는 중요한 역할을 하며 트랜잭션에 대해 생각해보면 당연히 스레드 간에 공유는 불가능하며 트랜잭션 수행 후에 반드시 소멸(close) 시켜야한다.

 

 

[DB 접근 기술1] 트랜젝션(Transaction) 기초

트랜젝션 데이터를 저장할 때 단순 파일이 아닌 데이터베이스에 저장하는 가장 큰 이유는 데이터베이스가 트랜젝션이라는 개념을 지원하기 때문이다. 트랜젝션은 데이터베이스에서 하나의 거

treecode.tistory.com

 

 

// Persistence에서 하나의 EntityManagerFactory를 생성해서 애플리케이션 전체에서 공유
EntityManagerFactory emf = Persistence.createEntityManagerFactory("hello");

// EntityManagerFactory에서 EntityManager 생성, 쓰레드간에 공유할 수 없고 사용하고 버려야 한다.
EntityManager em = emf.createEntityManager();

// JPA의 모든 데이터 변경은 트랜잭션 안에서 실행
EntityTransaction tx = em.getTransaction();

tx.begin();

try {
	Member member = new Member();
	member.setId(1L);
	member.setName("Kim");
	
	//save
	em.persist(member);
	//read
	Member findMember = em.find(Member.class, 1L);
	//update
	findMember.setName("Lee");
	//delete
	em.remove();

	tx.commit();
} catch (Exception e) {
    // 문제가 생기면 롤백
	tx.rollback();
} finally {
	// 나중에 생성된 순서대로 close
	em.close();	
}
emf.close();

 

 

영속성 컨텍스트

영속성 컨텍스트는 자바 애플리케이션과 데이터베이스 사이에서 엔티티를 저장, 관리하는 논리적인 개념으로 엔티티 매니저를 통해 접근할 수 있다.

 

엔티티의 생명 주기

  • 비영속(new/transient) : 영속성 컨텍스트와 전혀 관계가 없는 새로운 상태 (new Member)
  • 영속(managed) : 영속성 컨텍스트에 관리되는 상태 (em.persist)
  • 준영속 (datached) : 영속성 컨텍스트에 저장되었다가 분리된 상태 (em.detach)
  • 삭제 (removed) : 삭제된 상태 (em.remove)

 

 

특징

 

1) 1차 캐시

 

영속성 컨텍스트는 애플리케이션과 데이터베이스 사이에서 엔티티를 저장, 관리하기 위해서 내부에 1차 캐시 저장소를 가지고 있는데 엔티티를 persist() 하면 1차 캐시에 Key(@Id, PK) Value(Entity), 최초 상태의 엔티티를 복사해둔 스냅샷(변경 감지)을 저장해둔다.

 

2) 동일성

 

엔티티 매니저를 통해 조회(em.find)를 하면 먼저 1차 캐시를 확인해서 엔티티 참조값을 반환해주기 때문에 새로운 객체를 생성하지 않고 동일성을 보장한다. 조회하려는 엔티티가 1차 캐시에 없을 경우 DB에서 조회를 해서 1차 캐시에 저장을 하고 반환을 해준다.

 

3) 쓰기 지연

 

영속성 컨텍스트는 SQL을 쓰기 지연 저장소에 보관해두었다가 flush()가 호출되는 시점에 SQL을 버퍼처럼 모아서 전송한다.

 

4) 변경 감지

 

1차 캐시에 엔티티 스냅샷을 만들어두고 flush() 호출 시, 엔티티와 스냅샷을 비교하여 변경이 있을 경우 쓰기 지연 저장소에 update 쿼리를 추가하고 flush()를 실행한다.

 

5) 지연 로딩

 

JPA에서 테이블 간 연관 관계는 객체의 참조를 통해 이루어지는데 이는 하나의 객체를 조회하려다가 해당 객체가 참조하는 N개의 객체를 전부 조회하게 되는(N+1) 등의 문제를 발생시킬 수 있다. 이를 방지하기 위해 엔티티가 실제 사용되기 전까지 DB 조회를 지연하는 지연 로딩(Lazy Loading) 전략을 지원한다.


JPQL

 

JPQL은 객체 지향 쿼리를 작성하기 위해 JPA가 제공하는 문법으로 SQL을 추상화하여 특정 데이터베이스에 의존하지 않는다.

JPQL은 SQL을 바로 실행하기 때문에 영속 엔티티가 DB 동기화 되지 않아 의도하지 않은 결과가 반환될 수 있어 JPA는 쿼리를 실행할 때 플러시를 자동 호출하는 것을 기본값으로 사용한다. 

em.persist(memberA);
em.persist(memberB);
em.persist(memberC);

// JPQL 실행
query = em.createQuery("select m from Member m", Member.class);

// flush 호출이 안 됐다고 하면 memberA,B,C가 없는 상태인데 의도한 것은 memberA,B,C가 포함된 리스트
List<Member> members= query.getResultList();
더보기

JPQL는 조회 시 영속성 컨텍스트가 아닌 데이터베이스에 우선적으로 조회를 하고 반환값을 1차 캐시에 저장하는데 이때 해당 엔티티가 이미 1차 캐시에 존재하는 경우 반환값은 버리고 1차 캐시의 값을 반환한다.

 

이는 JPQL 조회 기능의 트랜잭션 격리 수준이 REPEATABLE READ이기 때문인데 이 단계에서는 하나의 트랜잭션 내에서 같은 SELECT 쿼리를 계속 실행하면 항상 같은 결과가 반환되어야 한다. (트랜잭션 격리 수준이란 동시에 여러 트랜잭션이 처리될 때, 특정 트랜잭션이 변경, 조회중인 데이터를 다른 트랜잭션에서 어느정도까지 접근 가능한지 레벨을 나눈 것) REPEATABLE READ는 MySQL의 InnoDB 스토리지 엔진에서 기본적으로 사용되는 격리수준인데 이는 트랜잭션의 롤백 가능성에 대비해 변경 전 레코드를 UNDO 영역에 백업해두고 실제 레코드 값을 변경하는 MVCC 방식을 사용한다.

 

모든 InnoDB 트랜잭션은 순차적으로 증가하는 고유한 트랜잭션 번호를 가지며 UNDO 영역에 백업된 레코드에는 변경을 발생시킨 트랜잭션의 번호가 저장되어 있다. 그래서 이 번호를 보고 어떤 데이터를 보여줄지 결정하게 되는데 트랜잭션 10번이 조회한 값을 트랜잭션 12번이 변경하고 커밋한 경우 트랜잭션 10번은 다시 이 값을 조회하면 바뀐 값이 아닌 변경 전 UNDO 영역의 값이 보여지는 것이다. (더티 리드 방지)

 

[참고]

https://cheese10yun.github.io/jpa-jpql/

 

 

플러시 (flush)

 

플러시는 쓰기 지연 SQL 저장소의 쿼리를 DB에 전송하여 영속성 컨텍스트의 변경 내용을 DB에 동기화 하는데 직접 수동으로 호출하거나 트랜젝션 커밋, JPQL 쿼리 실행에 의해 자동 호출이 된다. (JPA가 JPQL 쿼리 실행시 flush를 자동 호출하는 이유는 JPQL은 우선적으로 데이터베이스에서 조회를 하기 때문에 영속 상태의 엔티티가 조회가 안 되는   )

변경 감지를 통해 1차 캐시 엔티티의 변경 사항(스냅샷을 통해 비교)이 있을 경우 update 쿼리를 flush() 직전에 생성해주기 때문에 사용자는 UPDATE가 필요한 경우 엔티티의 값만 바꿔주면 된다. 영속성 컨텍스트를 비우는 것이 아니며 트랜젝션이라는 작업 단위가 중요하다.

 

 

준영속 상태

 

em.detach(entity) : 특정 엔티티만 준영속 상태로 전환

em.clear() : 영속성 컨텍스트를 완전히 초기화

em.close() : 영속성 컨텍스트를 종료

 

 

 

 

[참고]

 

JPA JPQL의 조회 동작 살펴보기 - Yun Blog | 기술 블로그

JPA JPQL의 조회 동작 살펴보기 - Yun Blog | 기술 블로그

cheese10yun.github.io

 

 

[JPA] 영속성 컨텍스트(Persistence Context)란 - Heee's Development Blog

Step by step goes a long way.

gmlwjd9405.github.io

 

자바 ORM 표준 JPA 프로그래밍 - 기본편 - 인프런 | 강의

JPA를 처음 접하거나, 실무에서 JPA를 사용하지만 기본 이론이 부족하신 분들이 JPA의 기본 이론을 탄탄하게 학습해서 초보자도 실무에서 자신있게 JPA를 사용할 수 있습니다., - 강의 소개 | 인프런

www.inflearn.com

 

MyBatis는 SQL을 XML에 편리하게 작성할 수 있고 동적 쿼리를 편리하게 작성할 수 있는 SQL Mapper 기술로 공식 사이트에 사용법이 잘 나와있다.

 

 

application.properties

#MyBatis
# 타입 정보를 사용할 때 패키지 경로 생략 가능, 지정한 패키지와 그 하위 패키지가 자동으로 인식
mybatis.type-aliases-package=hello.itemservice.domain
# 언더바 -> 카멜 자동 변경
mybatis.configuration.map-underscore-to-camel-case=true
# MyBatis 실행 쿼리 로그 확인
logging.level.hello.itemservice.repository.mybatis=trace
# resources/mapper 를 포함한 하위 폴터 XML을 XML 매핑 파일로 인식
mybatis.mapper-locations=classpath:mapper/**/*.xml

 

@Mapper

스프링 부트가 애플리케이션 로딩 시점에 @Mapper가 붙은 인터페이스를 동적 프록시로 구현체를 생성하고 스프링 빈으로 등록하기 때문에 인터페이스 만으로 XML의 데이터를 찾아서 호출할 수 있고 매퍼 구현체는 MyBatis에서 발생한 예외를 스프링 예외 추상화인 DataAccessException으로 변환해준다.

@Mapper
public interface ItemMapper {

    void save(Item item);

    // 파라미터가 2개 이상이면 @Param("name")으로 매핑
    void update(@Param("id") Long id, @Param("updateParam") ItemUpdateDto
updateParam);

    Optional<Item> findById(Long id);

    List<Item> findAll(ItemSearchCond itemSearch);
}

 

XML

XML 파일 경로는 resources 하위에 mapper 와 같은 패키지 위치로 맞추어 생성한다. 

<insert id="save" useGeneratedKeys="true" keyProperty="id">
    insert into item (item_name, price, quantity)
    values (#{itemName}, #{price}, #{quantity})
</insert>
  • id는 인터페이스에 설정한 메서드 명이랑 맞추고 파라미터는 #{} 문법을 사용하면 된다.
  • 파라미터가 2개 이상일 경우는 Mapper 인터페이스에 @Param으로 이름을 지정해서 파라미터를 구분해야 한다.
  • resultType은 반환 타입으로 결과를 해당 타입의 객체에 매핑해준다.
  • 반환 객체가 여러개면 컬렉션을 사용하면 되는데 주로 List를 사용한다.

 

<select id="findAll" resultType="Item">
    select id, item_name, price, quantity
    from item
    <where>
        <if test="itemName != null and itemName != ''">
            and item_name like concat('%',#{itemName},'%')
        </if>
        <if test="maxPrice != null">
            and price &lt;= #{maxPrice}
        </if>
    </where>
</select>
  • 동적 쿼리 문법인 <where>은 내부 동적 쿼리가 생성이 되면 맨 앞에 and를 where로 변환하고 생성이 되지 않을 경우는 where를 만들지 않는다.
  • XML에는 HTML 엔티티를 사용할 수 없는데 CDATA 문법을 사용하면 태그를 단순 문자로 인식하도록 하지만 <if>, <where>같은 동적 쿼리 문법도 적용되지 않기 때문에 잘 써야 한다.  (<![CDATA[ ... ]]>)

 

동적 SQL

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach

 

문자열 치환

#{} 문법은 ?를 넣고 파라미터를 바인딩하는 PreparedStatement 를 사용하는데 파라미터 바인딩이 아닌 문자 그대로를 처리하는 경우 ${} 를 사용하면 되는데 SQL Injection 공격을 당할 수 있기 때문에 사용하지 않는 것이 좋다.

 

SQL Fragment

<sql>는 fragment 기능으로 사용할 수 있는데 <include>를 통해 반복 코드를 편리하게 불러올 수 있다.

 

resultMap, 별칭(alias)

컬럼명과 객체의 프로퍼티 명이 다를 경우 별칭 "as"를 사용하거나 resultMap으로 선언해서 사용하면 된다.

 

 

 

* 문법은 필요할 때 찾아서 직접 사용해보는 것이 좋다.

 

 

 

[참고] 인프런 김영한님 강의를 공부한 내용입니다.

HTTP(HyperText Transfer Protocol)

 

HTML, 이미지, 영상, JSON 등 거의 모든 형태의 데이터를 전송할 수 있는 프로토콜로, 클라이언트와 서버의 요청(Request),응답(Response) 구조를 가진다. 서버가 클라이언트의 상태를 보존하지 않는 무상태(Stateless) 설계로 현재 서버에 문제가 생길 경우 다른 서버가 응답이 가능하여 서버 확장성에 큰 장점이 있으며, 로그인 같이 상태 유지가 필요한 경우 브라우저 쿠키, 서버 세션 등으로 상태를 유지할 수 있다.

 

 

HTTP 메시지 구조

 

1. 시작 라인

  • 요청 시 - HTTP 메서드, 경로, 쿼리 파라미터, HTTP 버전
  • (GET /search?page=5 HTTP/1.1)
  • 응답 시 - HTTP 버전, 상태 코드, 상태 설명
  • (HTTP/1.1 200 OK)

 

2. 헤더

  • HTTP 전송에 필요한 모든 부가 정보
  • 요청 시 - Host: www.google.com
  • 응답 시 - Content-Type, charset, Content-Length
  • Content-Type : 미디어 타입, 문자 인코딩(text/html; charset=utf-8, application/json)
  • Content-Language : 표현 데이터의 자연 언어를 표현 (ko, en ..)
  • Accept : 협상 헤더, 클라이언트가 선호하는 표현 요청 (Accept, Accept-Charset, Accept-Language ..)
  • (Content-Type:text/html;charset=UTF-8 Content-Length: 3423)

 

3. 공백 라인

  • 헤더와 메시지 바디 사이에 엔터 공백 (CRLF)

 

4. 메시지 바디

  • 실제 전송할 데이터
  • HTML 문서, 이미지, 영상 ,JSON 등 byte로 표현할 수 있는 모든 데이터 전송 가능

 

HTTP API

 

URI 설계에서 가장 중요한 것은 리소스 식별로 행위는 배제하고 리소스(member) 자체를 URL에 매핑한다.

계층 구조를 활용하여 상위를 컬렉션으로 보고 복수 단어로 사용하는 것이 좋다. (특정 회원 : members/id)

URL 설계의 핵심은 리소스와 행위를 분리하고 리소스만 식별하는 것인데 행위는 HTTP 메서드로 구분한다.

 

 

문서(document)

  • 단일 개념
  • ex. members/1

 

컬렉션(collection)

  • 서버가 관리하는 리소스 디렉토리
  • 서버가 리소스의 URL를 샌성하고 관리
  • ex. /members

 

스토어(store)

  • 클라이언트가 관리하는 리소스 저장소
  • 클라이언트가 리소스의 URL를 알고 관리
  • ex. /files

 

컨트롤러(controller)

  • 문서, 컬렉션, 스토어로 해결하기 어려운 경우 추가 프로세스
  • 동사를 직접 사용
  • ex. /membmers/3/delete

 

 

 

HTTP 메서드

[정리]

더보기

GET

  • 리소스 조회
  • 서버에 전달할 데이터는 쿼리를 통해 전달
  • 요청에 메시지 바디가 없다.
  • 멱등성(몇번을 해도 같은 결과)

 

POST

  • 요청 데이터 처리
  • 메시지 바디를 통해 서버로 데이터 전달
  • HTML FORM에 입력한 정보를 전송
  • 새로운 리소스를 생성하거나 프로세스의 상태가 변경되는 경우, 다른 메서드로 처리하기 애매한 경우 주로 사용
  • 같은 요청 반복 시 중복 처리 위험 (멱등성 X)

 

PUT

  • 리소스 전체 대체(부분 변경 X), 해당 리소스가 없으면 생성 (덮어쓰기)
  • 클라이언트가 리소스 위치를 알고 URI 지정(POST와 차이점)

 

PATCH

  • 리소스 부분 변경

 

DELETE

  • 리소스 삭제
  • 요청에 메시지 바디가 없다.

 

HTML FORM 데이터 전송 

  • GET, POST만 지원 (대부분 POST 전송)
  • Content-Type : application/x-www-form-urlencoded (form 데이터를 메시지 바디를 통해 key&value 형식 전송)
  • 전송 데이터를 url encoding 처리 (abc김 -> abc%EA%B9%80)
  • Content-Type: multipart/form-data (파일 업로드 같은 바이너리 데이터 전송, 다른 종류의 파일과 form 내용 전송 가능)

 

HTTP API 데이터 전송

  • 서버 <-> 서버 : 백엔드 시스템 통신
  • 앱 클아이언트(아이폰, 안드로이드)
  • 자바스크립트, React, VueJs 같은 웹 클라이언트와 통신
  • Content-Type: application/json 으로 대부분 사용

 

 

HTTP 상태 코드

 

1xx : 요청이 수신되어 처리중

2xx : 요청 정상 처리 (200 OK, 201 Created, 202 Accepted, 204 No Content)

3xx : 요청을 완료하려면 추가 행동이 필요 (응답 결과에 Location 헤더가 있으면 Location 위치로 리다이렉트)

4xx : 클라이언트 오류, 문법 에러 등으로 서버가 요청을 수행할 수 없는 경우 (400 Bad Request, 404 Not Found)

5xx : 서버 오류, 서버가 정상 요청을 처리하지 못하는 경우 (500 Internal Server Error)

 

클라이언트는 인식할 수 없는 상태코드를 응답 시 상위 상태코드로 해석 (299 -> 2xx, 451 -> 4xx)

 


 

 

쿠키

  • HTTP의 무상태성(Stateless)을 보완하기 위해 상태 유지를 위해 사용
  • Set-Cookie : 서버에서 클라이언트로 쿠키 전달 (응답시 전달)
  • Cookie : 클라이언트가 서버에서 받은 쿠키를 저장, HTTP 요청시 서버로 전달 (요청시 전달)
  • 쿠키 정보는 항상 서버에 전송되기 때문에 트래픽 유발 위험, 최소한의 정보만 사용
  • 보안에 취약
  • 만료일(expires) 또는 시간(max-age)으로 생명 주기, 시간을 0 또는 음수로 지정 시 쿠키 삭제
  • 세션 쿠키 : 만료 날짜 생략 시 브라우저 종료시 까지만 유지
  • 영속 쿠키 : 만료 날짜 입력하면 해당 날짜까지 유지
  • Secure : https인 경우에만 전송
  • HttpOnly : XSS 공격 방지, HTTP 전송에만 사용
  • SameSite : 요청 도메인과 쿠키에 설정된 도메인이 같은 경우에만 쿠키 전송

 

캐시

  • 응답 결과를 브라우저 캐시에 저장하고 같은 데이터 조회 시 캐시에서 조회
  • 네트워크 사용량이 줄어들고, 브라우저 로딩 속도가 빨라진다.
  • 응답 캐시 지시어 cache-control: max-age(유효 시간), no-cache(서버 검증 후 사용), no-store(저장x)
  • 캐시 시간이 만료된 경우
    • 클라이언트는 서버가 보낸 응답 헤더 정보로 캐시에 저장되어 있는 데이터를 재활용하여 갱신 가능
    • 변경이 있으면 서버를 통해 다시 데이터를 조회하고, 캐시 시간을 갱신
    • Last-Modified, ETag : 캐시 데이터와 서버 데이터를 비교하는 검증 헤더 (응답!)
    • ETag : 캐시 데이터에 고유한 버전 이름을 달아두고 데이터가 변경되면 ETag를 바꾸어서 변경
    • 요청 헤더 : if-Modified-Since(Last-Modified), if-None-Match(ETag)
    • 서버의 데이터가 갱신되지 않았으면 304 Not Modified + 헤더 정보만 응답(바디 x)

 

* 자세한 것은 찾아보고 사용하는 것이 좋음

 

[참고] 인프런 김영한님 강의를 공부한 내용입니다.

+ Recent posts