MapStruct! JPA Entity λ§€ν κ° μ£Όμν΄μ μ¬μ©νμ
μ΅κ·Ό λΉμ κ²°μ λͺ¨λλ₯Ό κ°λ° λ° λ°°ν¬νλ©°,
μ°ΈμΌλ‘ μ΄μ΄ μλ μμ μ½λ© κ΄λ ¨ μ΄μλ‘ μΈν΄ λ°μ΄ν°μ μ΄μμ΄ μμλ€.
λ°λ‘, MapStruct μ λλ¬ΈμΈλ°..
νλ² λμ΄ν΄λ³΄κ² λ€.
(νμ μκ°νλ κ±°μ§λ§, μ½λ λ¨ νμ€μ νκΈν¨κ³Όκ° μμ² λλ€.)
MapStruct λ?
- Java Bean μ ν κ° λ§€ν ꡬνμ λμμ£Όλ μ½λ μμ±κΈ°
- μ»΄νμΌ νμμ μ½λ μμ± λ° λ°νμμμ μμ μ± λ³΄μ₯
- μμ Java code λ₯Ό νΈμΆνλ―λ‘ λ€λ₯Έ λ§€ν λΌμ΄λΈλ¬λ¦¬λ³΄λ€ μλκ° λΉ λ₯΄λ€. (Reflection μ μ¬μ©νμ§ μκΈ° λλ¬Έ)
- Annotation Processor λ₯Ό μ΄μ©νμ¬ λ§€ν λ°©μμ νΈλ¦¬ν¨μ μ 곡
μ¬μ© μμ
JAVA
@Mapper
public interface UserMapper {
UserMapper INSTANCE = Mappers.getMapper(UserMapper.class);
UserResponseDTO convertToUserResponseDTO(User user);
}
Kotlin
@Entity
@Table(name = "users")
class User(
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
val id: Long? = null,
val uid: String,
val name: String,
val address: String,
val phoneNumber: String,
val createdAt: LocalDateTime = LocalDateTime.now(),
@LastModifiedDate
val updatedAt: LocalDateTime = LocalDateTime.now(),
)
data class UserResponseDTO(
val uid: String,
val name: String,
val address: String,
)
@Mapper(
componentModel = ComponentModel.SPRING,
nullValuePropertyMappingStrategy = NullValuePropertyMappingStrategy.IGNORE,
nullValueMappingStrategy = NullValueMappingStrategy.RETURN_DEFAULT,
unmappedTargetPolicy = ReportingPolicy.IGNORE,
unmappedSourcePolicy = ReportingPolicy.IGNORE
)
interface UserMapper {
companion object {
val INSTANCE = Mappers.getMapper(UserMapper::class.java)
}
fun convertToResponseDTO(user: User): UserResponseDTO
}
μμ κ°μ΄ μμ μ½λκ° μλ€.
gradle μ classes task λ₯Ό μ€ννμ¬, build νκ² λλ©΄ μλμ κ°μ΄ MapStruct κ° UserMapper λ₯Ό ꡬνν, UserMapperImpl ν΄λμ€λ₯Ό μμ±ν΄μ£Όλ κ²μ νμΈν μ μλ€.
MapStruct μ¬μ© μ€ μ΄μ λ°μ
μ΅κ·Ό MapStruct μ¬μ© μ€ μ΄μκ° λ°μνλ€.
μ΄μ μ€λͺ
- Jpa Entity λ§€ν μ€, id (PK) κΉμ§ λ§€ν λμ΄ λ²λ¦° μ΄μ
- Jpa Entity λ₯Ό μμ±νμ¬ Id νλ κ°μ΄ 0 μ΄κ±°λ, null μΌ κ²½μ°, mysql μ auto_increment μμ±μ μν΄ μ νμ μΌλ‘ μ¦κ°ν λ°μ΄ν°λ‘ μ μ₯ν μ μλ€.
- νμ§λ§, id νλ κ°μ ν λΉν ν save λ₯Ό νκ² λλ©΄, JPA λ ν΄λΉ μλ³ ν€λ₯Ό κ°μ§ DB Record κ° μ‘΄μ¬ν κ²½μ°, Update 쿼리λ₯Ό, μ‘΄μ¬νμ§ μμ κ²½μ° Insert 쿼리λ₯Ό μμ±νμ¬ Flush νλ€.
- μ΄ λΆλΆμμ κ°λ°κ³, μ΄μκ³ DB λ°μ΄ν°κ° μμ΄νμ¬ μΈμ§ λμ§ λͺ»ν μ± λ¬Έμ λ₯Ό μΌμΌμΌ°λ€.
μ΄μ νΈλνΉ κ³Όμ
@Mapping(
source = "dto.orderItemStatus",
target = "status"
)
@Mapping(
source = "order",
target = "order"
)
@Mapping(
target = "oiid",
expression = "java(OrderItem.generateOIID(hashedId))"
)
fun convertToOrderItem(
dto: OrderSyncRequestDTO.OrderItem,
order: Order,
sellerId: Long,
hashedId: String,
): OrderItem
- λ¬Έμ κ° λμλ ν¨μ
- Order JPA Entity λ₯Ό μΈμλ‘ λκΈ΄ μν©
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2024-03-21T14:37:36+0900",
comments = "version: 1.5.4.Final, compiler: IncrementalProcessingEnvironment from kotlin-annotation-processing-gradle-1.7.22.jar, environment: Java 17.0.7 (Azul Systems, Inc.)"
)
@Component
public class OrderItemMapperImpl implements OrderItemMapper {
@Override
public OrderItem convertToOrderItem(OrderSyncRequestDTO.OrderItem dto, Order order, long sellerId, String hashedId) {
OrderItemStatus status = null;
String productName = null;
BigDecimal productPrice = null;
int quantity = 0;
BigDecimal vat = null;
BigDecimal vos = null;
BigDecimal discountedPrice = null;
BigDecimal originalPrice = null;
String categoryName = null;
Integer originalQuantity = null;
if ( dto != null ) {
isVat = dto.getVatted();
status = dto.getOrderItemStatus();
productName = dto.getProductName();
productPrice = dto.getProductPrice();
quantity = dto.getQuantity();
totalPrice = dto.getTotalPrice();
vat = dto.getVat();
vos = dto.getVos();
discountedPrice = dto.getDiscountedPrice();
originalPrice = dto.getOriginalPrice();
priceType = dto.getPriceType();
originalQuantity = dto.getOriginalQuantity();
}
Order order1 = null;
long sellerId1 = 0L;
if ( order != null ) {
order1 = order;
sellerId1 = order.getSellerId();
}
String oiid = OrderItem.generateOIID(hashedId);
LocalDateTime paidAt = null;
LocalDateTime refundedAt = null;
Long productId = null;
OrderItem orderItem = new OrderItem( oiid, productName, productPrice, quantity, options, paidAt, refundedAt, productId, sellerId1, totalPrice, isVat, vat, vos, originalPrice, status, order1 );
// μ΄ λΆλΆμ΄ λ¬Έμ
if ( order != null ) {
orderItem.setId( order.getId() );
orderItem.setCreatedAt( order.getCreatedAt() );
orderItem.setUpdatedAt( order.getUpdatedAt() );
}
return orderItem;
}
}
OrderItemMapperImpl ν΄λμ€μμ ν΄λΉ μΆμ λ©μλλ₯Ό ꡬνν μ½λλ κ°μ μ΄λ¦μ νλλ₯Ό λ§€ννκΈ° μν΄ order μ id, createdAt, updatedAt λ₯Ό getter λ₯Ό ν΅ν΄ νΈμΆ, μλ‘κ² μμ±λ orderItem κ°μ²΄μ μμ±ν΄ setter λ₯Ό ν΅ν΄ λΆμ¬νκ³ μλ λΆλΆμ νμΈν μ μλ€.
κ°λ°κ³
κ°λ°κ³μμ order_items ν μ΄λΈ μ€ μ€κ°μ λΉμ΄ μλ λ μ½λκ° λ€μ μμκ³ , auto_increment λλ order μ id μ ν΄λΉ λλ λ μ½λκ° μμμΌλ―λ‘ Insert Query κ° λ°μνλ€.

κ°λ°κ³ λλ²κ·Έ ν μ€νΈ
μꡬ μ¬ν
- order λ° order_items λ₯Ό μμ±νκ³ μ μ₯
λλ²κ·Έ κ³Όμ
- μλ‘μ΄, auto_increment λ order μ id = 9098875
- order_items id κ° 9098875 μΈ λ μ½λλ μλ μν©


- order_items jpa entity λ 2κ°κ° μκ²Όμ§λ§, id (order entity μ id μ λμΌ) λ λμΌν κ²μ νμΈν μ μλ€..
- κ·Έ κ²°κ³Ό insert λ λμ§λ§, order_item λ μ½λλ 1κ°λ§ μμΈλ€.
- λ§μ½ μμλ€λ©΄, κΈ°μ‘΄ λ°μ΄ν°λ update λλ€.

β λμΌν μμ²μ ν΅ν΄ update κ° λμ΄λ²λ¦° order_item λ°μ΄ν° νμΈ κ°λ₯
ν μ€νΈκ³
- μ΄μκ³μμ order_items ν μ΄λΈμ λΉμ΄ μλ λ°μ΄ν°κ° μμμΌλ―λ‘, μλ‘κ² auto_increment λλ order id κ°κ³Ό λμΌν order_item μ id κ°μ μ΄λ―Έ μ‘΄μ¬νλ―λ‘, update Query κ° λ°μλμλ€.

β order_items κ° 2κ° μΈ μν©μμ update μΏΌλ¦¬λ§ λ°μ λλ κ²μ νμΈν μ μμλ€. (rollback μ ν΅ν΄, DB λ°μμ μ μμΌ°λ€.)
β μ€μ λ‘ 2κ° λ‘μ°κ° μλ‘κ² μ μ¬λμ΄μΌ νλ order_item μ λμΌν order_id id κ°μ ν λΉ λ°μ ν κΈ°μ‘΄μ μ μ₯ λμ΄ μλ order_item λ μ½λμ λ°μ΄ν°λ₯Ό update νλ κ²°κ³Όλ‘ μ΄μ΄μ‘λ€.
ν΄κ²°
- hotfix μν©μ΄λ―λ‘, κ°λ΅ν MapStruct μ λ Έν μ΄μ Mapping μ μμ± μ€ ignore μμ±μ ν΅ν΄ id, createdAt, updatedAt νλλ₯Ό true λ‘ μ€μ νλ€.
@Mapping(
source = "dto.orderItemStatus",
target = "status"
)
@Mapping(
source = "order",
target = "order"
)
@Mapping(
target = "oiid",
expression = "java(OrderItem.generateOIID(hashedId))"
)
@Mapping( // μΆκ°
target = "id",
ignore = true
)
@Mapping( // μΆκ°
target = "createdAt",
ignore = true
)
@Mapping( // μΆκ°
target = "updatedAt",
ignore = true
)
fun convertToOrderItem(
dto: OrderSyncRequestDTO.OrderItem,
order: Order,
sellerId: Long,
hashedId: String,
): OrderItem
- ignore μμ±μ μ΄μ©ν λͺ¨μ΅μ νμΈ κ°λ₯νλ€.
- μ΄ μνλ‘ λ€μ λΉλλ₯Ό μ§ννλ©΄, MapStruct λ Target μ ν΄λΉ νλ λ§€νμ 무μνλ€.
κ²°λ‘
- Object κ° λ§€νμ νΈμμ±μ λνκΈ° μν΄ μ¬μ©νλ MapStruct!
- JPA Entity κ° λ§€νμ μ μνμ..
- JPA Entity λ§€νμ νμν μΈμλ‘ JPA Entity λ₯Ό μ΄μ©μ μ§μνμ..