2021. 8. 23. 08:00ใBack-End ์์ ์ค/Spring Framework
์๋ ํ์ธ์? ์ฃผ๋ํ๋ ์ ๋๋ค.
๋ณธ ๋ด์ฉ์ ์ฃผ๋ํ๋์ด Web ๊ฐ๋ฐ ํ๋ก์ ํธ๋ฅผ ํ๋ฉด์ ๋์ ํ๋ ๊ธฐ์ ๋ค์ ๋ํ ๋ด์ฉ์ ์ ๋ฆฌํ๋ ๊ณณ ์ ๋๋ค!
๋ฐ๋ก ์ฃผ๋ํ๋์ด ์ ๋ฆฌํด์ ์์ฑํ ๊ฒ์ด์์.
์ฃผ๋ํ๋์ Back End ๊ฐ๋ฐ์ ๋งก์์๋ต๋๋ค!
๐ Web Layer๋ฅผ ๊ตฌ์ฑํ๋ฉด์ ๋๋์
๊ตญ๋น์ง์ ๊ต์ก์ ๋ฐ์ผ๋ฉด์ ์ฒซ๋ฒ์งธ ๊ต์ก์ด JAVA Servlet์ ์ด์ฉํ MVC๋ฅผ ์ด์ฉํด์ JSP์ ํจ๊ป Web Programing์ ํ๋ ๊ฒ์ด์์ต๋๋ค. ์ด ๋, VO, Controller, DAO, VIEW์ ๋ํด ์ดํด๋ฅผ ํ์๊ณ , ์ด๋ฅผ ๊ธฐ๋ฐ์ผ๋ก Project๋ฅผ ํ์์ต๋๋ค.
๊ทธ๋ฐ๋ฐ, Spring Boot๋ฅผ ์ด์ฉํ๋ฉฐ, Web Layer์ ๊ฐ๋ ์ ์ดํดํ๋ ค๊ณ ํ๋ ์๊พธ VO, Controller, DAO, VIEW๋ผ๋ ๊ตฌ๋ถ์ผ๋ก Layer๋ฅผ ์ดํดํ๋ ค๊ณ ํ์ฌ ์ดํด๊ฐ ๋์ง ์๊ณ , ์ด๋ ต๊ฒ ๋๊ปด์ก์ต๋๋ค.
๊ทธ๋ฌ๋ค ๋ณด๋ ๋น์ฆ๋์ค ๋ก์ง์ด๋ผ๋ ๋จ์ด๋ ์ดํด๊ฐ ๋์ง ์์๊ณ , ์ด ๊ธฐ๋ณธ์ด ์ดํด๊ฐ ๋์ง ์๋ค๋ณด๋ ์ฝ๋ฉ์ ํ๋ ๊ฒ์ด ์ด๋ ต๊ฒ ๋ค๊ฐ์์ต๋๋ค.
๊ฐ์ด ํ๋ก์ ํธ๋ฅผ ํ๋ฉฐ, BackEnd ํ์ฅ์ด ์ด๊ฒ์ ๋ํ ์ข์ ์๋ฃ๋ฅผ ๋ง์ด ์๋ ค์ฃผ์๊ณ , Googling์ ํตํด ๊ณต๋ถ๋ฅผ ํ ๊ฒฐ๊ณผ ์กฐ๊ธ์ฉ ์ดํด๊ฐ ๋์๊ณ , ์ด๋ ๊ฒ ๋๋์ด์ ๊ตฌ์ฑ์ ํด์ผ SoC (Separation of Concerns)์ ๊ด์ฌ์ฌ ๋ถ๋ฆฌ ์์น๊ณผ KISS(Keep It Simple Stupid)๋ผ๋ ๊ฐ๋จํ๊ณ ์๊ธฐ ์ฝ๊ฒ ๋ง๋ค์๋ผ๋ ์์น์ ์งํฌ ์ ์๋ค๋ ๊ฒ์ ์๊ฒ ๋์๊ณ , ์ํคํ ์ฒ ๋์์ธ์ ์ค๊ณํ๋๋ฐ, ์ง์ผ์ผํ ๊ธฐ๋ณธ ์์น์ด๋ผ๋ ๊ฒ์ ์๊ฒ ๋์์ต๋๋ค.
๐ Web Layer
- Controller์ View Templat ์์ญ
- Client Request / Respone์ ๊ดํ ์ ๋ฐ์ ์ธ ์์ญ
- @Filter, Intercepter, @ControllerAdvice ๋ฑ
์ ํฌ๊ฐ ์ด๋ฒ Project์์ ์ฌ์ฉํ๋ Web Layer๋ ํฌ๊ฒ Controller์ Reate๋ฅผ ์ด์ฉํ View Templat ๊ทธ๋ฆฌ๊ณ Interceptor๊ฐ ์์ผ๋ฉฐ, Controller๋ RESTFulํ๊ฒ ๊ตฌํํ๊ธฐ ์ํ์ฌ REST Controller๋ฅผ ๊ตฌํํ์์ต๋๋ค.
์ด๋ฅผ ์ํด @RestController๋ฅผ ์ฌ์ฉํ์๊ณ , ์ด๊ฒ์ Spring MVC Controller์ @ResponseBody๊ฐ ์ถ๊ฐ๋ ๊ฒ์ผ๋ก REST Controller๋ Json ํํ๋ก Entity Data๋ฅผ ๋ฐํ ํด์ฃผ๋ ์ญํ ์ ํฉ๋๋ค.
- Client๋ URI ํ์์ผ๋ก Web Service์ ์์ฒญ
- Mapping ๋๋ Handler์ Type์ ์ฐพ๋ Dispatcher Servlet์ด Request๋ฅผ Interceptor
- REST Controller๋ ํด๋น Request๋ฅผ ์ฒ๋ฆฌํ๊ณ , ๊ฒฐ๊ณผ๊ฐ Data ๋ฐํ
packageudodog.goGetterServer.controller.api.bookreport;
importio.swagger.annotations.*;
importlombok.RequiredArgsConstructor;
importorg.springframework.data.domain.Page;
importorg.springframework.data.domain.Pageable;
importorg.springframework.data.domain.Sort;
importorg.springframework.data.web.PageableDefault;
importorg.springframework.hateoas.EntityModel;
importorg.springframework.http.HttpStatus;
importorg.springframework.http.ResponseEntity;
importorg.springframework.web.bind.annotation.GetMapping;
importorg.springframework.web.bind.annotation.RestController;
importudodog.goGetterServer.model.converter.bookreport.BookReportConverter;
importudodog.goGetterServer.model.dto.DefaultRes;
importudodog.goGetterServer.model.dto.response.bookreport.BookreportResponseDto;
importudodog.goGetterServer.service.bookreport.BookreportService;
@Api(tags = {"๋
์ ๊ธฐ๋ก ๊ด๋ จ API"})
@RestController
@RequiredArgsConstructor
public classBookReportController {
private finalBookReportConverter bookReportConverter;
private finalBookreportService bookreportService;
@ApiOperation( value = "๋
์ ๊ธฐ๋ก ์ ์ฒด ๋ชฉ๋ก ์กฐํ API", notes = "๋
์ ๊ธฐ๋ก์ ์ ์ฒด ๋ชฉ๋ก ์กฐํ API" )
@ApiResponses( value = {
@ApiResponse( code=200, message = "1. ์กฐํ์ฑ๊ณต \t\n 2. ๋ฐ์ดํฐ์์ \t\n 3. ํ ํฐ์๋ฌ" )
})
//์ ์ฒด ์กฐํ ๊ด๋ จ Method
@GetMapping("/api/book-reports")
publicResponseEntity<EntityModel<DefaultRes<Page<BookreportResponseDto>>>> totalBookReportFindAll(
@PageableDefault( sort = "id", direction = Sort.Direction.DESC, size = 10 )Pageablepageable ) {// Index Value๋ฅผ ์ด์ฉ ์ต์ ๋ ์ง์์ ๊ธฐ์ค์ผ๋ก ๋ด๋ฆผ์ฐจ์์ผ๋ก ํ์ด์ง๋น 10๊ฐ์ฉ ์ถ๋ ฅ
return newResponseEntity<>(bookReportConverter.toModel( bookreportService.searchBookReportList(pageable)), HttpStatus.OK);
}// totalBookReportFindAll()๋
}// Class๋
๐โ๏ธ Service Layer
Client Request์ ๋ฐ๋ฅธ Data ์ฒ๋ฆฌ๋ฅผ ํ๋ ๊ณ์ธต์ผ๋ก ๋น์ฆ๋์ค, ํธ๋์ญ์ ์ฒ๋ฆฌ๋ฅผ ๋ด๋นํ๋ ์์ญ์ผ๋ก, ๊ฐ๋ฐ ๋์์ ๋ชจ๋ ์ฌ๋์ด ๋์ผํ ๊ด์ ์์ ์ดํดํ ์ ์๊ณ , ๊ณต์ ํ ์ ์๋๋ก ๋จ์ํ.
๐ BookReportService.Class
@Service
@RequiredArgsConstructor
public class BookreportService {
private finalUserRepositoryuserRepository;
private finalBookReportRepositorybookReportRepository;
private finalBookReportQueryRepository bookReportQueryRepository;
//์ ์ฒด ์กฐํ
publicDefaultRes<Page<BookreportResponseDto>> searchBookReportList(Pageablepageable) {//ํ์ด์ง ์ฒ๋ฆฌ
Page<BookreportResponseDto> bookReportPage = bookReportQueryRepository.findAllWithFetchJoin(pageable);
if(bookReportPage.getTotalElements() == 0) {// bookreport์ ๋ด์ฉ์ด ์๋ค๋ฉด?
returnDefaultRes.response(HttpStatus.OK.value(), "๋ฐ์ดํฐ์์");
}else{// bookreport์ ๋ด์ฉ์ด ์๋ค๋ฉด?
returnDefaultRes.response(HttpStatus.OK.value(), "์กฐํ์ฑ๊ณต", bookReportPage);
}// if-else๋
}// searchBookReportList()๋
๐งท Domain Model
Back Ground์์ ๋์ํ๋ ์ฃผ์ Logic ์ฒ๋ฆฌํ๋ฉฐ, Data๋ฅผ ๋ด๋น.
๋ณดํต Data Base์ ์ง์ ์ ์ธ ์ฐ๊ด์ ๋งบ์ผ๋ฉฐ ๋์.
๐จ Entity
Data Base Table๊ณผ 1:1๋ก Maping๋๋ Class.
์ธ๋ถ์์ ๋ฌด๋ถ๋ณํ getter์ setter๋ก ์ธํด Data์ ์ค์ผ์ด ๋ฐ์ํ๋ฉด ์๋๊ธฐ์ ๋ด๋ถ Method๋ก Data ์กฐ์ ํ์.
์ ํฌ๋ ์์์ ์์ฑํ Data ์ค์ผ์ ๋ฐฉ์งํ๊ธฐ ์ํ์ฌ Setter๋ฅผ ์ ์ธํ๊ณ , @Getter์ @Bulder๋ฅผ ํตํด Data ์ฃผ์ ์ ์ฌ์ฉํ์์ต๋๋ค.
Effective Java ๊ท์น 2 - ์กฐ์์ ๋ธ๋กํฌ
์์ฑ์์ ์ธ์๊ฐ ๋ง์ ๋๋ ๋น๋ ํจํด์ ๊ณ ๋ คํ๋ผ.
๋น๋ ํจํด(Builder Pattern)์ด๋ ๋ณตํฉ ๊ฐ์ฒด์ ์์ฑ ๊ณผ์ ๊ณผ ํํ ๋ฐฉ๋ฒ์ ๋ถ๋ฆฌํ์ฌ ๋์ผํ ์์ฑ ์ ์ฐจ์์ ์๋ก ๋ค๋ฅธ ํํ ๊ฒฐ๊ณผ๋ฅผ ๋ง๋ค ์ ์๊ฒ ํด ์ฃผ๋ ๊ฒ์ ๋๋ค.
Builder Pattern์ ์ฌ์ฉํ๋ฉด ๊ฐ์ฒด๋ค๋ง๋ค ๋ค์ด๊ฐ์ผํ ์ธ์๊ฐ ๊ฐ๊ฐ ๋ค๋ฅผ ๋ ์ ์ฐํ๊ฒ ์ฌ์ฉํ ์ ์์ผ๋ฉฐ, ๋ถ๋ถ๋ณํ Setter ์์ฑ์ ๋ฐฉ์งํ๊ณ , ๋ถํธ ๊ฐ์ฒด๋ก ๋ง๋ค ์๊ฐ ์์ต๋๋ค. ๋ํ, ํ์ Argument๋ฅผ ์ง์ ํ ์ ์๊ฒ ๋ฉ๋๋ค. (๋ณดํต์ ๊ฒฝ์ฐ PK ์ญํ ์ ํ ID Value) ์ด๋ ๊ฒ ํ์ฌ Service์ ๊ฐ์ ๊ณณ์์ BookReport.builder(). { ์ฌ๋ฌ๊ฐ์ง Field์ ์ด๊ธฐ๊ฐ ์ ์ธ }.build() ํํ๋ก ๊ฐ์ฒด๋ฅผ ๋ง๋ค ์ ์๊ฒ ํ์์ต๋๋ค.
์์ ๋ด์ฉ์ผ๋ก ์ ํฌ๋ ์๋์ ๊ฐ์ด ์ฝ๋๋ฅผ ์์ฑํ์ฌ Entity๋ฅผ ์ฌ์ฉํ์์ต๋๋ค.
๐ BookReport.java
@Getter
@NoArgsConstructor
@Entity
@EntityListeners(AuditingEntityListener.class)
public class BookReport {
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
privateLong bookReportId;
@ManyToOne(targetEntity = User.class, fetch = FetchType.LAZY)
@JoinColumn(name = "user_id")
privateUser user;
privateString title;
privateString bookName;
privateString content;
@CreatedDate privateLocalDateTime createdAt;
@Builder
publicBookReport(User user, String title, String bookName, String content) {
this.user = user;
this.title = title;
this.bookName = bookName;
this.content = content;
}//์์ฑ์ ๋
@ManyToOne์ ํด๋น Entity์ ๋ค๋ฅธ Entity ๊ฐ์ ๊ด๊ณ๋ฅผ ์ง๊ธฐ ์ํ์ฌ ์ฌ์ฉํ์์ต๋๋ค.
์ด๊ฒ์ Writer ์ ์ฅ์์ BookReport๊ฐ ์ฌ๋ฌ ๊ฐ๊ฐ ๋ ์ ์๊ธฐ ๋๋ฌธ์ Writer : BookReport = 1 : N ๊ด๊ณ๊ฐ ๋๊ธฐ ๋๋ฌธ์ ์ฌ์ฉ ํ์์ต๋๋ค.
@Id, @GeneratedValue๋ ํด๋น Entity์ ์ฃผ์ ํค (Primary Key, PK)๊ฐ ๋ ๊ฐ์ ์ง์ ํ๊ธฐ ์ํ์ฌ @Id๋ฅผ ์ฌ์ฉํ์๊ณ , @GenneratedValue๋ ์ด PK๊ฐ ์๋์ผ๋ก 1์ฉ ์ฆ๊ฐํ๋ ํํ๋ก ์์ฑ๋ ์ง ๋ฑ์ ๊ฒฐ์ ํ๊ธฐ ์ํด ์ฌ์ฉํ์์ต๋๋ค.
๐จ Data Transfer Object
Data Transfer Object์ ์ฝ์, Data ์ ์ก ๊ณ์ธต ๋ด๋น.
Data๋ฅผ Layer(Controller, Service ๋ฑ) ๊ฐ ์ด๋ ์ํฌ ๋ ์ฌ์ฉ.
๊ฐ๋ฅํ Data ์ ๋ฌ ์ด์ธ ๊ธฐ๋ฅ์ ์ด ๊ณณ์ ๋ฃ์ง ์์.
๊ณ์ธต ๊ฐ Data ๊ตํ์ ์ํ Object.
๐ BookreportResponseDto.java
@Getter
public class BookreportResponseDto {
privateLong bookReportId; //๋
์ ๊ธฐ๋ก Index๋ฒํธ
privateString userNickname; // User Nickname
privateString bookName; //์ฑ
์ ๋ชฉ
privateString title; //๋
์ ๊ธฐ๋ก ๊ธ ์ ๋ชฉ
privateLocalDate createAt; //์์ฑ์ผ
publicBookreportResponseDto ( Long bookReportId, String userNickname, String bookName, String title, LocalDate createAt ) {
this.bookReportId = bookReportId;
this.userNickname = userNickname;
this.bookName = bookName;
this.title = title;
this.createAt = createAt;
} //์์ฑ์ ๋
์์ ์ฝ๋๋ Client์๊ฒ ์ ๋ฌํ Value์ ๋ํด Data๋ฅผ ์ ์กํ๊ธฐ ์ํด ๋ง๋ Response DTO ์ ๋๋ค.
DATA ์ฃผ์ ์ ๋ํ ๋ด์ฉ ์ธ์๋ DTO์ ๋ค๋ฅธ ๋ด์ฉ์ ๋ฃ์ผ๋ฉด ์๋๋ค๋ ๊ฒ์ ๊ธฐ๋ฐ์ผ๋ก ํ์ฌ Field ๋ณ์์ ์์ฑ์, @Getter ์ธ์๋ ๋ด์ฉ์ ์ถ๊ฐํ์ง ์์์ต๋๋ค.
๐จ Repository
DataBase์ ์ง์ ์ ๊ทผํ๋ ๊ณ์ธต์ผ๋ก, DAO ์์ญ์ ํด๋น.
๐ BookReportRepository.java
public interface BookReportRepository extends JpaRepository < BookReport, Long > {
}
์์ BookReportRepository๋ interface์ด๋ฉฐ, JpaRepository๋ฅผ ์์ํ๊ฒ ๊ตฌ์ฑํ์์ต๋๋ค.
ID Value ๊ธฐ๋ฐ์ผ๋ก ๊ธฐ๋ณธ์ ์ธ interface๋ค์ด ์ ์ธ ๋์ด ์๊ธฐ ๋๋ฌธ์ ๊ทธ ์ธ ๊ธฐ๋ฅ์ ์์ ๊ฐ์ด ๊ท์น์ ๋ง๊ฒ ๊ตฌ์ฑ์ ํ์์ต๋๋ค.
๐ BookReportQueryRepository.java (For querydsl)
@RequiredArgsConstructor
@Repository
public class BookReportQueryRepository {
private finalJPAQueryFactory jpaQueryFactory;
private finalEntityManagerentityManager;
//์ ์ฒด ์กฐํ
publicPage<BookreportResponseDto> findAllWithFetchJoin(Pageablepageable) { //ํ์ด์ง ์ฒ๋ฆฌ
List<BookreportResponseDto> reportList = jpaQueryFactory.select(Projections.fields(
BookreportResponseDto.class,
bookReport.bookReportId,
bookReport.user,
bookReport.title,
bookReport.createdAt))
.from(bookReport)
.innerJoin(bookReport.user,user)
.fetch();
intstart = (int)pageable.getOffset();
intend = Math.min((start + pageable.getPageSize()), reportList.size() );
return newPageImpl<>(reportList.subList(start, end), pageable, reportList.size());
} // reportList๋
} // Class๋
์ ํฌ๊ฐ Querydsl์ ์ฌ์ฉํ ์ด์ ๋ Jap๋ฅผ ํตํด ๊ธฐ๋ณธ์ ์ผ๋ก ์ ๊ณตํด ์ฃผ๋ @Query๋ก๋ ๋ค์ํ ์กฐํ ๊ธฐ๋ฅ์ ์ฌ์ฉํ๋๋ฐ, ํ๊ณ๊ฐ ์๊ธฐ ๋๋ฌธ์ ์ฌ์ฉํ์์ต๋๋ค.
๊ทธ๋์ ์์ ๊ฐ์ ๋ฌธ์ ๋ฅผ ํด๊ฒฐํ๊ธฐ ์ํด Static Type์ ์ง์ํ๋ ์กฐํ ํ๋ ์์ํฌ๋ฅผ ์ฌ์ฉํ๊ธฐ ์ํด Querydsl์ ์ ํํ์ฌ ์ฌ์ฉํ์์ต๋๋ค.
์ฃผ๋ํ๋์ ๊ธ์ด ๋ง์์ ๋์ จ๋์? ๊ตฌ๋ ๊ณผ ๊ณต๊ฐ! ๊ทธ๋ฆฌ๊ณ , ๋๊ธ์ ์ฃผ๋ํ๋์๊ฒ ๋ง์ ํ์ด ๋ฉ๋๋ค
'Back-End ์์ ์ค > Spring Framework' ์นดํ ๊ณ ๋ฆฌ์ ๋ค๋ฅธ ๊ธ
[๋์ ๊ธฐ์ !] JPA์ Querydsl (0) | 2021.08.25 |
---|---|
[๋์ ๊ธฐ์ !] ํ ์คํธ ์ฝ๋ (0) | 2021.08.24 |
[Spring Boot] AOP (0) | 2021.08.20 |
[Spring Boot] Spring Data JPA (0) | 2021.08.19 |
[Spring Boot] JPA (0) | 2021.08.18 |