This commit is contained in:
mindol1004
2024-12-13 16:59:09 +09:00
parent e3569a06b2
commit f6fba4f09f
9 changed files with 158 additions and 23 deletions

View File

@@ -0,0 +1,65 @@
package com.spring.common.jpa;
import java.util.ArrayList;
import java.util.List;
import java.util.function.Function;
import java.util.function.Supplier;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.util.StringUtils;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public class SpecBuilder {
public static <T> Builder<T> builder(Class<T> type) {
return new Builder<>();
}
public static class Builder<T> {
private List<Specification<T>> specs = new ArrayList<>();
private void addSpec(Specification<T> spec) {
if (spec != null) {
specs.add(spec);
}
}
public Builder<T> and(Specification<T> spec) {
addSpec(spec);
return this;
}
public Builder<T> ifHasText(String str, Specification<T> spec) {
if (StringUtils.hasText(str)) {
addSpec(spec);
}
return this;
}
public Builder<T> ifTrue(Boolean cond, Supplier<Specification<T>> specSupplier) {
if (cond != null && cond.booleanValue()) {
addSpec(specSupplier.get());
}
return this;
}
public <V> Builder<T> ifNotNull(V value, Function<V, Specification<T>> specSupplier) {
if (value != null) {
addSpec(specSupplier.apply(value));
}
return this;
}
public Specification<T> toSpec() {
Specification<T> spec = Specification.where(null);
for (Specification<T> s : specs) {
spec = spec.and(s);
}
return spec;
}
}
}

View File

@@ -1,14 +1,10 @@
package com.spring.domain.schedule.api; package com.spring.domain.schedule.api;
import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import javax.validation.Valid; import javax.validation.Valid;
import javax.validation.constraints.NotNull;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
@@ -17,7 +13,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import com.spring.domain.schedule.dto.BatchJobHistoryProjection; import com.spring.domain.schedule.dto.BatchJobHistoryResponse;
import com.spring.domain.schedule.dto.FindJobHistoryRequest;
import com.spring.domain.schedule.dto.ReScheduleJobRequest; import com.spring.domain.schedule.dto.ReScheduleJobRequest;
import com.spring.domain.schedule.dto.ScheduleJobResponse; import com.spring.domain.schedule.dto.ScheduleJobResponse;
import com.spring.domain.schedule.service.FindJobHistoryService; import com.spring.domain.schedule.service.FindJobHistoryService;
@@ -30,7 +27,6 @@ import lombok.RequiredArgsConstructor;
@RequestMapping("/api/schedule") @RequestMapping("/api/schedule")
@RestController @RestController
@RequiredArgsConstructor @RequiredArgsConstructor
@Validated
public class ScheduleJobApi { public class ScheduleJobApi {
private final FindScheduleJobService findScheduleJobService; private final FindScheduleJobService findScheduleJobService;
@@ -76,14 +72,12 @@ public class ScheduleJobApi {
@GetMapping("/history") @GetMapping("/history")
@PreAuthorize("hasAnyRole('SUPER', 'ADMIN')") @PreAuthorize("hasAnyRole('SUPER', 'ADMIN')")
public List<BatchJobHistoryProjection> getJobHistory( public List<BatchJobHistoryResponse> getJobHistory(
@NotNull(message = "시작일자는 필수값 입니다.") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") LocalDateTime fromDate, @Valid FindJobHistoryRequest request,
@NotNull(message = "종료일자는 필수값 입니다.") @RequestParam(required = false) @DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss") LocalDateTime toDate,
@RequestParam(required = false) String jobName,
@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "50") int size @RequestParam(defaultValue = "50") int size
) { ) {
return findJobHistoryService.getJobHistory(fromDate, toDate, jobName, page, size); return findJobHistoryService.getJobHistory(request, page, size);
} }
} }

View File

@@ -0,0 +1,23 @@
package com.spring.domain.schedule.dto;
import java.time.LocalDateTime;
import com.spring.domain.schedule.entity.BatchJobExecution;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public class BatchJobHistoryResponse {
private final String jobName;
private final LocalDateTime startTime;
private final LocalDateTime endTime;
private final String status;
public static BatchJobHistoryResponse fromEntity(BatchJobExecution batch) {
return new BatchJobHistoryResponse(batch.getJobInstance().getJobName(), batch.getStartTime(), batch.getEndTime(), batch.getStatus());
}
}

View File

@@ -0,0 +1,51 @@
package com.spring.domain.schedule.dto;
import java.time.LocalDateTime;
import javax.persistence.criteria.Join;
import javax.persistence.criteria.JoinType;
import javax.validation.constraints.NotNull;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.format.annotation.DateTimeFormat;
import com.spring.common.jpa.SpecBuilder;
import com.spring.domain.schedule.entity.BatchJobExecution;
import com.spring.domain.schedule.entity.BatchJobInstance;
import lombok.Getter;
import lombok.RequiredArgsConstructor;
@Getter
@RequiredArgsConstructor
public class FindJobHistoryRequest {
@NotNull(message = "시작일자는 필수값 입니다.")
@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
private final LocalDateTime fromDate;
@NotNull(message = "종료일자는 필수값 입니다.")
@DateTimeFormat(pattern = "yyyy-MM-dd'T'HH:mm:ss")
private final LocalDateTime toDate;
private final String jobName;
public Specification<BatchJobExecution> betweenCreateTime() {
return (root, query, builder) -> builder.between(root.get("createTime"), fromDate, toDate);
}
public Specification<BatchJobExecution> equalJobName() {
return (root, query, builder) -> {
Join<BatchJobExecution, BatchJobInstance> join = root.join("jobInstance", JoinType.INNER);
return builder.equal(join.get("jobName"), jobName);
};
}
public Specification<BatchJobExecution> toPredicate() {
return SpecBuilder.builder(BatchJobExecution.class)
.and(betweenCreateTime())
.ifHasText(jobName, equalJobName())
.toSpec();
}
}

View File

@@ -5,6 +5,7 @@ import java.util.List;
import org.springframework.data.domain.Pageable; import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.data.jpa.repository.Query; import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param; import org.springframework.data.repository.query.Param;
@@ -15,7 +16,7 @@ import com.spring.domain.schedule.dto.BatchJobHourProjection;
import com.spring.domain.schedule.dto.BatchJobStatusCountProjection; import com.spring.domain.schedule.dto.BatchJobStatusCountProjection;
import com.spring.domain.schedule.entity.BatchJobExecution; import com.spring.domain.schedule.entity.BatchJobExecution;
public interface BatchJobExecutionRepository extends JpaRepository<BatchJobExecution, Long> { public interface BatchJobExecutionRepository extends JpaRepository<BatchJobExecution, Long>, JpaSpecificationExecutor<BatchJobExecution> {
@Query("SELECT bji.jobName AS jobName, " + @Query("SELECT bji.jobName AS jobName, " +
"bje.startTime AS startTime, " + "bje.startTime AS startTime, " +

View File

@@ -1,15 +1,14 @@
package com.spring.domain.schedule.service; package com.spring.domain.schedule.service;
import java.time.LocalDateTime;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.PageRequest;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import com.spring.domain.schedule.dto.BatchJobHistoryProjection; import com.spring.domain.schedule.dto.BatchJobHistoryResponse;
import com.spring.domain.schedule.dto.FindJobHistoryRequest;
import com.spring.domain.schedule.repository.BatchJobExecutionRepository; import com.spring.domain.schedule.repository.BatchJobExecutionRepository;
import lombok.RequiredArgsConstructor; import lombok.RequiredArgsConstructor;
@@ -21,9 +20,9 @@ public class FindJobHistoryService {
private final BatchJobExecutionRepository batchJobExecutionRepository; private final BatchJobExecutionRepository batchJobExecutionRepository;
@Transactional(readOnly = true) @Transactional(readOnly = true)
public List<BatchJobHistoryProjection> getJobHistory(LocalDateTime fromDate, LocalDateTime toDate, String jobName, int page, int size) { public List<BatchJobHistoryResponse> getJobHistory(FindJobHistoryRequest request, int page, int size) {
return batchJobExecutionRepository.findJobHistory(fromDate, toDate, PageRequest.of(page, size)).stream() return batchJobExecutionRepository.findAll(request.toPredicate(), PageRequest.of(page, size)).stream()
.filter(jobHistory -> !StringUtils.hasText(jobName) || jobHistory.getJobName().equals(jobName)) .map(BatchJobHistoryResponse::fromEntity)
.collect(Collectors.toList()); .collect(Collectors.toList());
} }

View File

@@ -170,6 +170,8 @@ logging:
level: level:
root: info root: info
org: org:
springframework:
web: debug
hibernate: hibernate:
SQL: debug SQL: debug
type: type:

View File

@@ -41,10 +41,9 @@ const scheduleService = {
return true; return true;
}, },
getJobHistory: async (fromDate, toDate, jobName, page, size) => { getJobHistory: async (searchParams) => {
const response = await apiClient.get('/api/schedule/history', { console.log(searchParams);
params: { fromDate, toDate, jobName, page, size } const response = await apiClient.get('/api/schedule/history', { params: searchParams });
});
return response.data.data; return response.data.data;
} }

View File

@@ -57,7 +57,8 @@ const fetchDataAndRender = async () => {
const jobName = document.getElementById('jobName').value; const jobName = document.getElementById('jobName').value;
const fromDate = dayjs(document.getElementById('fromDate').value).format('YYYY-MM-DDTHH:mm:ss'); const fromDate = dayjs(document.getElementById('fromDate').value).format('YYYY-MM-DDTHH:mm:ss');
const toDate = dayjs(document.getElementById('toDate').value).endOf('day').format('YYYY-MM-DDTHH:mm:ss'); const toDate = dayjs(document.getElementById('toDate').value).endOf('day').format('YYYY-MM-DDTHH:mm:ss');
const jobHistories = await scheduleService.getJobHistory(fromDate, toDate, jobName, currentPage, pageSize); const searchParams = { fromDate: fromDate, toDate: toDate, jobName: jobName, page: currentPage, size: pageSize };
const jobHistories = await scheduleService.getJobHistory(searchParams);
if (jobHistories.length > 0) { if (jobHistories.length > 0) {
appendJobHistories(jobHistories); appendJobHistories(jobHistories);