6-1 REST 방식의 서비스
Ajax와 REST 방식의 이해
Ajax(Asynchronous JavaScript And XML) 방식은 브라우저에서 서버를 호출하지만 모든 작업이 브라우저 내부에서 이루어지기 때문에 브라우저 화면의 변화 없이 서버와 통신할 수 있습니다.
Ajax가 가져온 변화는 모바일에서도 Ajax방식으로 데이터를 교환할 수 있습니다. 모바일에서도 서버의 데이터가 필요한데 화면과 관련된 부분은 필요하지 않기 때문에 순수한 데이터만 전송하는 방식이라면 데이터를 재사용할 수 있습니다.
JSON 문자열
문자열은 어떠한 프로그래밍 언어나 기술에 종속되지 않는다는 장점이 있습니다. 문자열을 이용하면 데이터를 주고받는 것에 신경 써야 하는 일은 없지만 문자열로 복잡한 구조의 데이터를 표현하는 문제가 발생합니다. 복잡한 데이터를 표현하기 위해서 고려되는 것이 XML과 JSON입니다. JSON은 단순한 문자열이지만 객체를 표현할 때'{}'에 키:값 의 형태로 객체를 표현하는 방식입니다.
자바는 데이터를 표현하기 위해 별도의 클래스를 만들고 인스턴스를 만드는 방식과 달리 JSON은 자바스크립트 문법에 맞는 문자열로 데이터를 표현하기 때문에 클라이언트에서 어떤 기술을 이용하든 공통적으로 인식할 수 있습니다.
REST방식
REST 방식은 클라이언트 프로그램인 브라우저나 앱이 서버와 데이터를 어떻게 주고받는 것이 좋을지에 대한 가이드라고 할 수 있습니다.
Ajax를 이용하면 브라우저의 주소가 이동할 필요 없이 서버와 데이터를 교환할 수 있기 때문에 URL은 행위나 작업이 아닌 원하는 대상 그 자체를 의미하고, GET/POST 방식과 PUT/DELETE 등의 추가적인 전송 방식을 활용해서 '행위나 작업'을 의미하게 되었습니다.
예시: 게시물 수정
이전 표현 | REST 방식 표현 |
/board/modify -> 게시물의 수정 <form> -> 데이터의 묶음 |
/board/123 -> 게시물 자원 자체 PUT 방식 -> 행위나 목적 |
REST에 대한 공식 문서의 설명은 다음과 같습니다.
REST는 효율적, 안정적이며 확장가능한 분산 시스템을 자여올 수 있는 소프트웨어 아키텍처 디자인 제약의 모음을 나타냅니다. 그리고 그 제약들을 준수했을 때 그 시스템은 RESTful하다고 일컬어집니다.
REST 방식은 하나의 자원을 하나의 주소로 표현이 가능하며 유일무이해야 합니다. REST 방식에서 URL 하나는 하나의 자원을 식별할 수 있는 고유값이고 GET/POST 등은 이에 대한 작업을 의미합니다.
Swager UI
REST 방식의 테스트는 특별한 화면을 구성하는 것이 아니라 데이터를 전송하고 결과를 확인하는 방법이기 때문에 기존의 웹 개발 방식과는 조금 차이가 있습니다. API를 테스트할 수 있는 Postman이나 Swagger UI를 사용합니다. Swagger UI는 개발할 때 어노테이션 설정으로 API 문서와 테스트 할 수 있는 화면을 생성할 수 있으므로 개발자는 한번에 테스트 환경까지 구성할 수 있다는 장점이 있습니다.
@Configuration
public class SwaggerConfig {
@Bean
public Docket api() {
return new Docket(DocumentationType.OAS_30)
.useDefaultResponseMessages(false)
.select()
.apis(RequestHandlerSelectors.basePackage("org.zerock.b01.controller"))
.paths(PathSelectors.any())
.build()
.apiInfo(apiInfo());
}
private ApiInfo apiInfo() {
return new ApiInfoBuilder()
.title("Boot 01 Project Swagger")
.build();
}
}
@Configuration
@EnableWebMvc
public class CustomServletConfig {
}
6-2 다대일 연관관계 실습
연관관계를 결정하는 방법
데이터베이스에서는 PK와 FK를 이용해서 엔티티 간의 관계를 표현합니다. 반면에 객체지향을 이용하는 JPA는 얘기가 다릅니다. 객체지향은 방향성을 결정하는 것이 어렵습니다. JPA의 연관 관계의 판단 기준을 결정할 때 다음 기준을 적용하는 것이 좋습니다.
1. 연관관계의 기준은 항상 변화가 많은 쪽을 기준으로 결정
2. ERD의 FK를 기준으로 결정
단방향과 양방향
객체지향이 관계형 데이터베이스와 다른 점 중의 하나는 객체가 다른 객체를 참조하는 방식의 차이가 있다는 점입니다. 데이터베이스에 특정한 PK를 다른 테이블에서 FK로 참조해서 사용하지만 객체지향에서는 'A가B의 참조를 가질 수도 있고 B가 A의 참조를 가질 수 있다는 점이 다릅니다.
JPA는 참조를 결정할 때 다양한 방식이 존재할 수 있습니다. 한쪽만 참조를 유지하는 방식을 단방향, 양쪽 모두를 참조하는 방식을 양방향이라고 분류합니다.
- 양방향: 양쪽 객체가 모두 서로 참조를 유지하기 때문에 모든 관리를 양쪽 객체에 동일하게 적용해야만 하는 불편함이 있지만 JPA에서 필요한 데이터를 탐색하는 작업에서는 편리함을 제공합니다.
- 단방향: 구현이 단순하고 에러 발생의 여지를 많이 줄일 수 있지만 조인 처리와 같이 다른 엔티티 객체의 내용을 사용하는 데 더 어렵다는 단점이 있습니다.
다대일 연관 관계
필요한 엔티티 클래스에 @ManyToOne을 이용해서 연관 관계를 작성합니다. 연관 관계를 구성할 때 다음과 같은 점들을 주의해야 합니다.
- @ToString을 할 때 참조하는 객체를 사용하지 않도록 반드시 excluder 속성값을 지정합니다.
- @ManyToOne과 같이 연관 관계를 나타낼 때는 반드시 fetch 속성은 LAZY로 합니다.
@Entity
@Getter
@Builder
@AllArgsConstructor
@NoArgsConstructor
@ToString(exclude = "board")
public class Reply extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long rno;
@ManyToOne(fetch = FetchType.LAZY)
private Board board;
private String replyText;
private String replyer;
}
ReplyRepository 생성과 테스트
Reply는 Board와 별도로 CRUD가 일어날 수 있기 때문에 별도의 Repository를 작성해서 관리합니다.
public interface ReplyRepository extends JpaRepository<Reply, Long> {
}
@SpringBootTest
@Log4j2
public class ReplyRepositoryTests {
@Autowired
private ReplyRepository replyRepository;
@Test
public void testInsert() {
Long bno=100L;
Board board= Board.builder().bno(bno).build();
Reply reply=Reply.builder()
.board(board)
.replyText("댓글")
.replyer("replyer1")
.build();
replyRepository.save(reply);
}
}
6-3 댓글의 자바스크립트 처리
비동기 처리와 Axios
동기화된 방식의 단점은 여러 작업을 처리할 수 없다는 것입니다. 비동기 방식의 핵심은 통보에 있습니다. 여러 작업을 처리하기 때문에 나중에 결과나 나오면 통보해주는 방식을 사용합니다.
자바스크립트에는 Promise라는 개념을 도입해서 비동기 호출을 동기화된 방식으로 작성할 수 있는 문법적인 장치를 만들어 줬는데 Axios는 이를 활용하는 라이브러리입니다.
Axios 호출해보기
Axios를 이용할 때 async/await을 같이 이용하면 비동기 처리를 동기화된 코드처럼 작성할 수 있습니다. async는 함수 선언시에 사용하는데 해당 함수가 비동기 처리를 위한 함수라는 것을 명시하기 위해서 사용하고 awit는 async 함수 내에서 비동기 호출하는 부분에 사용합니다.
async function get1(bno){
const result= await axios.get(`/replies/list/${bno}`)
console.log(result)
}
<script layout:fragment="script" th:inlune="javascript">
const bno=[[${dto.bno}]]
get1(bno)
</script>
비동기 함수의 반환
화면에서 결과가 필요하다면 Axios의 호출 결과를 반환받아야 하기 때문에 다음과 같이 작성될 것입니다.
async function get1(bno){
const result= await axios.get(`/replies/list/${bno}`)
return result.data
}
get1()을 호출하는 쪽에서 호출 결과를 받기 위해서 다음과 같이 처리하려고 시도할 것입니다.
<script layout:fragment="script" th:inlune="javascript">
const bno=[[${dto.bno}]]
console.log(get1(bno))
</script>
코드를 실행하면 Promise가 반환됩니다. 실행 결과는 console.log(get1(bno))이후에 실행됩니다.
이것은 get1()이 비동기 함수이므로 get1()을 호출한 시점에서는 반환할 것이 없지만 나중에 무언가를 반환하기로 한 약속만을 반환하기 때문입니다.
비동기 처리 방식의 결정
비동기 처리할 때는 앞선 방법처럼 일반적인 함수와 동작 방식이 다르므로 이를 어떻게 사용해서 일관성 있게 처리할 것인지를 결정해야 합니다.
비동기 함수를 이용해서 결과 데이터를 처리하는 방식은 크게 다음과 같습니다.
- 비동기 함수에는 순수하게 비동기 통신만 처리하고 호출한 쪽에서 then()이나 catch()등을 이용해서 처리하는 방식
- 비동기 함수를 호출할 때 나중에 처리해야 하는 내용을 같이 별도의 함수로 구성해서 파라미터로 전송하는 방식
댓글 처리와 자바스크립트
async function getList({bno, page, size, goLast}){
const result = await axios.get(`/replies/list/${bno}`, {params: {page, size}})
return result.data
}
<script layout:fragment="script" th:inlune="javascript">
const bno=[[${dto.bno}]]
function printReplies(page,size,goLast){
getList({bno, page, size, goLast}).then(
data=> {console.log(data)}
).catch(e=? {
console.error(e)
})
}
printReplies(1,10)printReplies(1,10)
</script>
read.html에는 getList()를 호출하는 함수와 현재 페이지가 로딩되면 해당 함수를 호출하도록 작성했습니다.
@JsonFormat, @JsonIgnore
@JsonFormat을 이용해 JSON 처리 시에 포맷팅을 지정해봅니다.
댓글 수정시간의 경우 화면에서 출력할 일이 없으므로 JSON으로 변환될 때 제외도록 @JsonIgnore를 적용합니다.
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ReplyDTO {
private Long rno;
@NotNull
private Long bno;
@NotEmpty
private String replyText;
@NotEmpty
private String replyer;
@JsonFormat(pattern="yyyy=MM-dd HH:mm:ss")
private LocalDateTime replyDate;
@JsonIgnore
private LocalDateTime modDate;
}
async function addReply(replyobj){
const response = await axios.post('/replies/', replyObj)
return response.data
}
새로운 댓글을 등록하는 기능을 추가했습니다.
'자바웹개발 워크북' 카테고리의 다른 글
자바웹개발 워크북(8) (0) | 2025.01.26 |
---|---|
자바웹개발 워크북(7) (0) | 2025.01.20 |
자바웹개발 워크북(5) (0) | 2025.01.16 |
자바웹개발 워크북(4) (0) | 2025.01.05 |
자바웹개발 워크북(3) (0) | 2025.01.02 |