feat: 들여쓰기

This commit is contained in:
2025-11-17 14:19:29 +09:00
parent 92f5b61114
commit dc9b40e78b
29 changed files with 735 additions and 777 deletions

View File

@@ -60,30 +60,13 @@ bootJar {
archiveFileName = 'ROOT.jar' archiveFileName = 'ROOT.jar'
} }
// Spotless configuration for code formatting // Spotless configuration for code formatting (2-space indent)
//spotless {
// java {
// target 'src/**/*.java'
// googleJavaFormat('1.19.2').aosp().reflowLongStrings()
// indentWithSpaces(2)
// trimTrailingWhitespace()
// endWithNewline()
// importOrder()
// removeUnusedImports()
// formatAnnotations()
// }
//}
spotless { spotless {
java { java {
target 'src/**/*.java' target 'src/**/*.java'
indentWithSpaces(2) googleJavaFormat('1.19.2') // Default Google Style = 2 spaces (NO .aosp()!)
trimTrailingWhitespace() trimTrailingWhitespace()
endWithNewline() endWithNewline()
importOrder()
removeUnusedImports()
formatAnnotations()
} }
} }

View File

@@ -6,7 +6,7 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication @SpringBootApplication
public class KamcoBackApplication { public class KamcoBackApplication {
public static void main(String[] args) { public static void main(String[] args) {
SpringApplication.run(KamcoBackApplication.class, args); SpringApplication.run(KamcoBackApplication.class, args);
} }
} }

View File

@@ -12,14 +12,14 @@ import org.springframework.web.bind.annotation.RestController;
@RequestMapping("/api/hello") @RequestMapping("/api/hello")
public class HelloApiController { public class HelloApiController {
private final HelloService helloService; private final HelloService helloService;
@GetMapping @GetMapping
public HelloDto.Res hello(HelloDto.Req req) { public HelloDto.Res hello(HelloDto.Req req) {
req.valid(); req.valid();
Res res = helloService.sayHello(req); Res res = helloService.sayHello(req);
return res; return res;
} }
} }

View File

@@ -8,29 +8,29 @@ import lombok.Setter;
public class HelloDto { public class HelloDto {
@Getter @Getter
@Setter @Setter
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
public static class Req { public static class Req {
private String id; private String id;
public void valid() { public void valid() {
if (id == null) { if (id == null) {
throw new IllegalArgumentException(id); throw new IllegalArgumentException(id);
} }
}
} }
}
@Getter @Getter
public static class Res { public static class Res {
private String id; private String id;
private String name; private String name;
@Builder @Builder
public Res(String id, String name) { public Res(String id, String name) {
this.id = id; this.id = id;
this.name = name; this.name = name;
}
} }
}
} }

View File

@@ -13,26 +13,26 @@ import org.springframework.data.domain.Page;
*/ */
public interface BaseCoreService<T, ID, S> { public interface BaseCoreService<T, ID, S> {
/** /**
* ID로 엔티티를 삭제합니다. * ID로 엔티티를 삭제합니다.
* *
* @param id 삭제할 엔티티의 ID * @param id 삭제할 엔티티의 ID
*/ */
void remove(ID id); void remove(ID id);
/** /**
* ID로 단건 조회합니다. * ID로 단건 조회합니다.
* *
* @param id 조회할 엔티티의 ID * @param id 조회할 엔티티의 ID
* @return 조회된 엔티티 * @return 조회된 엔티티
*/ */
T getOneById(ID id); T getOneById(ID id);
/** /**
* 검색 조건과 페이징으로 조회합니다. * 검색 조건과 페이징으로 조회합니다.
* *
* @param searchReq 검색 조건 * @param searchReq 검색 조건
* @return 페이징 처리된 검색 결과 * @return 페이징 처리된 검색 결과
*/ */
Page<T> search(S searchReq); Page<T> search(S searchReq);
} }

View File

@@ -9,9 +9,9 @@ import org.springframework.stereotype.Service;
@Service @Service
public class HelloService { public class HelloService {
public HelloDto.Res sayHello(HelloDto.Req req) { public HelloDto.Res sayHello(HelloDto.Req req) {
log.info("hello"); log.info("hello");
String name = UUID.randomUUID().toString(); String name = UUID.randomUUID().toString();
return HelloDto.Res.builder().id(req.getId()).name(name).build(); return HelloDto.Res.builder().id(req.getId()).name(name).build();
} }
} }

View File

@@ -11,26 +11,26 @@ import org.springframework.util.StringUtils;
public class GeometryDeserializer<T extends Geometry> extends StdDeserializer<T> { public class GeometryDeserializer<T extends Geometry> extends StdDeserializer<T> {
public GeometryDeserializer(Class<T> targetType) { public GeometryDeserializer(Class<T> targetType) {
super(targetType); super(targetType);
}
// TODO: test code
@SuppressWarnings("unchecked")
@Override
public T deserialize(JsonParser jsonParser, DeserializationContext deserializationContext)
throws IOException, JacksonException {
String json = jsonParser.readValueAsTree().toString();
if (!StringUtils.hasText(json)) {
return null;
} }
// TODO: test code try {
@SuppressWarnings("unchecked") GeoJsonReader reader = new GeoJsonReader();
@Override return (T) reader.read(json);
public T deserialize(JsonParser jsonParser, DeserializationContext deserializationContext) } catch (Exception e) {
throws IOException, JacksonException { throw new IllegalArgumentException("Failed to deserialize GeoJSON into Geometry", e);
String json = jsonParser.readValueAsTree().toString();
if (!StringUtils.hasText(json)) {
return null;
}
try {
GeoJsonReader reader = new GeoJsonReader();
return (T) reader.read(json);
} catch (Exception e) {
throw new IllegalArgumentException("Failed to deserialize GeoJSON into Geometry", e);
}
} }
}
} }

View File

@@ -10,22 +10,22 @@ import org.locationtech.jts.io.geojson.GeoJsonWriter;
public class GeometrySerializer<T extends Geometry> extends StdSerializer<T> { public class GeometrySerializer<T extends Geometry> extends StdSerializer<T> {
// TODO: test code // TODO: test code
public GeometrySerializer(Class<T> targetType) { public GeometrySerializer(Class<T> targetType) {
super(targetType); super(targetType);
} }
@Override @Override
public void serialize( public void serialize(
T geometry, JsonGenerator jsonGenerator, SerializerProvider serializerProvider) T geometry, JsonGenerator jsonGenerator, SerializerProvider serializerProvider)
throws IOException { throws IOException {
if (Objects.nonNull(geometry)) { if (Objects.nonNull(geometry)) {
// default: 8자리 강제로 반올림시킴. 16자리로 늘려줌 // default: 8자리 강제로 반올림시킴. 16자리로 늘려줌
GeoJsonWriter writer = new GeoJsonWriter(16); GeoJsonWriter writer = new GeoJsonWriter(16);
String json = writer.write(geometry); String json = writer.write(geometry);
jsonGenerator.writeRawValue(json); jsonGenerator.writeRawValue(json);
} else { } else {
jsonGenerator.writeNull(); jsonGenerator.writeNull();
}
} }
}
} }

View File

@@ -14,49 +14,47 @@ import org.springframework.stereotype.Component;
@RequiredArgsConstructor @RequiredArgsConstructor
public class StartupLogger { public class StartupLogger {
private final Environment environment; private final Environment environment;
private final DataSource dataSource; private final DataSource dataSource;
@EventListener(ApplicationReadyEvent.class) @EventListener(ApplicationReadyEvent.class)
public void logStartupInfo() { public void logStartupInfo() {
String[] activeProfiles = environment.getActiveProfiles(); String[] activeProfiles = environment.getActiveProfiles();
String profileInfo = String profileInfo = activeProfiles.length > 0 ? String.join(", ", activeProfiles) : "default";
activeProfiles.length > 0 ? String.join(", ", activeProfiles) : "default";
// Database connection information // Database connection information
String dbUrl = environment.getProperty("spring.datasource.url"); String dbUrl = environment.getProperty("spring.datasource.url");
String dbUsername = environment.getProperty("spring.datasource.username"); String dbUsername = environment.getProperty("spring.datasource.username");
String dbDriver = environment.getProperty("spring.datasource.driver-class-name"); String dbDriver = environment.getProperty("spring.datasource.driver-class-name");
// HikariCP pool settings // HikariCP pool settings
String poolInfo = ""; String poolInfo = "";
if (dataSource instanceof HikariDataSource hikariDs) { if (dataSource instanceof HikariDataSource hikariDs) {
poolInfo = poolInfo =
String.format( String.format(
""" """
│ Pool Size : min=%d, max=%d │ Pool Size : min=%d, max=%d
│ Connection Timeout: %dms │ Connection Timeout: %dms
│ Idle Timeout : %dms │ Idle Timeout : %dms
│ Max Lifetime : %dms""", │ Max Lifetime : %dms""",
hikariDs.getMinimumIdle(), hikariDs.getMinimumIdle(),
hikariDs.getMaximumPoolSize(), hikariDs.getMaximumPoolSize(),
hikariDs.getConnectionTimeout(), hikariDs.getConnectionTimeout(),
hikariDs.getIdleTimeout(), hikariDs.getIdleTimeout(),
hikariDs.getMaxLifetime()); hikariDs.getMaxLifetime());
} }
// JPA/Hibernate settings // JPA/Hibernate settings
String showSql = environment.getProperty("spring.jpa.show-sql", "false"); String showSql = environment.getProperty("spring.jpa.show-sql", "false");
String ddlAuto = environment.getProperty("spring.jpa.hibernate.ddl-auto", "none"); String ddlAuto = environment.getProperty("spring.jpa.hibernate.ddl-auto", "none");
String batchSize = String batchSize =
environment.getProperty("spring.jpa.properties.hibernate.jdbc.batch_size", "N/A"); environment.getProperty("spring.jpa.properties.hibernate.jdbc.batch_size", "N/A");
String batchFetchSize = String batchFetchSize =
environment.getProperty( environment.getProperty("spring.jpa.properties.hibernate.default_batch_fetch_size", "N/A");
"spring.jpa.properties.hibernate.default_batch_fetch_size", "N/A");
String startupMessage = String startupMessage =
String.format( String.format(
""" """
╔════════════════════════════════════════════════════════════════════════════════╗ ╔════════════════════════════════════════════════════════════════════════════════╗
║ 🚀 APPLICATION STARTUP INFORMATION ║ ║ 🚀 APPLICATION STARTUP INFORMATION ║
@@ -83,16 +81,16 @@ public class StartupLogger {
│ Fetch Batch Size : %s │ Fetch Batch Size : %s
╚════════════════════════════════════════════════════════════════════════════════╝ ╚════════════════════════════════════════════════════════════════════════════════╝
""", """,
profileInfo, profileInfo,
dbUrl != null ? dbUrl : "N/A", dbUrl != null ? dbUrl : "N/A",
dbUsername != null ? dbUsername : "N/A", dbUsername != null ? dbUsername : "N/A",
dbDriver != null ? dbDriver : "PostgreSQL JDBC Driver (auto-detected)", dbDriver != null ? dbDriver : "PostgreSQL JDBC Driver (auto-detected)",
poolInfo, poolInfo,
showSql, showSql,
ddlAuto, ddlAuto,
batchSize, batchSize,
batchFetchSize); batchFetchSize);
log.info(startupMessage); log.info(startupMessage);
} }
} }

View File

@@ -14,18 +14,18 @@ import org.springframework.http.converter.json.Jackson2ObjectMapperBuilder;
@Configuration @Configuration
public class WebConfig { public class WebConfig {
@Bean @Bean
public ObjectMapper objectMapper() { public ObjectMapper objectMapper() {
SimpleModule module = new SimpleModule(); SimpleModule module = new SimpleModule();
module.addSerializer(Geometry.class, new GeometrySerializer<>(Geometry.class)); module.addSerializer(Geometry.class, new GeometrySerializer<>(Geometry.class));
module.addDeserializer(Geometry.class, new GeometryDeserializer<>(Geometry.class)); module.addDeserializer(Geometry.class, new GeometryDeserializer<>(Geometry.class));
module.addSerializer(Polygon.class, new GeometrySerializer<>(Polygon.class)); module.addSerializer(Polygon.class, new GeometrySerializer<>(Polygon.class));
module.addDeserializer(Polygon.class, new GeometryDeserializer<>(Polygon.class)); module.addDeserializer(Polygon.class, new GeometryDeserializer<>(Polygon.class));
module.addSerializer(Point.class, new GeometrySerializer<>(Point.class)); module.addSerializer(Point.class, new GeometrySerializer<>(Point.class));
module.addDeserializer(Point.class, new GeometryDeserializer<>(Point.class)); module.addDeserializer(Point.class, new GeometryDeserializer<>(Point.class));
return Jackson2ObjectMapperBuilder.json().modulesToInstall(module).build(); return Jackson2ObjectMapperBuilder.json().modulesToInstall(module).build();
} }
} }

View File

@@ -10,111 +10,111 @@ import lombok.ToString;
@ToString @ToString
public class ApiResponseDto<T> { public class ApiResponseDto<T> {
private T data; private T data;
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
private Error error; private Error error;
@JsonInclude(JsonInclude.Include.NON_NULL) @JsonInclude(JsonInclude.Include.NON_NULL)
private T errorData; private T errorData;
public ApiResponseDto(T data) { public ApiResponseDto(T data) {
this.data = data; this.data = data;
}
public ApiResponseDto(ApiResponseCode code) {
this.error = new Error(code.getId(), code.getMessage());
}
public ApiResponseDto(ApiResponseCode code, String message) {
this.error = new Error(code.getId(), message);
}
public ApiResponseDto(ApiResponseCode code, String message, T errorData) {
this.error = new Error(code.getId(), message);
this.errorData = errorData;
}
public static <T> ApiResponseDto<T> createOK(T data) {
return new ApiResponseDto<>(data);
}
public static ApiResponseDto<String> createException(ApiResponseCode code) {
return new ApiResponseDto<>(code);
}
public static ApiResponseDto<String> createException(ApiResponseCode code, String message) {
return new ApiResponseDto<>(code, message);
}
public static <T> ApiResponseDto<T> createException(
ApiResponseCode code, String message, T data) {
return new ApiResponseDto<>(code, message, data);
}
@Getter
public static class Error {
private final String code;
private final String message;
public Error(String code, String message) {
this.code = code;
this.message = message;
}
}
@Getter
@RequiredArgsConstructor
public enum ApiResponseCode implements EnumType {
// @formatter:off
OK("요청이 성공하였습니다."),
BAD_REQUEST("요청 파라미터가 잘못되었습니다."),
ALREADY_EXIST_MALL("이미 등록된 쇼핑센터입니다."),
NOT_FOUND_MAP("지도를 찾을 수 없습니다."),
UNAUTHORIZED("권한이 없습니다."),
CONFLICT("이미 등록된 컨텐츠입니다."),
NOT_FOUND("Resource를 찾을 수 없습니다."),
NOT_FOUND_DATA("데이터를 찾을 수 없습니다."),
NOT_FOUND_WEATHER_DATA("날씨 데이터를 찾을 수 없습니다."),
FAIL_SEND_MESSAGE("메시지를 전송하지 못했습니다."),
TOO_MANY_CONNECTED_MACHINES("연결된 기기가 너무 많습니다."),
UNAUTHENTICATED("인증에 실패하였습니다."),
INVALID_TOKEN("잘못된 토큰입니다."),
EXPIRED_TOKEN("만료된 토큰입니다."),
INTERNAL_SERVER_ERROR("서버에 문제가 발생 하였습니다."),
FORBIDDEN("권한을 확인해주세요."),
INVALID_PASSWORD("잘못된 비밀번호 입니다."),
NOT_FOUND_CAR_IN("입차정보가 없습니다."),
WRONG_STATUS("잘못된 상태입니다."),
FAIL_VERIFICATION("인증에 실패하였습니다."),
INVALID_EMAIL("잘못된 형식의 이메일입니다."),
REQUIRED_EMAIL("이메일은 필수 항목입니다."),
WRONG_PASSWORD("잘못된 패스워드입니다.."),
DUPLICATE_EMAIL("이미 가입된 이메일입니다."),
DUPLICATE_DATA("이미 등록되여 있습니다."),
DATA_INTEGRITY_ERROR("요청을 처리할수 없습니다."),
FOREIGN_KEY_ERROR("참조 중인 데이터가 있어 삭제할 수 없습니다."),
DUPLICATE_EMPLOYEEID("이미 가입된 사번입니다."),
NOT_FOUND_USER_FOR_EMAIL("이메일로 유저를 찾을 수 없습니다."),
NOT_FOUND_USER("사용자를 찾을 수 없습니다."),
INVALID_EMAIL_TOKEN(
"You can only reset your password within 24 hours from when the email was sent.\n"
+ "To reset your password again, please submit a new request through \"Forgot"
+ " Password.\""),
;
// @formatter:on
private final String message;
@Override
public String getId() {
return name();
} }
public ApiResponseDto(ApiResponseCode code) { @Override
this.error = new Error(code.getId(), code.getMessage()); public String getText() {
} return message;
public ApiResponseDto(ApiResponseCode code, String message) {
this.error = new Error(code.getId(), message);
}
public ApiResponseDto(ApiResponseCode code, String message, T errorData) {
this.error = new Error(code.getId(), message);
this.errorData = errorData;
}
public static <T> ApiResponseDto<T> createOK(T data) {
return new ApiResponseDto<>(data);
}
public static ApiResponseDto<String> createException(ApiResponseCode code) {
return new ApiResponseDto<>(code);
}
public static ApiResponseDto<String> createException(ApiResponseCode code, String message) {
return new ApiResponseDto<>(code, message);
}
public static <T> ApiResponseDto<T> createException(
ApiResponseCode code, String message, T data) {
return new ApiResponseDto<>(code, message, data);
}
@Getter
public static class Error {
private final String code;
private final String message;
public Error(String code, String message) {
this.code = code;
this.message = message;
}
}
@Getter
@RequiredArgsConstructor
public enum ApiResponseCode implements EnumType {
// @formatter:off
OK("요청이 성공하였습니다."),
BAD_REQUEST("요청 파라미터가 잘못되었습니다."),
ALREADY_EXIST_MALL("이미 등록된 쇼핑센터입니다."),
NOT_FOUND_MAP("지도를 찾을 수 없습니다."),
UNAUTHORIZED("권한이 없습니다."),
CONFLICT("이미 등록된 컨텐츠입니다."),
NOT_FOUND("Resource를 찾을 수 없습니다."),
NOT_FOUND_DATA("데이터를 찾을 수 없습니다."),
NOT_FOUND_WEATHER_DATA("날씨 데이터를 찾을 수 없습니다."),
FAIL_SEND_MESSAGE("메시지를 전송하지 못했습니다."),
TOO_MANY_CONNECTED_MACHINES("연결된 기기가 너무 많습니다."),
UNAUTHENTICATED("인증에 실패하였습니다."),
INVALID_TOKEN("잘못된 토큰입니다."),
EXPIRED_TOKEN("만료된 토큰입니다."),
INTERNAL_SERVER_ERROR("서버에 문제가 발생 하였습니다."),
FORBIDDEN("권한을 확인해주세요."),
INVALID_PASSWORD("잘못된 비밀번호 입니다."),
NOT_FOUND_CAR_IN("입차정보가 없습니다."),
WRONG_STATUS("잘못된 상태입니다."),
FAIL_VERIFICATION("인증에 실패하였습니다."),
INVALID_EMAIL("잘못된 형식의 이메일입니다."),
REQUIRED_EMAIL("이메일은 필수 항목입니다."),
WRONG_PASSWORD("잘못된 패스워드입니다.."),
DUPLICATE_EMAIL("이미 가입된 이메일입니다."),
DUPLICATE_DATA("이미 등록되여 있습니다."),
DATA_INTEGRITY_ERROR("요청을 처리할수 없습니다."),
FOREIGN_KEY_ERROR("참조 중인 데이터가 있어 삭제할 수 없습니다."),
DUPLICATE_EMPLOYEEID("이미 가입된 사번입니다."),
NOT_FOUND_USER_FOR_EMAIL("이메일로 유저를 찾을 수 없습니다."),
NOT_FOUND_USER("사용자를 찾을 수 없습니다."),
INVALID_EMAIL_TOKEN(
"You can only reset your password within 24 hours from when the email was sent.\n"
+ "To reset your password again, please submit a new request through \"Forgot"
+ " Password.\""),
;
// @formatter:on
private final String message;
@Override
public String getId() {
return name();
}
@Override
public String getText() {
return message;
}
} }
}
} }

View File

@@ -2,7 +2,7 @@ package com.kamco.cd.kamcoback.config.enums;
public interface EnumType { public interface EnumType {
String getId(); String getId();
String getText(); String getText();
} }

View File

@@ -13,22 +13,22 @@ import org.springframework.data.annotation.LastModifiedDate;
@MappedSuperclass @MappedSuperclass
public class CommonDateEntity { public class CommonDateEntity {
@CreatedDate @CreatedDate
@Column(name = "created_date", updatable = false, nullable = false) @Column(name = "created_date", updatable = false, nullable = false)
private ZonedDateTime createdDate; private ZonedDateTime createdDate;
@LastModifiedDate @LastModifiedDate
@Column(name = "modified_date", nullable = false) @Column(name = "modified_date", nullable = false)
private ZonedDateTime modifiedDate; private ZonedDateTime modifiedDate;
@PrePersist @PrePersist
protected void onPersist() { protected void onPersist() {
this.createdDate = ZonedDateTime.now(); this.createdDate = ZonedDateTime.now();
this.modifiedDate = ZonedDateTime.now(); this.modifiedDate = ZonedDateTime.now();
} }
@PreUpdate @PreUpdate
protected void onUpdate() { protected void onUpdate() {
this.modifiedDate = ZonedDateTime.now(); this.modifiedDate = ZonedDateTime.now();
} }
} }

View File

@@ -10,10 +10,10 @@ import org.springframework.context.annotation.Configuration;
@Configuration @Configuration
public class QueryDslConfig { public class QueryDslConfig {
private final EntityManager entityManager; private final EntityManager entityManager;
@Bean @Bean
public JPAQueryFactory jpaQueryFactory() { public JPAQueryFactory jpaQueryFactory() {
return new JPAQueryFactory(entityManager); return new JPAQueryFactory(entityManager);
} }
} }

View File

@@ -16,67 +16,59 @@ import org.springframework.transaction.annotation.Transactional;
@RequiredArgsConstructor @RequiredArgsConstructor
@Transactional(readOnly = true) @Transactional(readOnly = true)
public class AnimalCoreService public class AnimalCoreService
implements BaseCoreService<AnimalDto.Basic, Long, AnimalDto.SearchReq> { implements BaseCoreService<AnimalDto.Basic, Long, AnimalDto.SearchReq> {
private final AnimalRepository animalRepository; private final AnimalRepository animalRepository;
private final ZooRepository zooRepository; private final ZooRepository zooRepository;
@Transactional(readOnly = true) @Transactional(readOnly = true)
public AnimalDto.Basic getDataByUuid(String uuid) { public AnimalDto.Basic getDataByUuid(String uuid) {
AnimalEntity getZoo = AnimalEntity getZoo =
animalRepository animalRepository
.getAnimalByUuid(uuid) .getAnimalByUuid(uuid)
.orElseThrow( .orElseThrow(() -> new EntityNotFoundException("Zoo not found with uuid: " + uuid));
() -> return getZoo.toDto();
new EntityNotFoundException( }
"Zoo not found with uuid: " + uuid));
return getZoo.toDto(); // AddReq를 받는 추가 메서드
@Transactional
public AnimalDto.Basic create(AnimalDto.AddReq req) {
ZooEntity zoo = null;
if (req.getZooId() != null) {
zoo =
zooRepository
.getZooByUid(req.getZooId())
.orElseThrow(
() -> new EntityNotFoundException(" not found with id: " + req.getZooId()));
} }
AnimalEntity entity = new AnimalEntity(req.getCategory(), req.getSpecies(), req.getName(), zoo);
AnimalEntity saved = animalRepository.save(entity);
return saved.toDto();
}
// AddReq를 받는 추가 메서드 @Override
@Transactional @Transactional
public AnimalDto.Basic create(AnimalDto.AddReq req) { public void remove(Long id) {
ZooEntity zoo = null; AnimalEntity getAnimal =
if (req.getZooId() != null) { animalRepository
zoo = .getAnimalByUid(id)
zooRepository .orElseThrow(() -> new EntityNotFoundException("getAnimal not found with id: " + id));
.getZooByUid(req.getZooId()) getAnimal.deleted();
.orElseThrow( }
() ->
new EntityNotFoundException(
"Zoo not found with id: " + req.getZooId()));
}
AnimalEntity entity =
new AnimalEntity(req.getCategory(), req.getSpecies(), req.getName(), zoo);
AnimalEntity saved = animalRepository.save(entity);
return saved.toDto();
}
@Override @Override
@Transactional public AnimalDto.Basic getOneById(Long id) {
public void remove(Long id) { AnimalEntity getAnimal =
AnimalEntity getZoo = animalRepository
animalRepository .getAnimalByUid(id)
.getAnimalByUid(id) .orElseThrow(() -> new EntityNotFoundException("Zoo not found with id: " + id));
.orElseThrow( return getAnimal.toDto();
() -> new EntityNotFoundException("Zoo not found with id: " + id)); }
getZoo.deleted();
}
@Override @Override
public AnimalDto.Basic getOneById(Long id) { public Page<AnimalDto.Basic> search(AnimalDto.SearchReq searchReq) {
AnimalEntity getZoo =
animalRepository
.getAnimalByUid(id)
.orElseThrow(
() -> new EntityNotFoundException("Zoo not found with id: " + id));
return getZoo.toDto();
}
@Override Page<AnimalEntity> animalEntities = animalRepository.listAnimal(searchReq);
public Page<AnimalDto.Basic> search(AnimalDto.SearchReq searchReq) { return animalEntities.map(AnimalEntity::toDto);
}
Page<AnimalEntity> zooEntities = animalRepository.listAnimal(searchReq);
return zooEntities.map(AnimalEntity::toDto);
}
} }

View File

@@ -15,66 +15,61 @@ import org.springframework.transaction.annotation.Transactional;
@Transactional(readOnly = true) @Transactional(readOnly = true)
public class ZooCoreService implements BaseCoreService<ZooDto.Detail, Long, ZooDto.SearchReq> { public class ZooCoreService implements BaseCoreService<ZooDto.Detail, Long, ZooDto.SearchReq> {
private final ZooRepository zooRepository; private final ZooRepository zooRepository;
@Transactional(readOnly = true) @Transactional(readOnly = true)
public ZooDto.Detail getDataByUuid(String uuid) { public ZooDto.Detail getDataByUuid(String uuid) {
ZooEntity zoo = ZooEntity zoo =
zooRepository zooRepository
.getZooByUuid(uuid) .getZooByUuid(uuid)
.orElseThrow( .orElseThrow(() -> new EntityNotFoundException("Zoo not found with uuid: " + uuid));
() -> return toDetailDto(zoo);
new EntityNotFoundException( }
"Zoo not found with uuid: " + uuid));
return toDetailDto(zoo);
}
// AddReq를 받는 추가 메서드 // AddReq를 받는 추가 메서드
@Transactional @Transactional
public ZooDto.Detail create(ZooDto.AddReq req) { public ZooDto.Detail create(ZooDto.AddReq req) {
ZooEntity entity = new ZooEntity(req.getName(), req.getLocation(), req.getDescription()); ZooEntity entity = new ZooEntity(req.getName(), req.getLocation(), req.getDescription());
ZooEntity saved = zooRepository.save(entity); ZooEntity saved = zooRepository.save(entity);
return toDetailDto(saved); return toDetailDto(saved);
} }
@Override @Override
@Transactional @Transactional
public void remove(Long id) { public void remove(Long id) {
ZooEntity zoo = ZooEntity zoo =
zooRepository zooRepository
.getZooByUid(id) .getZooByUid(id)
.orElseThrow( .orElseThrow(() -> new EntityNotFoundException("Zoo not found with id: " + id));
() -> new EntityNotFoundException("Zoo not found with id: " + id)); zoo.deleted();
zoo.deleted(); }
}
@Override @Override
public ZooDto.Detail getOneById(Long id) { public ZooDto.Detail getOneById(Long id) {
ZooEntity zoo = ZooEntity zoo =
zooRepository zooRepository
.getZooByUid(id) .getZooByUid(id)
.orElseThrow( .orElseThrow(() -> new EntityNotFoundException("Zoo not found with id: " + id));
() -> new EntityNotFoundException("Zoo not found with id: " + id)); return toDetailDto(zoo);
return toDetailDto(zoo); }
}
@Override @Override
public Page<ZooDto.Detail> search(ZooDto.SearchReq searchReq) { public Page<ZooDto.Detail> search(ZooDto.SearchReq searchReq) {
Page<ZooEntity> zooEntities = zooRepository.listZoo(searchReq); Page<ZooEntity> zooEntities = zooRepository.listZoo(searchReq);
return zooEntities.map(this::toDetailDto); return zooEntities.map(this::toDetailDto);
} }
// Entity -> Detail DTO 변환 (동물 개수 포함) // Entity -> Detail DTO 변환 (동물 개수 포함)
private ZooDto.Detail toDetailDto(ZooEntity zoo) { private ZooDto.Detail toDetailDto(ZooEntity zoo) {
Long activeAnimalCount = zooRepository.countActiveAnimals(zoo.getUid()); Long activeAnimalCount = zooRepository.countActiveAnimals(zoo.getUid());
return new ZooDto.Detail( return new ZooDto.Detail(
zoo.getUid(), zoo.getUid(),
zoo.getUuid().toString(), zoo.getUuid().toString(),
zoo.getName(), zoo.getName(),
zoo.getLocation(), zoo.getLocation(),
zoo.getDescription(), zoo.getDescription(),
zoo.getCreatedDate(), zoo.getCreatedDate(),
zoo.getModifiedDate(), zoo.getModifiedDate(),
activeAnimalCount); activeAnimalCount);
} }
} }

View File

@@ -28,48 +28,48 @@ import lombok.NoArgsConstructor;
@Table(name = "tb_animal") @Table(name = "tb_animal")
public class AnimalEntity extends CommonDateEntity { public class AnimalEntity extends CommonDateEntity {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long uid; private Long uid;
@Column(unique = true) @Column(unique = true)
private UUID uuid; private UUID uuid;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
private Category category; private Category category;
@Enumerated(EnumType.STRING) @Enumerated(EnumType.STRING)
private Species species; private Species species;
private String name; private String name;
private Boolean isDeleted; private Boolean isDeleted;
@ManyToOne(fetch = FetchType.LAZY) @ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "zoo_id") @JoinColumn(name = "zoo_id")
private ZooEntity zoo; private ZooEntity zoo;
// Construct // Construct
public AnimalEntity(Category category, Species species, String name, ZooEntity zoo) { public AnimalEntity(Category category, Species species, String name, ZooEntity zoo) {
this.uuid = UUID.randomUUID(); this.uuid = UUID.randomUUID();
this.category = category; this.category = category;
this.species = species; this.species = species;
this.name = name; this.name = name;
this.isDeleted = false; this.isDeleted = false;
this.zoo = zoo; this.zoo = zoo;
} }
public AnimalDto.Basic toDto() { public AnimalDto.Basic toDto() {
return new AnimalDto.Basic( return new AnimalDto.Basic(
this.uid, this.uid,
this.uuid.toString(), this.uuid.toString(),
this.name, this.name,
this.category, this.category,
this.species, this.species,
super.getCreatedDate(), super.getCreatedDate(),
super.getModifiedDate()); super.getModifiedDate());
} }
public void deleted() { public void deleted() {
this.isDeleted = true; this.isDeleted = true;
} }
} }

View File

@@ -23,44 +23,44 @@ import lombok.NoArgsConstructor;
@Table(name = "tb_zoo") @Table(name = "tb_zoo")
public class ZooEntity extends CommonDateEntity { public class ZooEntity extends CommonDateEntity {
@Id @Id
@GeneratedValue(strategy = GenerationType.IDENTITY) @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long uid; private Long uid;
@Column(unique = true, nullable = false) @Column(unique = true, nullable = false)
private UUID uuid; private UUID uuid;
@Column(nullable = false, length = 200) @Column(nullable = false, length = 200)
private String name; private String name;
@Column(length = 300) @Column(length = 300)
private String location; private String location;
@Column(columnDefinition = "TEXT") @Column(columnDefinition = "TEXT")
private String description; private String description;
@Column(nullable = false) @Column(nullable = false)
private Boolean isDeleted; private Boolean isDeleted;
@OneToMany(mappedBy = "zoo", fetch = FetchType.LAZY, cascade = CascadeType.ALL) @OneToMany(mappedBy = "zoo", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<AnimalEntity> animals = new ArrayList<>(); private List<AnimalEntity> animals = new ArrayList<>();
// Constructor // Constructor
public ZooEntity(String name, String location, String description) { public ZooEntity(String name, String location, String description) {
this.uuid = UUID.randomUUID(); this.uuid = UUID.randomUUID();
this.name = name; this.name = name;
this.location = location; this.location = location;
this.description = description; this.description = description;
this.isDeleted = false; this.isDeleted = false;
} }
// 논리 삭제 // 논리 삭제
public void deleted() { public void deleted() {
this.isDeleted = true; this.isDeleted = true;
} }
// 현재 활성 동물 개수 조회 (삭제되지 않은 동물만) // 현재 활성 동물 개수 조회 (삭제되지 않은 동물만)
public long getActiveAnimalCount() { public long getActiveAnimalCount() {
return animals.stream().filter(animal -> !animal.getIsDeleted()).count(); return animals.stream().filter(animal -> !animal.getIsDeleted()).count();
} }
} }

View File

@@ -4,4 +4,4 @@ import com.kamco.cd.kamcoback.postgres.entity.AnimalEntity;
import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.JpaRepository;
public interface AnimalRepository public interface AnimalRepository
extends JpaRepository<AnimalEntity, Long>, AnimalRepositoryCustom {} extends JpaRepository<AnimalEntity, Long>, AnimalRepositoryCustom {}

View File

@@ -7,9 +7,9 @@ import org.springframework.data.domain.Page;
public interface AnimalRepositoryCustom { public interface AnimalRepositoryCustom {
Optional<AnimalEntity> getAnimalByUid(Long uid); Optional<AnimalEntity> getAnimalByUid(Long uid);
Optional<AnimalEntity> getAnimalByUuid(String uuid); Optional<AnimalEntity> getAnimalByUuid(String uuid);
Page<AnimalEntity> listAnimal(AnimalDto.SearchReq req); Page<AnimalEntity> listAnimal(AnimalDto.SearchReq req);
} }

View File

@@ -16,84 +16,81 @@ import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport; import org.springframework.data.jpa.repository.support.QuerydslRepositorySupport;
public class AnimalRepositoryImpl extends QuerydslRepositorySupport public class AnimalRepositoryImpl extends QuerydslRepositorySupport
implements AnimalRepositoryCustom { implements AnimalRepositoryCustom {
private final JPAQueryFactory queryFactory; private final JPAQueryFactory queryFactory;
public AnimalRepositoryImpl(JPAQueryFactory queryFactory) { public AnimalRepositoryImpl(JPAQueryFactory queryFactory) {
super(AnimalEntity.class); super(AnimalEntity.class);
this.queryFactory = queryFactory; this.queryFactory = queryFactory;
}
public Optional<AnimalEntity> getAnimalByUid(Long uid) {
QAnimalEntity animal = QAnimalEntity.animalEntity;
return Optional.ofNullable(
queryFactory.selectFrom(animal).where(animal.uid.eq(uid)).fetchFirst());
}
public Optional<AnimalEntity> getAnimalByUuid(String uuid) {
QAnimalEntity animal = QAnimalEntity.animalEntity;
return Optional.ofNullable(
queryFactory.selectFrom(animal).where(animal.uuid.eq(UUID.fromString(uuid))).fetchFirst());
}
@Override
public Page<AnimalEntity> listAnimal(AnimalDto.SearchReq req) {
QAnimalEntity animal = QAnimalEntity.animalEntity;
Pageable pageable = req.toPageable();
List<AnimalEntity> content =
queryFactory
.selectFrom(animal)
.where(
animal.isDeleted.eq(false),
eqCategory(animal, req.getCategory()),
eqSpecies(animal, req.getSpecies()),
likeName(animal, req.getName()))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.orderBy(animal.createdDate.desc())
.fetch();
// count 쿼리
Long total =
queryFactory
.select(animal.count())
.from(animal)
.where(
animal.isDeleted.eq(false),
eqCategory(animal, req.getCategory()),
eqSpecies(animal, req.getSpecies()),
likeName(animal, req.getName()))
.fetchOne();
return new PageImpl<>(content, pageable, total);
}
private BooleanExpression likeName(QAnimalEntity animal, String nameStr) {
if (nameStr == null || nameStr.isEmpty()) {
return null;
} }
return animal.name.containsIgnoreCase(nameStr.trim());
}
public Optional<AnimalEntity> getAnimalByUid(Long uid) { private BooleanExpression eqCategory(QAnimalEntity animal, Category category) {
QAnimalEntity animal = QAnimalEntity.animalEntity; if (category == null) {
return null;
return Optional.ofNullable(
queryFactory.selectFrom(animal).where(animal.uid.eq(uid)).fetchFirst());
} }
return animal.category.eq(category);
}
public Optional<AnimalEntity> getAnimalByUuid(String uuid) { private BooleanExpression eqSpecies(QAnimalEntity animal, Species species) {
QAnimalEntity animal = QAnimalEntity.animalEntity; if (species == null) {
return null;
return Optional.ofNullable(
queryFactory
.selectFrom(animal)
.where(animal.uuid.eq(UUID.fromString(uuid)))
.fetchFirst());
}
@Override
public Page<AnimalEntity> listAnimal(AnimalDto.SearchReq req) {
QAnimalEntity animal = QAnimalEntity.animalEntity;
Pageable pageable = req.toPageable();
List<AnimalEntity> content =
queryFactory
.selectFrom(animal)
.where(
animal.isDeleted.eq(false),
eqCategory(animal, req.getCategory()),
eqSpecies(animal, req.getSpecies()),
likeName(animal, req.getName()))
.offset(pageable.getOffset())
.limit(pageable.getPageSize())
.orderBy(animal.createdDate.desc())
.fetch();
// count 쿼리
Long total =
queryFactory
.select(animal.count())
.from(animal)
.where(
animal.isDeleted.eq(false),
eqCategory(animal, req.getCategory()),
eqSpecies(animal, req.getSpecies()),
likeName(animal, req.getName()))
.fetchOne();
return new PageImpl<>(content, pageable, total);
}
private BooleanExpression likeName(QAnimalEntity animal, String nameStr) {
if (nameStr == null || nameStr.isEmpty()) {
return null;
}
return animal.name.containsIgnoreCase(nameStr.trim());
}
private BooleanExpression eqCategory(QAnimalEntity animal, Category category) {
if (category == null) {
return null;
}
return animal.category.eq(category);
}
private BooleanExpression eqSpecies(QAnimalEntity animal, Species species) {
if (species == null) {
return null;
}
return animal.species.eq(species);
} }
return animal.species.eq(species);
}
} }

View File

@@ -7,11 +7,11 @@ import org.springframework.data.domain.Page;
public interface ZooRepositoryCustom { public interface ZooRepositoryCustom {
Page<ZooEntity> listZoo(ZooDto.SearchReq searchReq); Page<ZooEntity> listZoo(ZooDto.SearchReq searchReq);
Optional<ZooEntity> getZooByUuid(String uuid); Optional<ZooEntity> getZooByUuid(String uuid);
Optional<ZooEntity> getZooByUid(Long uid); Optional<ZooEntity> getZooByUid(Long uid);
Long countActiveAnimals(Long zooId); Long countActiveAnimals(Long zooId);
} }

View File

@@ -20,68 +20,66 @@ import org.springframework.stereotype.Repository;
@RequiredArgsConstructor @RequiredArgsConstructor
public class ZooRepositoryImpl implements ZooRepositoryCustom { public class ZooRepositoryImpl implements ZooRepositoryCustom {
private final JPAQueryFactory queryFactory; private final JPAQueryFactory queryFactory;
private final QZooEntity qZoo = QZooEntity.zooEntity; private final QZooEntity qZoo = QZooEntity.zooEntity;
private final QAnimalEntity qAnimal = QAnimalEntity.animalEntity; private final QAnimalEntity qAnimal = QAnimalEntity.animalEntity;
@Override @Override
public Page<ZooEntity> listZoo(ZooDto.SearchReq searchReq) { public Page<ZooEntity> listZoo(ZooDto.SearchReq searchReq) {
Pageable pageable = searchReq.toPageable(); Pageable pageable = searchReq.toPageable();
JPAQuery<ZooEntity> query = JPAQuery<ZooEntity> query =
queryFactory queryFactory
.selectFrom(qZoo) .selectFrom(qZoo)
.where( .where(
qZoo.isDeleted.eq(false), qZoo.isDeleted.eq(false),
nameContains(searchReq.getName()), nameContains(searchReq.getName()),
locationContains(searchReq.getLocation())); locationContains(searchReq.getLocation()));
long total = query.fetchCount(); long total = query.fetchCount();
List<ZooEntity> content = List<ZooEntity> content =
query.offset(pageable.getOffset()) query
.limit(pageable.getPageSize()) .offset(pageable.getOffset())
.orderBy(qZoo.createdDate.desc()) .limit(pageable.getPageSize())
.fetch(); .orderBy(qZoo.createdDate.desc())
.fetch();
return new PageImpl<>(content, pageable, total); return new PageImpl<>(content, pageable, total);
} }
@Override @Override
public Optional<ZooEntity> getZooByUuid(String uuid) { public Optional<ZooEntity> getZooByUuid(String uuid) {
return Optional.ofNullable( return Optional.ofNullable(
queryFactory queryFactory
.selectFrom(qZoo) .selectFrom(qZoo)
.where(qZoo.uuid.eq(UUID.fromString(uuid)), qZoo.isDeleted.eq(false)) .where(qZoo.uuid.eq(UUID.fromString(uuid)), qZoo.isDeleted.eq(false))
.fetchOne()); .fetchOne());
} }
@Override @Override
public Optional<ZooEntity> getZooByUid(Long uid) { public Optional<ZooEntity> getZooByUid(Long uid) {
return Optional.ofNullable( return Optional.ofNullable(
queryFactory queryFactory.selectFrom(qZoo).where(qZoo.uid.eq(uid), qZoo.isDeleted.eq(false)).fetchOne());
.selectFrom(qZoo) }
.where(qZoo.uid.eq(uid), qZoo.isDeleted.eq(false))
.fetchOne());
}
@Override @Override
public Long countActiveAnimals(Long zooId) { public Long countActiveAnimals(Long zooId) {
Long count = Long count =
queryFactory queryFactory
.select(qAnimal.count()) .select(qAnimal.count())
.from(qAnimal) .from(qAnimal)
.where(qAnimal.zoo.uid.eq(zooId), qAnimal.isDeleted.eq(false)) .where(qAnimal.zoo.uid.eq(zooId), qAnimal.isDeleted.eq(false))
.fetchOne(); .fetchOne();
return count != null ? count : 0L; return count != null ? count : 0L;
} }
private BooleanExpression nameContains(String name) { private BooleanExpression nameContains(String name) {
return name != null && !name.isEmpty() ? qZoo.name.contains(name) : null; return name != null && !name.isEmpty() ? qZoo.name.contains(name) : null;
} }
private BooleanExpression locationContains(String location) { private BooleanExpression locationContains(String location) {
return location != null && !location.isEmpty() ? qZoo.location.contains(location) : null; return location != null && !location.isEmpty() ? qZoo.location.contains(location) : null;
} }
} }

View File

@@ -15,68 +15,68 @@ import org.springframework.web.bind.annotation.*;
@RequestMapping({"/api/animals", "/v1/api/animals"}) @RequestMapping({"/api/animals", "/v1/api/animals"})
public class AnimalApiController { public class AnimalApiController {
private final AnimalService animalService; private final AnimalService animalService;
/** /**
* 동물 생성 * 동물 생성
* *
* @param req 동물 생성 요청 * @param req 동물 생성 요청
* @return 생성된 동물 정보 * @return 생성된 동물 정보
*/ */
@PostMapping @PostMapping
public ResponseEntity<AnimalDto.Basic> createAnimal(@RequestBody AnimalDto.AddReq req) { public ResponseEntity<AnimalDto.Basic> createAnimal(@RequestBody AnimalDto.AddReq req) {
AnimalDto.Basic created = animalService.createAnimal(req); AnimalDto.Basic created = animalService.createAnimal(req);
return ResponseEntity.status(HttpStatus.CREATED).body(created); return ResponseEntity.status(HttpStatus.CREATED).body(created);
} }
/** /**
* UUID로 동물 조회 * UUID로 동물 조회
* *
* @param uuid 동물 UUID * @param uuid 동물 UUID
* @return 동물 정보 * @return 동물 정보
*/ */
@GetMapping("/{uuid}") @GetMapping("/{uuid}")
public ResponseEntity<AnimalDto.Basic> getAnimal(@PathVariable String uuid) { public ResponseEntity<AnimalDto.Basic> getAnimal(@PathVariable String uuid) {
Long id = animalService.getAnimalByUuid(uuid); Long id = animalService.getAnimalByUuid(uuid);
AnimalDto.Basic animal = animalService.getAnimal(id); AnimalDto.Basic animal = animalService.getAnimal(id);
return ResponseEntity.ok(animal); return ResponseEntity.ok(animal);
} }
/** /**
* UUID로 동물 삭제 (논리 삭제) * UUID로 동물 삭제 (논리 삭제)
* *
* @param uuid 동물 UUID * @param uuid 동물 UUID
* @return 삭제 성공 메시지 * @return 삭제 성공 메시지
*/ */
@DeleteMapping("/{uuid}") @DeleteMapping("/{uuid}")
public ResponseEntity<Void> deleteAnimal(@PathVariable String uuid) { public ResponseEntity<Void> deleteAnimal(@PathVariable String uuid) {
Long id = animalService.getAnimalByUuid(uuid); Long id = animalService.getAnimalByUuid(uuid);
animalService.deleteZoo(id); animalService.deleteZoo(id);
return ResponseEntity.noContent().build(); return ResponseEntity.noContent().build();
} }
/** /**
* 동물 검색 (페이징) * 동물 검색 (페이징)
* *
* @param name 동물 이름 (선택) * @param name 동물 이름 (선택)
* @param category 서식지 타입 (선택) * @param category 서식지 타입 (선택)
* @param species 동물종 (선택) 개, 고양이등. * @param species 동물종 (선택) 개, 고양이등.
* @param page 페이지 번호 (기본값: 0) * @param page 페이지 번호 (기본값: 0)
* @param size 페이지 크기 (기본값: 20) * @param size 페이지 크기 (기본값: 20)
* @param sort 정렬 조건 (예: "name,asc") * @param sort 정렬 조건 (예: "name,asc")
* @return 페이징 처리된 동물 목록 * @return 페이징 처리된 동물 목록
*/ */
@GetMapping @GetMapping
public ResponseEntity<Page<AnimalDto.Basic>> searchAnimals( public ResponseEntity<Page<AnimalDto.Basic>> searchAnimals(
@RequestParam(required = false) String name, @RequestParam(required = false) String name,
@RequestParam(required = false) Category category, @RequestParam(required = false) Category category,
@RequestParam(required = false) Species species, @RequestParam(required = false) Species species,
@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size, @RequestParam(defaultValue = "20") int size,
@RequestParam(required = false) String sort) { @RequestParam(required = false) String sort) {
AnimalDto.SearchReq searchReq = AnimalDto.SearchReq searchReq =
new AnimalDto.SearchReq(name, category, species, page, size, sort); new AnimalDto.SearchReq(name, category, species, page, size, sort);
Page<AnimalDto.Basic> animals = animalService.search(searchReq); Page<AnimalDto.Basic> animals = animalService.search(searchReq);
return ResponseEntity.ok(animals); return ResponseEntity.ok(animals);
} }
} }

View File

@@ -56,20 +56,20 @@ public class ZooApiController {
/** /**
* 동물원 검색 (페이징) * 동물원 검색 (페이징)
* *
* @param name 동물원 이름 (선택) * @param name 동물원 이름 (선택)
* @param location 위치 (선택) * @param location 위치 (선택)
* @param page 페이지 번호 (기본값: 0) * @param page 페이지 번호 (기본값: 0)
* @param size 페이지 크기 (기본값: 20) * @param size 페이지 크기 (기본값: 20)
* @param sort 정렬 조건 (예: "name,asc") * @param sort 정렬 조건 (예: "name,asc")
* @return 페이징 처리된 동물원 목록 (각 동물원의 현재 동물 개수 포함) * @return 페이징 처리된 동물원 목록 (각 동물원의 현재 동물 개수 포함)
*/ */
@GetMapping @GetMapping
public ResponseEntity<Page<ZooDto.Detail>> searchZoos( public ResponseEntity<Page<ZooDto.Detail>> searchZoos(
@RequestParam(required = false) String name, @RequestParam(required = false) String name,
@RequestParam(required = false) String location, @RequestParam(required = false) String location,
@RequestParam(defaultValue = "0") int page, @RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size, @RequestParam(defaultValue = "20") int size,
@RequestParam(required = false) String sort) { @RequestParam(required = false) String sort) {
ZooDto.SearchReq searchReq = new ZooDto.SearchReq(name, location, page, size, sort); ZooDto.SearchReq searchReq = new ZooDto.SearchReq(name, location, page, size, sort);
Page<ZooDto.Detail> zoos = zooService.search(searchReq); Page<ZooDto.Detail> zoos = zooService.search(searchReq);
return ResponseEntity.ok(zoos); return ResponseEntity.ok(zoos);

View File

@@ -29,33 +29,32 @@ public class AnimalDto {
@Getter @Getter
public static class Basic { public static class Basic {
@JsonIgnore @JsonIgnore private Long id;
private Long id;
private String uuid; private String uuid;
private Category category; private Category category;
private Species species; private Species species;
private String name; private String name;
@JsonFormat( @JsonFormat(
shape = JsonFormat.Shape.STRING, shape = JsonFormat.Shape.STRING,
pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", pattern = "yyyy-MM-dd'T'HH:mm:ssXXX",
timezone = "Asia/Seoul") timezone = "Asia/Seoul")
private ZonedDateTime createdDate; private ZonedDateTime createdDate;
@JsonFormat( @JsonFormat(
shape = JsonFormat.Shape.STRING, shape = JsonFormat.Shape.STRING,
pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", pattern = "yyyy-MM-dd'T'HH:mm:ssXXX",
timezone = "Asia/Seoul") timezone = "Asia/Seoul")
private ZonedDateTime modifiedDate; private ZonedDateTime modifiedDate;
public Basic( public Basic(
Long id, Long id,
String uuid, String uuid,
String name, String name,
Category category, Category category,
Species species, Species species,
ZonedDateTime createdDate, ZonedDateTime createdDate,
ZonedDateTime modifiedDate) { ZonedDateTime modifiedDate) {
this.id = id; this.id = id;
this.uuid = uuid; this.uuid = uuid;
this.name = name; this.name = name;
@@ -70,16 +69,16 @@ public class AnimalDto {
@AllArgsConstructor @AllArgsConstructor
public enum Category implements EnumType { public enum Category implements EnumType {
// @formatter:off // @formatter:off
MAMMALS("100", "포유류"), // 땅에 사는 동물 MAMMALS("100", "포유류"), // 땅에 사는 동물
BIRDS("200", "조류"), // 하늘을 나는 동물 BIRDS("200", "조류"), // 하늘을 나는 동물
FISH("300", "어류"), FISH("300", "어류"),
AMPHIBIANS("400", "양서류"), AMPHIBIANS("400", "양서류"),
REPTILES("500", "파충류"), REPTILES("500", "파충류"),
INSECTS("500", "곤충"), INSECTS("500", "곤충"),
INVERTEBRATES("500", "무척추동물"), INVERTEBRATES("500", "무척추동물"),
; ;
// @formatter:on // @formatter:on
private final String id; private final String id;
private final String text; private final String text;
} }
@@ -87,15 +86,15 @@ public class AnimalDto {
@AllArgsConstructor @AllArgsConstructor
public enum Species implements EnumType { public enum Species implements EnumType {
// @formatter:off // @formatter:off
DOG("101", ""), DOG("101", ""),
CAT("102", "강아지"), CAT("102", "강아지"),
DOVE("201", "비둘기"), DOVE("201", "비둘기"),
EAGLE("202", "독수리"), EAGLE("202", "독수리"),
SALMON("301", "연어"), SALMON("301", "연어"),
TUNA("302", "참치"), TUNA("302", "참치"),
; ;
// @formatter:on // @formatter:on
private final String id; private final String id;
private final String text; private final String text;
} }
@@ -120,9 +119,7 @@ public class AnimalDto {
String[] sortParams = sort.split(","); String[] sortParams = sort.split(",");
String property = sortParams[0]; String property = sortParams[0];
Sort.Direction direction = Sort.Direction direction =
sortParams.length > 1 sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC;
? Sort.Direction.fromString(sortParams[1])
: Sort.Direction.ASC;
return PageRequest.of(page, size, Sort.by(direction, property)); return PageRequest.of(page, size, Sort.by(direction, property));
} }
return PageRequest.of(page, size); return PageRequest.of(page, size);

View File

@@ -27,33 +27,32 @@ public class ZooDto {
@Getter @Getter
public static class Basic { public static class Basic {
@JsonIgnore @JsonIgnore private Long id;
private Long id;
private String uuid; private String uuid;
private String name; private String name;
private String location; private String location;
private String description; private String description;
@JsonFormat( @JsonFormat(
shape = JsonFormat.Shape.STRING, shape = JsonFormat.Shape.STRING,
pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", pattern = "yyyy-MM-dd'T'HH:mm:ssXXX",
timezone = "Asia/Seoul") timezone = "Asia/Seoul")
private ZonedDateTime createdDate; private ZonedDateTime createdDate;
@JsonFormat( @JsonFormat(
shape = JsonFormat.Shape.STRING, shape = JsonFormat.Shape.STRING,
pattern = "yyyy-MM-dd'T'HH:mm:ssXXX", pattern = "yyyy-MM-dd'T'HH:mm:ssXXX",
timezone = "Asia/Seoul") timezone = "Asia/Seoul")
private ZonedDateTime modifiedDate; private ZonedDateTime modifiedDate;
public Basic( public Basic(
Long id, Long id,
String uuid, String uuid,
String name, String name,
String location, String location,
String description, String description,
ZonedDateTime createdDate, ZonedDateTime createdDate,
ZonedDateTime modifiedDate) { ZonedDateTime modifiedDate) {
this.id = id; this.id = id;
this.uuid = uuid; this.uuid = uuid;
this.name = name; this.name = name;
@@ -70,14 +69,14 @@ public class ZooDto {
private Long activeAnimalCount; private Long activeAnimalCount;
public Detail( public Detail(
Long id, Long id,
String uuid, String uuid,
String name, String name,
String location, String location,
String description, String description,
ZonedDateTime createdDate, ZonedDateTime createdDate,
ZonedDateTime modifiedDate, ZonedDateTime modifiedDate,
Long activeAnimalCount) { Long activeAnimalCount) {
super(id, uuid, name, location, description, createdDate, modifiedDate); super(id, uuid, name, location, description, createdDate, modifiedDate);
this.activeAnimalCount = activeAnimalCount; this.activeAnimalCount = activeAnimalCount;
} }
@@ -103,9 +102,7 @@ public class ZooDto {
String[] sortParams = sort.split(","); String[] sortParams = sort.split(",");
String property = sortParams[0]; String property = sortParams[0];
Sort.Direction direction = Sort.Direction direction =
sortParams.length > 1 sortParams.length > 1 ? Sort.Direction.fromString(sortParams[1]) : Sort.Direction.ASC;
? Sort.Direction.fromString(sortParams[1])
: Sort.Direction.ASC;
return PageRequest.of(page, size, Sort.by(direction, property)); return PageRequest.of(page, size, Sort.by(direction, property));
} }
return PageRequest.of(page, size); return PageRequest.of(page, size);

View File

@@ -11,51 +11,52 @@ import org.springframework.transaction.annotation.Transactional;
@Service @Service
@Transactional(readOnly = true) @Transactional(readOnly = true)
public class ZooService { public class ZooService {
private final ZooCoreService zooCoreService;
// 동물원의 UUID로 id조회 private final ZooCoreService zooCoreService;
public Long getZooByUuid(String uuid) {
return zooCoreService.getDataByUuid(uuid).getId();
}
/** // 동물원의 UUID로 id조회
* 동물원 생성 public Long getZooByUuid(String uuid) {
* return zooCoreService.getDataByUuid(uuid).getId();
* @param req 동물원 생성 요청 }
* @return 생성된 동물원 정보 (동물 개수 포함)
*/
@Transactional
public ZooDto.Detail createZoo(ZooDto.AddReq req) {
return zooCoreService.create(req);
}
/** /**
* 동물원 삭제 (논리 삭제) * 동물원 생성
* *
* @param id 동물원 ID * @param req 동물원 생성 요청
*/ * @return 생성된 동물원 정보 (동물 개수 포함)
@Transactional */
public void deleteZoo(Long id) { @Transactional
zooCoreService.remove(id); public ZooDto.Detail createZoo(ZooDto.AddReq req) {
} return zooCoreService.create(req);
}
/** /**
* 동물원 단건 조회 * 동물원 삭제 (논리 삭제)
* *
* @param id 동물원 ID * @param id 동물원 ID
* @return 동물원 정보 (동물 개수 포함) */
*/ @Transactional
public ZooDto.Detail getZoo(Long id) { public void deleteZoo(Long id) {
return zooCoreService.getOneById(id); zooCoreService.remove(id);
} }
/** /**
* 동물원 검색 (페이징) * 동물원 단건 조회
* *
* @param searchReq 검색 조건 * @param id 동물원 ID
* @return 페이징 처리된 동물원 목록 (각 동물원의 동물 개수 포함) * @return 동물원 정보 (동물 개수 포함)
*/ */
public Page<ZooDto.Detail> search(ZooDto.SearchReq searchReq) { public ZooDto.Detail getZoo(Long id) {
return zooCoreService.search(searchReq); return zooCoreService.getOneById(id);
} }
/**
* 동물원 검색 (페이징)
*
* @param searchReq 검색 조건
* @return 페이징 처리된 동물원 목록 (각 동물원의 동물 개수 포함)
*/
public Page<ZooDto.Detail> search(ZooDto.SearchReq searchReq) {
return zooCoreService.search(searchReq);
}
} }

View File

@@ -6,6 +6,6 @@ import org.springframework.boot.test.context.SpringBootTest;
@SpringBootTest @SpringBootTest
class KamcoBackApplicationTests { class KamcoBackApplicationTests {
@Test @Test
void contextLoads() {} void contextLoads() {}
} }