1.REST API는 REST(REpresentational State Transfer) 아키텍처 스타일의 디자인 원칙을 준수하는 API입니다. 이러한 이유로 REST API를 RESTful API라고도 합니다.
컴퓨터 과학자인 Roy Fielding 박사가 2000년에 자신의 박사학위 논문에서 처음으로 정의한 REST는 개발자에게 비교적 높은 수준의 유연성과 자유를 제공합니다. 이러한 유연성은 REST API가 마이크로서비스 아키텍처에서 컴포넌트와 애플리케이션을 연결하는 일반적인 방법으로 부상하게 된 이유 중 하나에 불과합니다.
[출처사이트] : https://www.ibm.com/kr-ko/topics/rest-apis
2. 트랜잭션 JPA
JPA(Java Persistence API)에서의 트랜잭션은 데이터베이스 상태를 변경하는 연산들을 묶어서 원자적으로 실행하는 데 사용됩니다. 트랜잭션은 ACID라고 알려진 성질을 따릅니다:
- 원자성(Atomicity): 모든 트랜잭션 연산은 성공하거나 실패로 완전히 실행되거나 실행되지 않아야 합니다. 즉, 트랜잭션 내의 모든 작업은 하나의 원자적인 단위로 처리됩니다.
- 일관성(Consistency): 트랜잭션이 완료된 후에도 데이터베이스는 일관된 상태여야 합니다. 즉, 트랜잭션이 실행되기 전과 후에 데이터베이스의 일관성이 유지되어야 합니다.
- 격리성(Isolation): 동시에 여러 트랜잭션이 실행될 때, 각 트랜잭션은 다른 트랜잭션의 작업에 영향을 받지 않고 독립적으로 실행되는 것처럼 보여야 합니다.
- 지속성(Durability): 성공적으로 완료된 트랜잭션은 영구적으로 데이터베이스에 반영되어야 합니다. 즉, 시스템 장애 또는 전원 이상과 같은 문제가 발생해도 데이터베이스는 트랜잭션이 완료된 상태로 유지되어야 합니다.
JPA에서는 트랜잭션을 관리하기 위해 여러 방법을 제공합니다. 가장 일반적으로는 Spring Framework와 같은 프레임워크를 사용하여 트랜잭션을 관리합니다. Spring의 @Transactional 어노테이션을 사용하여 메서드나 클래스에 트랜잭션을 적용할 수 있습니다. 이 어노테이션을 사용하면 트랜잭션의 시작과 종료를 자동으로 처리할 수 있습니다.
트랜잭션은 데이터베이스 연산을 보호하고 데이터 일관성을 유지하는 데 중요한 역할을 합니다. 따라서 트랜잭션을 적절하게 관리하여 데이터베이스 상태를 안정적으로 유지하는 것이 매우 중요합니다.
[출처사이트: 챗GPT]
3. JPA 구동순서
JPA(Java Persistence API)를 사용하여 애플리케이션을 개발할 때는 일반적으로 다음과 같은 순서로 작업을 수행합니다:
의존성 설정:
- 먼저 프로젝트의 의존성 관리 도구를 사용하여 JPA 라이브러리를 포함시킵니다. 대표적으로는 Maven, Gradle 등이 있습니다. 필요한 의존성을 추가한 후 프로젝트를 빌드하여 라이브러리를 다운로드하고 사용할 수 있도록 설정합니다.
엔티티 클래스 작성:
- JPA를 사용하여 데이터베이스와 상호 작용하는데 필요한 엔티티 클래스를 작성합니다. 엔티티 클래스는 데이터베이스 테이블과 매핑되며, 각각의 엔티티 클래스는 데이터베이스의 테이블과 매핑되는 필드를 가지고 있습니다.
데이터베이스 설정:
- JPA를 사용하여 애플리케이션과 상호 작용할 데이터베이스를 설정합니다. 이는 persistence.xml 파일이나 Spring의 설정 파일을 통해 수행될 수 있습니다. 데이터베이스 연결 정보, 드라이버 클래스, 데이터베이스 유저 및 패스워드 등을 설정합니다.
#참고
일반적으로 JPA의 properties 파일은 persistence.xml 파일이나 Spring의 설정 파일과 함께 사용됩니다. persistence.xml 파일은 JPA 설정을 정의하고 데이터베이스 연결 정보, 엔티티 클래스 위치, 데이터베이스 테이블과 엔티티 간의 매핑 등을 설정합니다. 이러한 설정 중 일부는 properties 파일을 사용하여 정의될 수 있습니다.
properties 파일은 애플리케이션이 시작될 때 클래스 경로에서 읽힐 수 있습니다. 예를 들어, Spring Boot 애플리케이션에서는 application.properties 파일이 자동으로 클래스 경로에서 로드되어 애플리케이션의 설정에 사용됩니다.
또한 properties 파일을 직접 읽어서 사용할 수도 있습니다. Java에서는 java.util.Properties 클래스를 사용하여 properties 파일을 읽고, 그 내용을 애플리케이션에서 사용할 수 있습니다. 이 경우에는 필요한 시점에 파일을 읽어와서 설정에 사용하게 됩니다.
EntityManagerFactory 생성:
- 애플리케이션의 시작점에서 EntityManagerFactory를 생성합니다. EntityManagerFactory는 JPA 엔티티 매니저를 생성하는데 사용됩니다. 대부분의 경우 persistence.xml 파일을 사용하여 EntityManagerFactory를 구성합니다.
EntityManager 생성:
- EntityManagerFactory를 사용하여 EntityManager를 생성합니다. EntityManager는 JPA에서 가장 중요한 인터페이스로, 엔티티를 관리하고 데이터베이스와의 상호 작용을 처리합니다.
트랜잭션 시작:
- 데이터베이스 상호 작용이 필요한 메서드나 코드 블록에서 트랜잭션을 시작합니다. 이는 Spring의 @Transactional 어노테이션을 사용하거나 직접 코드로 트랜잭션을 시작하는 방법을 사용할 수 있습니다.
CRUD 작업 수행:
- EntityManager를 사용하여 엔티티의 CRUD(Create, Read, Update, Delete) 작업을 수행합니다. 이를 통해 데이터베이스와의 상호 작용을 처리하고 데이터를 저장하거나 조회할 수 있습니다.
트랜잭션 커밋 또는 롤백:
- 데이터베이스 작업이 성공적으로 완료되었을 경우, 트랜잭션을 커밋하여 변경사항을 영구적으로 반영합니다. 그렇지 않은 경우에는 트랜잭션을 롤백하여 이전 상태로 되돌립니다.
EntityManager 종료:
- 작업이 완료되면 EntityManager를 종료합니다. 이는 데이터베이스 리소스를 해제하고 메모리 누수를 방지하기 위해 필요합니다.
애플리케이션 종료:
- 애플리케이션의 생명주기가 끝나면 EntityManagerFactory를 닫고 애플리케이션을 종료합니다. 이는 애플리케이션의 정상적인 종료를 보장하고 리소스를 해제하는데 도움이 됩니다.
[출처사이트 : 챗GPT]
3.) JPA 생명주기
Transient(비영속) 상태:
- 엔티티 객체가 생성되었지만, 아직 영속성 컨텍스트와 관련이 없는 상태입니다. 즉, 데이터베이스에 저장되지 않은 상태입니다. 객체가 new 연산자를 통해 생성되거나, EntityManager를 통해 persist() 메서드를 호출하지 않은 상태가 비영속 상태입니다.
Managed(영속) 상태:
- 엔티티가 영속성 컨텍스트에 관리되는 상태입니다. EntityManager를 통해 persist() 메서드를 호출하여 영속성 컨텍스트에 엔티티를 등록하면 영속 상태가 됩니다. 이후에 영속 상태인 엔티티의 상태 변화는 자동으로 데이터베이스에 반영됩니다.
Detached(준영속) 상태:
- 영속 상태의 엔티티가 영속성 컨텍스트와의 관계가 끊긴 상태입니다. 즉, 영속성 컨텍스트에서 관리되지 않는 상태입니다. 영속 상태의 엔티티가 영속성 컨텍스트를 벗어나면(detached) 해당 엔티티는 준영속 상태가 됩니다.
Removed(삭제) 상태:
- 영속성 컨텍스트에서 관리되던 엔티티가 삭제되는 상태입니다. EntityManager를 통해 remove() 메서드를 호출하여 엔티티를 삭제하면 삭제 상태가 됩니다. 삭제된 엔티티는 데이터베이스에서도 삭제됩니다.
이러한 엔티티의 생명주기 상태 변화는 JPA가 데이터베이스와 상호 작용하는 동안 발생하며, 영속성 컨텍스트를 통해 관리됩니다. 영속성 컨텍스트는 엔티티의 생명주기를 추적하고 관리하여 데이터베이스와의 일관성을 유지하고 변경사항을 관리합니다.
[참고사이트: 챗GPT]
4.디자인패턴 23개
[출처사이트: https://effortguy.tistory.com/182]
5.클린코드
[출처사이트 :https://www.samsungsds.com/kr/insights/cleancode-0823.html ]
[출처사이트 : https://www.nextree.io/basic-of-clean-code/]
6.데이터베이스 정규화
ㅡㅡㅡ
1
2
3
4
5
6
7
8
ㅡㅡㅡ
●TodoService
package com.shopapi.service;
import com.shopapi.dto.PageRequestDTO;
import com.shopapi.dto.PageResponseDTO;
import com.shopapi.dto.TodoDTO;
public interface TodoService {
//crud
//#1. 등록 기능
Long register(TodoDTO todoDTO);
//#2. 조회
TodoDTO get(Long tno);
//#3. 업데이트(=갱신)
void modify(TodoDTO todoDTO);
//#4. 삭제
void remove(Long tno);
//#5. 페이징
PageResponseDTO<TodoDTO> list(PageRequestDTO pageRequestDTO); //추상메소드
PageResponseDTO<TodoDTO> getList(PageRequestDTO pageRequestDTO); //추상메소드
}
●TodoServiceImpl
package com.shopapi.service;
import com.shopapi.domain.Todo;
import com.shopapi.dto.PageRequestDTO;
import com.shopapi.dto.PageResponseDTO;
import com.shopapi.dto.TodoDTO;
import com.shopapi.repository.TodoRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.modelmapper.ModelMapper;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
//트랜잭셔널 : 중요
@Service //서비스담당
@Transactional // import org.springframework.transaction.annotation.Transactional;
@Log4j2 //롬복것 사용
@RequiredArgsConstructor
public class TodoServiceImpl implements TodoService {
//TodoService가 추상메소드이기때문에, 자식(TodoServiceImpl)이 다 구해줘야합니다.
private final ModelMapper modelMapper;
private final TodoRepository todoRepository;
//방식1) 모델맵퍼라는 라이브러리
//방식2) todoservice (interface에서 버전업 , default을 활용해서 구현)
//상속받는 모든 자식들이 사용할수있게 쓰는 방식
//엔티티 > DTO
/*
디폴트는 처음에 쓰기에는 복잡할수있는데, 다대다 관계
DTO > 엔티티
엔티티 > DTO(자료가져옴)
모델맵퍼라이브러리가 도와줍니다.
*/
//#1. 등록(create) (레포지토리 하나주입받아야합니다.
//모델맵퍼를 주입받아야합니다. 자동주입 받게할것
//자동주입 : 1) autowired(생성자주입받는방식) 2)롬복이용 @RequiredArgsConstructor
@Override
public Long register(TodoDTO todoDTO) {
log.info(".........");
Todo todo = modelMapper.map(todoDTO, Todo.class);
Todo savedTodo = todoRepository.save(todo);
return savedTodo.getTno();
//엔티티에서 DTO로 왓다갓다 자유롭게하는 것 도와주는 것 : 모델맵퍼라는 라이브러리 활용
//파라미터로 이동합니다.
}
//#2.조회(select)
@Override
public TodoDTO get(Long tno) {
Optional<Todo> result = todoRepository.findById(tno);
//Optinal : 래퍼 클래스
// 이것을 사용하지 않으면 매번 if문을 이용해서 null인지 체크한 후 사용
// 이것을 이용하면 null 체크를 위한 if문 없이도 NullPointerException이 발생하지
// 않음
Todo todo = result.orElseThrow();
TodoDTO dto = modelMapper.map(todo, TodoDTO.class);
return dto;
}
//#3. 업데이트(update)
@Override
public void modify(TodoDTO todoDTO) {
Optional<Todo> result = todoRepository.findById(todoDTO.getTno());
Todo todo = result.orElseThrow();
todo.changeTitle(todoDTO.getTitle());
todo.changeDueDate(todoDTO.getDueDate());
todo.changeComplete(todoDTO.isComplete());
todoRepository.save(todo);
}
//#4. 삭제(delete)
@Override
public void remove(Long tno) {
todoRepository.deleteById(tno);
}
//#5. 페이징 처리
@Override
public PageResponseDTO<TodoDTO> list(PageRequestDTO pageRequestDTO) {
Pageable pageable =
PageRequest.of(
pageRequestDTO.getPage() - 1 ,
pageRequestDTO.getSize(),
Sort.by("tno").descending());
Page<Todo> result = todoRepository.findAll(pageable);
List<TodoDTO> dtoList = result.getContent().stream()
.map(todo -> modelMapper.map(todo, TodoDTO.class))
.collect(Collectors.toList());
long totalCount = result.getTotalElements();
PageResponseDTO<TodoDTO> responseDTO = PageResponseDTO.<TodoDTO>withAll()
.dtoList(dtoList)
.pageRequestDTO(pageRequestDTO)
.totalCount(totalCount)
.build();
return responseDTO;
}
@Override
public PageResponseDTO<TodoDTO> getList(PageRequestDTO pageRequestDTO) {
Page<Todo> result = todoRepository.search1(pageRequestDTO);
List<TodoDTO> dtoList = result.getContent().stream()
.map(todo -> modelMapper.map(todo, TodoDTO.class))
.collect(Collectors.toList());
log.info("dtoList: " + dtoList);
long totalCount = result.getTotalElements();
log.info("totalCount: " + totalCount);
PageResponseDTO<TodoDTO> responseDTO = PageResponseDTO.<TodoDTO>withAll()
.dtoList(dtoList)
.pageRequestDTO(pageRequestDTO)
.totalCount(totalCount)
.build();
return responseDTO;
}
}
#모델맵퍼 라이브러리 추가(gradle)
plugins {
id 'java'
id 'org.springframework.boot' version '3.1.11'
id 'io.spring.dependency-management' version '1.1.4'
}
group = 'com'
version = '0.0.1-SNAPSHOT'
java {
sourceCompatibility = '17'
}
configurations {
compileOnly {
extendsFrom annotationProcessor
}
}
repositories {
mavenCentral()
}
dependencies {
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-web'
compileOnly 'org.projectlombok:lombok'
developmentOnly 'org.springframework.boot:spring-boot-devtools'
runtimeOnly 'com.mysql:mysql-connector-j'
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testCompileOnly 'org.projectlombok:lombok'
testAnnotationProcessor 'org.projectlombok:lombok'
//implementation 'org.modelmapper:modelmapper:3.1.0'
implementation 'org.modelmapper:modelmapper:3.1.1'
}
tasks.named('bootBuildImage') {
builder = 'paketobuildpacks/builder-jammy-base:latest'
}
tasks.named('test') {
useJUnitPlatform()
}
●RootConfig
package com.shopapi.config;
import org.springframework.context.annotation.Configuration;
import org.modelmapper.ModelMapper;
import org.modelmapper.convention.MatchingStrategies;
import org.springframework.context.annotation.Bean;
@Configuration
public class RootConfig {
@Bean
public ModelMapper getMapper() {
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration()
.setFieldMatchingEnabled(true)
.setFieldAccessLevel(org.modelmapper.config.Configuration.AccessLevel.PRIVATE)
.setMatchingStrategy(MatchingStrategies.LOOSE);
//MatchingStrategies
//Loose : 느슨한 모드
//STANDARD : 덜 엄격
//STRICT : 엄격하게 사용(소스,필드명,
// .setFieldMatchingEnabled(true)
// .setFieldAccessLevel(org.modelmapper.config.Configuration.AccessLevel.PRIVATE)
//기본값 : 퍼블릭
//PRIVATE :
return modelMapper;
}
}
config = 설정파일
어노테이션을 줌으로써 모델 맵퍼에 대한 설정사항을 여기다가 잡아준다는 것을 의미합니다.
스프링부트한테, 설정파일임을 알려줍니다.
@bean
한곳에 쓸곳 아니라, 전체적으로 전역저으로 사용하기위해, 빈으로 등록합니다.
어느곳에서 전역적으로 만드러주는 역할
검색 : 모델맵퍼…
#
#
쿼리DSL, 시큐리티가 버전에 민감합니다.
쓰고있는 버전에서 공식문서를 활용해서, 내게맞는 디펜던시 버전을 활용해서, 사용하기를 권장합니다.
의존관계 설정, 버전
●2
●TodoServiceImpl
package com.shopapi.service;
import com.shopapi.domain.Todo;
import com.shopapi.dto.PageRequestDTO;
import com.shopapi.dto.PageResponseDTO;
import com.shopapi.dto.TodoDTO;
import com.shopapi.repository.TodoRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.modelmapper.ModelMapper;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
//트랜잭셔널 : 중요
@Service //서비스담당
@Transactional // import org.springframework.transaction.annotation.Transactional;
@Log4j2 //롬복것 사용
@RequiredArgsConstructor
public class TodoServiceImpl implements TodoService {
//TodoService가 추상메소드이기때문에, 자식(TodoServiceImpl)이 다 구해줘야합니다.
private final ModelMapper modelMapper;
private final TodoRepository todoRepository;
//방식1) 모델맵퍼라는 라이브러리
//방식2) todoservice (interface에서 버전업 , default을 활용해서 구현)
//상속받는 모든 자식들이 사용할수있게 쓰는 방식
//엔티티 > DTO
/*
디폴트는 처음에 쓰기에는 복잡할수있는데, 다대다 관계
DTO > 엔티티
엔티티 > DTO(자료가져옴)
모델맵퍼라이브러리가 도와줍니다.
*/
//#1. 등록(create) (레포지토리 하나주입받아야합니다.
//모델맵퍼를 주입받아야합니다. 자동주입 받게할것
//자동주입 : 1) autowired(생성자주입받는방식) 2)롬복이용 @RequiredArgsConstructor
@Override
public Long register(TodoDTO todoDTO) {
log.info(".........");
Todo todo = modelMapper.map(todoDTO, Todo.class);
Todo savedTodo = todoRepository.save(todo);
return savedTodo.getTno();
//엔티티에서 DTO로 왓다갓다 자유롭게하는 것 도와주는 것 : 모델맵퍼라는 라이브러리 활용
//파라미터로 이동합니다.
}
//#2.조회(select)
@Override
public TodoDTO get(Long tno) {
Optional<Todo> result = todoRepository.findById(tno);
//Optinal : 래퍼 클래스
// 이것을 사용하지 않으면 매번 if문을 이용해서 null인지 체크한 후 사용
// 이것을 이용하면 null 체크를 위한 if문 없이도 NullPointerException이 발생하지
// 않음
Todo todo = result.orElseThrow();
TodoDTO dto = modelMapper.map(todo, TodoDTO.class);
return dto;
}
//#3. 업데이트(update)
@Override
public void modify(TodoDTO todoDTO) {
Optional<Todo> result = todoRepository.findById(todoDTO.getTno());
Todo todo = result.orElseThrow();
todo.changeTitle(todoDTO.getTitle());
todo.changeDueDate(todoDTO.getDueDate());
todo.changeComplete(todoDTO.isComplete());
todoRepository.save(todo);
}
//#4. 삭제(delete)
@Override
public void remove(Long tno) {
todoRepository.deleteById(tno);
}
//#5. 페이징 처리
@Override
public PageResponseDTO<TodoDTO> list(PageRequestDTO pageRequestDTO) {
Pageable pageable =
PageRequest.of(
pageRequestDTO.getPage() - 1 ,
pageRequestDTO.getSize(),
Sort.by("tno").descending());
Page<Todo> result = todoRepository.findAll(pageable);
List<TodoDTO> dtoList = result.getContent().stream()
.map(todo -> modelMapper.map(todo, TodoDTO.class))
.collect(Collectors.toList());
long totalCount = result.getTotalElements();
PageResponseDTO<TodoDTO> responseDTO = PageResponseDTO.<TodoDTO>withAll()
.dtoList(dtoList)
.pageRequestDTO(pageRequestDTO)
.totalCount(totalCount)
.build();
return responseDTO;
}
@Override
public PageResponseDTO<TodoDTO> getList(PageRequestDTO pageRequestDTO) {
Page<Todo> result = todoRepository.search1(pageRequestDTO);
List<TodoDTO> dtoList = result.getContent().stream()
.map(todo -> modelMapper.map(todo, TodoDTO.class))
.collect(Collectors.toList());
log.info("dtoList: " + dtoList);
long totalCount = result.getTotalElements();
log.info("totalCount: " + totalCount);
PageResponseDTO<TodoDTO> responseDTO = PageResponseDTO.<TodoDTO>withAll()
.dtoList(dtoList)
.pageRequestDTO(pageRequestDTO)
.totalCount(totalCount)
.build();
return responseDTO;
}
}
●TodoServiceImplTest
package com.shopapi.service;
import com.shopapi.dto.TodoDTO;
import lombok.extern.log4j.Log4j2;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import java.time.LocalDate;
import static org.junit.jupiter.api.Assertions.*;
@SpringBootTest
@Log4j2
class TodoServiceImplTest {
@Autowired
private TodoService todoService;
@Test
@DisplayName("service 등록 테스트...")
public void testRegister(){
TodoDTO todoDTO = TodoDTO.builder()
.title("서비스 테스트1")
.writer("홍길동 테스터")
.dueDate(LocalDate.of(2024,04,30))
.build();
Long tno = todoService.register(todoDTO);
log.info("서비스 테스트 결과 INO :" + tno);
}
// 스트림(Stream)
// 데이터 소스를 추상화하고, 데이터를 다루는데 자주 사용되는 메소드들을 정의해 놓음
// 데이터 소스를 추상화 - 데이터 소스가 무엇이던 간에 같은 방식으로 다룰 수 있게
// 만들어 놓은 것, 코드의 재사용성이 높아짐
// 스트림을 이용하면 배열이나 컬렉션 뿐만 아니라 파일에 저장된 데이터도 모두
// 같은 방식으로 다룰 수 있음
// 단, 한번 사용하면 닫혀서 다시 사용할 수 없음, 필요하다면 스트림을 다시 생성해야 함
// 스트림은 데이터소스를 변경하지 않는다. 데이터 소스로부터 데이터를 읽기만 할 뿐
// 정렬된 결과를 컬렉션이나 배열에 담아서 반환할 수 있음
// List<String> sortList = strStream.sorted().collect(Collectors.toList());
// forEach(람다식)
//
//이터레이터처리 : 한번 사용한 것을 다시사용할때, 에러가 떨어지는 경우
//다시 재사용이 불가능,
//다시 슬려면, 재선언해서, 사용해야합니다.
//스트림도 마찬가지입니다. 한번사용하고 나면은 닫혀서 재 사용할 수 없음,
}
●3
#ABC(쓸려고 만든게 아니라, 예시를 들려고 임시로 작성했습니다.)
package com.shopapi;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.stream.Stream;
public class Abc {
public static void main(String[] args) {
String[] strArr = {"aaa", "bbb", "ccc", "ddd"};
//배열을 문자열로 저장
List<String> strList = Arrays.asList(strArr);
//정렬하고 출력
//1. 기존 방식
Arrays.sort(strArr);
Collections.sort(strList);
for(String str : strArr){
System.out.println(str);
}
for(String str : strList){
System.out.println(str);
}
// => 위 데이터 소스를 기반으로 하는 스트림을 생성
Stream<String> strStream1 = strList.stream();
Stream<String> strStream2 = Arrays.stream(strArr);
strStream1.sorted().forEach(System.out::println);
strStream2.sorted().forEach(System.out::println); //스트림 처리하고 여기서 '닫음'
//int numOfStr = strStream2.count(); //에러, 스트림이 이미 닫혔음(한번쓰고 다시사용x)
// 단, 한번 사용하면 닫혀서 다시 사용할 수 없음, 필요하다면 스트림을 다시 생성해야 함
//중간 연산 : 연산 결과가 스트림인 연산, 스트림에 연속해서 중간 연산할 수 있음
// 스트림에서 중간 연산은 연산결과를 스트림으로 반환하기 때문에 중간 연산을 연속해서 연결할 수 있음
//최종 연산 : 연산 결과가 스트림이 아닌 연산, 스트림의 요소를 소모하므로 단 한번만 가능
strStream1.distinct().limit(2).sorted().forEach(System.out::println);
// 중간연산 중간연산 중간연산 최종연산
//스트림에서 중간 연산은 연산결과를 스트림으로 반환하기 때문에 중간 연산을 연속해서 연결할 수 있음
//지연된 연산 : 스트림의 중간 연산은 최종 연산이 수행되기 전까지는 수행되지 않음
// distinct(), Limit(), sorted() 등은 즉각적인 연산이 수행되는 것이 아니라
// 최종 연산이 수행되어야 비로소 스트림의 요소들이 중간 연산을 거쳐 최종 연산에서 소모 됨
//distinct() : 중복을 제거
//filter(Predicate<T> predcate) : 조건에 안 맞는 요소는 제외
//limit(long maxSize) : 스트림의 일부를 잘라냄
//sorted() : 스트림의 요소를 정렬
//Stream<T> map() : 스트림의 요소를 반환
//DoubleStream mapToDouble(ToDoubleFunction<T> mapper)
//InStream mapToInt(ToIntFunction<T> mapper)
// ...
//Stream<T> flatMap(Function<T, Stream<R>> mapper>, 스트림 요소를 반환
//long count() : 스트림의 요소의 개수 반환
// ...
//Optional<T> reduce(BinaryOperator<T> accumulator) : 스트림의 요소를 하나씩줄여(리듀싱하면서)가면서 계산
//중간연산은 map(), flatMap() 많이 사용
//최종연산 reduce(), collect()
// sorted() : 정렬
}
}
●4
DTO만든다면,
공통적으로 사용한다면,
#PageRequestDTO
package com.shopapi.dto;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.experimental.SuperBuilder;
@Data
@SuperBuilder //자바 빈(java Bean) 클래스를 빌더 패턴으로 간편하게 구현
//상속받은 필드도 빌더에서 사용할 수 있도록 처리하기 위해
//@Builder는 상속 받은 필드를 빌더에서 사용하지 못함
// 쓰는 핵심 : 상속받을 것
//@Builder 만쓴다면, 상속이 안됩니다. 그렇기 떄문에, 상속받은 필드에서 사용할려고하면 문제가됩니다.
//그래서 @SuperBuilder 이것을 사용합니다.
@AllArgsConstructor
@NoArgsConstructor
public class PageRequestDTO {
@Builder.Default // 시작
private int page = 1;
@Builder.Default // 끝
private int size = 10;
}
#superBuilder
#PageResponseDTO
package com.shopapi.dto;
import lombok.Builder;
import lombok.Data;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
// PageResponseDTO는 나중에 다른 타입의 DTO들을 이용할 수 있도록 제네릭 타입으로 작성
// PageResponseDTO는 DTO의 리스트와 전체 데이터의 수를 지정하면 페이지 처리에
// 필요한 번호나 이전 다음에 대한 처리를 함
@Data
public class PageResponseDTO<E> { //TodoDTO
private List<E> dtoList; //dto
private List<Integer> pageNumList; // 페이지 처리를 위해 필요한 번호
private PageRequestDTO pageRequestDTO;
private boolean prev, next; //이전,이후 변수(버튼)
private int totalCount, prevPage, nextPage, totalPage, current;
@Builder(builderMethodName = "withAll")
public PageResponseDTO(List<E> dtoList, PageRequestDTO pageRequestDTO, long totalCount) { // totalCount : 전체 숫자
this.dtoList = dtoList;
this.pageRequestDTO = pageRequestDTO;
this.totalCount = (int)totalCount;
int end = (int)(Math.ceil( pageRequestDTO.getPage() / 10.0 )) * 10;
int start = end - 9;
int last = (int)(Math.ceil((totalCount/(double)pageRequestDTO.getSize())));
end = end > last ? last: end;
this.prev = start > 1; //트루이면 true :0부터시작 //fauls
this.next = totalCount > end * pageRequestDTO.getSize();
this.pageNumList = IntStream.rangeClosed(start,end).boxed().collect(Collectors.toList());
if(prev) {
this.prevPage = start -1; //스타트페이지 -1
}
if(next) {
this.nextPage = end + 1; //라스트페이지 +1
}
this.totalPage = this.pageNumList.size();
this.current = pageRequestDTO.getPage();
}
}
#페이징 원리(계산
시작과 끝 중에 끝페이지를 먼저.
ex)3번째를 1페이지에 넣고싶을떄
3/10.0 으로 나눈다 => 0.3(올림처리) = >1이됩니다. (1페이지안에 들어갑니다.)
ex)25번째를 3페이지에 넣고 싶을때
25/10.0 => 2.5(올림) => 3 *10 => 30
#끝페이지(요청한게 3이면,
3/100
정수형 다운캐스팅,
#시작페이지
1(start) 10(end) => (9씩차이)
11(start) 20(end) => (9씩차이)
21(start) 30(end) => (9씩차이)
#ex)
내가 100페이지 표현한다고 가정한다면, 요청한게 50페이지로 들어왔따면,
●5
#
목록페이지구현
포스트맨
레스트풀 = 제이슨방식
리턴타입을 String
리턴타입ㅇ
레스트풀쓸때 반환타입이 없었던 이전방식
●TodoServiceImpl
package com.shopapi.service;
import com.shopapi.domain.Todo;
import com.shopapi.dto.PageRequestDTO;
import com.shopapi.dto.PageResponseDTO;
import com.shopapi.dto.TodoDTO;
import com.shopapi.repository.TodoRepository;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.modelmapper.ModelMapper;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Optional;
import java.util.stream.Collectors;
@Service
@Transactional
@Log4j2
@RequiredArgsConstructor //생성자 자동 주입
public class TodoServiceImpl implements TodoService {
//자동 주입 대상은 final
private final ModelMapper modelMapper;
private final TodoRepository todoRepository;
//#1. 등록(insert)
@Override
public Long register(TodoDTO todoDTO) {
log.info("등록!!! .........");
Todo todo = modelMapper.map(todoDTO, Todo.class);
Todo savedTodo = todoRepository.save(todo);
return savedTodo.getTno();
}
//#2. 조회(select)
@Override
public TodoDTO get(Long tno) {
Optional<Todo> result = todoRepository.findById(tno);
//Optional :래퍼 클래스
// 이것을 사용하지 않으면 매번 if문을 이욯새 null인지 체크한 후 사용
// 이것을 이용하면 null 체크를 위한 if문 없이도 NullPointerException이 발생하지
// 않음
Todo todo = result.orElseThrow();
TodoDTO dto = modelMapper.map(todo, TodoDTO.class);
return dto;
}
//#3. 업데이트(update)
@Override
public void modify(TodoDTO todoDTO) {
Optional<Todo> result = todoRepository.findById(todoDTO.getTno());
Todo todo = result.orElseThrow();
todo.changeTitle(todoDTO.getTitle());
todo.changeDueDate(todoDTO.getDueDate());
todo.changeComplete(todoDTO.isComplete());
todoRepository.save(todo);
}
//#4. 삭제(delete)
@Override
public void remove(Long tno) {
todoRepository.deleteById(tno);
}
//#5. 페이징 처리
@Override
public PageResponseDTO<TodoDTO> list(PageRequestDTO pageRequestDTO) {
Pageable pageable =
PageRequest.of(
pageRequestDTO.getPage() - 1,
pageRequestDTO.getSize(),
Sort.by("tno").descending());
Page<Todo> result = todoRepository.findAll(pageable);
List<TodoDTO> dtoList = result.getContent().stream()
.map(todo -> modelMapper.map(todo, TodoDTO.class))
.collect(Collectors.toList());
long totalCount = result.getTotalElements();
PageResponseDTO<TodoDTO> responseDTO = PageResponseDTO.<TodoDTO>withAll()
.dtoList(dtoList)
.pageRequestDTO(pageRequestDTO)
.totalCount(totalCount)
.build();
return responseDTO;
}
@Override
public PageResponseDTO<TodoDTO> getList(PageRequestDTO pageRequestDTO) {
return null;
}
}
●TodoController
package com.shopapi.controller;
import com.shopapi.dto.PageRequestDTO;
import com.shopapi.dto.PageResponseDTO;
import com.shopapi.dto.TodoDTO;
import com.shopapi.service.TodoService;
import lombok.RequiredArgsConstructor;
import lombok.extern.log4j.Log4j2;
import org.springframework.web.bind.annotation.*;
import java.util.Map;
@RestController //JSON 형태의 객체로 반환하는 것입니다.
@RequiredArgsConstructor //생성자주입
@Log4j2
@RequestMapping("/api/todo")
public class TodoController {
private final TodoService service;
@GetMapping("/{tno}") //번호 입력
public TodoDTO get(@PathVariable(name ="tno") Long tno) { //PathVariable : @GetMapping("/{tno}") 이것을 넘겨받은 것
return service.get(tno); // TodoDTO 타입으로 전송 할것, service로부터 get(tno) 할겁니다.
}
@GetMapping("/list")
public PageResponseDTO<TodoDTO> list(PageRequestDTO pageRequestDTO ) {
log.info(pageRequestDTO);
return service.list(pageRequestDTO);
}
@PostMapping("/")
public Map<String, Long> register(@RequestBody TodoDTO todoDTO){
log.info("TodoDTO: " + todoDTO);
Long tno = service.register(todoDTO);
return Map.of("TNO", tno);
}
@PutMapping("/{tno}")
public Map<String, String> modify(
@PathVariable(name="tno") Long tno,
@RequestBody TodoDTO todoDTO) {
todoDTO.setTno(tno);
log.info("Modify: " + todoDTO);
service.modify(todoDTO);
return Map.of("RESULT", "SUCCESS");
}
@DeleteMapping("/{tno}")
public Map<String, String> remove( @PathVariable(name="tno") Long tno ){
log.info("Remove: " + tno);
service.remove(tno);
return Map.of("RESULT", "SUCCESS");
}
}
#포트번호 : 80이면 생략가능합니다. localhost/api/todo/11
포스트맨 사용
#
http://localhost/api/todo/list?page=3&size=10
●6
●CustomControllerAdvice
package com.shopapi.controller.advice;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import java.util.Map;
import java.util.NoSuchElementException;
@RestControllerAdvice
public class CustomControllerAdvice {
@ExceptionHandler(NoSuchElementException.class) // NoSuchElementException : 이런예외가 발생했을때, .class :
protected ResponseEntity<?> notExist(NoSuchElementException e) {
String msg = e.getMessage();
return ResponseEntity.status(HttpStatus.NOT_FOUND).body(Map.of("msg", msg));
//status : 상태표시
//NOT_FOUND : 상태가 어떤것인지
//body : 에 map(key와 value)
//오류를 가지고 핸들러안에다가 넣습니다.
//그 객체에 메세지를 주고 처리합니다.
//상태표시를 해줍니다.
}
@ExceptionHandler(MethodArgumentNotValidException.class)
protected ResponseEntity<?> handleIllegalArgumentException(MethodArgumentNotValidException e) {
String msg = e.getMessage();
return ResponseEntity.status(HttpStatus.NOT_ACCEPTABLE).body(Map.of("msg", msg));
}
}
#controllerAdvice에 대한 정보
[출처사이트 : https://tecoble.techcourse.co.kr/post/2023-05-03-ExceptionHandler-ControllerAdvice/]
#
POST 방식 : 바디라는 것에서 처리합니다.
PUT 방식 : URL
DELETE 방식 :
GET 방식 :
포스트맨 :
데이터전송이, 브라우저에서 바로확인못하는 상황에서, 이것이 전송된다면, 어떻게 될지 확인해볼수가 있습니다. 웹관련해서 네트워킹이 잘 되는지 확인하는 용도로 포스트맨을 활용합니다.
#주객이 전도되는 경우
ex)프로젝트의 핵심
JSON
LocaldateTime 으로 처리해야하는 경우
문자열로 처리하게 하는 것
시간변환을 제공해주는 ‘포맷터를 스프링에서 제공해줍니다.’
#
●LocalDateFormatter
package com.shopapi.controller.formatter;
import org.springframework.format.Formatter;
import java.time.LocalDate;
import java.time.format.DateTimeFormatter;
import java.util.Locale;
public class LocalDateFormatter implements Formatter<LocalDate> {
@Override
public LocalDate parse(String text, Locale locale) {
return LocalDate.parse(text, DateTimeFormatter.ofPattern("yyyy-MM-dd"));
}
@Override
public String print(LocalDate object, Locale locale) {
return DateTimeFormatter.ofPattern("yyyy-MM-dd").format(object);
}
//데이터를 가져다가, 날짜형식으로 바꿔주는 것
}
●CustomServletConfig
package com.shopapi.config;
import com.shopapi.controller.formatter.LocalDateFormatter;
import org.springframework.context.annotation.Configuration;
import org.springframework.format.FormatterRegistry;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
public class CustomServletConfig implements WebMvcConfigurer {
@Override
public void addFormatters(FormatterRegistry registry) {
registry.addFormatter(new LocalDateFormatter());
//mvc에게 처리하는 것에 대해서 자동으로 잡을수 있도록
}
// @Override
// public void addCorsMappings(CorsRegistry registry) {
//
// registry.addMapping("/**")
// .allowedOrigins("*")
// .allowedMethods("HEAD", "GET", "POST", "PUT", "DELETE", "OPTIONS")
// .maxAge(300)
// .allowedHeaders("Authorization", "Cache-Control", "Content-Type");
// }
}
●7자습
todocontroller
●8자습
ㅡㅡㅡ
'7.springBoot > 1)개념_springBoot' 카테고리의 다른 글
springBoot_개념_Day_19 (0) | 2024.05.02 |
---|---|
springBoot_개념_Day_18 (0) | 2024.05.01 |
springBoot_개념_Day_16 (0) | 2024.04.29 |
springBoot_개념_Day_15 (0) | 2024.04.26 |
springBoot_개념_Day_14 (0) | 2024.04.15 |