1108 - 1114


# 1108 - 1114

# 1108 - ssl

# SSL์ด๋ž€?

SSL(Secure Socket Layer)์€ ์•”ํ˜ธํ™” ๊ธฐ๋ฐ˜ ์ธํ„ฐ๋„ท ๋ณด์•ˆ ํ”„๋กœํ† ์ฝœ์ด๋‹ค. ์ธํ„ฐ๋„ท ํ†ต์‹ ์˜ ๊ฐœ์ธ์ •๋ณด ๋ณดํ˜ธ, ์ธ์ฆ, ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ์„ ๋ณด์žฅํ•˜๊ธฐ ์œ„ํ•ด Netscape๊ฐ€ 1995๋…„ ์ฒ˜์Œ์œผ๋กœ ๊ฐœ๋ฐœํ–ˆ๋‹ค. SSL์€ ํ˜„์žฌ ์‚ฌ์šฉ ์ค‘์ธ TLS ์•”ํ˜ธํ™”์˜ ์ „์‹ ์ด๋‹ค.

SSL/TLS๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ์›น์‚ฌ์ดํŠธ์˜ URL์—๋Š” "HTTP"๋Œ€์‹  "HTTPS"๊ฐ€ ์žˆ๋‹ค.

# SSL/TLS ์ž‘๋™์›๋ฆฌ

  • SSL์€ ๋†’์€ ์ˆ˜์ค€์˜ ๊ฐœ์ธ์ •๋ณด ๋ณดํ˜ธ๋ฅผ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด, ์›น์—์„œ ์ „์†ก๋˜๋Š” ๋ฐ์ดํ„ฐ๋ฅผ ์•”ํ˜ธํ™”ํ•œ๋‹ค. ๋”ฐ๋ผ์„œ, ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€๋กœ์ฑ„๋ ค๋Š” ์ž๋Š” ๊ฑฐ์˜ ํ•ด๋…ํ•  ์ˆ˜ ์—†๋Š” ๋ณต์žกํ•œ ๋ฌธ์ž๋งŒ ๋ณด๊ฒŒ ๋œ๋‹ค.
  • SSL์€ ๋‘ ํ†ต์‹  ์žฅ์น˜ ์‚ฌ์ด์— ํ•ธ๋“œ์…ฐ์ดํฌ๋ผ๋Š” ์ธ์ฆ ํ”„๋กœ์„ธ์Šค๋ฅผ ์‹œ์ž‘ํ•˜์—ฌ ๋‘ ์žฅ์น˜์˜ ID๋ฅผ ํ™•์ธํ•œ๋‹ค.
  • SSL์€ ๋˜ํ•œ ๋ฐ์ดํ„ฐ ๋ฌด๊ฒฐ์„ฑ์„ ์ œ๊ณตํ•˜๊ธฐ ์œ„ํ•ด ๋ฐ์ดํ„ฐ์— ๋””์ง€ํ„ธ ์„œ๋ช…ํ•˜์—ฌ ๋ฐ์ดํ„ฐ๊ฐ€ ์˜๋„๋œ ์ˆ˜์‹ ์ž์— ๋„์ฐฉํ•˜๊ธฐ ์ „์— ์กฐ์ž‘๋˜์ง€ ์•Š์•˜๋‹ค๋Š” ๊ฒƒ์„ ํ™•์ธํ•˜๋‹ค.

SSL์€ ์—ฌ๋Ÿฌ ๋ฒˆ ๊ฐœ์„ ๋˜์–ด ๋งค๋ฒˆ ์„ฑ๋Šฅ์ด ๊ฐœ์„ ๋˜์—ˆ์œผ๋ฉฐ, 1999๋…„์— SSL์€ TLS๋กœ ์—…๋ฐ์ดํŠธ ๋๋‹ค.

# SSL/TLS์˜ ์ค‘์š”์„ฑ

  • ์˜ˆ, ์ฃผ๋ฌธ ๊ณผ์ •์—์„œ ๊ณ ๊ฐ์˜ ์‹ ์šฉ ์นด๋“œ ๋ฒˆํ˜ธ๊ฐ€ ์‚ฌ์šฉ๋์„๋–„ ํƒˆ์ทจ์ž์—๊ฒŒ ๋…ธ์ถœ๋˜๊ฒŒ ๋œ๋‹ค.
  • ๋˜ํ•œ SSL์€ ํŠน์ •ํ•œ ์œ ํ˜•์˜ ์‚ฌ์ด๋ฒ„ ๊ณต๊ฒฉ๋„ ์ฐจ๋‹จํ•œ๋‹ค.

# SSL/TLS์˜ ์ฐจ์ด

  • SSL์€ TLS(Transport Layer Security)๋ผ๋Š” ๋˜ ๋‹ค๋ฅธ ํ”„๋กœํ† ์ฝœ์˜ ๋ฐ”๋กœ ์ด์ „ ๋ฒ„์ „์ด๋‹ค. 1999๋…„ IETF๋Š” SSL์— ๋Œ€ํ•œ ์—…๋ฐ์ดํŠธ๋ฅผ ์ œ์•ˆํ–ˆ๊ณ , Netscape๊ฐ€ ๋” ์ด์ƒ ์ฐธ์—ฌํ•˜์ง€ ์•Š๊ฒŒ ๋˜๋ฉด์„œ, ์ด๋ฆ„์ด TLS๋กœ ๋ณ€๊ฒฝ๋˜์—ˆ๋‹ค.

# SSL์ธ์ฆ์ด๋ž€?

SSL์€ SSL ์ธ์ฆ์„œ๊ฐ€ ์žˆ๋Š” ์›น์‚ฌ์ดํŠธ๋งŒ ์‹คํ–‰ํ•  ์ˆ˜ ์žˆ๋‹ค. SSL ์ธ์ฆ์„œ๋Š” ์›น์‚ฌ์ดํŠธ๋‚˜ ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜ ์„œ๋ฒ„๊ฐ€ ์›น์— ์ €์žฅํ•˜๊ณ  ํ‘œ์‹œํ•œ๋‹ค.

SSL ์ธ์ฆ์„œ์— ํฌํ•จ๋œ ๊ฐ€์žฅ ์ค‘์š”ํ•œ ์ •๋ณด์— ์›น์‚ฌ์ดํŠธ์˜ ๊ณต๊ฐœ ํ‚ค๊ฐ€ ์žˆ๋‹ค. ์ด ๊ณต๊ฐœ ํ‚ค ๋•๋ถ์— ์•”ํ˜ธํ™”๊ฐ€ ๊ฐ€๋Šฅํ•˜๋ฉฐ, ์‚ฌ์šฉ์ž์˜ ์žฅ์น˜๋Š” ๊ณต๊ฐœ ํ‚ค๋ฅผ ๋ณด๊ณ  ์ด๋ฅผ ์ด์šฉํ•˜์—ฌ ์›น ์„œ๋ฒ„์™€ ์•ˆ์ „ํ•œ ์•”ํ˜ธํ™” ํ‚ค๋ฅผ ์ˆ˜๋ฆฝํ•œ๋‹ค. ํ•œํŽธ, ์›น ์„œ๋ฒ„์—๋„ ๊ธฐ๋ฐ€๋กœ ์œ ์ง€ํ•˜๋Š” ๊ฐœ์ธ ํ‚ค๊ฐ€ ์žˆ๋‹ค. ๊ฐœ์ธ ํ‚ค๋Š” ๊ณต๊ฐœ ํ‚ค๋กœ ์•”ํ˜ธํ™”๋œ ๋ฐ์ดํ„ฐ๋ฅผ ํ•ด๋…ํ•œ๋‹ค. CA(์ธ์ฆ ๊ธฐ๊ด€)๋Š” SSL ์ธ์ฆ์„œ ๋ฐœํ–‰์„ ๋‹ด๋‹นํ•œ๋‹ค.

# SSL ์ธ์ฆ์„œ ์œ ํ˜•

  • ๋‹จ์ผ ๋„๋ฉ”์ธ
  • ์™€์ผ๋“œ์นด๋“œ
  • ๋ฉ€ํ‹ฐ ๋„๋ฉ”์ธ
  • ๋„๋ฉ”์ธ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
  • ์กฐ์ง ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ
  • ํ™•์žฅ ์œ ํšจ์„ฑ ๊ฒ€์‚ฌ

# 1110 - MapStruct(๋งต์ŠคํŠธ๋ŸญํŠธ)

Model mapping์„ ์•„์ฃผ ์‰ฝ๊ฒŒ ํ•ด์ฃผ๋Š” ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ.

# ๋งต์ŠคํŠธ๋ŸญํŠธ ์‚ฌ์šฉ๋ฒ•

# build.gradle

buildscript {
	ext {
		mapstructVersion = '1.3.1.Final'
	}
}

...

// Mapstruct
implementation "org.mapstruct:mapstruct:${mapstructVersion}"
annotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"
testAnnotationProcessor "org.mapstruct:mapstruct-processor:${mapstructVersion}"

...

compileJava {
	options.compilerArgs = [
			'-Amapstruct.suppressGeneratorTimestamp=true',
			'-Amapstruct.suppressGeneratorVersionInfoComment=true'
	]
}

# java

@Data
@AllArgsConstructor(staticName = "of")
@NoArgsConstructor
public class CarDto {
    private String name;
    private String color;
}
 
---

@Data
@AllArgsConstructor(staticName = "of")
@NoArgsConstructor
public class Car {
    private String modelName;
    private String modelColor;
}
 
---

@Mapper
public interface CarMapper {
 
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
 
    @Mapping(source = "name", target = "modelName")
    @Mapping(source = "color", target = "modelColor")
    Car to(CarDto carDto);
}

๊ฐ๊ฐ dto, domain, mapper

public class MapstructTest {
 
    @Test
    public void test() {
        CarDto carDto = CarDto.of("bmw x4", "black");
        Car car = CarMapper.INSTANCE.to(carDto);
 
        assertEquals(carDto.getName(), car.getModelName());
        assertEquals(carDto.getColor(), car.getModelColor());
    }
}

๊ฐ„๋‹จํ•œ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ

  • ์‹ค์ œ ์ƒ์„ฑ๋œ ์ฝ”๋“œ
@Generated(
    value = "org.mapstruct.ap.MappingProcessor"
)
public class CarMapperImpl implements CarMapper {
 
    @Override
    public Car to(CarDto carDto) {
        if ( carDto == null ) {
            return null;
        }
 
        Car car = new Car();
 
        car.setModelName( carDto.getName() );
        car.setModelColor( carDto.getColor() );
 
        return car;
    }
}

# ํ•˜๋‚˜์˜ ๊ฐ์ฒด๋กœ ํ•ฉ์น˜๊ธฐ

  • ์—ฌ๋Ÿฌ ๊ฐ์ฒด์˜ ํ•„๋“œ๊ฐ’์„ ํ•˜๋‚˜์˜ ๊ฐ์ฒด๋กœ ํ•ฉ์น˜๊ธฐ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค. ํŠน๋ณ„ํžˆ ๋‹ค๋ฅธ ์˜ต์…˜์„ ๋„ฃ๋Š” ๊ฒƒ์€ ์•„๋‹ˆ๋‹ค.
@Data
@AllArgsConstructor(staticName = "of")
@NoArgsConstructor
public class UserDto {
    private String name;
}

--- 

@Data
@AllArgsConstructor(staticName = "of")
@NoArgsConstructor
public class AddressDto {
    private String si;
    private String dong;
}
 
---

@Mapper
public interface UserInfoMapper {
 
    UserInfoMapper INSTANCE = Mappers.getMapper(UserInfoMapper.class);
 
    @Mapping(source = "user.name", target = "userName")
    @Mapping(source = "address.si", target = "si")
    @Mapping(source = "address.dong", target = "dong")
    UserInfo to(UserDto user, AddressDto address);
}
 
--- 

@Generated(
    value = "org.mapstruct.ap.MappingProcessor"
)
public class UserInfoMapperImpl implements UserInfoMapper {
 
    @Override
    public UserInfo to(UserDto user, AddressDto address) {
        if ( user == null && address == null ) {
            return null;
        }
 
        UserInfo userInfo = new UserInfo();
 
        if ( user != null ) {
            userInfo.setUserName( user.getName() );
        }
        if ( address != null ) {
            userInfo.setDong( address.getDong() );
            userInfo.setSi( address.getSi() );
        }
 
        return userInfo;
    }
}

# ์ด๋ฏธ ์ƒ์„ฑ๋œ ๊ฐ์ฒด์— ๋งคํ•‘

  • ์ƒˆ๋กœ์šด ์ธ์Šคํ„ด์Šค๋ฅผ ์ƒ์„ฑํ•˜๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ, ๊ธฐ์กด์— ์ด๋ฏธ ์ƒ์„ฑ๋˜์–ด ์žˆ๋Š” ๊ฐ์ฒด์— ๋งคํ•‘์ด ํ•„์š”ํ•œ ๊ฒฝ์šฐ
@Mapper
public interface UserInfoMapper {
 
    UserInfoMapper INSTANCE = Mappers.getMapper(UserInfoMapper.class);
 
    @Mapping(source = "user.name", target = "userName")
    @Mapping(source = "address.si", target = "si")
    @Mapping(source = "address.dong", target = "dong")
    void write(UserDto user, AddressDto address, @MappingTarget UserInfo userInfo);
}
 
@Generated(
    value = "org.mapstruct.ap.MappingProcessor"
)
public class UserInfoMapperImpl implements UserInfoMapper {
 
    @Override
    public void write(UserDto user, AddressDto address, UserInfo userInfo) {
        if ( user == null && address == null ) {
            return;
        }
 
        if ( user != null ) {
            userInfo.setUserName( user.getName() );
        }
        if ( address != null ) {
            userInfo.setDong( address.getDong() );
            userInfo.setSi( address.getSi() );
        }
    }
}

# ํƒ€์ž… ๋ณ€ํ™˜

  • ๋Œ€๋ถ€๋ถ„์˜ ์•”์‹œ์ ์ธ ์ž๋™ ํ˜•๋ณ€ํ™˜์ด ๊ฐ€๋Šฅํ•˜๋‹ค. (Integer -> String ...) ๋‹ค์Œ์€ ์กฐ๊ธˆ ์œ ์šฉํ•œ ๊ธฐ๋Šฅ์ด๋‹ค.
@Mapper
public interface CarMapper {
 
    @Mapping(source = "price", numberFormat = "$#.00")
    CarDto carToCarDto(Car car);
 
    @IterableMapping(numberFormat = "$#.00")
    List<String> prices(List<Integer> prices);
}

# Source, Target mapping policy

  • ๋งคํ•‘๋  ํ•„๋“œ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š์„ ๋•Œ, ์—„๊ฒฉํ•œ ์ •์ฑ…์„ ๊ฐ€์ ธ๊ฐ€๊ธฐ ์œ„ํ•œ ๊ธฐ๋Šฅ์„ ์ œ๊ณตํ•œ๋‹ค.
@Data
@AllArgsConstructor(staticName = "of")
@NoArgsConstructor
public class Car {
    private String modelName;
    private String modelColor;
    private String modelPrice;
    private String description;
}
 
@Data
@AllArgsConstructor(staticName = "of")
@NoArgsConstructor
public class CarDto {
    private String name;
    private String color;
    private Integer price;
}
 
@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR)
public interface CarMapper {
 
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
 
    @Mapping(source = "name", target = "modelName")
    @Mapping(source = "color", target = "modelColor")
    @Mapping(source = "price", target = "modelPrice", numberFormat = "$#.00")
    Car to(CarDto carDto);
 
}

์œ„ ์ฝ”๋“œ๋Š” ํƒ€๊ฒŸ์ด ๋˜๋Š” ์˜ค๋ธŒ์ ํŠธ ํ•„๋“œ์— ๋Œ€ํ•œ ์ •์ฑ…์„ ๊ฐ€์ ธ๊ฐ„๋‹ค. Car ํด๋ž˜์Šค์—๋Š” description ํ•„๋“œ๊ฐ€ ์žˆ๋Š”๋ฐ, CarDto ํด๋ž˜์Šค์—๋Š” ํ•ด๋‹น ํ•„๋“œ๊ฐ€ ์กด์žฌํ•˜์ง€ ์•Š๊ธฐ ๋•Œ๋ฌธ์— ์ปดํŒŒ์ผ์‹œ ์ปดํŒŒ์ผ์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ•œ๋‹ค.(ERROR, IGNORE, WARN ์ •์ฑ… ์กด์žฌ) ๋งŒ์•ฝ ํŠน์ • ํ•„๋“œ๋Š” ํ•ด๋‹น ์ •์ฑ…์„ ํ”ผํ•˜๊ณ  ์‹ถ๋‹ค๋ฉด ์•„๋ž˜์™€ ๊ฐ™์ด ์–ด๋…ธํ…Œ์ด์…˜ํ•˜๋‚˜๋ฅผ ๋‹ฌ์•„์ค€๋‹ค.

@Mapper(unmappedTargetPolicy = ReportingPolicy.ERROR)
public interface CarMapper {
 
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
 
    @Mapping(source = "name", target = "modelName")
    @Mapping(source = "color", target = "modelColor")
    @Mapping(source = "price", target = "modelPrice", numberFormat = "$#.00")
    @Mapping(target = "description", ignore = true)
    Car to(CarDto carDto);
 
}

# null ์ •์ฑ…

  • Source๊ฐ€ null์ด๊ฑฐ๋‚˜ ํ˜น์€ Source์˜ ํŠน์ • ํ•„๋“œ๊ฐ€ null์ผ๋•Œ ์ ์šฉ๊ฐ€๋Šฅํ•œ ์ •์ฑ…์ด ์กด์žฌํ•œ๋‹ค.
@Mapper(
        unmappedTargetPolicy = ReportingPolicy.ERROR,
        nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT
)
public interface CarMapper {
 
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
 
    @Mapping(source = "name", target = "modelName")
    @Mapping(source = "color", target = "modelColor")
    @Mapping(source = "price", target = "modelPrice", numberFormat = "$#.00")
    @Mapping(target = "description", ignore = true)
    Car to(CarDto carDto);
 
}

์œ„ ์ฝ”๋“œ๋Š” Source ์˜ค๋ธŒ์ ํŠธ๊ฐ€ null์ผ๋•Œ, ๊ธฐ๋ณธ์ƒ์„ฑ์ž๋กœ ํ•„๋“œ๊ฐ€ ๋น„์–ด์žˆ๋Š” Target ์˜ค๋ธŒ์ ํŠธ๋ฅผ ๋ฐ˜ํ™˜ํ•ด์ค€๋‹ค.

@Mapper(
        unmappedTargetPolicy = ReportingPolicy.ERROR,
        nullValueMappingStrategy = NullValueMappingStrategy.RETURN_NULL
)
public interface CarMapper {
 
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
 
    @Mapping(source = "name", target = "modelName")
    @Mapping(source = "color", target = "modelColor")
    @Mapping(source = "price", target = "modelPrice", numberFormat = "$#.00")
    @Mapping(
            source = "description", 
            target = "description", 
            ignore = true,
            nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.SET_TO_DEFAULT
    )
    Car to(CarDto carDto);
 
}

์œ„ ์ฝ”๋“œ๋Š” ๊ฐ ํ•„๋“œ์— ๋Œ€ํ•ด null์ •์ฑ…์„ ๋ถ€์—ฌํ•œ๋‹ค. ๋งŒ์•ฝ SET_TO_DEFAULT๋กœ ์„ค์ •ํ•˜๋ฉด, List ์ผ๋•Œ๋Š” ๋นˆ ArrayList๋ฅผ ์ƒ์„ฑํ•ด์ฃผ๊ณ , String์€ ๋นˆ๋ฌธ์ž์—ด, ํŠน์ • ์˜ค๋ธŒ์ ํŠธ๋ผ๋ฉด ํ•ด๋‹น ์˜ค๋ธŒ์ ํŠธ์˜ ๊ธฐ๋ณธ ์ƒ์„ฑ์ž ๋“ฑ์œผ๋กœ ๊ธฐ๋ณธ๊ฐ’์„ ์ƒ์„ฑํ•ด์ค€๋‹ค.

# ํŠน์ • ํ•„๋“œ ๋งคํ•‘ ๋ฌด์‹œ

  • ํŠน์ • ํ•„๋“œ๋Š” ๋งคํ•‘๋˜์ง€ ์•Š๊ธธ ์›ํ•œ๋‹ค๋ฉด @Mapping ์–ด๋…ธํ…Œ์ด์…˜์— ignore = true ์†์„ฑ์„ ๋„ฃ์–ด์ค€๋‹ค.
@Mapper(
        unmappedTargetPolicy = ReportingPolicy.ERROR,
        nullValueMappingStrategy = NullValueMappingStrategy.RETURN_NULL
)
public interface CarMapper {
 
    CarMapper INSTANCE = Mappers.getMapper(CarMapper.class);
 
    @Mapping(source = "name", target = "modelName")
    @Mapping(source = "color", target = "modelColor")
    @Mapping(source = "price", target = "modelPrice", numberFormat = "$#.00")
    @Mapping(target = "description", ignore = true)
    Car to(CarDto carDto);
 
}
 
public class MapstructTest {
 
    @Test
    public void test() {
        CarDto carDto = CarDto.of(
                "bmw x4",
                "black",
                10000,
                "description");
        Car car = CarMapper.INSTANCE.to(carDto);
        System.out.println(car.toString());
    }
}
 
result =>
 
Car(modelName=bmw x4, modelColor=black, modelPrice=$10000.00, description=null)

# ๋งคํ•‘ ์ „์ฒ˜๋ฆฌ, ํ›„์ฒ˜๋ฆฌ

  • ๋งคํ•‘ํ•˜๊ธฐ ์ด์ „๊ณผ ๋งคํ•‘ ์ดํ›„ ํŠน์ • ๋กœ์ง์„ ์ฃผ์ž…์‹œํ‚ฌ ์ˆ˜ ์žˆ๋‹ค.
@Mapper(
        unmappedTargetPolicy = ReportingPolicy.ERROR,
        nullValueMappingStrategy = NullValueMappingStrategy.RETURN_NULL,
        componentModel = "spring"
)
public abstract class CarMapper {
 
    @BeforeMapping
    protected void setColor(CarDto carDto, @MappingTarget Car car) {
        if (carDto.getName().equals("bmw x4")) {
            car.setModelColor("red");
        } else {
            car.setModelColor("black");
        }
 
    }
 
    @Mapping(source = "name", target = "modelName")
    @Mapping(target = "modelColor", ignore = true)
    @Mapping(source = "price", target = "modelPrice", numberFormat = "$#.00")
    public abstract Car to(CarDto carDto);
 
    @AfterMapping
    protected void setDescription(@MappingTarget Car car) {
        car.setDescription(car.getModelName() + " " + car.getModelColor());
    }
}
 
<Generate Code>
 
@Generated(
    value = "org.mapstruct.ap.MappingProcessor"
)
@Component
public class CarMapperImpl extends CarMapper {
 
    @Override
    public Car to(CarDto carDto) {
        if ( carDto == null ) {
            return null;
        }
 
        Car car = new Car();
 
        setColor( carDto, car );
 
        car.setModelName( carDto.getName() );
        if ( carDto.getPrice() != null ) {
            car.setModelPrice( new DecimalFormat( "$#.00" ).format( carDto.getPrice() ) );
        }
        car.setDescription( carDto.getDescription() );
 
        setDescription( car );
 
        return car;
    }
}

์ „์ฒ˜๋ฆฌ์™€ ํ›„์ฒ˜๋ฆฌ๋ฅผ ์œ„ํ•œ ๋ฉ”์„œ๋“œ๋Š” private๋ฅผ ์‚ฌ์šฉํ•ด์„œ๋Š” ์•ˆ๋œ๋‹ค. ๊ทธ ์ด์œ ๋Š” generate๋œ ์ฝ”๋“œ์— ์ „, ํ›„ ์ฒ˜๋ฆฌ ๋ฉ”์„œ๋“œ๊ฐ€ ๋“ค์–ด๊ฐ€๋Š” ๊ฒƒ์ด ์•„๋‹ˆ๋ผ ์ถ”์ƒ ํด๋ž˜์Šค์— ์žˆ๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ๊ทธ๋Œ€๋กœ ์‚ฌ์šฉํ•˜๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.

  • ๊ณต์‹ ๋ ˆํผ๋Ÿฐ์Šค https://mapstruct.org/documentation/stable/reference/html/

# 1112 - custom exception ์žฅ๋‹จ์ 

# ํ‘œ์ค€ ์˜ˆ์™ธ ์‚ฌ์šฉ์‹œ ์žฅ์ 

  1. ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€๋กœ๋„ ์ถฉ๋ถ„ํžˆ ์˜๋ฏธ๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์œ ํšจํ•˜์ง€ ์•Š์€ ์ž…๋ ฅ๊ฐ’์— ๋Œ€ํ•œ ์˜ˆ์™ธ ๊ฐ™์€ ๊ฒฝ์šฐ ์ž๋ฐ”์—์„œ ์ •์˜ํ•ด ๋†“์€ IllegalArgumentException์„ ์‚ฌ์šฉํ•˜๊ณ  ๋ฉ”์‹œ์ง€๋งŒ ์˜ˆ์™ธ์‚ฌํ•ญ์— ๋งž๊ฒŒ ์žฌ์ •์˜ํ•ด์ค€๋‹ค๋ฉด ์ถฉ๋ถ„ํžˆ ๊ทธ ์˜๋ฏธ๋ฅผ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๋‹ค.
  1. ํ‘œ์ค€ ์˜ˆ์™ธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๊ฐ€๋…์„ฑ์ด ๋†’์•„์ง„๋‹ค.
  • ์ธ์ˆ˜๋กœ ๋ถ€์ ์ ˆํ•œ ๊ฐ’์ด ๋“ค์–ด์˜ฌ ๋•Œ ๋˜์ง€๋Š” ์˜ˆ์™ธ์ธ IllegalArgumentException, ์ผ์„ ์ˆ˜ํ–‰ํ•˜๊ธฐ์— ์ ํ•ฉํ•˜์ง€ ์•Š์€ ์ƒํƒœ์˜ ๊ฐ์ฒด์ธ ๊ฒฝ์šฐ ๋˜์ง€๋Š” ์˜ˆ์™ธ์ธ IllegalStateException, ์š”์ฒญ๋ฐ›์€ ์ž‘์—…์„ ์ง€์›ํ•˜์ง€ ์•Š๋Š” ๊ฒฝ์šฐ์— ๋˜์ง€๋Š” ์˜ˆ์™ธ์ธ UnsupportedOperationException ๋“ฑ, ์šฐ๋ฆฌ๋Š” ์ด๋ฏธ ์ต์ˆ™ํ•˜๊ณ , ์“ฐ์ž„์— ๋Œ€ํ•ด ์ž˜ ์•Œ๊ณ ์žˆ๋Š” ์˜ˆ์™ธ๋“ค์ด ๋งŽ๋‹ค.
    ์ด๋Ÿฐ ์˜ˆ์™ธ๋“ค์ด ์•„๋‹Œ ์ฒ˜์Œ ๋ณด๋Š” ์˜ˆ์™ธ๋“ค์€ ๋‹น์—ฐํžˆ ๊ตฌ์ฒด์ ์ธ ์“ฐ์ž„์„ ์ž˜ ๋ชจ๋ฅธ๋‹ค. ์ด๋Ÿฐ ์ด์œ ๋กœ ๋‚ฏ์„  ์˜ˆ์™ธ๋ณด๋‹ค๋Š” ์ต์ˆ™ํ•œ ์˜ˆ์™ธ๋ฅผ ๋งˆ์ฃผ์น˜๋Š” ๊ฒƒ์ด ๋‹น์—ฐํžˆ ๊ฐ€๋™์„ฑ์ด ๋†’์„ ์ˆ˜ ๋ฐ–์— ์—†๋‹ค.
    ๋˜ํ•œ, ๋‚ฏ์„  ์˜ˆ์™ธ๋ฅผ ๋งŒ๋‚ฌ์„ ๋•, ๋‹น์—ฐํ•˜๊ฒŒ๋„ ๊ทธ ์ปค์Šคํ…€ ์ต์…‰์…˜์„ ํŒŒ์•…ํ•˜๋Š” ์ž‘์—…์ด ๋”ฐ๋ผ์˜จ๋‹ค. ์ด ๋˜ํ•œ ๋น„์šฉ์ด ๋  ์ˆ˜ ์žˆ๋‹ค.
    ํ‘œ์ค€ ์˜ˆ์™ธ์— ๋Œ€ํ•œ ์“ฐ์ž„์€ ๊ณต์‹๋ฌธ์„œ๋ฅผ ์ฐธ๊ณ ํ•˜๋ฉด ๋œ๋‹ค. https://docs.oracle.com/javase/8/docs/api/?java/lang/RuntimeException.html
  1. ์ผ์ผํžˆ ์˜ˆ์™ธ ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“ค๋‹ค๋ณด๋ฉด ์ง€๋‚˜์น˜๊ฒŒ ์ปค์Šคํ…€ ์˜ˆ์™ธ๊ฐ€ ๋งŽ์•„์งˆ ์ˆ˜ ์žˆ๋‹ค.
  • ๋งŽ์€ domain์ด ์ƒ๊ธฐ๊ณ  ๋„๋ฉ”์ธ ๋ณ„ ์ •์˜๋œ custom Exception๋“ค์ด ์ƒ๊ธธ ๊ฒฝ์šฐ ์ง€๋‚˜์น˜๊ฒŒ ๋งŽ์•„์งˆ ์ˆ˜ ์žˆ๋‹ค. ์ด ๋””๋ ‰ํ† ๋ฆฌ์™€ ํด๋ž˜์Šค๋ฅผ ๊ด€๋ฆฌํ•˜๋Š” ๊ฒƒ ์—ญ์‹œ ์ผ์ด๋ฉฐ, ์ง€๋‚˜์น˜๊ฒŒ ๋งŽ์•„์ง„๋‹ค๋ฉด ๋ฉ”๋ชจ๋ฆฌ ๋ฌธ์ œ๋„ ๋ฐœ์ƒํ•  ์ˆ˜ ์žˆ๊ณ , ํด๋ž˜์Šค ๋กœ๋”ฉ์—๋„ ์‹œ๊ฐ„์ด ๋” ์†Œ์š”๋  ๊ฐ€๋Šฅ์„ฑ์ด ์žˆ๋‹ค.

# ์ปค์Šคํ…€ ์˜ˆ์™ธ ์‚ฌ์šฉ์‹œ ์žฅ์ 

์‚ฌ์šฉ์ž ์ •์˜ ์˜ˆ์™ธ๊ฐ€ ํ•„์š”ํ•˜๋‹ค.

  • Spring์˜ @Valid ์–ด๋…ธํ…Œ์ด์…˜์€ ์˜ˆ์™ธ ์ƒํ™ฉ์—์„œ MethodArgumentNotValidException์„ ๋ฐœ์ƒ์‹œํ‚จ๋‹ค. ํ•ด๋‹น ์˜ˆ์™ธ์˜ ํŒจํ‚ค์ง€ ๊ตฌ์กฐ๋Š” org.springframework.web.bind.MethodArgumentNotValidException ์œผ๋กœ Spring์—์„œ ๋งŒ๋“  ์‚ฌ์šฉ์ž ์ •์˜ ์˜ˆ์™ธ๋‹ค.
  1. ์ด๋ฆ„์œผ๋กœ๋„ ์ •๋ณด ์ „๋‹ฌ์ด ๊ฐ€๋Šฅํ•˜๋‹ค.
  • NoSuchElementException ๋งŒ์œผ๋กœ๋Š” ์–ด๋–ค ์š”์†Œ๊ฐ€ ์—†๋Š”์ง€ ์•Œ ์ˆ˜ ์—†๋‹ค. ํ•˜์ง€๋งŒ, PostNotFoundException์ด ๋ฐœ์ƒํ–ˆ๋”ฐ๋ฉด, Post๋ฅผ ์ฐพ๋Š” ์š”์ฒญ์„ ๋ณด๋ƒˆ์ง€๋งŒ ํ•ด๋‹น ์š”์†Œ๊ฐ€ ์—†๋‹ค๋Š” ์ƒํ™ฉ์„ ์œ ์ถ”ํ•  ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค.
    ์ด์ฒ˜๋Ÿผ Custom Exception์€ ์ด๋ฆ„์„ ํ†ตํ•ด ์ผ์ฐจ์ ์œผ๋กœ ์˜ˆ์™ธ ๋ฐœ์ƒ ์ƒํ™ฉ์— ๋Œ€ํ•ด ์œ ์ถ”ํ•  ์ˆ˜ ์žˆ๋Š” ์ •๋ณด๋ฅผ ์ œ๊ณตํ•œ๋‹ค.
  1. ์ƒ์„ธํ•œ ์˜ˆ์™ธ ์ •๋ณด๋ฅผ ์ œ๊ณตํ•  ์ˆ˜ ์žˆ๋‹ค.
  • ์ปฌ๋ ‰์…˜์˜ ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚œ index ์ ‘๊ทผ ์š”์ฒญ์ด ์ƒ๊ฒผ๋‹ค๊ณ  ๊ฐ€์ •ํ–ˆ์„๋•Œ, ๊ธฐ์กด์˜ ์˜ˆ์™ธ์—์„  IllegalArgumentException์ด๋‚˜ IndexOutOfBoundsException์„ ํ›„๋ณด๋กœ ์ƒ๊ฐํ•ด๋ณผ ์ˆ˜ ์žˆ์„ ๊ฒƒ์ด๋‹ค. ์˜ˆ์™ธ ๋ฉ”์‹œ์ง€๋กœ๋Š” "๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚ฌ์Šต๋‹ˆ๋‹ค." ์ •๋„๋ฉด ์ ๋‹นํ•˜๋‹ค.
if (index >= arr.length) {
  throw new IndexOutOfBoundsException("๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚ฌ์Šต๋‹ˆ๋‹ค.");
}
  • ํ•˜์ง€๋งŒ ์ „์ฒด ๋ฒ”์œ„๊ฐ€ ์–ผ๋งˆ์ธ์ง€, ์š”์ฒญํ•œ index๊ฐ€ ๋ช‡์ธ์ง€ ํŒŒ์•…ํ•˜๊ธฐ ์šฐํ•ด์„œ๋Š” ํ”„๋กœ๊ทธ๋ž˜๋จธ๊ฐ€ ์ง์ ‘ ๋””๋ฒ„๊น…ํ•˜๊ฑฐ๋‚˜ ์ •๋ณด๋ฅผ ๋‹ด์€ ๋ฉ”์‹œ์ง€๋ฅผ ๋งŒ๋“ค์–ด์ค˜์•ผ ํ•œ๋‹ค. ๋””๋ฒ„๊ทธ๋ฅด๋ฅ˜ ํ†ตํ•ด ์ง์ ‘ ์ •๋ณด๋ฅผ ์ฐพ์•„๋‚ด๋Š” ํ–‰์œ„๋Š” ์ฉ ์ข‹์€ ์ผ์ด ์•„๋‹ˆ๋‹ค. ๊ฐœ๋ฐœ ๊ณผ์ •์— ํ”ผ๋กœํ•จ๋งŒ ๋Š˜์–ด๊ฐ„๋‹ค. ๋ฉ”์‹œ์ง€๋Š” ๋‹จ ํ•œ ๊ณณ์—์„œ๋งŒ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ๋ผ๋ฉด ์ƒ๊ด€์—†์ง€๋งŒ ์—ฌ๋Ÿฌ ๊ณณ์—์„œ ๋ฐœ์ƒํ•œ๋‹ค๋ฉด ๋ฆฌํŒฉํ† ๋งํ•˜๊ธฐ๊ฐ€ ํž˜๋“ค์–ด ์งˆ ๊ฒƒ์ด๋‹ค.
    ์ด๋Ÿฌํ•œ ์ƒํ™ฉ์—์„œ ์‚ฌ์šฉ์ž ์ •์˜ ์˜ˆ์™ธ๋Š” ์ข‹์€ ํ•ด๊ฒฐ์ฑ…์ด ๋  ์ˆ˜ ์žˆ๋‹ค.
public class IllegalIndexException extends IndexOutOfBoundsException {
	private static final String message = "๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚ฌ์Šต๋‹ˆ๋‹ค.";

	public IllegalIndexException(List<?> target, int index) {
		super(message + " size: "  + target.size() + " index: " + index);
	}
}
  • ์˜ˆ๋ฅผ ๋ณด๋ฉด ์š”์ฒญ ๋ฐ›์€ ์ปฌ๋ ‰์…˜์˜ ์ตœ๋Œ€ ๋ฒ”์œ„๊ฐ€ ์–ด๋””๊นŒ์ง€์ธ์ง€, ์š”์ฒญํ•œ index๋Š” ๋ช‡์ธ์ง€ ํ™•์ธ ๊ฐ€๋Šฅํ•˜๋‹ค. try/catch๋ฅผ ํ†ตํ•ด ๋ฐœ์ƒํ•œ ์˜ˆ์™ธ๋ฅผ ๋ถ™์žก์•„ ์ƒˆ๋กœ ๋งŒ๋“  ์˜ˆ์™ธ๋ฅผ ๋˜์ ธ๋„ ๋˜๊ณ , ๊ธฐ์กด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•˜๊ธฐ ์ „์— index๋ฅผ ๊ฒ€์‚ฌํ•ด ์ƒˆ๋กœ ๋ง๋Š” ์˜ˆ์™ธ๋ฅผ ์ง์ ‘ ๋ฐœ์ƒ์‹œ์ผœ๋„ ๊ดœ์ฐฎ๋‹ค.
    ์ „๋‹ฌํ•˜๋Š” ์ •๋ณด์˜ ์ˆ˜์ •์ด ํ•„์š”ํ•  ๋•Œ๋Š” IllegalIndexException ํด๋ž˜์Šค๋ฅผ ์ˆ˜์ •ํ•˜๋ฉด ๋œ๋‹ค. ๊ฐ™์€ ์˜ˆ์™ธ๋ฅผ ๋ฐœ์ƒ์‹œํ‚ค๋Š” ๋ชจ๋“  ์ƒํ™ฉ์— ์ ์šฉ๋  ๊ฒƒ์ด๋‹ค.
  1. ์˜ˆ์™ธ์— ๋Œ€ํ•œ ์‘์ง‘๋„๊ฐ€ ํ–ฅ์ƒ๋œ๋‹ค.
  • ํด๋ž˜์Šค๋ฅผ ๋งŒ๋“œ๋Š” ํ–‰์œ„๋Š” ๊ด€๋ จ ์ •๋ณด๋ฅผ ํ•ด๋‹น ํด๋ž˜์Šค์—์„œ ์ตœ๋Œ€ํ•œ ๊ด€๋ฆฌํ•˜๊ฒ ๋‹ค๋Š” ์ด์•ผ๊ธฐ๋‹ค.
    ํ‘œ์ค€์˜ˆ์™ธ์™€ ๋ฉ”์‹œ์ง€๋กœ๋„ ์ถฉ๋ถ„ํžˆ ์ •๋ณด๋ฅผ ์ „๋‹ฌํ•  ์ˆ˜ ์žˆ์ง€๋งŒ, ์ „๋‹ฌํ•˜๋Š” ์ •๋ณด์˜ ์–‘์ด ๋งŽ์•„์ง€๊ฑฐ๋‚˜ ์—ฌ๋Ÿฌ๊ณณ์—์„œ ๋ฐœ์ƒํ•˜๋Š” ์˜ˆ์™ธ๋ผ๋ฉด ์ฑ…์ž„์†Œ์žฌ๊ฐ€ ๋ถˆ๋ถ„๋ช…ํ•ด์ง„๋‹ค.
    ์‚ฌ์‹ค ์ •์  ๋ฉ”์†Œ๋“œ๋ฅผ ๋‹ด์€ ์œ ํ‹ธ์„ฑ ํด๋ž˜์Šค๋กœ๋„ ์ถฉ๋ถ„ํžˆ ํ‘œ์ค€ ์˜ˆ์™ธ๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด์„œ ์ด๋ฌธ์ œ๋ฅผ ํ•ด๊ฒฐํ•  ์ˆ˜ ๋„ ์žˆ์ง€๋งŒ ์‚ฌ์šฉ์ž ์ •์˜ ์˜ˆ์™ธ๋ฅผ ์‚ฌ์šฉํ•œ๋‹ค๋ฉด ๊ฐ์ฒด์˜ ์ฑ…์ž„์ด ๋ถ„๋ฆฌ๋œ ๊น”๋”ํ•œ ์ฝ”๋“œ๋ฅผ ์–ป์„ ์ˆ˜ ์žˆ๋‹ค.
  1. ์˜ˆ์™ธ ๋ฐœ์ƒ ํ›„์ฒ˜๋ฆฌ๊ฐ€ ์šฉ์ดํ•˜๋‹ค.
  • ์˜ˆ์™ธ๋Š” ์ƒ์† ๊ด€๊ณ„์— ์žˆ๊ธฐ ๋•Œ๋ฌธ์—, Exception์ด๋‚˜ RuntimeException์„ ์žก์•„๋‘๋ฉด ํ”„๋กœ๊ทธ๋žจ ๋‚ด์—์„œ ๋ฐœ์ƒํ•˜๋Š” ๊ฑฐ์˜ ๋ชจ๋“  ์˜ˆ์™ธ์— ๋Œ€ํ•ด ์ฒ˜๋ฆฌ๊ฐ€ ๊ฐ€๋Šฅํ•˜๋‹ค. ํ•˜์ง€๋งŒ ์ด๋Š” ํ”„๋กœ๊ทธ๋ž˜๋จธ๊ฐ€ ์˜๋„ํ•˜์ง€ ์•Š์€ ์˜ˆ์™ธ๊นŒ์ง€ ๋ชจ๋‘ ์žก์•„๋‚ด ํ˜ผ๋ž€์„ ์•ผ๊ธฐํ•  ์ˆ˜ ์žˆ๋‹ค.
    ์žฌ์‚ฌ์šฉ์„ฑ์ด ๋†’์€ ๊ฒƒ์€ ํ‘œ์ค€ ์˜ˆ์™ธ๋“ค์˜ ์žฅ์ ์ด๋‹ค. ํ•˜์ง€๋งŒ ๊ทธ ์žฅ์  ๋•Œ๋ฌธ์— ๋ฐœ์ƒ ์œ„์น˜๋ฅผ ์ •ํ™•ํ•˜๊ฒŒ ํŒŒ์•…ํ•˜๊ธฐ ํž˜๋“ค๋‹ค๋Š” ๋‹จ์ ๋„ ์ƒ๊ธด๋‹ค.
  1. ์˜ˆ์™ธ ์ƒ์„ฑ ๋น„์šฉ์„ ์ ˆ๊ฐํ•œ๋‹ค.
  • ์ž๋ฐ”์—์„œ ์˜ˆ์™ธ๋ฅผ ์ƒ์„ฑํ•˜๋Š” ํ–‰์œ„๋Š” ์ƒ๊ฐ๋ณด๋‹ค ๋งŽ์€ ๋น„์šฉ์ด ์†Œ๋ชจ๋œ๋‹ค. ๋ฐ”๋กœ stack trace ๋•Œ๋ฌธ์ด๋‹ค.
    stack trace๋Š” ์˜ˆ์™ธ ๋ฐœ์ƒ ์‹œ call stack์— ์žˆ๋Š” ๋ฉ”์†Œ๋“œ ๋ฆฌ์ŠคํŠธ๋ฅผ ์ €์žฅํ•œ๋‹ค. ์ด๋ฅผ ํ†ตํ•ด ์˜ˆ์™ธ๊ฐ€ ๋ฐœ์ƒํ•œ ์ •ํ™•ํ•œ ์œ„์น˜๋ฅผ ํŒŒ์•…ํ•  ์ˆ˜ ์žˆ๋‹ค. ํ•˜์ง€๋งŒ try/catch๋‚˜ Advice๋ฅผ ํ†ตํ•ด ์˜ˆ์™ธ๋ฅผ ์ฒ˜๋ฆฌํ•œ๋‹ค๋ฉด ํ•ด๋‹ค ์˜ˆ์™ธ์˜ stack trace๋Š” ์‚ฌ์šฉํ•˜์ง€ ์•Š์„ ๋•Œ๊ฐ€ ๋งŽ๋‹ค. ๋น„์šฉ์„ ๋“ค์—ฌ ๋งŒ๋“ค์—ˆ๋‹ค์ง€๋งŒ ์‚ฌ์šฉํ•˜์ง€ ์•Š๊ณ  ์‚ฌ๋ผ์ง€๋Š” ํ˜•ํƒœ, ๋„ˆ๋ฌด๋‚˜๋„ ๋น„์š”์œจ์ ์ด๋‹ค.

stack trace์˜ ์ƒ์„ฑ์€ ์˜ˆ์™ธ์˜ ๋ถ€๋ชจ ํด๋ž˜์Šค ์ค‘ Throwable์˜ fillInStackTrace() ๋ฉ”์†Œ๋“œ๋ฅผ ํ†ตํ•ด ์ด๋ฃจ์–ด์ง„๋‹ค. ์‚ฌ์šฉ์ž ์ •์˜ ์˜ˆ์™ธ๋Š” ํ•ด๋‹น ๋ฉ”์†Œ๋“œ๋ฅผ Override ํ•จ์œผ๋กœ stack trace์˜ ์ƒ์„ฑ ๋น„์šฉ์„ ์ค„์ผ ์ˆ˜ ์žˆ๋‹ค. ํ•„์š”ํ•˜๋‹ค๋ฉด ์งง๊ฒŒ ์ผ๋ถ€๋งŒ์„ ์ƒ์„ฑํ•  ์ˆ˜๋„, ์•„์˜ˆ ์ƒ์„ฑํ•˜์ง€ ์•Š์„ ์ˆ˜๋„ ์žˆ๋‹ค.

@Override
public synchronized Throwable fillInStackTrace() {
	  return this;
}
public class CustomException extends RuntimeException {
	  public static final CustomException CUSTOM_EXCEPTION = new CustomException("๋Œ€์ถฉ ์˜ˆ์™ธ๋ผ๋Š” ๋‚ด์šฉ");
	  //...
}
  • ๋งŒ์ผ ๊ตฌํ˜„ํ•ด ๋‚ธ Custom Exception์ด stack trace๋„ ๊ฐ–์ง€ ์•Š๊ณ , ์ƒํ™ฉ์— ๋”ฐ๋ผ ์ •๋ณด๋ฅผ ๋‹ค๋ฅด๊ฒŒ ์ฃผ๋Š” ์˜ˆ์™ธ๊ฐ€ ์•„๋‹ˆ๋ผ ๋‹จ์ˆœํ•˜๊ฒŒ ๋ฉ”์„ธ์ง€๋งŒ ๋„˜๊ฒจ์ค€๋‹ค๋ฉด ํ•ด๋‹น ์˜ˆ์™ธ๋ฅผ ์บ์‹ฑํ•ด ๋‘๋Š” ๊ฒƒ๋„ ๋น„์šฉ ์ ˆ๊ฐ์˜ ๋ฐฉ๋ฒ•์ด๋‹ค.

# 1113 - ์ž„๋ฒ ๋””๋“œ ํƒ€์ž…(embedded type)

# ์ž„๋ฒ ๋””๋“œ ํƒ€์ž…(๋ณตํ•ฉ ๊ฐ’ ํƒ€์ž…)

์ƒˆ๋กœ์šด ๊ฐ’ ํƒ€์ž…์„ ์ง์ ‘ ์ •์˜ํ•ด์„œ ์‚ฌ์šฉํ•  ์ˆ˜ ์žˆ๋”ฐ. JPA์—์„œ๋Š” ์ด๊ฒƒ์„ ์ž„๋ฒ ๋””๋“œ ํƒ€์ž…(embedded type)์ด๋ผ ํ•œ๋‹ค. ์ค‘์š”ํ•œ ๊ฒƒ์€ ์ง์ ‘ ์ •์˜ํ•œ ์ž„๋ฒ ๋””๋“œ ํƒ€์ž…๋„ int, String ์ฒ˜๋Ÿผ ๊ฐ’ ํƒ€์ž…์ด๋‹ค.

# ์˜ˆ์ œ

  • ์ž„๋ฒ ๋””๋“œ ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜์„ ๋•Œ
@Entity
public class Member {
  
  @Id @GeneratedValue
  private Long id;
  private String name;
  
  // ๊ทผ๋ฌด ๊ธฐ๊ฐ„
  @Temporal(TemporalType.DATE)
  Date startDate;
  @Temporal(TemporalType.DATE)
  Date endDate;
  
  // ์ง‘ ์ฃผ์†Œ ํ‘œํ˜„
  private String city;
  private String street;
  private String zipcode;
  // ...
}
  • ์ž„๋ฒ ๋””๋“œ ํƒ€์ž… ์‚ฌ์šฉ
@Entity
public class Member {
  
  @Id @GeneratedVAlue
  private Long id;
  private String name;
  
  @Embedded
  private Period workPeriod;	// ๊ทผ๋ฌด ๊ธฐ๊ฐ„
  
  @Embedded
  private Address homeAddress;	// ์ง‘ ์ฃผ์†Œ
}

---

@Embeddable
public class Peroid {
  
  @Temporal(TemporalType.DATE)
  Date startDate;
  @Temporal(TemporalType/Date)
  Date endDate;
  // ...
  
  public boolean isWork (Date date) {
    // .. ๊ฐ’ ํƒ€์ž…์„ ์œ„ํ•œ ๋ฉ”์„œ๋“œ๋ฅผ ์ •์˜ํ•  ์ˆ˜ ์žˆ๋‹ค
  }
}

---

@Embeddable
public class Address {
  
  @Column(name="city") // ๋งคํ•‘ํ•  ์ปฌ๋Ÿผ ์ •์˜ ๊ฐ€๋Šฅ
  private String city;
  private String street;
  private String zipcode;
  // ...
}

# ์ž„๋ฒ ๋””๋“œ ํƒ€์ž… ์‚ฌ์šฉ ๋ฐฉ๋ฒ•

  • @Embeddable : ๊ฐ’ ํƒ€์ž…์„ ์ •์˜ํ•˜๋Š” ๊ณณ์— ํ‘œ์‹œ
  • @Embedded : ๊ฐ’ ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜๋Š” ๊ณณ์— ํ‘œ์‹œ
  • ์ž„๋ฒ ๋””๋“œ ํƒ€์ž…์€ ๊ธฐ๋ณธ ์ƒ์„ฑ์ž๊ฐ€ ํ•„์ˆ˜

์ž„๋ฒ ๋””๋“œ ํƒ€์ž…์„ ํฌํ•จํ•œ ๋ชจ๋“  ๊ฐ’ ํƒ€์ž…์€ ์—”ํ‹ฐํ‹ฐ์˜ ์ƒ๋ช…์ฃผ๊ธฐ์— ์˜์กดํ•˜๋ฏ€๋กœ ์—”ํ‹ฐํ‹ฐ์™€ ์ž„๋ฒ ๋””๋“œ ํƒ€์ž… ๊ด€๊ณ„๋ฅผ UML๋กœ ํ‘œํ˜„ ํ•˜๋ฉด ์ปดํฌ์ง€์…˜(composition) ๊ด€๊ณ„๊ฐ€ ๋œ๋‹ค.

ํ•˜์ด๋ฒ„๋„ค์ดํŠธ๋Š” ์ž„๋ฒ ๋””๋“œ ํƒ€์ž…์„ ์ปดํฌ๋„ŒํŠธ(components)๋ผ ํ•œ๋‹ค.

uml <ํšŒ์›-์ปดํฌ์ง€์…˜ ๊ด€๊ณ„ UML (์ถœ์ฒ˜: ๊น€์˜ํ•œ๋‹˜ ๊ฐ•์˜)>

# ์ž„๋ฒ ๋””๋“œ ํƒ€์ž…์˜ ์žฅ์ 

  • ์žฌ์‚ฌ์šฉ
  • ๋†’์€ ์‘์ง‘๋„
  • Period ๊ฐ์ฒด์˜ isWork() ๋ฉ”์„œ๋“œ์ฒ˜๋Ÿผ ํ•ด๋‹น ๊ฐ’ ํƒ€์ž…๋งŒ ์‚ฌ์šฉํ•˜๋Š” ์˜๋ฏธ์žˆ๋Š” ๋ฉ”์„œ๋“œ๋ฅผ ๋งŒ๋“ค ์ˆ˜ ์žˆ๋‹ค.

# ์ž„๋ฒ ๋””๋“œ ํƒ€์ž…๊ณผ ํ…Œ์ด๋ธ” ๋งคํ•‘

uml2 <์ž„๋ฒ ๋””๋“œ ํƒ€์ž…์„ ์‚ฌ์šฉํ•œ ํšŒ์›-ํ…Œ์ด๋ธ” ๋งคํ•‘ (์ถœ์ฒ˜: ๊น€์˜ํ•œ๋‹˜ ๊ฐ•์˜)>

์ž„๋ฒ ๋””๋“œ ํƒ€์ž…์€ ์—”ํ‹ฐํ‹ฐ์˜ ๊ฐ’์ผ ๋ฟ์ด๋‹ค. ๋”ฐ๋ผ์„œ ๊ฐ’์ด ์†ํ•œ ์—”ํ‹ฐํ‹ฐ์˜ ํ…Œ์ด๋ธ”์— ๋งคํ•‘๋˜๋ฉฐ, ์ž„๋ฒ ๋””๋“œ ํƒ€์ž…์„ ์‚ฌ์šฉํ•˜๊ธฐ ์ „๊ณผ ํ›„์— ๋งคํ•‘๋˜๋Š” ํ…Œ์ด๋ธ”์€ ๋™์ผํ•˜๋‹ค.

์ž„๋ฒ ๋””๋“œ ํƒ€์ž… ๋•๋ถ„์— ๊ฐ์ฒด์™€ ํ…Œ์ด๋ธ”์„ ์•„์ฃผ ์„ธ๋ฐ€ํ•˜๊ฒŒ(fine-grained) ๋งคํ•‘ํ•˜๋Š” ๊ฒƒ์ด ๊ฐ€๋Šฅํ•˜๋‹ค. ์ž˜ ์„ค๊ณ„ํ•œ ORM ์• ํ”Œ๋ฆฌ์ผ€์ด์…˜์€ ๋งคํ•‘ํ•œ ํ…Œ์ด๋ธ”์˜ ์ˆ˜๋ณด๋‹ค ํด๋ž˜์Šค์˜ ์ˆ˜๊ฐ€ ๋” ๋งŽ๋‹ค.

# ์ž„๋ฒ ๋””๋“œ ํƒ€์ž…๊ณผ ์—ฐ๊ด€๊ด€๊ณ„

์ž„๋ฒ ๋””๋“œ ํƒ€์ž…์€ ๊ฐ’ ํƒ€์ž…์„ ํฌํ•จํ•˜๊ฑฐ๋‚˜ ์—”ํ‹ฐํ‹ฐ๋ฅผ ์ฐธ์กฐํ•  ์ˆ˜ ์žˆ๋‹ค.

@Entity
public class Member {
  
  @Embedded
  Address address;	// ์ž„๋ฒ ๋””๋“œ ํƒ€์ž… ํฌํ•จ
  @Embedded
  PhoneNumber phoneNumber;	// ์ž„๋ฒ ๋””๋“œ ํƒ€์ž… ํฌํ•จ

  // ...
}


@Embeddable
public class Address {
  String street;
  String city;
  String state;
  @Embedded
  Zipcode zipcode; // ์ž„๋ฒ ๋””๋“œ ํƒ€์ž… ํฌํ•จ
}


@Embeddable
public class Zipcode {
  String zip;
  String plusFour;
}


@Embeddable
public class PhoneNumber {
 
  String areaCode;
  String localNumber;
  
  @ManyToOne
  PhoneServiceProvider provider;	// ์—”ํ‹ฐํ‹ฐ ์ฐธ์กฐ
}


@Entity
public class PhoneServiceProvider {
  @Id
  String name;
 	// ...
}

๊ฐ’ ํƒ€์ž…์ธ Address๊ฐ€ ๊ฐ’ ํƒ€์ž…์ธ Zipcode๋ฅผ ํฌํ•จํ•˜๊ณ , ๊ฐ’ ํƒ€์ž…์ธ PhoneNumber๊ฐ€ ์—”ํ‹ฐํ‹ฐ ํƒ€์ž…์ธ PhoneServiceProvider๋ฅผ ์ฐธ์กฐํ•œ๋‹ค.

# @AttributeOverride: ์†์„ฑ ์žฌ์ •์˜

์ž„๋ฒ ๋””๋“œ ํƒ€์ž…์— ์ •์˜ํ•œ ๋งคํ•‘์ •๋ณด๋ฅผ ์žฌ์ •์˜ํ•˜๋ ค๋ฉด ์—”ํ‹ฐํ‹ฐ์— @AttributeOverride๋ฅผ ์‚ฌ์šฉํ•˜๋ฉด ๋œ๋‹ค.

// ๊ฐ™์€ ์ž„๋ฒ ๋””๋“œ ํƒ€์ž…์„ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ํšŒ์›
@Entity
public class Member {
  
  @Id @GeneratedValue
  private Long id;
  private String name;
  
  @Embedded
  Address homeAddress;
  
  @Embedded
  Address companyAddress;
}

์œ„ ์ฝ”๋“œ์˜ ๋ฌธ์ œ์ ์€ ํ…Œ์ด๋ธ”์— ๋งคํ•‘ํ•˜๋Š” ์ปฌ๋Ÿผ๋ช…์ด ์ค‘๋ณต๋œ๋‹ค. ์ด๋–„๋Š” ์•„๋ž˜์™€ ๊ฐ™์ด @AttributeOberrides๋ฅผ ์‚ฌ์šฉํ•ด์„œ ๋งคํ•‘์ •๋ณด๋ฅผ ์žฌ์ •์˜ํ•ด์•ผ ํ•œ๋‹ค.

@Entity
public class Member {
  
  @Id @GeneratedValue
  private Long id;
  private String name;
  
  @Embedded
  Address homeAddress;
  
  @Embedded
  @AttributeOverrides({
    @AttributeOverride(name="city", column=@Column(name="COMPANY_CITY")),
    @AttributeOverride(name="street", column=@Column(name="COMPANY_STREET")),
    @AttributeOverride(name="zipcode", column=@Column(name="COMPANY_ZIPCODE"))
  })
  Address companyAddress;
}

// ์ƒ์„ฑ๋œ ํ…Œ์ด๋ธ”
------------------------------
CREATE TABLE MEMBER (
	COMPANY_CITY varchar(255),
  COMPANY_STREET varchar(255),
  COMPANY_ZIPCODE varchar(255),
  city varchar(255),
  street varchar(255),
  zipcode varchar(255),
  ...
)
------------------------------

@AttributeOverrides๋Š” ์—”ํ‹ฐํ‹ฐ์— ์„ค์ •ํ•ด์•ผ ํ•œ๋‹ค. ์ž„๋ฒ ๋””๋“œ ํƒ€์ž…์ด ์ž„๋ฒ ๋””๋“œ ํƒ€์ž…์„ ๊ฐ€์ง€๊ณ  ์žˆ์–ด๋„ ์—”ํ‹ฐํ‹ฐ์— ์„ค์ •ํ•ด์•ผ ํ•œ๋‹ค.

# ์ž„๋ฒ ๋””๋“œ ํƒ€์ž…๊ณผ Null

์ž„๋ฒ ๋””๋“œ ํƒ€์ž…์ด null์ด๋ฉด ๋งคํ•‘ํ•œ ์ปฌ๋Ÿผ ๊ฐ’์€ ๋ชจ๋‘ null์ด ๋œ๋‹ค.

Last update: September 13, 2022 21:44
Contributors: ahnjs