출처: https://mangkyu.tistory.com/18 [MangKyu's Diary:티스토리]
DispatcherServlet이란?
dispatcher-servlet이 해당 어플리케이션으로 들어오는 모든 요청을 핸들링해주고 공통 작업을 처리함.
모든 요청을 처리하다보니 이미지나 HTML/CSS/JavaScript 등과 같은 정적 파일에 대한 요청마저 모두 가로채는 까닭에 정적자원(Static Resources)을 불러오지 못하는 상황도 발생함.
애플리케이션 요청을 탐색하고 없으면 정적 자원 요청으로 처리함.
Dispatcher Servlet이 요청을 처리할 컨트롤러를 먼저 찾고, 요청에 대한 컨트롤러를 찾을 수 없는 경우에, 2차적으로 설정된 자원(Resource) 경로를 탐색하여 자원을 탐색하는 것입니다. 이렇게 영역을 분리하면 효율적인 리소스 관리를 지원할 뿐 아니라 추후에 확장을 용이하게 해준다는 장점이 있습니다.
@ResponseBody
그냥 데이터를 반환하고 싶을 때 사용. (http x)
스프링은 내부적으로 자바의 객체를 json 형태로 반환함.
RestController를 어노테이션으로 달아주면 따로 RequestBody를 달아주지 않아도 객체가 json 형태로 반환됨.
@ModelAttribute 어노테이션
쿼리스트링, 혹은 requestParam으로 들어온 body값을 객체에 반환할 수 있게 해줌.
setter 혹은 오버로딩 된 생성자가 필요함
생략해도 동작함.
*스프링은 파라미터가 simple value일 경우 requestParam이, 객체일 경우 modelAttribute가 생략되었다고 판단함. (예외도 있음. 나는 아직 모름)
%d = decimal
%s = string
%f = float
C 언어와 같은 몇몇 프로그래밍 언어에서 형식화된 문자열을 생성할 때 사용되는 서식 지정자입니다. 문자열 안에 넣어도 대체됨
DTO(Data Transfer Object)
데이터 전송 및 이동을 위해 생성되는 객체
데이터베이스와 소통하는 클래스는 조심스럽게 다뤄야 함. 그래서 따로 생성하는 것.
각 필드는 private으로 선언하여 사용할 것.
따라서 getter, setter. 생성자가 필요함.
*requestDto를 받아오려면 해당 값을 파라미터로 받는 생성자를 생성하고 거쳐온 데이터를 메인 데이터 클래스에 저장해주면 됨. this.userName = requestDto.getUserName();
*.stream() : 자료구조형의 데이터 하나하나를 for문처럼 돌려줌
.stream().map(MemoResponseDto::new).toList();
dependency에 해당하는 sql, jdbc를 추가해주면 됩니다.
(그런데 현업에서 사용할 확률이 낮다고 합니다.)
Bean?
Bean 객체로 등록(스프링이 관리)하는 법.
IoC를 위해 필요.
controller, service, repository등의 애너테이션 사용해 bean에 등록.
그 bean 객체는 메서드, 필드 : @Autowired로 주입.
생성자 주입 : 필요 X.
lombok을 통해서 주입하려면 @RequiredArgsConstructor를 통해 눈에 보이지 않는 bean(final 꼭 붙여야 함) 객체를 사용하는 생성자 사용.
수동으로 가져오는 방법:(그냥 예시일뿐)
ApplicationContext : IoC 컨테이너
1. bean 이름으로 가져오기
(해당 빈 타입으로 한 번 캐스팅해주기) .getBean("빈의 이름(가장 앞단 대문자는 소문자로 바뀌어 등록됨.)");
-> 그 다음 this 사용하여 직접 넣어주기
2. bean 클래스 형식으로 가져오기
.getBean(빈.class);
-> 그 다음 직접 넣어주기
JPA?
ORM(Object Relational Mapping)은 sql 작성 시 번거로운 점을 줄여주는 기술입니다. 객체 관계 매핑
그 중 대표적인 것 중 하나가 JPA입니다. (자바 orm 기술 명세)
애플리캐이션 서버와 jdbc api 사이에서 동작합니다.
스프링환경이 아닐 때(순수 자바환경)는 여기에 환경을 설정. (application.properties x)
hibernate는 sql을 보기 좋게 해줌.
영속성 컨텍스트? entity 객체를 효율적으로 관리하기 위해 만들어진 공간.
직접 SQL을 작성하지 않아도 JPA를 사용하여 DB에 데이터를 작성하거나 조회할 수 있습니다. 이런 과정을 효율적으로 처리하기 위해 Entity 객체들을 저장하여 관리하면서 DB와 소통합니다.
영속성 컨텍스트에 접근하여 Entity 객체들을 조작하기 위해서는 Entity 매니저가 필요합니다.
EntityManagerFactory는 일반적으로 DB 하나에만 생성되어 애플리케이션이 동작하는 동안 사용됩니다. 이 공장에 DB에 대한 정보를 전달하기 위해서는 persistence.xml을 resoueces/META_INF/ 경로에 만들어 정보를 넣어두면 됩니다.
Persistence 클래스를 만든다고 생각해볼 수 있습니다.
@BeforeEach
아래의 메서드가 각각 실행되기 전 무조건 실행됨.
.persist();
영속성 컨텍스트에 담고싶은 객체를 매니저를 통해 담아줍니다.
영속성 컨텍스트는 캐시 저장소 key에 저장한 식별자 값을 사용하여 entity 객체를 구분하고 관리합니다.
매니저.find(엔티티.class, primeryKey);로 찾아올 수 있습니다.
1차 캐시에 없으면 셀렉트문을 날려서 1차 캐시에 저장하고, 있으면 그냥 가져옵니다.
객체의 동일성을 보장하는데, 한 로우당 객체가 하나라는 뜻입니다.
순수자바의 경우 주소값 문제가 있을 수 있는데, 이 경우는 DB라서 다릅니다...?
매니저.remove(삭제 객체);를 이용해 delteted 상태를 캐시 저장하고, 커밋으로 최종 반영합니다. (그 전에 find 해야겟죠?)
사실은 commit 후에 매니저.flush(); 메서드가 있습니다.
자동으로 따라붙는데, 영속성 컨텍스트에 저장하는 메서드입니다.
트랜젝션 환경은 조회 외에 모든 sql 문에 필요한 환경입니다.
** JPA에서는 update를 어떻게 처리할까요?
find로 호출하여 1차캐시 저장, loadedState에 로딩하고,
set등을 이용해 값을 수정하여 1차 캐시에 저장하고,
두 값을 비교하여 변경이 감지되면 update SQL을 생성하여 쓰기 지연 저장소에 저장하고 그 SQL을 DB에 요청하고 최종 커밋 때 반영합니다.
매니저.clear(); 캐시를 지워요...
Entity?
@Entity 어노테이션으로 지정해주기.
@Id : 테이블의 기본 키 (Primery Key 지정. 식별자 역할. 없으면 엔티티에서 오류-영속성과 관련)
@GeneratredValue(strategy = GenerationType.IDENTITY) : auto_increament와 같은 역할
@Table : 테이블의 이름 지정
@Column : 필드와 매핑할 데이터베이스의 열을 지정할 수 있음.
스프링부트 환경에서는 매니저와 공장을 자동으로 생성해줍니다!
apllication.properties에 정보를 잘 넣어주면 됩니다.
그리고 @PersistanceContext 에너테이션을 통해 매니저를 가져올 수 있습니다.
스프링부트 환경에서 shift 두 번 누르고 SimpleJpaRepository를 검색해볼까요?
@Transactional 에너테이션을 통해 그 환경을 구현할 수 있는 것을 볼 수 있습니다.
메서드에 이 에너테이션을 사용하면 메서드 전체를 트랜젝션 하나로 묶어서, 메서드의 시작과 종료를 자동으로 세팅합니다.
그런데 readOnly = true 옵션이 기본으로 걸려있습니다.
읽기 작업에만 최적화되어있기 때문에, 수정을 하려면 해당 옵션을 false로 바꿔주어야 하겠네요.
조회할 때 Transaction은 필수가 아니라고 했는데, 필요한 상황도 있긴 하니 이런 옵션을 잘 기억합시다.
그리고 @Transactional 애너테이션을 두 번 덮어써서 false 옵션이 적용되는 예시도 볼 수 있네요.
테스트 코드 환경에서 transactional을 사용하면 그 환경이 끝난 후에 자동으로 rollBack하기 때문에! @Rollback에너테이션에 value false 옵션을 넣어주세요.
transaction의 전파?(propagation)
부모 메서드에 이 옵션이 걸려있고 자식메서드에도 걸려있을 경우, 자식 메서드는 기본으로 전파되어 부모 메서드에 합류됩니다.
Spring Data JPA는 하나의 모듈입니다.
Repository 인터페이스를 제공합니다.
Hibernate와 같은 JPA 구현체를 사용해서 구현한 클래스를 통해 사용됩니다.
(스프링에서 자동으로 simpleJpaRepository를 ...)
Repostitory를 interface로 선언하고 JpaRepository<엔티티, id 타입>을 상속하면 됩니다.
simpleJpaRepository 안에 이미 Repository 에너테이션이 있어서 따로 더 안 달아줘도 됩니다.
이 안에 이미 delete, save가 다 있네요?
굉장하다~
굉장하다~
JpaAuditing?
클래스를 새로 만들어서 사용할 수 있습니다.
@MappedSuperclass
엔티티가 이 새로 만든 추상 클래스를 상속하면 새 클래스의 멤버변수를 column으로 인식해줍니다. 추상이 아니어도 되지만 굳이 추상화하는 이유는 이 새 클래스 자체를 객체로 생성할 일이 없기 때문입니다.
@EntityListners(AuditingEntityLisner.class)
새로 만든 클래스에 auditing기능을 주는 겁니다.
@CreatedDate
새 클래스를 상속받은 엔티티가 생성되고 저장될 때의 시간을 저장합니다.
최초 생성시간만 저장되고 그 이후 시간은 저장되면 안되겟조?
그래서 @Column에 updatable 옵션을 false로 주어야 합니다.
@LastModifiedDate
업데이트가 발생될 때마다 그 시간을 저장합니다.
@Temporal
날짜등을 매핑할 때 사용합니다. (Date, Time, TimeStamp)
타입 지정해 주세요!
이렇게 만들고 필요한 엔티티에 새 클래스를 상속시켜줍니다.
그리고 ResponseDto에 시간을 반환해주면 될 것 같습니다. (필드 선언, 생성자 속 반환)
메인 어플리케이션에 @EnableJpaAuditing을 달아주어야 합니다.