23.12.20
김영한 인프런강의 보면서 공부했던 기록들 노션에서 옮겨둡니다!
0. 서론 및 준비
[ lombok의 생성자 기능 ]
- @AllargumnetConstructor : 모든 필드를 받는 생성자
- @RequiredArgumentConstructor : final 필드를 받는 생성자 + final 주입대상
- @NoArgsConstructor(access = AccessLevel.PROTECTED) : 생성자 이용금지 = protected 생성자 효과 / 이는 생성메서드를 따로 구비한 경우 일관성유지를 위한 조치
[ 기능 테스트 관련 ]
- @SpringBootTest : 실제 스프링 부트를 구동시켜서 테스트를 실행한다. = Bean 주입가능( autowired ) = 스프링컨테이너를 활용
- @RunWith(SpringRunner.class) : 스프링과 integration해서( 스프링과 엮어서 테스트하겠다. )
- @Test( expected = 예외명.class ) : 테스트 수행시 해당 예외가 발생하면 성공이다. try~catch로 예외 발생여부를 확인할 필요가 없다.
Assert.fail() : 예외가 발생하지 않는 경우를 대비, 코드가 이곳까지 도달하면 안된다!!!!
AssertError를 인위적으로 발생시켜서 테스트를 실패시킨다..
[ 메모리DB로 테스트하기 ]
- 운영파트(main)에서는 해당 resources를 이용하지만( propeties에 등록한 DB설정대로 ) 테스트파트(test)에서 해당 resources를 구축해서 스프링지원 무료 메모리DB사용가능
- 테스트케이스수행시에는 운영 yml을 무시하고 자신의 yml을 참조해서 수행한다.
- 어플리케이션 설정에서 spring 설정을 다 안해버리면 기본적으로 메모리모드로 돌아간다..
[ 중요 고려사항 ]
화면기능(form)과 엔티티를 분리하자!!!! ⇒ 엔티티는 핵심비즈니스 로직만 가질 수 있도록 순수하게 유지할것!!
DTO 사용권장 : 엔티티에서 필요한 정보만을 추출한 객체!!
특히 API에서 절대 엔티티사용하지 말 것이다!! ( 템플릿 엔진에서는 사용가능 )
[ 성능관련 주의사항 ]
- 즉시로딩(EAGER) : 연관된 모든 엔티티들을 함께 조회 ⇒ 예측이 어렵고 추적이 어려움
- X to One의 기본값
- @ManyToOne(fetch = FetchType.LAZY), @OneToOne(fetch = FetchType.LAZY) 변경해줄 것
- 지연로딩(LAZY) : 실무에서 모든 연관관계의 세팅값
- One to many의 기본값
1. 엔티티 설계
@Entity: JPA가 관리할 객체
@Id: 데이터베이스 PK와 매핑
@Embeddable : JPA의 내장타입, 관계X / 하나의 valueType으로 기능
[ 엔티티 매핑 ]
- 객체와 테이블 매핑
- @Entity : JPA가 관리하는 클래스
- name속성 : JPA에서 사용할 엔티티 이름, 클래스 이름이 기본값
- @Table : 엔티티와 매핑할 테이블 지정
- @Entity : JPA가 관리하는 클래스
- 필드와 컬럼 매핑:
- @Column
- name속성 : 필드와 매핑할 테이블 컬럼명
- insertable, updatable : 등록, 변경가능여부 / 기본 true
- nullable = true || false ( not null 제약조건 )
- unique = true || false ( unique 제약조건 )
- length : 문자길이 제약조건( String )
- @Temporal : 날짜타입매핑
- TemporalType.DATE: 날짜, 데이터베이스 date 타입과 매핑
- TemporalType.TIME: 시간, 데이터베이스 time 타입과 매핑
- TemporalType.TIMESTAMP: 날짜와 시간, 데이터베이 스 timestamp 타입과 매핑
- @Enumerated : enum타입매핑
- *@Enumerated*(EnumType.STRING) : 문자
- *@Enumerated*(EnumType.ORDINAL) : 숫자
- @Lob : BLOB, CLOB 매핑
- 필드 타입이 문자면 CLOB 매핑, 나머지는 BLOB 매핑
- @Transient : 특정 필드를 컬럼에 매핑하지 않음(매핑 무시)
- @Column
- 기본 키 매핑: @Id
- @GeneratedValue : 자동생성 → 전략(strategy)을 결정해줄 것
- identity전략 @GeneratedValue(strategy = GenerationType.IDENTITY)
- sequence전략 : 유일한 값을 순서대로 생성하는 특별한 데이터베이스 오브젝트
- table전략 : 키 생성 전용 테이블을 만들어 활용
- @GeneratedValue : 자동생성 → 전략(strategy)을 결정해줄 것
- 연관관계 매핑: @ManyToOne,@JoinColumn
[ 연관관계 ]
- 방향(direction) : 단방향, 양방향
- 연관관계 주인(owner) : 양방향 연관관계의 관리주인
- 다중성(multiplicity) :
- 다대일, 일대다 → JoinColumn명시(fk)
- 일대일 → JoinColumn명시(fk)
- 다대다 → JoinTable명시
- 단방향 관계 :: 테이블은 외래 키로 조인을 사용해서 연관된 테이블을 찾고 저장 ↔ 객체는 참조(임베드)를 사용해서 연관된 객체를 찾고 저장
- 양방향관계 :: 두 클래스 모두 저장하겠다. 이때 외래 키가 있는 있는 곳을 주인으로 정한다.
- mapped by 주인의 필드
- @JoinColumn(name=”피주인의 컬럼”)
외래키를 가진 주인만이 등록, 수정 가능하고 아닌쪽은 읽기만 가능하다.
객체의 변경포인트는 2가지인데, 테이블에서는 FK만 변경하면 된다
양방향 참조는 양쪽에 값이 변경될 수 있음
테이블에서는 조인키로 연결 ↔ 객체참조방식 & 객체 컬렉션을 만들어 참조
순수한 객체 관계를 고려하면 항상 양쪽 다 값을 입력해야 한다.
2. 기본 아키텍처
- 계층형 구조 사용
- controller, web: 웹 계층
- service: 비즈니스 로직, 트랜잭션 처리
- repository: JPA를 직접 사용하는 계층, 엔티티 매니저 사용
- domain: 엔티티가 모여 있는 계층, 모든 계층에서 사용
* 개발 순서: 서비스, 리포지토리 계층을 개발→ 테스트 케이스를 작성해서 검증→ 웹 계층 적용*
3. 리포지토리 계층
@Repository : 스프링 빈으로 등록, JPA 예외를 스프링 기반 예외로 예외 변환
@PersistenceContext : 엔티티 메니저( EntityManager ) 주입
- 단 하나만 생성해서 애플리케이션 전체 공유, JPA의 모든 데이터 변경은 트랜잭션 안에서 실행
- 영속성 컨텍스트 : 엔티티를 영구 저장, 관리하는 환경( 1차 캐시 & 쓰기지연 SQL을 이용 ) ⇒ 엔티티매니저를 주입받아 persistenceContext를 구축한다. 기본적인 JPA이용환경 구축
- 삽입(save)
- em.persist( 엔티티객체 ) : 객체를 저장하는 상태 ( 1차 캐시에 저장 & 쓰기지연 SQL저장소에 insert문 생성 → 트랜잭션 commit시에 DB에 반영 )
- 단건 조회(findOne)
- em.find( 엔티티클래스, “식별자” ) : 객체를 조회 ( 1차 캐시에서 조회 , 없다면 DB에서 조회하여 1차캐시에 저장 )
- 수정
- setter를 이용한 객체값 변경작업 수행 → commit시 1차캐시의 스냅샷과 비교하여 다른 부분에 대한 update SQL을 생성
- 삭제
- em.remove( 엔티티객체 )
- JPQL : SQL을 추상화한 객체 지향 쿼리 언어 ( findAll, findByName… )
- JPQL은 엔티티 객체를 대상으로 쿼리 ↔ SQL은 데이터베이스 테이블을 대상으로 쿼리
- JPQL : SQL을 추상화한 객체 지향 쿼리 언어 ⇒ 알맞은 SQL로 변환
- JPQL은 엔티티 객체를 대상으로 쿼리 수행 ↔ SQL은 데이터베이스 테이블을 대상으로 쿼리
- 엔티티이름을 사용, 테이블이름X, 별칭필수
- 반환타입
- TypeQuery: 반환 타입이 명확할 때 사용 / em.createquery( “쿼리”, 반환타입 )
- Query: 반환 타입이 명확하지 않을 때 사용 / em.createquery( “쿼리” )
- 반환메서드
- query.getResultList() : 결과가 하나이상일때 리스트 반환
- query.getSingleResult() : 결과가 하나일때 단일객체 반환
- 파라미터 바인딩 :변수명, ?
- query.setParameter()로 값설정
- 페이징 API
- setFirstResult(int startPosition) : 조회 시작 위치
- setMaxResults(int maxResult) : 조회할 데이터 수
- ⇒ MySQL : order by + desc limit로 처리
- ⇒ Oracle : ROWNUM을 이용하여 처리
** flush
: Flush 작업은 영속성 컨텍스트의 변경 내용을 데이터베이스에 동기화하는 작업= DB에 쿼리를 날린다.
- 영속성 컨텍스트의 변경내용을 데이터베이스에 반영
- 영속성 컨텍스트의 변경내용을 데이터베이스에 동기화( commit 처리 )
- 쓰기지연 SQL 저장소의 쿼리를 데이터베이스에 전송
3가지 형태로 호출된다.
-
- em.flush() : 직접호출
- 트랜잭션 커밋 —> 플러시 자동호출
- JPQL 쿼리 실행 —> 플러시 자동호출
Entity Manager의 flush는 변경된 엔터티의 상태를 데이터베이스에 동기화하는 작업이지만, 이것만으로는 트랜잭션의 commit이 수행되지 않습니다. 트랜잭션을 완료하고 영속성 컨텍스트의 변경 사항을 데이터베이스에 반영하려면 명시적으로 commit을 호출해야 합니다.
=> commit이 호출되면 flush가 진행되지만 flush가 호출된다고 commit이 되는 것은 아니다.
**준영속 상태
영속상태의 엔티티가 영속성 컨텍스트에서 분리( detached )
영속성 컨텍스트의 기능을 사용하지 못함
-
- em.detach( 엔티티객체 ) : 특정 엔티티만 준영속 상태로 전환
- em.clear() : 영속성 컨텍스트의 완전한 초기화
- em.close() : 영속성 컨텍스트 종료
4. 서비스 계층
- @Transactional
: 스프링 프레임워크에서 제공하는 어노테이션 중 하나로, 트랜잭션을 적용하는 데 사용
EntityManager를 통한 데이터변경은 항상 트랜잭션 안에서 이루어져야 하기에 엔티티 매니저를 활용하는 서비스, 레파지토리 페스트에서 활용한다.
- 기본적으로 트랜잭션을 시작하고 성공적으로 완료되면 커밋, 예외발생시 롤백
- 이때 테스트 케이스에서는 테스트 종료후 롤백처리를 한다..!!!!! → 반복적인 테스트를 위함
- @Transactional(readOnly =true) : JPA 조회작업(find)시 성능최적화( readonly )
⇒ 기본값 false, 전체적으로 클래스에 걸어주고 세부적으로 조회가 아닌 로직에는 해제시켜준다!
영속성 컨텍스트를 플러시 하지 않으므로 약간의 성능 향상(읽기 전용에는 다 적용)
5. 도메인 모델패턴
: 엔티티 내에 핵심로직이 위치, 레파지토리는 단순 조회저장기능 & 서비스는 위임하는 역할 ⇒ 객체지향의 극대화
*엔티티 내에 비즈니스 로직을 구현하는 것과 리포지토리에 해당 동작을 추가하는 것 사이에는 몇 가지 디자인 관점에서의 선택이 있다
- 엔티티 클래스에 비즈니스 로직을 포함시키면 객체 지향 설계 원칙에 따라 도메인 객체에 해당 동작이 자연스럽게 속하게 됩니다 ⇒ 엔티티가 자체적으로 자신의 상태와 동작을 책임지는 것이 바람직하다
- 메서드를 엔티티 내에 구현함으로써 해당 엔티티의 내부 상태 변경에 대한 캡슐화가 유지, 외부에서 직접적으로 상태를 변경하는 것을 방지
*SQL을 직접다루는 경우( mybatis, jdbc template.. ) 은 서비스 계층에서 상세한 비즈니스 로직을 기록 → 상쇄적인 변경작업이 같이 이루어져야한다( 하나의 변경작업후 파라미터로 꺼내오고 다시 바꾸고 ) ⇒ 서비스단의 로직이 굉장히 복잡해진다..
하지만 JPA를 사용하는 경우 바뀌는 변경포인트들을 찾아서 update해준다 == dirty checking(변경내역감지) / 하나의 메서드 로직으로 구현해놓으면 관련 sql들이 한꺼번에 수행된다…
Dirty Checking은 영속성 컨텍스트에서 엔터티의 상태 변화를 감지하고, 변경된 내용을 자동으로 데이터베이스에 반영하는 기능( 적절한 update문을 생성한다 ) ex. Order의 상태를 바꿈 → orderitem의 재고수량이 변경됨(원복) (자동update)
command성 데이터의 경우 controller에서 식별자만 넘긴다.
service단에서 식별자로 찾는다( 엔티티, 리파지토리, JPA 적용 ) 그리고 처리도 한다 ⇒ 트랜잭션안에서 처리되어서 영속상태를 유지할 수 있어서 개이득이다!!!
일반적으로 엔티티 매니저(EntityManager)를 사용하여 엔티티를 조회하거나 저장하면, 해당 엔티티는 영속 상태가 되고 영속성 컨텍스트에 관리됩니다. 변경 감지는 이러한 영속 상태의 엔티티에 대해서만 이루어지며, 엔티티 매니저에 의해 호출된 객체가 영속성 컨텍스트에 속하는 엔티티일 경우에만 해당됩니다.
- 비즈니스 로직 :
- 필드값 변경관련 → setter대신 활용
- 생성로직
- 연관관계 메서드 : 양방향인 경우
- 조회로직 : 검색포함
'web programming' 카테고리의 다른 글
[ 스프링 입문 ] 스프링 빈과 의존관계 (0) | 2024.09.02 |
---|---|
[ 스프링 입문 ] 회원관리 예제 (1) | 2024.09.02 |
[ 스프링 입문 ] 프로젝트 환경설정, 스프링 웹개발 기초 (1) | 2024.08.24 |
SpringBoot 기본개념 (0) | 2024.03.10 |
JQuery를 이용한 웹프론트 페이지 클론코딩 (0) | 2024.03.10 |