Springboot JPA Entity 변수 매핑 정리 (Java, Kotlin)

2025. 5. 8. 21:32·Programming/BackEnd

- 이번 포스팅에선 Java Springboot 에서 JPA 라이브러리를 사용할 때,

Entity 에서 각 변수를 어떻게 매핑해야 할 지에 대해 정리하겠습니다.

 

- JPA 는 ORM(Object Relational Mapping) 입니다.

즉, Database 의 저장 정보를 Programming Language 에서의 객체로 다루는 기술로,

Database 의 테이블 단위로 Java Class 에 매핑하여 사용합니다.

 

즉, Database 테이블이 하나의 class 로 표현해야 하며,

이때에 각 컬럼은 프로그래밍 언어의 변수로 표현됩니다.

 

본 게시글에서는 Database 컬럼 타입별 Java 언어에서 어떠한 변수로 매핑해야 하는지에 대해 정리할 것입니다.

 

RDBMS 는 MySQL 을 기준으로 하며,

이에따라 MySQL 에서 제공하는 대다수의 변수에 대하여 매핑하는 예시와 더불어,

실제 제대로 매핑이 되는지에 대한 테스트 코드를 제공해드리겠습니다.

(아래 글의 설명은 java 를 기준으로 하지만, kotlin 용 코드도 제공드립니다.)

 

java-github

kotlin-github

 

위 템플릿 코드에서 module-sample-jpa 안에서 아래 코드를 확인할 수 있습니다.

 

(JPA 매핑 Entity)

Db1_Template_DataTypeMappingTest.class

package com.raillylinker.jpa_beans.db1_main.entities;

import com.raillylinker.converters.JsonMapConverter;
import com.raillylinker.converters.MySqlSetConverter;
import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import org.locationtech.jts.geom.Geometry;
import org.locationtech.jts.geom.LineString;
import org.locationtech.jts.geom.Point;
import org.locationtech.jts.geom.Polygon;
import org.jetbrains.annotations.*;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Map;
import java.util.Set;

@Entity
@Table(
        name = "data_type_mapping_test",
        catalog = "template"
)
@Comment("ORM 과 Database 간 데이터 타입 매핑을 위한 테이블")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Db1_Template_DataTypeMappingTest {
    // [기본 입력값이 존재하는 변수들]
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "uid", nullable = false, columnDefinition = "BIGINT UNSIGNED")
    @Comment("행 고유값")
    private Long uid;

    @Column(name = "row_create_date", nullable = false, columnDefinition = "DATETIME(3)")
    @CreationTimestamp
    @Comment("행 생성일")
    private LocalDateTime rowCreateDate;

    @Column(name = "row_update_date", nullable = false, columnDefinition = "DATETIME(3)")
    @UpdateTimestamp
    @Comment("행 수정일")
    private LocalDateTime rowUpdateDate;


    // ---------------------------------------------------------------------------------------------
    // [입력값 수동 입력 변수들]
    // 숫자 데이터
    @Column(name = "sample_tiny_int", nullable = false, columnDefinition = "TINYINT")
    @Comment("-128 ~ 127 정수 (1Byte)")
    private @NotNull Byte sampleTinyInt;
    @Column(name = "sample_tiny_int_unsigned", nullable = false, columnDefinition = "TINYINT UNSIGNED")
    @Comment("0 ~ 255 정수 (1Byte)")
    private @NotNull Short sampleTinyIntUnsigned;
    @Column(name = "sample_small_int", nullable = false, columnDefinition = "SMALLINT")
    @Comment("-32,768 ~ 32,767 정수 (2Byte)")
    private @NotNull Short sampleSmallInt;
    @Column(name = "sample_small_int_unsigned", nullable = false, columnDefinition = "SMALLINT UNSIGNED")
    @Comment("0 ~ 65,535 정수 (2Byte)")
    private @NotNull Integer sampleSmallIntUnsigned;
    @Column(name = "sample_medium_int", nullable = false, columnDefinition = "MEDIUMINT")
    @Comment("-8,388,608 ~ 8,388,607 정수 (3Byte)")
    private @NotNull Integer sampleMediumInt;
    @Column(name = "sample_medium_int_unsigned", nullable = false, columnDefinition = "MEDIUMINT UNSIGNED")
    @Comment("0 ~ 16,777,215 정수 (3Byte)")
    private @NotNull Integer sampleMediumIntUnsigned;
    @Column(name = "sample_int", nullable = false, columnDefinition = "INT")
    @Comment("-2,147,483,648 ~ 2,147,483,647 정수 (4Byte)")
    private @NotNull Integer sampleInt;
    @Column(name = "sample_int_unsigned", nullable = false, columnDefinition = "INT UNSIGNED")
    @Comment("0 ~ 4,294,967,295 정수 (4Byte)")
    private @NotNull Long sampleIntUnsigned;
    @Column(name = "sample_big_int", nullable = false, columnDefinition = "BIGINT")
    @Comment("-2^63 ~ 2^63-1 정수 (8Byte)")
    private @NotNull Long sampleBigInt;
    @Column(name = "sample_big_int_unsigned", nullable = false, columnDefinition = "BIGINT UNSIGNED")
    @Comment("0 ~ 2^64-1 정수 (8Byte)")
    private @NotNull BigInteger sampleBigIntUnsigned;
    @Column(name = "sample_float", nullable = false, columnDefinition = "FLOAT")
    @Comment("-3.4E38 ~ 3.4E38 단정밀도 부동소수점 (4Byte)")
    private @NotNull Float sampleFloat;
    @Column(name = "sample_float_unsigned", nullable = false, columnDefinition = "FLOAT UNSIGNED")
    @Comment("0 ~ 3.402823466E+38 단정밀도 부동소수점 (4Byte)")
    private @NotNull Float sampleFloatUnsigned;
    @Column(name = "sample_double", nullable = false, columnDefinition = "DOUBLE")
    @Comment("-1.7E308 ~ 1.7E308 배정밀도 부동소수점 (8Byte)")
    private @NotNull Double sampleDouble;
    @Column(name = "sample_double_unsigned", nullable = false, columnDefinition = "DOUBLE UNSIGNED")
    @Comment("0 ~ 1.7976931348623157E+308 배정밀도 부동소수점 (8Byte)")
    private @NotNull Double sampleDoubleUnsigned;
    @Column(name = "sample_decimal_p65_s10", nullable = false, columnDefinition = "DECIMAL(65, 10)")
    @Comment("p(전체 자릿수, 최대 65), s(소수점 아래 자릿수, p 보다 작거나 같아야 함) 설정 가능 고정 소수점 숫자")
    private @NotNull BigDecimal sampleDecimalP65S10;

    // 시간 데이터
    @Column(name = "sample_date", nullable = false, columnDefinition = "DATE")
    @Comment("1000-01-01 ~ 9999-12-31 날짜 데이터")
    private @NotNull LocalDate sampleDate;
    @Column(name = "sample_datetime", nullable = false, columnDefinition = "DATETIME(3)")
    @Comment("1000-01-01 00:00:00 ~ 9999-12-31 23:59:59 날짜 데이터")
    private @NotNull LocalDateTime sampleDateTime;
    @Column(name = "sample_time", nullable = false, columnDefinition = "TIME(3)")
    @Comment("-838:59:59 ~ 838:59:59 시간 데이터")
    private @NotNull LocalTime sampleTime;
    @Column(name = "sample_timestamp", nullable = false, columnDefinition = "TIMESTAMP(3)")
    @Comment("1970-01-01 00:00:01 ~ 2038-01-19 03:14:07 날짜 데이터 저장시 UTC 기준으로 저장되고, 조회시 시스템 설정에 맞게 반환")
    private @NotNull LocalDateTime sampleTimestamp;
    @Column(name = "sample_year", nullable = false, columnDefinition = "YEAR")
    @Comment("1901 ~ 2155 년도")
    private @NotNull Integer sampleYear;

    // 문자 데이터
    /*
        문자 관련 데이터는 영문, 숫자를 기준으로 1 바이트 1 문자,
        그외 문자는 그 이상으로, 인커딩에 따라 달라집니다.
        UTF-8 에서 한글은 3 바이트, 특수문자는 4 바이트입니다.
     */
    @Column(name = "sample_char12", nullable = false, columnDefinition = "CHAR(12)")
    @Comment("고정 길이 문자열 (최대 255 Byte), CHAR 타입은 항상 지정된 길이만큼 공간을 차지하며, 실제 저장되는 문자열이 그보다 짧으면 빈 공간으로 패딩하여 저장합니다.")
    private @NotNull String sampleChar12;
    @Column(name = "sample_varchar12", nullable = false, columnDefinition = "VARCHAR(12)")
    @Comment("가변 길이 문자열 (최대 65,535 Byte), CHAR 과 달리 저장되는 데이터의 길이에 따라 실제 저장되는 공간이 달라집니다. CHAR 에 비해 저장 공간 활용에 강점이 있고 성능에 미비한 약점이 있습니다.")
    private @NotNull String sampleVarchar12;
    @Column(name = "sample_tiny_text", nullable = false, columnDefinition = "TINYTEXT")
    @Comment("가변 길이 문자열 최대 255 Byte")
    private @NotNull String sampleTinyText;
    @Column(name = "sample_text", nullable = false, columnDefinition = "TEXT")
    @Comment("가변 길이 문자열 최대 65,535 Byte")
    private @NotNull String sampleText;
    @Column(name = "sample_medium_text", nullable = false, columnDefinition = "MEDIUMTEXT")
    @Comment("가변 길이 문자열 최대 16,777,215 Byte")
    private @NotNull String sampleMediumText;
    @Column(name = "sample_long_text", nullable = false, columnDefinition = "LONGTEXT")
    @Comment("가변 길이 문자열 최대 4,294,967,295 Byte")
    private @NotNull String sampleLongText;

    // Bit 데이터
    @Column(name = "sample_one_bit", nullable = false, columnDefinition = "BIT(1)")
    @Comment("1 bit 값 (Boolean 으로 사용할 수 있습니다. (1 : 참, 0 : 거짓))")
    private @NotNull Boolean sampleOneBit;
    @Column(name = "sample_6_bit", nullable = false, columnDefinition = "BIT(6)")
    @Comment("n bit 값 (bit 사이즈에 따라 변수 사이즈를 맞춰 매핑)")
    private @NotNull Byte sample6Bit;

    // 컬렉션 데이터
    @Column(name = "sample_json", columnDefinition = "JSON")
    @Convert(converter = JsonMapConverter.class)
    @Comment("JSON 타입")
    private @Nullable Map<String, Object> sampleJson;
    @Column(name = "sample_enum_abc", nullable = false, columnDefinition = "ENUM('A', 'B', 'C')")
    @Enumerated(EnumType.STRING)
    @Comment("A, B, C 중 하나")
    private @NotNull EnumAbc sampleEnumAbc;
    @Column(name = "sample_set_abc", columnDefinition = "SET('A', 'B', 'C')")
    @Convert(converter = MySqlSetConverter.class)
    @Comment("A, B, C Set 컬렉션")
    private @Nullable Set<EnumAbc> sampleSetAbc;

    // 공간 데이터
    @Column(name = "sample_geometry", nullable = false, columnDefinition = "GEOMETRY")
    @Comment("GEOMETRY 타입(Point, Line, Polygon 데이터 중 어느것이라도 하나를 넣을 수 있습니다.)")
    private @NotNull Geometry sampleGeometry;
    @Column(name = "sample_point", nullable = false, columnDefinition = "POINT")
    @Comment("(X, Y) 공간 좌표")
    private @NotNull Point samplePoint;
    @Column(name = "sample_linestring", nullable = false, columnDefinition = "LINESTRING")
    @Comment("직선의 시퀀스")
    private @NotNull LineString sampleLinestring;
    @Column(name = "sample_polygon", nullable = false, columnDefinition = "POLYGON")
    @Comment("다각형")
    private @NotNull Polygon samplePolygon;

    // Binary 데이터
    @Column(name = "sample_binary2", nullable = false, columnDefinition = "BINARY(2)")
    @Comment("고정 길이 이진 데이터 (최대 65535 바이트), 암호화된 값, UUID, 고정 길이 해시값 등을 저장하는 역할")
    private @NotNull byte[] sampleBinary2;
    @Column(name = "sample_varbinary2", nullable = false, columnDefinition = "VARBINARY(2)")
    @Comment("가변 길이 이진 데이터 (최대 65535 바이트), 동적 크기의 바이너리 데이터, 이미지 등을 저장하는 역할")
    private @NotNull byte[] sampleVarbinary2;

    public enum EnumAbc {
        A, B, C
    }
}

 

첫번째 매핑 클래스는 위와 같습니다.

 

보시는 바와 같이 숫자 데이터에 속하는 TINYINT 나 INT, BIGINT 등의 예시를 적음으로써,

변수 값에 대한 오버플로우가 일어나지 않는 적절한 변수를 사용하였습니다.

 

예를들어 DB 의 INT 타입은 4바이트 사이즈를 가지고 있기에 java 에서도 동일한 Integer 변수로 받을 수 있지만,

UNSIGNED INT 타입은, 동일한 4바이트를 가지고 있지만, 음수값이 무시되기에 java 의 Integer 변수의 범위를 넘어서므로 java 변수 타입으로는 Long 을 사용하여 매핑합니다.

 

이와 같이 DB 변수가 나타내는 값의 범위를 고려하여 적절한 java 변수를 매핑하는 것이 매우 중요하기에 미리 정해진 위와 같은 코드를 참고하여 앞으로의 매핑에 활용하는 것이 좋습니다.

 

그외에도 DATE 타입, BIT 타입, GEOMETRY 타입 등의 특수한 타입을 받는 예시가 있으며,

자주 사용되는 타입으로는 ENUM 과 같은 타입이 존재합니다.

 

위 코드의 특이점으로는, @Convert 어노테이션이 붙은 변수가 단 2개가 존재하는데,

JSON 타입과 SET 타입입니다.

 

이에대한 converter 클래스는,

 

JsonMapConverter.class

package com.raillylinker.converters;

import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import org.jetbrains.annotations.*;

import java.util.Map;

// [JPA 에서 JSON 타입을 Map<String, Any?>? 타입으로 입출력하기 위한 컨버터]
@Converter
public class JsonMapConverter implements AttributeConverter<Map<String, Object>, String> {
    private final @NotNull ObjectMapper objectMapper = new ObjectMapper();

    @Override
    public @Nullable String convertToDatabaseColumn(
            @Nullable Map<String, Object> attribute
    ) {
        try {
            return attribute == null ? null : objectMapper.writeValueAsString(attribute);
        } catch (@NotNull Exception e) {
            throw new IllegalArgumentException("Error converting map to JSON string", e);
        }
    }

    @Override
    public @Nullable Map<String, Object> convertToEntityAttribute(
            @Nullable String dbData
    ) {
        try {
            return dbData == null ? null : objectMapper.readValue(dbData, new TypeReference<>() {
            });
        } catch (@NotNull Exception e) {
            throw new IllegalArgumentException("Error converting JSON string to map", e);
        }
    }
}

 

MySqlSetConverter.class

package com.raillylinker.converters;

import jakarta.persistence.AttributeConverter;
import jakarta.persistence.Converter;
import org.jetbrains.annotations.*;

import java.util.Arrays;
import java.util.Collections;
import java.util.Set;
import java.util.stream.Collectors;

// [JPA 에서 Set<String> 타입을 입출력하기 위한 컨버터]
@Converter
public class MySqlSetConverter implements AttributeConverter<Set<String>, String> {
    @Override
    public @Nullable String convertToDatabaseColumn(
            @Nullable Set<String> attribute
    ) {
        if (attribute == null || attribute.isEmpty()) {
            return null;
        }

        @NotNull String attributeStr = attribute.toString();

        return attributeStr.replace("[", "")
                .replace("]", "")
                .replace(" ", "");
    }

    @Override
    public @Nullable Set<String> convertToEntityAttribute(
            @Nullable String dbData
    ) {
        return (dbData == null || dbData.isEmpty())
                ? Collections.emptySet()
                : Arrays.stream(dbData.split(","))
                .collect(Collectors.toSet());
    }
}

 

 

위와 같습니다.

 

단언컨데, 제가 제공드린 방식대로만 사용하신다면 converter 클래스는 이 이외에 더이상 작성하실 필요는 없습니다.

모든 set 데이터에는 MySqlSetConverter 를 적용하면 되며, 모든 json 데이터에는, JsonMapConverter 를 적용하여, JSON 으로 나타낼 class 객체를 Map 형식으로 변환하여 적용하면 됩니다.(이에 대한 사용법은 아래 테스트 코드에서 수록)

 

또 하나 특이한 점으로,

GEOMETRY 나 POINT 와 같은 기하학적 데이터 저장시에 사용되는 DB 컬럼 타입은,

Geometry, Point 와 같은 클래스를 사용하는데, 이 클래스를 사용하기 위해서는,

gradle 에서,

    // (Hibernate ORM)
    // : ORM Geometry 등 매핑
    implementation 'org.hibernate:hibernate-spatial:6.6.4.Final'

 

위와 같은 라이브러리를 implement 하시면 됩니다.

 

기본적인 타입의 변수 매핑은 위와 같고,

다음으로 파일 등의 ByteArray 정보를 저장할 때 사용하는 Blob 타입 데이터의 경우는 아래와 같이 매핑합니다.

 

Db1_Template_DataTypeBlobMappingTest.class

package com.raillylinker.jpa_beans.db1_main.entities;

import jakarta.persistence.*;
import lombok.*;
import org.hibernate.annotations.Comment;
import org.hibernate.annotations.CreationTimestamp;
import org.hibernate.annotations.UpdateTimestamp;
import org.jetbrains.annotations.NotNull;

import java.time.LocalDateTime;

@Entity
@Table(
        name = "data_type_blob_mapping_test",
        catalog = "template"
)
@Comment("ORM 과 Database 간 Blob 데이터 타입 매핑을 위한 테이블")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Db1_Template_DataTypeBlobMappingTest {
    // [기본 입력값이 존재하는 변수들]
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "uid", nullable = false, columnDefinition = "BIGINT UNSIGNED")
    @Comment("행 고유값")
    private Long uid;

    @Column(name = "row_create_date", nullable = false, columnDefinition = "DATETIME(3)")
    @CreationTimestamp
    @Comment("행 생성일")
    private LocalDateTime rowCreateDate;

    @Column(name = "row_update_date", nullable = false, columnDefinition = "DATETIME(3)")
    @UpdateTimestamp
    @Comment("행 수정일")
    private LocalDateTime rowUpdateDate;


    // ---------------------------------------------------------------------------------------------
    // Blob 데이터
    // BLOB 타입 데이터는 주로 이미지, 음악 파일, 문서 파일 등 이진 데이터를 저장하는 데 활용됩니다.
    @Column(name = "sample_tiny_blob_file_name", nullable = false, columnDefinition = "VARCHAR(255)")
    @Comment("최대 255 바이트 이진 데이터 파일 이름")
    private @NotNull String sampleTinyBlobFileName;
    @Lob
    @Column(name = "sample_tiny_blob", nullable = false, columnDefinition = "TINYBLOB")
    @Comment("최대 255 바이트 이진 데이터")
    private @NotNull byte[] sampleTinyBlob;
    @Column(name = "sample_blob_file_name", nullable = false, columnDefinition = "VARCHAR(255)")
    @Comment("최대 65,535바이트 이진 데이터 파일 이름")
    private @NotNull String sampleBlobFileName;
    @Lob
    @Column(name = "sample_blob", nullable = false, columnDefinition = "BLOB")
    @Comment("최대 65,535바이트 이진 데이터")
    private @NotNull byte[] sampleBlob;
    @Column(name = "sample_medium_blob_file_name", nullable = false, columnDefinition = "VARCHAR(255)")
    @Comment("최대 16,777,215 바이트 이진 데이터 파일 이름")
    private @NotNull String sampleMediumBlobFileName;
    @Lob
    @Column(name = "sample_medium_blob", nullable = false, columnDefinition = "MEDIUMBLOB")
    @Comment("최대 16,777,215 바이트 이진 데이터")
    private @NotNull byte[] sampleMediumBlob;
    @Column(name = "sample_long_blob_file_name", nullable = false, columnDefinition = "VARCHAR(255)")
    @Comment("최대 4,294,967,295 바이트 이진 데이터 파일 이름")
    private @NotNull String sampleLongBlobFileName;
    @Lob
    @Column(name = "sample_long_blob", nullable = false, columnDefinition = "LONGBLOB")
    @Comment("최대 4,294,967,295 바이트 이진 데이터")
    private @NotNull byte[] sampleLongBlob;
}

 

보시다시피 어떠한 BLOB 타입이건 byte array 를 사용하여 데이터를 주고받으며,

위 테이블은 multipart 로 받은 파일의 정보를 DB 에 저장하는 예시 코드를 작성하기 위한 목적으로 작성되었습니다.

 

(Repository)

테스트를 위한 JPA Repository 는 기본적인 CRUD 만을 이용할 것이므로 아래와 같이 작성만 하시면 됩니다.

package com.raillylinker.jpa_beans.db1_main.repositories;

import com.raillylinker.jpa_beans.db1_main.entities.Db1_Template_DataTypeMappingTest;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface Db1_Template_DataTypeMappingTest_Repository extends JpaRepository<Db1_Template_DataTypeMappingTest, Long> {
}
package com.raillylinker.jpa_beans.db1_main.repositories;

import com.raillylinker.jpa_beans.db1_main.entities.Db1_Template_DataTypeBlobMappingTest;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface Db1_Template_DataTypeBlobMappingTest_Repository extends JpaRepository<Db1_Template_DataTypeBlobMappingTest, Long> {
}

 

 

(테스트 코드)

이제 위와 같이 준비된 테이블에 실제 값이 잘 입력되는지를 확인하기 위한 테스트용 API 를 작성할 것입니다.

저는 제 코딩 스타일에 맞도록 Controller 와 Service 로 나누어 작성하였으며,

Swagger 를 사용하기에 이에 따른 코드가 존재하는데,

참고하세요.

 

Controller.class

package com.raillylinker.controllers;

import com.fasterxml.jackson.annotation.JsonProperty;
import com.raillylinker.jpa_beans.db1_main.entities.Db1_Template_DataTypeMappingTest;
import com.raillylinker.services.JpaTestService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
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 jakarta.servlet.http.HttpServletResponse;
import lombok.Data;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Controller;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.jetbrains.annotations.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.util.List;
import java.util.Set;

@Tag(name = "/jpa-test APIs", description = "JPA 테스트 API 컨트롤러")
@Controller
@RequestMapping("/jpa-test")
@Validated
public class JpaTestController {
    public JpaTestController(
            @NotNull JpaTestService service
    ) {
        this.service = service;
    }

    // <멤버 변수 공간>
    private final @NotNull JpaTestService service;


    // ---------------------------------------------------------------------------------------------
    // <매핑 함수 공간>
    @Operation(
            summary = "ORM Datatype Mapping 테이블 Row 입력 테스트 API",
            description = "ORM Datatype Mapping 테이블에 값이 잘 입력되는지 테스트"
    )
    @ApiResponses(
            value = {
                    @ApiResponse(
                            responseCode = "200",
                            description = "정상 동작"
                    )
            }
    )
    @PostMapping(
            path = {"/orm-datatype-mapping-test"},
            consumes = MediaType.APPLICATION_JSON_VALUE,
            produces = MediaType.APPLICATION_JSON_VALUE
    )
    @ResponseBody
    public @Nullable OrmDatatypeMappingTestOutputVo ormDatatypeMappingTest(
            @Parameter(hidden = true)
            @NotNull HttpServletResponse httpServletResponse,
            @RequestBody
            @NotNull OrmDatatypeMappingTestInputVo inputVo
    ) {
        return service.ormDatatypeMappingTest(
                httpServletResponse,
                inputVo
        );
    }

    @Data
    public static class OrmDatatypeMappingTestInputVo {
        @Schema(description = "TINYINT 타입 컬럼(-128 ~ 127 정수 (1Byte))", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        @JsonProperty("sampleTinyInt")
        private final @NotNull Short sampleTinyInt;
        @Schema(description = "TINYINT UNSIGNED 타입 컬럼(0 ~ 255 정수 (1Byte))", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        @JsonProperty("sampleTinyIntUnsigned")
        private final @NotNull Short sampleTinyIntUnsigned;
        @Schema(description = "SMALLINT 타입 컬럼(-32,768 ~ 32,767 정수 (2Byte))", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        @JsonProperty("sampleSmallInt")
        private final @NotNull Short sampleSmallInt;
        @Schema(description = "SMALLINT UNSIGNED 타입 컬럼(0 ~ 65,535 정수 (2Byte))", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        @JsonProperty("sampleSmallIntUnsigned")
        private final @NotNull Integer sampleSmallIntUnsigned;
        @Schema(description = "MEDIUMINT 타입 컬럼(-8,388,608 ~ 8,388,607 정수 (3Byte))", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        @JsonProperty("sampleMediumInt")
        private final @NotNull Integer sampleMediumInt;
        @Schema(description = "MEDIUMINT UNSIGNED 타입 컬럼(0 ~ 16,777,215 정수 (3Byte))", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        @JsonProperty("sampleMediumIntUnsigned")
        private final @NotNull Integer sampleMediumIntUnsigned;
        @Schema(description = "INT 타입 컬럼(-2,147,483,648 ~ 2,147,483,647 정수 (4Byte))", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        @JsonProperty("sampleInt")
        private final @NotNull Integer sampleInt;
        @Schema(description = "INT UNSIGNED 타입 컬럼(0 ~ 4,294,967,295 정수 (4Byte))", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        @JsonProperty("sampleIntUnsigned")
        private final @NotNull Long sampleIntUnsigned;
        @Schema(description = "BIGINT 타입 컬럼(-2^63 ~ 2^63-1 정수 (8Byte))", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        @JsonProperty("sampleBigInt")
        private final @NotNull Long sampleBigInt;
        @Schema(description = "BIGINT UNSIGNED 타입 컬럼(0 ~ 2^64-1 정수 (8Byte))", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        @JsonProperty("sampleBigIntUnsigned")
        private final @NotNull BigInteger sampleBigIntUnsigned;
        @Schema(description = "FLOAT 타입 컬럼(-3.4E38 ~ 3.4E38 단정밀도 부동소수점 (4Byte))", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.1")
        @JsonProperty("sampleFloat")
        private final @NotNull Float sampleFloat;
        @Schema(description = "FLOAT UNSIGNED 타입 컬럼(0 ~ 3.402823466E+38 단정밀도 부동소수점 (4Byte))", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.1")
        @JsonProperty("sampleFloatUnsigned")
        private final @NotNull Float sampleFloatUnsigned;
        @Schema(description = "DOUBLE 타입 컬럼(-1.7E308 ~ 1.7E308 배정밀도 부동소수점 (8Byte))", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.1")
        @JsonProperty("sampleDouble")
        private final @NotNull Double sampleDouble;
        @Schema(description = "DOUBLE UNSIGNED 타입 컬럼(0 ~ 1.7976931348623157E+308 배정밀도 부동소수점 (8Byte))", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.1")
        @JsonProperty("sampleDoubleUnsigned")
        private final @NotNull Double sampleDoubleUnsigned;
        @Schema(description = "DECIMAL(65, 10) 타입 컬럼(p(전체 자릿수, 최대 65), s(소수점 아래 자릿수, p 보다 작거나 같아야 함) 설정 가능 고정 소수점 숫자)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.1")
        @JsonProperty("sampleDecimalP65S10")
        private final @NotNull BigDecimal sampleDecimalP65S10;
        @Schema(description = "DATE 타입 컬럼(1000-01-01 ~ 9999-12-31, yyyy_MM_dd_z, 00시 00분 00초 기준)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024_05_02_KST")
        @JsonProperty("sampleDate")
        private final @NotNull String sampleDate;
        @Schema(description = "DATETIME 타입 컬럼(1000-01-01 00:00:00 ~ 9999-12-31 23:59:59, yyyy_MM_dd_'T'_HH_mm_ss_SSS_z)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024_05_02_T_15_14_49_552_KST")
        @JsonProperty("sampleDatetime")
        private final @NotNull String sampleDatetime;
        @Schema(description = "TIME 타입 컬럼(-838:59:59 ~ 838:59:59, HH_mm_ss_SSS)", requiredMode = Schema.RequiredMode.REQUIRED, example = "01_01_01_111")
        @JsonProperty("sampleTime")
        private final @NotNull String sampleTime;
        @Schema(description = "TIMESTAMP 타입 컬럼(1970-01-01 00:00:01 ~ 2038-01-19 03:14:07 날짜 데이터 저장시 UTC 기준으로 저장되고, 조회시 시스템 설정에 맞게 반환, yyyy_MM_dd_'T'_HH_mm_ss_SSS_z)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024_05_02_T_15_14_49_552_KST")
        @JsonProperty("sampleTimestamp")
        private final @NotNull String sampleTimestamp;
        @Schema(description = "YEAR 타입 컬럼(1901 ~ 2155 년도)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024")
        @JsonProperty("sampleYear")
        private final @NotNull Integer sampleYear;
        @Schema(description = "CHAR(12) 타입 컬럼(12 바이트 입력 허용)", requiredMode = Schema.RequiredMode.REQUIRED, example = "test")
        @JsonProperty("sampleChar12")
        private final @NotNull String sampleChar12;
        @Schema(description = "VARCHAR(12) 타입 컬럼(12 바이트 입력 허용)", requiredMode = Schema.RequiredMode.REQUIRED, example = "test")
        @JsonProperty("sampleVarchar12")
        private final @NotNull String sampleVarchar12;
        @Schema(description = "TINYTEXT 타입 컬럼(가변 길이 문자열 최대 255 Byte)", requiredMode = Schema.RequiredMode.REQUIRED, example = "test")
        @JsonProperty("sampleTinyText")
        private final @NotNull String sampleTinyText;
        @Schema(description = "TEXT 타입 컬럼(가변 길이 문자열 최대 65,535 Byte)", requiredMode = Schema.RequiredMode.REQUIRED, example = "test")
        @JsonProperty("sampleText")
        private final @NotNull String sampleText;
        @Schema(description = "MEDIUMTEXT 타입 컬럼(가변 길이 문자열 최대 16,777,215 Byte)", requiredMode = Schema.RequiredMode.REQUIRED, example = "test")
        @JsonProperty("sampleMediumText")
        private final @NotNull String sampleMediumText;
        @Schema(description = "LONGTEXT 타입 컬럼(가변 길이 문자열 최대 4,294,967,295 Byte)", requiredMode = Schema.RequiredMode.REQUIRED, example = "test")
        @JsonProperty("sampleLongText")
        private final @NotNull String sampleLongText;
        @Schema(description = "1 bit 값 (Boolean 으로 사용할 수 있습니다. (1 : 참, 0 : 거짓))", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
        @JsonProperty("sampleOneBit")
        private final @NotNull Boolean sampleOneBit;
        @Schema(description = "6 bit 값 (bit 사이즈에 따라 변수 사이즈를 맞춰 매핑)", requiredMode = Schema.RequiredMode.REQUIRED, example = "123")
        @JsonProperty("sample6Bit")
        private final @NotNull Short sample6Bit;
        @Schema(description = "JSON 타입", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
        @JsonProperty("sampleJson")
        private final @Nullable SampleJsonVo sampleJson;
        @Schema(description = "A, B, C 중 하나", requiredMode = Schema.RequiredMode.REQUIRED, example = "A")
        @JsonProperty("sampleEnumAbc")
        private final @NotNull Db1_Template_DataTypeMappingTest.EnumAbc sampleEnumAbc;
        @Schema(description = "A, B, C 중 하나 (SET)", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "[\"A\", \"B\"]")
        @JsonProperty("sampleSetAbc")
        private final @Nullable Set<Db1_Template_DataTypeMappingTest.EnumAbc> sampleSetAbc;
        @Schema(description = "GEOMETRY 타입(Point, Line, Polygon 데이터 중 어느것이라도 하나를 넣을 수 있습니다.), 여기선 Point", requiredMode = Schema.RequiredMode.REQUIRED)
        @JsonProperty("sampleGeometry")
        private final @NotNull PointVo sampleGeometry;
        @Schema(description = "(X, Y) 공간 좌표", requiredMode = Schema.RequiredMode.REQUIRED)
        @JsonProperty("samplePoint")
        private final @NotNull PointVo samplePoint;
        @Schema(description = "직선 좌표 시퀀스", requiredMode = Schema.RequiredMode.REQUIRED)
        @JsonProperty("sampleLinestring")
        private final @NotNull LinestringVo sampleLinestring;
        @Schema(description = "고정 길이 이진 데이터 (최대 65535 바이트), 암호화된 값, UUID, 고정 길이 해시값 등을 저장하는 역할", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        @JsonProperty("sampleBinary2")
        private final @NotNull Short sampleBinary2;
        @Schema(description = "가변 길이 이진 데이터 (최대 65535 바이트), 동적 크기의 바이너리 데이터, 이미지 등을 저장하는 역할", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        @JsonProperty("sampleVarbinary2")
        private final @NotNull Short sampleVarbinary2;

        @Schema(description = "Sample Json Value Object")
        @Data
        public static class SampleJsonVo {
            @Schema(description = "json 으로 입력할 String", requiredMode = Schema.RequiredMode.REQUIRED, example = "sampleJsonStr")
            @JsonProperty("sampleJsonStr")
            private final @NotNull String sampleJsonStr;
            @Schema(description = "json 으로 입력할 Int", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1")
            @JsonProperty("sampleJsonInt")
            private final @Nullable Integer sampleJsonInt;
        }

        @Schema(description = "Point Object")
        @Data
        public static class PointVo {
            @Schema(description = "x value", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1.3")
            @JsonProperty("x")
            private final @NotNull Double x;
            @Schema(description = "y value", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2.9")
            @JsonProperty("y")
            private final @NotNull Double y;
        }

        @Schema(description = "Linestring Object")
        @Data
        public static class LinestringVo {
            @Schema(description = "첫번째 점", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
            @JsonProperty("point1")
            private final @NotNull PointVo point1;
            @Schema(description = "두번째 점", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
            @JsonProperty("point2")
            private final @NotNull PointVo point2;
        }
    }

    @Data
    public static class OrmDatatypeMappingTestOutputVo {
        @Schema(description = "TINYINT 타입 컬럼(-128 ~ 127 정수 (1Byte))", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        @JsonProperty("sampleTinyInt")
        private final @NotNull Short sampleTinyInt;
        @Schema(description = "TINYINT UNSIGNED 타입 컬럼(0 ~ 255 정수 (1Byte))", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        @JsonProperty("sampleTinyIntUnsigned")
        private final @NotNull Short sampleTinyIntUnsigned;
        @Schema(description = "SMALLINT 타입 컬럼(-32,768 ~ 32,767 정수 (2Byte))", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        @JsonProperty("sampleSmallInt")
        private final @NotNull Short sampleSmallInt;
        @Schema(description = "SMALLINT UNSIGNED 타입 컬럼(0 ~ 65,535 정수 (2Byte))", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        @JsonProperty("sampleSmallIntUnsigned")
        private final @NotNull Integer sampleSmallIntUnsigned;
        @Schema(description = "MEDIUMINT 타입 컬럼(-8,388,608 ~ 8,388,607 정수 (3Byte))", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        @JsonProperty("sampleMediumInt")
        private final @NotNull Integer sampleMediumInt;
        @Schema(description = "MEDIUMINT UNSIGNED 타입 컬럼(0 ~ 16,777,215 정수 (3Byte))", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        @JsonProperty("sampleMediumIntUnsigned")
        private final @NotNull Integer sampleMediumIntUnsigned;
        @Schema(description = "INT 타입 컬럼(-2,147,483,648 ~ 2,147,483,647 정수 (4Byte))", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        @JsonProperty("sampleInt")
        private final @NotNull Integer sampleInt;
        @Schema(description = "INT UNSIGNED 타입 컬럼(0 ~ 4,294,967,295 정수 (4Byte))", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        @JsonProperty("sampleIntUnsigned")
        private final @NotNull Long sampleIntUnsigned;
        @Schema(description = "BIGINT 타입 컬럼(-2^63 ~ 2^63-1 정수 (8Byte))", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        @JsonProperty("sampleBigInt")
        private final @NotNull Long sampleBigInt;
        @Schema(description = "BIGINT UNSIGNED 타입 컬럼(0 ~ 2^64-1 정수 (8Byte))", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        @JsonProperty("sampleBigIntUnsigned")
        private final @NotNull BigInteger sampleBigIntUnsigned;
        @Schema(description = "FLOAT 타입 컬럼(-3.4E38 ~ 3.4E38 단정밀도 부동소수점 (4Byte))", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.1")
        @JsonProperty("sampleFloat")
        private final @NotNull Float sampleFloat;
        @Schema(description = "FLOAT UNSIGNED 타입 컬럼(0 ~ 3.402823466E+38 단정밀도 부동소수점 (4Byte))", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.1")
        @JsonProperty("sampleFloatUnsigned")
        private final @NotNull Float sampleFloatUnsigned;
        @Schema(description = "DOUBLE 타입 컬럼(-1.7E308 ~ 1.7E308 배정밀도 부동소수점 (8Byte))", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.1")
        @JsonProperty("sampleDouble")
        private final @NotNull Double sampleDouble;
        @Schema(description = "DOUBLE UNSIGNED 타입 컬럼(0 ~ 1.7976931348623157E+308 배정밀도 부동소수점 (8Byte))", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.1")
        @JsonProperty("sampleDoubleUnsigned")
        private final @NotNull Double sampleDoubleUnsigned;
        @Schema(description = "DECIMAL(65, 10) 타입 컬럼(p(전체 자릿수, 최대 65), s(소수점 아래 자릿수, p 보다 작거나 같아야 함) 설정 가능 고정 소수점 숫자)", requiredMode = Schema.RequiredMode.REQUIRED, example = "1.1")
        @JsonProperty("sampleDecimalP65S10")
        private final @NotNull BigDecimal sampleDecimalP65S10;
        @Schema(description = "DATE 타입 컬럼(1000-01-01 ~ 9999-12-31, yyyy_MM_dd_z, 00시 00분 00초 기준)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024_05_02_KST")
        @JsonProperty("sampleDate")
        private final @NotNull String sampleDate;
        @Schema(description = "DATETIME 타입 컬럼(1000-01-01 00:00:00 ~ 9999-12-31 23:59:59, yyyy_MM_dd_'T'_HH_mm_ss_SSS_z)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024_05_02_T_15_14_49_552_KST")
        @JsonProperty("sampleDatetime")
        private final @NotNull String sampleDatetime;
        @Schema(description = "TIME 타입 컬럼(-838:59:59 ~ 838:59:59, HH_mm_ss_SSS)", requiredMode = Schema.RequiredMode.REQUIRED, example = "01_01_01_111")
        @JsonProperty("sampleTime")
        private final @NotNull String sampleTime;
        @Schema(description = "TIMESTAMP 타입 컬럼(1970-01-01 00:00:01 ~ 2038-01-19 03:14:07 날짜 데이터 저장시 UTC 기준으로 저장되고, 조회시 시스템 설정에 맞게 반환, yyyy_MM_dd_'T'_HH_mm_ss_SSS_z)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024_05_02_T_15_14_49_552_KST")
        @JsonProperty("sampleTimestamp")
        private final @NotNull String sampleTimestamp;
        @Schema(description = "YEAR 타입 컬럼(1901 ~ 2155 년도)", requiredMode = Schema.RequiredMode.REQUIRED, example = "2024")
        @JsonProperty("sampleYear")
        private final @NotNull Integer sampleYear;
        @Schema(description = "CHAR(12) 타입 컬럼(12 바이트 입력 허용)", requiredMode = Schema.RequiredMode.REQUIRED, example = "test")
        @JsonProperty("sampleChar12")
        private final @NotNull String sampleChar12;
        @Schema(description = "VARCHAR(12) 타입 컬럼(12 바이트 입력 허용)", requiredMode = Schema.RequiredMode.REQUIRED, example = "test")
        @JsonProperty("sampleVarchar12")
        private final @NotNull String sampleVarchar12;
        @Schema(description = "TINYTEXT 타입 컬럼(가변 길이 문자열 최대 255 Byte)", requiredMode = Schema.RequiredMode.REQUIRED, example = "test")
        @JsonProperty("sampleTinyText")
        private final @NotNull String sampleTinyText;
        @Schema(description = "TEXT 타입 컬럼(가변 길이 문자열 최대 65,535 Byte)", requiredMode = Schema.RequiredMode.REQUIRED, example = "test")
        @JsonProperty("sampleText")
        private final @NotNull String sampleText;
        @Schema(description = "MEDIUMTEXT 타입 컬럼(가변 길이 문자열 최대 16,777,215 Byte)", requiredMode = Schema.RequiredMode.REQUIRED, example = "test")
        @JsonProperty("sampleMediumText")
        private final @NotNull String sampleMediumText;
        @Schema(description = "LONGTEXT 타입 컬럼(가변 길이 문자열 최대 4,294,967,295 Byte)", requiredMode = Schema.RequiredMode.REQUIRED, example = "test")
        @JsonProperty("sampleLongText")
        private final @NotNull String sampleLongText;
        @Schema(description = "1 bit 값 (Boolean 으로 사용할 수 있습니다. (1 : 참, 0 : 거짓))", requiredMode = Schema.RequiredMode.REQUIRED, example = "true")
        @JsonProperty("sampleOneBit")
        private final @NotNull Boolean sampleOneBit;
        @Schema(description = "6 bit 값 (bit 사이즈에 따라 변수 사이즈를 맞춰 매핑)", requiredMode = Schema.RequiredMode.REQUIRED, example = "123")
        @JsonProperty("sample6Bit")
        private final @NotNull Short sample6Bit;
        @Schema(description = "JSON 타입", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
        @JsonProperty("sampleJson")
        private final @Nullable String sampleJson;
        @Schema(description = "A, B, C 중 하나", requiredMode = Schema.RequiredMode.REQUIRED, example = "A")
        @JsonProperty("sampleEnumAbc")
        private final @NotNull Db1_Template_DataTypeMappingTest.EnumAbc sampleEnumAbc;
        @Schema(description = "A, B, C 중 하나 (SET)", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "[\"A\", \"B\"]")
        @JsonProperty("sampleSetAbc")
        private final @Nullable Set<Db1_Template_DataTypeMappingTest.EnumAbc> sampleSetAbc;
        @Schema(description = "GEOMETRY 타입(Point, Line, Polygon 데이터 중 어느것이라도 하나를 넣을 수 있습니다.), 여기선 Point", requiredMode = Schema.RequiredMode.REQUIRED)
        @JsonProperty("sampleGeometry")
        private final @NotNull PointVo sampleGeometry;
        @Schema(description = "(X, Y) 공간 좌표", requiredMode = Schema.RequiredMode.REQUIRED)
        @JsonProperty("samplePoint")
        private final @NotNull PointVo samplePoint;
        @Schema(description = "직선 좌표 시퀀스", requiredMode = Schema.RequiredMode.REQUIRED)
        @JsonProperty("sampleLinestring")
        private final @NotNull LinestringVo sampleLinestring;
        @Schema(description = "폴리곤", requiredMode = Schema.RequiredMode.REQUIRED)
        @JsonProperty("samplePolygon")
        private final @NotNull List<PointVo> samplePolygon;
        @Schema(description = "고정 길이 이진 데이터 (최대 65535 바이트), 암호화된 값, UUID, 고정 길이 해시값 등을 저장하는 역할", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        @JsonProperty("sampleBinary2")
        private final @NotNull Short sampleBinary2;
        @Schema(description = "가변 길이 이진 데이터 (최대 65535 바이트), 동적 크기의 바이너리 데이터, 이미지 등을 저장하는 역할", requiredMode = Schema.RequiredMode.REQUIRED, example = "1")
        @JsonProperty("sampleVarbinary2")
        private final @NotNull Short sampleVarbinary2;

        @Schema(description = "Sample Json Value Object")
        @Data
        public static class SampleJsonVo {
            @Schema(description = "json 으로 입력할 String", requiredMode = Schema.RequiredMode.REQUIRED, example = "sampleJsonStr")
            @JsonProperty("sampleJsonStr")
            private final @NotNull String sampleJsonStr;
            @Schema(description = "json 으로 입력할 Int", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1")
            @JsonProperty("sampleJsonInt")
            private final @Nullable Integer sampleJsonInt;
        }

        @Schema(description = "Point Object")
        @Data
        public static class PointVo {
            @Schema(description = "x value", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "1.3")
            @JsonProperty("x")
            private final @NotNull Double x;
            @Schema(description = "y value", requiredMode = Schema.RequiredMode.NOT_REQUIRED, example = "2.9")
            @JsonProperty("y")
            private final @NotNull Double y;
        }

        @Schema(description = "Linestring Object")
        @Data
        public static class LinestringVo {
            @Schema(description = "첫번째 점", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
            @JsonProperty("point1")
            private final @NotNull PointVo point1;
            @Schema(description = "두번째 점", requiredMode = Schema.RequiredMode.NOT_REQUIRED)
            @JsonProperty("point2")
            private final @NotNull PointVo point2;
        }
    }


    // ----
    @Operation(
            summary = "ORM Blob Datatype Mapping 테이블 Row 입력 테스트 API",
            description = "ORM Blob Datatype Mapping 테이블에 값이 잘 입력되는지 테스트"
    )
    @ApiResponses(
            value = {
                    @ApiResponse(
                            responseCode = "200",
                            description = "정상 동작"
                    )
            }
    )
    @PostMapping(
            path = {"/orm-blob-datatype-mapping-test"},
            consumes = MediaType.MULTIPART_FORM_DATA_VALUE,
            produces = MediaType.ALL_VALUE
    )
    @ResponseBody
    public void ormBlobDatatypeMappingTest(
            @Parameter(hidden = true)
            @NotNull HttpServletResponse httpServletResponse,
            @ModelAttribute
            @RequestBody
            @NotNull OrmBlobDatatypeMappingTestInputVo inputVo
    ) throws IOException {
        service.ormBlobDatatypeMappingTest(
                httpServletResponse,
                inputVo
        );
    }

    @Data
    public static class OrmBlobDatatypeMappingTestInputVo {
        @Schema(description = "최대 255 바이트 파일", requiredMode = Schema.RequiredMode.REQUIRED)
        @JsonProperty("sampleTinyBlob")
        private final @NotNull MultipartFile sampleTinyBlob;
        @Schema(description = "최대 65,535바이트 파일", requiredMode = Schema.RequiredMode.REQUIRED)
        @JsonProperty("sampleBlob")
        private final @NotNull MultipartFile sampleBlob;
        @Schema(description = "최대 16,777,215 바이트 파일", requiredMode = Schema.RequiredMode.REQUIRED)
        @JsonProperty("sampleMediumBlob")
        private final @NotNull MultipartFile sampleMediumBlob;
        @Schema(description = "최대 4,294,967,295 바이트 파일", requiredMode = Schema.RequiredMode.REQUIRED)
        @JsonProperty("sampleLongBlob")
        private final @NotNull MultipartFile sampleLongBlob;
    }
}

 

 

Service.class

package com.raillylinker.services;

import com.google.gson.Gson;
import com.google.gson.reflect.TypeToken;
import com.raillylinker.configurations.jpa_configs.Db1MainConfig;
import com.raillylinker.controllers.JpaTestController;
import com.raillylinker.jpa_beans.db1_main.entities.*;
import com.raillylinker.jpa_beans.db1_main.repositories.*;
import com.raillylinker.util_components.CustomUtil;
import jakarta.servlet.http.HttpServletResponse;
import org.jetbrains.annotations.*;
import org.locationtech.jts.geom.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.time.ZoneId;
import java.time.ZonedDateTime;
import java.time.format.DateTimeFormatter;
import java.util.*;

@Service
public class JpaTestService {
    public JpaTestService(
            // (프로젝트 실행시 사용 설정한 프로필명 (ex : dev8080, prod80, local8080, 설정 안하면 default 반환))
            @Value("${spring.profiles.active:default}")
            @NotNull String activeProfile,

            @NotNull CustomUtil customUtil,

            @NotNull Db1_Template_DataTypeMappingTest_Repository db1TemplateDataTypeMappingTestRepository,
            @NotNull Db1_Template_DataTypeBlobMappingTest_Repository db1TemplateDataTypeBlobMappingTestRepository
    ) {
        this.activeProfile = activeProfile;
        this.customUtil = customUtil;
        this.db1TemplateDataTypeMappingTestRepository = db1TemplateDataTypeMappingTestRepository;
        this.db1TemplateDataTypeBlobMappingTestRepository = db1TemplateDataTypeBlobMappingTestRepository;
    }

    // <멤버 변수 공간>
    // (프로젝트 실행시 사용 설정한 프로필명 (ex : dev8080, prod80, local8080, 설정 안하면 default 반환))
    private final @NotNull String activeProfile;

    // (스웨거 문서 공개 여부 설정)
    private final @NotNull CustomUtil customUtil;

    private final @NotNull Logger classLogger = LoggerFactory.getLogger(JpaTestService.class);

    private final @NotNull Db1_Template_DataTypeMappingTest_Repository db1TemplateDataTypeMappingTestRepository;
    private final @NotNull Db1_Template_DataTypeBlobMappingTest_Repository db1TemplateDataTypeBlobMappingTestRepository;


    // ---------------------------------------------------------------------------------------------
    // <공개 메소드 공간>
    // (ORM Datatype Mapping 테이블 Row 입력 테스트 API)
    @Transactional(transactionManager = Db1MainConfig.TRANSACTION_NAME)
    public JpaTestController.OrmDatatypeMappingTestOutputVo ormDatatypeMappingTest(
            @NotNull HttpServletResponse httpServletResponse,
            @NotNull JpaTestController.OrmDatatypeMappingTestInputVo inputVo
    ) {
        @NotNull Gson gson = new Gson();

        @NotNull String[] sampleDateParts = inputVo.getSampleDate().split("_");

        @NotNull GeometryFactory geometryFactory = new GeometryFactory();

        // Point 데이터
        @NotNull Point geometryPoint = geometryFactory.createPoint(
                new Coordinate(inputVo.getSampleGeometry().getX(), inputVo.getSampleGeometry().getY()));

        @NotNull Point point = geometryFactory.createPoint(
                new Coordinate(inputVo.getSampleGeometry().getX(), inputVo.getSampleGeometry().getY()));

        // Line 데이터
        @NotNull LineString lineString = geometryFactory.createLineString(new Coordinate[]{
                new Coordinate(inputVo.getSampleLinestring().getPoint1().getX(), inputVo.getSampleLinestring().getPoint1().getY()),
                new Coordinate(inputVo.getSampleLinestring().getPoint2().getX(), inputVo.getSampleLinestring().getPoint2().getY())
        });

        // Polygon 데이터
        @NotNull Polygon geometryPolygon = geometryFactory.createPolygon(new Coordinate[]{
                new Coordinate(1.0, 1.0),
                new Coordinate(1.0, 5.0),
                new Coordinate(4.0, 9.0),
                new Coordinate(6.0, 9.0),
                new Coordinate(9.0, 3.0),
                new Coordinate(7.0, 2.0),
                new Coordinate(1.0, 1.0) // 첫 번째 좌표로 돌아가야 함
        });

        @NotNull Db1_Template_DataTypeMappingTest result =
                db1TemplateDataTypeMappingTestRepository.save(
                        Db1_Template_DataTypeMappingTest.builder()
                                .sampleTinyInt(inputVo.getSampleTinyInt().byteValue())
                                .sampleTinyIntUnsigned(inputVo.getSampleTinyIntUnsigned())
                                .sampleSmallInt(inputVo.getSampleSmallInt())
                                .sampleSmallIntUnsigned(inputVo.getSampleSmallIntUnsigned())
                                .sampleMediumInt(inputVo.getSampleMediumInt())
                                .sampleMediumIntUnsigned(inputVo.getSampleMediumIntUnsigned())
                                .sampleInt(inputVo.getSampleInt())
                                .sampleIntUnsigned(inputVo.getSampleIntUnsigned())
                                .sampleBigInt(inputVo.getSampleBigInt())
                                .sampleBigIntUnsigned(inputVo.getSampleBigIntUnsigned())
                                .sampleFloat(inputVo.getSampleFloat())
                                .sampleFloatUnsigned(inputVo.getSampleFloatUnsigned())
                                .sampleDouble(inputVo.getSampleDouble())
                                .sampleDoubleUnsigned(inputVo.getSampleDoubleUnsigned())
                                .sampleDecimalP65S10(inputVo.getSampleDecimalP65S10())
                                .sampleDate(
                                        ZonedDateTime.parse(
                                                sampleDateParts[0] + "_" + sampleDateParts[1] + "_" + sampleDateParts[2] + "_T_00_00_00_000_" + sampleDateParts[3],
                                                DateTimeFormatter.ofPattern("yyyy_MM_dd_'T'_HH_mm_ss_SSS_z")
                                        ).withZoneSameInstant(ZoneId.systemDefault()).toLocalDate()
                                )
                                .sampleDateTime(
                                        ZonedDateTime.parse(
                                                inputVo.getSampleDatetime(),
                                                DateTimeFormatter.ofPattern("yyyy_MM_dd_'T'_HH_mm_ss_SSS_z")
                                        ).withZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime()
                                )
                                .sampleTime(LocalTime.parse(inputVo.getSampleTime(), DateTimeFormatter.ofPattern("HH_mm_ss_SSS")))
                                .sampleTimestamp(
                                        ZonedDateTime.parse(
                                                inputVo.getSampleTimestamp(),
                                                DateTimeFormatter.ofPattern("yyyy_MM_dd_'T'_HH_mm_ss_SSS_z")
                                        ).withZoneSameInstant(ZoneId.systemDefault()).toLocalDateTime()
                                )
                                .sampleYear(inputVo.getSampleYear())
                                .sampleChar12(inputVo.getSampleChar12())
                                .sampleVarchar12(inputVo.getSampleVarchar12())
                                .sampleTinyText(inputVo.getSampleTinyText())
                                .sampleText(inputVo.getSampleText())
                                .sampleMediumText(inputVo.getSampleMediumText())
                                .sampleLongText(inputVo.getSampleLongText())
                                .sampleOneBit(inputVo.getSampleOneBit())
                                .sample6Bit((byte) (inputVo.getSample6Bit().intValue() & 0x3F))
                                .sampleJson(
                                        inputVo.getSampleJson() == null ? null :
                                                gson.fromJson(gson.toJsonTree(inputVo.getSampleJson()),
                                                        new TypeToken<Map<String, Object>>() {
                                                        }.getType())
                                )
                                .sampleEnumAbc(inputVo.getSampleEnumAbc())
                                .sampleSetAbc(inputVo.getSampleSetAbc())
                                .sampleGeometry(geometryPoint)
                                .samplePoint(point)
                                .sampleLinestring(lineString)
                                .samplePolygon(geometryPolygon)
                                .sampleBinary2(
                                        new byte[]{
                                                (byte) (inputVo.getSampleBinary2().intValue() >> 8),
                                                (byte) (inputVo.getSampleBinary2().intValue() & 0xFF)
                                        }
                                )
                                .sampleVarbinary2(
                                        new byte[]{
                                                (byte) (inputVo.getSampleVarbinary2().intValue() >> 8),
                                                (byte) (inputVo.getSampleVarbinary2().intValue() & 0xFF)
                                        }
                                )
                                .build()
                );

        @NotNull List<JpaTestController.OrmDatatypeMappingTestOutputVo.PointVo> samplePolygonPoints = new ArrayList<>();
        for (@NotNull Coordinate polygonCoord : result.getSamplePolygon().getCoordinates()) {
            samplePolygonPoints.add(
                    new JpaTestController.OrmDatatypeMappingTestOutputVo.PointVo(
                            polygonCoord.getX(),
                            polygonCoord.getY()
                    )
            );
        }

        httpServletResponse.setStatus(HttpStatus.OK.value());
        return new JpaTestController.OrmDatatypeMappingTestOutputVo(
                result.getSampleTinyInt().shortValue(),
                result.getSampleTinyIntUnsigned(),
                result.getSampleSmallInt(),
                result.getSampleSmallIntUnsigned(),
                result.getSampleMediumInt(),
                result.getSampleMediumIntUnsigned(),
                result.getSampleInt(),
                result.getSampleIntUnsigned(),
                result.getSampleBigInt(),
                result.getSampleBigIntUnsigned(),
                result.getSampleFloat(),
                result.getSampleFloatUnsigned(),
                result.getSampleDouble(),
                result.getSampleDoubleUnsigned(),
                result.getSampleDecimalP65S10(),
                result.getSampleDate().atStartOfDay().atZone(ZoneId.systemDefault())
                        .format(DateTimeFormatter.ofPattern("yyyy_MM_dd_z")),
                result.getSampleDateTime().atZone(ZoneId.systemDefault())
                        .format(DateTimeFormatter.ofPattern("yyyy_MM_dd_'T'_HH_mm_ss_SSS_z")),
                result.getSampleTime().format(DateTimeFormatter.ofPattern("HH_mm_ss_SSS")),
                result.getSampleTimestamp().atZone(ZoneId.systemDefault())
                        .format(DateTimeFormatter.ofPattern("yyyy_MM_dd_'T'_HH_mm_ss_SSS_z")),
                result.getSampleYear(),
                result.getSampleChar12(),
                result.getSampleVarchar12(),
                result.getSampleTinyText(),
                result.getSampleText(),
                result.getSampleMediumText(),
                result.getSampleLongText(),
                result.getSampleOneBit(),
                result.getSample6Bit().shortValue(),
                result.getSampleJson() == null ? null : result.getSampleJson().toString(),
                result.getSampleEnumAbc(),
                result.getSampleSetAbc(),
                new JpaTestController.OrmDatatypeMappingTestOutputVo.PointVo(
                        ((Point) result.getSampleGeometry()).getX(),
                        ((Point) result.getSampleGeometry()).getY()
                ),
                new JpaTestController.OrmDatatypeMappingTestOutputVo.PointVo(
                        result.getSamplePoint().getX(),
                        result.getSamplePoint().getY()
                ),
                new JpaTestController.OrmDatatypeMappingTestOutputVo.LinestringVo(
                        new JpaTestController.OrmDatatypeMappingTestOutputVo.PointVo(
                                result.getSampleLinestring().getStartPoint().getX(),
                                result.getSampleLinestring().getStartPoint().getY()
                        ),
                        new JpaTestController.OrmDatatypeMappingTestOutputVo.PointVo(
                                result.getSampleLinestring().getEndPoint().getX(),
                                result.getSampleLinestring().getEndPoint().getY()
                        )
                ),
                samplePolygonPoints,
                (short) (((result.getSampleBinary2()[0] & 0xFF) << 8) | (result.getSampleBinary2()[1] & 0xFF)),
                (short) (((result.getSampleVarbinary2()[0] & 0xFF) << 8) | (result.getSampleVarbinary2()[1] & 0xFF))
        );
    }


    // ----
    // (ORM Blob Datatype Mapping 테이블 Row 입력 테스트 API)
    @Transactional(transactionManager = Db1MainConfig.TRANSACTION_NAME)
    public void ormBlobDatatypeMappingTest(
            @NotNull HttpServletResponse httpServletResponse,
            @NotNull JpaTestController.OrmBlobDatatypeMappingTestInputVo inputVo
    ) throws IOException {
        @NotNull Db1_Template_DataTypeBlobMappingTest newDb1TemplateDataTypeBlobMappingTestRepository =
                db1TemplateDataTypeBlobMappingTestRepository.save(
                        Db1_Template_DataTypeBlobMappingTest.builder()
                                .sampleTinyBlobFileName(inputVo.getSampleTinyBlob().getOriginalFilename() != null
                                        ? inputVo.getSampleTinyBlob().getOriginalFilename()
                                        : "unknown")
                                .sampleTinyBlob(inputVo.getSampleTinyBlob().getBytes())
                                .sampleBlobFileName(inputVo.getSampleBlob().getOriginalFilename() != null
                                        ? inputVo.getSampleBlob().getOriginalFilename()
                                        : "unknown")
                                .sampleBlob(inputVo.getSampleBlob().getBytes())
                                .sampleMediumBlobFileName(inputVo.getSampleTinyBlob().getOriginalFilename() != null
                                        ? inputVo.getSampleTinyBlob().getOriginalFilename()
                                        : "unknown")
                                .sampleMediumBlob(inputVo.getSampleMediumBlob().getBytes())
                                .sampleLongBlobFileName(inputVo.getSampleTinyBlob().getOriginalFilename() != null
                                        ? inputVo.getSampleTinyBlob().getOriginalFilename()
                                        : "unknown")
                                .sampleLongBlob(inputVo.getSampleLongBlob().getBytes())
                                .build()
                );

        @NotNull LocalDateTime nowDatetime = LocalDateTime.now();

        // 파일 저장 기본 디렉토리 경로
        @NotNull Path saveDirectoryPath = Paths.get("./by_product_files/sample_jpa/blob_test").toAbsolutePath().normalize();

        // 파일 저장 기본 디렉토리 생성
        Files.createDirectories(saveDirectoryPath);

        @NotNull CustomUtil.FilePathParts sampleTinyBlobFileNameSplit =
                customUtil.splitFilePath(newDb1TemplateDataTypeBlobMappingTestRepository.getSampleTinyBlobFileName());
        Path tinyBlobDestinationFile = saveDirectoryPath.resolve(
                sampleTinyBlobFileNameSplit.getFileName() + "(" +
                        nowDatetime.atZone(ZoneId.systemDefault())
                                .format(DateTimeFormatter.ofPattern("yyyy_MM_dd_'T'_HH_mm_ss_SSS_z")) +
                        ")(1)." + sampleTinyBlobFileNameSplit.getExtension()
        );
        Files.write(tinyBlobDestinationFile, newDb1TemplateDataTypeBlobMappingTestRepository.getSampleTinyBlob());

        @NotNull CustomUtil.FilePathParts sampleBlobFileNameSplit =
                customUtil.splitFilePath(newDb1TemplateDataTypeBlobMappingTestRepository.getSampleBlobFileName());
        Path blobDestinationFile = saveDirectoryPath.resolve(
                sampleBlobFileNameSplit.getFileName() + "(" +
                        nowDatetime.atZone(ZoneId.systemDefault())
                                .format(DateTimeFormatter.ofPattern("yyyy_MM_dd_'T'_HH_mm_ss_SSS_z")) +
                        ")(2)." + sampleBlobFileNameSplit.getExtension()
        );
        Files.write(blobDestinationFile, newDb1TemplateDataTypeBlobMappingTestRepository.getSampleBlob());

        @NotNull CustomUtil.FilePathParts sampleMediumBlobFileNameSplit =
                customUtil.splitFilePath(newDb1TemplateDataTypeBlobMappingTestRepository.getSampleMediumBlobFileName());
        Path mediumBlobDestinationFile = saveDirectoryPath.resolve(
                sampleMediumBlobFileNameSplit.getFileName() + "(" +
                        nowDatetime.atZone(ZoneId.systemDefault())
                                .format(DateTimeFormatter.ofPattern("yyyy_MM_dd_'T'_HH_mm_ss_SSS_z")) +
                        ")(3)." + sampleMediumBlobFileNameSplit.getExtension()
        );
        Files.write(mediumBlobDestinationFile, newDb1TemplateDataTypeBlobMappingTestRepository.getSampleMediumBlob());

        @NotNull CustomUtil.FilePathParts sampleLongBlobFileNameSplit =
                customUtil.splitFilePath(newDb1TemplateDataTypeBlobMappingTestRepository.getSampleLongBlobFileName());
        Path longBlobDestinationFile = saveDirectoryPath.resolve(
                sampleLongBlobFileNameSplit.getFileName() + "(" +
                        nowDatetime.atZone(ZoneId.systemDefault())
                                .format(DateTimeFormatter.ofPattern("yyyy_MM_dd_'T'_HH_mm_ss_SSS_z")) +
                        ")(4)." + sampleLongBlobFileNameSplit.getExtension()
        );
        Files.write(longBlobDestinationFile, newDb1TemplateDataTypeBlobMappingTestRepository.getSampleLongBlob());
    }
}

 

 

테스트용 컨트롤러와 서비스 코드는 위와 같습니다.

 

보시다시피 단순히 앞서 정의한 JPA Entity 클래스로 데이터를 저장하는 예시인데,

API 를 통하여 외부에서 데이터를 받아와 저장할 때에는 위와 같은 방식으로 저장한다고 이해하시고 참고하시면 좋습니다.

 

Service 코드는 단순하게 2 단계의 기능을 수행합니다.

 

1. 클라이언트가 요청시 넣어준 값을 DB에 저장

2. DB에 저장된 데이터를 가져와서 클라이언트에 응답으로 보내줌

 

위와 같은 두가지 기능으로 인하여 ORM 이 제대로 매핑되어 입출력 되는지를 확인할 수 있습니다.

 

외부에서 파일을 받아서 BLOB 으로 저장하는 API 역시 마찬가지로, byte array 로 DB 에 데이터를 저장하고, 이렇게 저장된 데이터를 가져와서 로컬에 파일로 저장을 하는 것으로 DB 파일 입출력 방식을 알 수 있습니다.

 

- 이상입니다.

참고로, JPA Entity 작성 방식, Controller 정의 방식, java 변수의 Null 체크 처리 등의 소소한 제 노하우도 공개해 드렸으므로, 본인의 코딩 스타일에 맞다고 판단된다면 참고하여 사용하시는 것도 좋을 것 같습니다.

저작자표시 비영리 변경금지 (새창열림)

'Programming > BackEnd' 카테고리의 다른 글

FastAPI 비동기 처리 주의사항  (0) 2025.04.28
FastAPI DataBase ORM 사용 방법 정리 (SQLAlchemy, 비동기 처리, Transactional 처리, 페이징 처리)  (0) 2025.04.27
서버 부하 테스트 - Locust 사용 (FastAPI, Springboot 비디오 스트리밍 성능 비교), FastAPI Media Streaming 코드 수록  (0) 2025.04.23
분산 소켓 서버 설명 및 구현(Springboot, SockJS, STOMP, Kafka, Redis, Javascript)  (0) 2025.03.28
Springboot Kafka Json Value 매핑하기  (3) 2024.10.28
'Programming/BackEnd' 카테고리의 다른 글
  • FastAPI 비동기 처리 주의사항
  • FastAPI DataBase ORM 사용 방법 정리 (SQLAlchemy, 비동기 처리, Transactional 처리, 페이징 처리)
  • 서버 부하 테스트 - Locust 사용 (FastAPI, Springboot 비디오 스트리밍 성능 비교), FastAPI Media Streaming 코드 수록
  • 분산 소켓 서버 설명 및 구현(Springboot, SockJS, STOMP, Kafka, Redis, Javascript)
Railly Linker
Railly Linker
IT 지식 정리 및 공유 블로그
Railly`s IT 정리노트IT 지식 정리 및 공유 블로그
  • Railly Linker
    Railly`s IT 정리노트
    Railly Linker
  • 전체
    오늘
    어제
  • 공지사항

    • 분류 전체보기 (116)
      • Programming (34)
        • BackEnd (17)
        • FrontEnd (4)
        • DBMS (1)
        • ETC (12)
      • Study (81)
        • Computer Science (21)
        • Data Science (22)
        • Computer Vision (20)
        • NLP (15)
        • ETC (3)
      • Error Note (1)
      • ETC (0)
  • 인기 글

  • 최근 글

  • 최근 댓글

  • 태그

    localhost
    list
    docker 배포
    unique
    kotlin linkedlist
    MacOS
    springboot 배포
    단축키
    데이터베이스 제약
    지리 정보
    network_mode: "host"
    jvm 메모리 누수
    논리적 삭제
    kotlin arraylist
    Kotlin
    docker compose
    kotlin mutablelist
  • 링크

    • RaillyLinker Github
  • hELLO· Designed By정상우.v4.10.0
Railly Linker
Springboot JPA Entity 변수 매핑 정리 (Java, Kotlin)
상단으로

티스토리툴바

단축키

내 블로그

내 블로그 - 관리자 홈 전환
Q
Q
새 글 쓰기
W
W

블로그 게시글

글 수정 (권한 있는 경우)
E
E
댓글 영역으로 이동
C
C

모든 영역

이 페이지의 URL 복사
S
S
맨 위로 이동
T
T
티스토리 홈 이동
H
H
단축키 안내
Shift + /
⇧ + /

* 단축키는 한글/영문 대소문자로 이용 가능하며, 티스토리 기본 도메인에서만 동작합니다.