diff --git a/build.gradle b/build.gradle index 0b1897e..7c9e266 100644 --- a/build.gradle +++ b/build.gradle @@ -2,7 +2,6 @@ plugins { id 'java' id 'org.springframework.boot' version '3.1.5' id 'io.spring.dependency-management' version '1.1.3' - id 'com.ewerk.gradle.plugins.querydsl' version '1.0.10' } group = 'com' @@ -43,7 +42,10 @@ dependencies { runtimeOnly group: 'io.jsonwebtoken', name: 'jjwt-jackson', version: '0.11.5' //query dsl - implementation 'com.querydsl:querydsl-jpa' + implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta' + annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta" + annotationProcessor "jakarta.persistence:jakarta.persistence-api" + annotationProcessor "jakarta.annotation:jakarta.annotation-api" annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' annotationProcessor 'org.projectlombok:lombok' @@ -54,23 +56,4 @@ dependencies { tasks.named('test') { useJUnitPlatform() -} - -def querydslDir = "$buildDir/generated/querydsl" - -querydsl { - jpa = true - querydslSourcesDir = querydslDir -} - -sourceSets { - main.java.srcDir querydslDir -} - -configurations { - querydsl.extendsFrom compileClasspath -} - -compileQuerydsl { - options.annotationProcessorPath = configurations.querydsl } \ No newline at end of file diff --git a/src/main/java/com/finfellows/domain/product/application/FinancialProductService.java b/src/main/java/com/finfellows/domain/product/application/FinancialProductService.java new file mode 100644 index 0000000..b6659ff --- /dev/null +++ b/src/main/java/com/finfellows/domain/product/application/FinancialProductService.java @@ -0,0 +1,16 @@ +package com.finfellows.domain.product.application; + +import com.finfellows.domain.product.dto.condition.DepositSearchCondition; +import com.finfellows.domain.product.dto.response.SearchDepositRes; +import com.finfellows.global.config.security.token.UserPrincipal; +import com.finfellows.global.payload.PagedResponse; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.util.List; + +public interface FinancialProductService { + + PagedResponse findDepositProducts(UserPrincipal userPrincipal, DepositSearchCondition depositSearchCondition, Pageable pageable); + +} diff --git a/src/main/java/com/finfellows/domain/product/application/FinancialProductServiceImpl.java b/src/main/java/com/finfellows/domain/product/application/FinancialProductServiceImpl.java new file mode 100644 index 0000000..1d3163d --- /dev/null +++ b/src/main/java/com/finfellows/domain/product/application/FinancialProductServiceImpl.java @@ -0,0 +1,30 @@ +package com.finfellows.domain.product.application; + +import com.finfellows.domain.product.domain.repository.FinancialProductRepository; +import com.finfellows.domain.product.dto.condition.DepositSearchCondition; +import com.finfellows.domain.product.dto.response.SearchDepositRes; +import com.finfellows.global.config.security.token.UserPrincipal; +import com.finfellows.global.payload.PagedResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@RequiredArgsConstructor +@Service +@Transactional(readOnly = true) +public class FinancialProductServiceImpl implements FinancialProductService { + + private final FinancialProductRepository financialProductRepository; + + @Override + public PagedResponse findDepositProducts(UserPrincipal userPrincipal, DepositSearchCondition depositSearchCondition, Pageable pageable) { + Page financialProducts = financialProductRepository.findFinancialProducts(depositSearchCondition, pageable); + + return new PagedResponse<>(financialProducts); + } + +} diff --git a/src/main/java/com/finfellows/domain/product/domain/FinancialProduct.java b/src/main/java/com/finfellows/domain/product/domain/FinancialProduct.java index 49a2303..39653fe 100644 --- a/src/main/java/com/finfellows/domain/product/domain/FinancialProduct.java +++ b/src/main/java/com/finfellows/domain/product/domain/FinancialProduct.java @@ -15,7 +15,6 @@ @Table(name = "Financial_Product") @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter -@Where(clause = "status = 'ACTIVE'") public class FinancialProduct extends BaseEntity { @Id diff --git a/src/main/java/com/finfellows/domain/product/domain/FinancialProductOption.java b/src/main/java/com/finfellows/domain/product/domain/FinancialProductOption.java index fc8601f..82035ac 100644 --- a/src/main/java/com/finfellows/domain/product/domain/FinancialProductOption.java +++ b/src/main/java/com/finfellows/domain/product/domain/FinancialProductOption.java @@ -12,7 +12,6 @@ @Table(name = "Financial_Product_Option") @NoArgsConstructor(access = AccessLevel.PROTECTED) @Getter -@Where(clause = "status = 'ACTIVE'") public class FinancialProductOption extends BaseEntity { @Id diff --git a/src/main/java/com/finfellows/domain/product/domain/repository/FinancialProductQueryDslRepository.java b/src/main/java/com/finfellows/domain/product/domain/repository/FinancialProductQueryDslRepository.java new file mode 100644 index 0000000..24b5d1f --- /dev/null +++ b/src/main/java/com/finfellows/domain/product/domain/repository/FinancialProductQueryDslRepository.java @@ -0,0 +1,15 @@ +package com.finfellows.domain.product.domain.repository; + +import com.finfellows.domain.product.domain.FinancialProduct; +import com.finfellows.domain.product.dto.condition.DepositSearchCondition; +import com.finfellows.domain.product.dto.response.SearchDepositRes; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; + +import java.util.List; + +public interface FinancialProductQueryDslRepository { + + Page findFinancialProducts(DepositSearchCondition depositSearchCondition, Pageable pageable); + +} diff --git a/src/main/java/com/finfellows/domain/product/domain/repository/FinancialProductQueryDslRepositoryImpl.java b/src/main/java/com/finfellows/domain/product/domain/repository/FinancialProductQueryDslRepositoryImpl.java new file mode 100644 index 0000000..4496eb2 --- /dev/null +++ b/src/main/java/com/finfellows/domain/product/domain/repository/FinancialProductQueryDslRepositoryImpl.java @@ -0,0 +1,82 @@ +package com.finfellows.domain.product.domain.repository; + +import com.finfellows.domain.product.domain.FinancialProduct; +import com.finfellows.domain.product.domain.FinancialProductType; +import com.finfellows.domain.product.domain.QFinancialProduct; +import com.finfellows.domain.product.domain.QFinancialProductOption; +import com.finfellows.domain.product.dto.condition.DepositSearchCondition; +import com.finfellows.domain.product.dto.response.QSearchDepositRes; +import com.finfellows.domain.product.dto.response.SearchDepositRes; +import com.querydsl.core.QueryResults; +import com.querydsl.core.types.dsl.BooleanExpression; +import com.querydsl.jpa.impl.JPAQuery; +import com.querydsl.jpa.impl.JPAQueryFactory; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageImpl; +import org.springframework.data.domain.Pageable; +import org.springframework.data.support.PageableExecutionUtils; +import org.springframework.stereotype.Repository; + +import java.util.List; + +import static com.finfellows.domain.product.domain.QFinancialProduct.*; +import static com.finfellows.domain.product.domain.QFinancialProductOption.*; + +@RequiredArgsConstructor +@Repository +public class FinancialProductQueryDslRepositoryImpl implements FinancialProductQueryDslRepository { + + private final JPAQueryFactory queryFactory; + + @Override + public Page findFinancialProducts(DepositSearchCondition depositSearchCondition, Pageable pageable) { + List results = queryFactory + .select(new QSearchDepositRes( + financialProduct.id, + financialProduct.productName, + financialProduct.companyName, + financialProductOption.maximumPreferredInterestRate, + financialProductOption.interestRate + )) + .from(financialProductOption) + .leftJoin(financialProductOption.financialProduct, financialProduct) + .where( + financialProduct.financialProductType.eq(FinancialProductType.DEPOSIT), + typeEq(depositSearchCondition.getType()), + preferentialConditionEq(depositSearchCondition.getPreferentialCondition()), + termEq(depositSearchCondition.getTerm()) + ) + .orderBy(financialProductOption.maximumPreferredInterestRate.desc()) + .offset(pageable.getOffset()) + .limit(pageable.getPageSize()) + .fetch(); + + JPAQuery countQuery = queryFactory + .select(financialProductOption.count()) + .from(financialProductOption) + .leftJoin(financialProductOption.financialProduct, financialProduct) + .where( + financialProduct.financialProductType.eq(FinancialProductType.DEPOSIT), + typeEq(depositSearchCondition.getType()), + preferentialConditionEq(depositSearchCondition.getPreferentialCondition()), + termEq(depositSearchCondition.getTerm()) + ); + + // Page 객체 생성 + return PageableExecutionUtils.getPage(results, pageable, countQuery::fetchOne); + } + + private BooleanExpression termEq(Integer term) { + return term != null ? financialProductOption.savingsTerm.eq(term) : null; + } + + private BooleanExpression typeEq(String type) { + return type != null ? financialProductOption.financialProduct.joinWay.contains(type) : null; + } + + private BooleanExpression preferentialConditionEq(String preferentialCondition) { + return preferentialCondition != null ? financialProductOption.financialProduct.specialCondition.contains(preferentialCondition) : null; + } + +} diff --git a/src/main/java/com/finfellows/domain/product/domain/repository/FinancialProductRepository.java b/src/main/java/com/finfellows/domain/product/domain/repository/FinancialProductRepository.java index 6a26ef5..62eb298 100644 --- a/src/main/java/com/finfellows/domain/product/domain/repository/FinancialProductRepository.java +++ b/src/main/java/com/finfellows/domain/product/domain/repository/FinancialProductRepository.java @@ -3,6 +3,8 @@ import com.finfellows.domain.product.domain.FinancialProduct; import org.springframework.data.jpa.repository.JpaRepository; -public interface FinancialProductRepository extends JpaRepository { +import java.util.List; + +public interface FinancialProductRepository extends JpaRepository, FinancialProductQueryDslRepository { } diff --git a/src/main/java/com/finfellows/domain/product/dto/condition/DepositSearchCondition.java b/src/main/java/com/finfellows/domain/product/dto/condition/DepositSearchCondition.java new file mode 100644 index 0000000..9ef7aab --- /dev/null +++ b/src/main/java/com/finfellows/domain/product/dto/condition/DepositSearchCondition.java @@ -0,0 +1,12 @@ +package com.finfellows.domain.product.dto.condition; + +import lombok.Data; + +@Data +public class DepositSearchCondition { + + private Integer term; + private String type; + private String preferentialCondition; + +} diff --git a/src/main/java/com/finfellows/domain/product/dto/response/SearchDepositPagingRes.java b/src/main/java/com/finfellows/domain/product/dto/response/SearchDepositPagingRes.java new file mode 100644 index 0000000..8652a6b --- /dev/null +++ b/src/main/java/com/finfellows/domain/product/dto/response/SearchDepositPagingRes.java @@ -0,0 +1,7 @@ +package com.finfellows.domain.product.dto.response; + +import lombok.Data; + +@Data +public class SearchDepositPagingRes { +} diff --git a/src/main/java/com/finfellows/domain/product/dto/response/SearchDepositRes.java b/src/main/java/com/finfellows/domain/product/dto/response/SearchDepositRes.java new file mode 100644 index 0000000..873493a --- /dev/null +++ b/src/main/java/com/finfellows/domain/product/dto/response/SearchDepositRes.java @@ -0,0 +1,24 @@ +package com.finfellows.domain.product.dto.response; + +import com.querydsl.core.annotations.QueryProjection; +import lombok.Data; + +@Data +public class SearchDepositRes { + + private Long id; + private String productName; + private String bankName; + private String maxInterestRate; + private String interestRate; + + @QueryProjection + public SearchDepositRes(Long id, String productName, String bankName, String maxInterestRate, String interestRate) { + this.id = id; + this.productName = productName; + this.bankName = bankName; + this.maxInterestRate = maxInterestRate; + this.interestRate = interestRate; + } + +} diff --git a/src/main/java/com/finfellows/domain/product/presentation/FinancialProductController.java b/src/main/java/com/finfellows/domain/product/presentation/FinancialProductController.java new file mode 100644 index 0000000..493ac70 --- /dev/null +++ b/src/main/java/com/finfellows/domain/product/presentation/FinancialProductController.java @@ -0,0 +1,47 @@ +package com.finfellows.domain.product.presentation; + +import com.finfellows.domain.product.application.FinancialProductServiceImpl; +import com.finfellows.domain.product.dto.condition.DepositSearchCondition; +import com.finfellows.domain.product.dto.response.SearchDepositRes; +import com.finfellows.global.config.security.token.CurrentUser; +import com.finfellows.global.config.security.token.UserPrincipal; +import com.finfellows.global.payload.ErrorResponse; +import com.finfellows.global.payload.PagedResponse; +import com.finfellows.global.payload.ResponseCustom; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.Parameter; +import io.swagger.v3.oas.annotations.media.ArraySchema; +import io.swagger.v3.oas.annotations.media.Content; +import io.swagger.v3.oas.annotations.media.Schema; +import io.swagger.v3.oas.annotations.responses.ApiResponse; +import io.swagger.v3.oas.annotations.responses.ApiResponses; +import io.swagger.v3.oas.annotations.tags.Tag; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@Tag(name = "FinancialProducts", description = "FinancialProducts API") +@RestController +@RequiredArgsConstructor +@RequestMapping("/financial-products") +public class FinancialProductController { + + private final FinancialProductServiceImpl financialProductServiceImpl; + + @Operation(summary = "예금 정보 조회", description = "예금 정보를 조건에 따라 조회합니다.") + @ApiResponses(value = { + @ApiResponse(responseCode = "200", description = "예금 정보 조회 성공", content = {@Content(mediaType = "application/json", array = @ArraySchema(schema = @Schema(implementation = SearchDepositRes.class)))}), + @ApiResponse(responseCode = "400", description = "예금 정보 조회 실패", content = {@Content(mediaType = "application/json", schema = @Schema(implementation = ErrorResponse.class))}), + }) + @GetMapping("/deposit") + public ResponseCustom> findDepositProducts( + @Parameter(description = "AccessToken 을 입력해주세요.", required = true) @CurrentUser UserPrincipal userPrincipal, + @ModelAttribute DepositSearchCondition depositSearchCondition, + Pageable pageable + ) { + return ResponseCustom.OK(financialProductServiceImpl.findDepositProducts(userPrincipal, depositSearchCondition, pageable)); + } + +} diff --git a/src/main/java/com/finfellows/global/config/QueryDslConfig.java b/src/main/java/com/finfellows/global/config/QueryDslConfig.java new file mode 100644 index 0000000..3cfb307 --- /dev/null +++ b/src/main/java/com/finfellows/global/config/QueryDslConfig.java @@ -0,0 +1,20 @@ +package com.finfellows.global.config; + +import com.querydsl.jpa.impl.JPAQueryFactory; +import jakarta.persistence.EntityManager; +import jakarta.persistence.PersistenceContext; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class QueryDslConfig { + + @PersistenceContext + private EntityManager entityManager; + + @Bean + public JPAQueryFactory queryFactory() { + return new JPAQueryFactory(entityManager); + } + +} diff --git a/src/main/java/com/finfellows/global/config/security/SecurityConfig.java b/src/main/java/com/finfellows/global/config/security/SecurityConfig.java index b12190e..0b8cfdb 100644 --- a/src/main/java/com/finfellows/global/config/security/SecurityConfig.java +++ b/src/main/java/com/finfellows/global/config/security/SecurityConfig.java @@ -60,7 +60,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { .permitAll() .requestMatchers("/login/**", "/auth/**", "/oauth2/**") .permitAll() - .requestMatchers("/blog/**") + .requestMatchers("/blog/**", "/financial-products/**") .permitAll() .anyRequest() .authenticated()); diff --git a/src/main/java/com/finfellows/global/payload/PagedResponse.java b/src/main/java/com/finfellows/global/payload/PagedResponse.java new file mode 100644 index 0000000..33ee083 --- /dev/null +++ b/src/main/java/com/finfellows/global/payload/PagedResponse.java @@ -0,0 +1,22 @@ +package com.finfellows.global.payload; + +import lombok.Data; +import org.springframework.data.domain.Page; + +import java.util.List; + +@Data +public class PagedResponse { + + private int pageNumber; + private int totalPages; + private List content; + + public PagedResponse(Page page) { + this.pageNumber = page.getNumber(); + this.totalPages = page.getTotalPages(); + this.content = page.getContent(); + } + +} +