Spring Data JPA 에서 Java8 Date-Time(JSR-310) 사용하기   

Java8에는 시간 데이터를 더 편리하게 처리할 수 있게 해주는 LocalDate, LocalDateTime 등의 클래스들이 java.time 패키지에 추가되었다. 날짜/시간 차이 계산, 비교, 년/월/일/시/분/초 단위 별 추출 등 풍부한 기능을 제공해주므로 사용성이 아주 좋다. Joda-Time을 사용하고 있었다면, Java8에서는 java.time (JSR-310)으로 마이크레이션 하는 것이 좋다.

하지만, java.time (JSR-310)를 별다른 처리 없이 JPA를 이용해서 MySQL에 저장하면 버전에 따라서 아래와 같은 에러가 날 수도 있다.

1
2
3
...
Caused by: com.mysql.jdbc.MsqlDataTruncation: Data truncation: Incorrect dateme value: '\xAC\xED\x00\x05sr\x0Djava.time.Ser\x95]\x84\xBA\x1B"H\xB2\x0C

Spring Data JPA 에서 Java8 Date-Time(JSR-310) 사용하기   

Java8에는 시간 데이터를 더 편리하게 처리할 수 있게 해주는 LocalDate, LocalDateTime 등의 클래스들이 java.time 패키지에 추가되었다. 날짜/시간 차이 계산, 비교, 년/월/일/시/분/초 단위 별 추출 등 풍부한 기능을 제공해주므로 사용성이 아주 좋다. Joda-Time을 사용하고 있었다면, Java8에서는 java.time (JSR-310)으로 마이크레이션 하는 것이 좋다.

하지만, java.time (JSR-310)를 별다른 처리 없이 JPA를 이용해서 MySQL에 저장하면 버전에 따라서 아래와 같은 에러가 날 수도 있다.

1
2
3
...
Caused by: com.mysql.jdbc.MsqlDataTruncation: Data truncation: Incorrect dateme value: '\xAC\xED\x00\x05sr\x0Djava.time.Ser\x95]\x84\xBA\x1B"H\xB2\x0C\0\x00xpw\x07\x03\x00\x00\x07\xE0\x05\x1Fx' for column 'start_date' at row 1
...

또는 날짜/시간을 나타내는 컬럼이 MySQL에서 datetime 타입이 아니라 tinyblob 타입으로 생성되어서 아래와 같이 원치 않는 형식으로 저장되기도 한다.

어느 경우든, JPA와 Java8 Date/Time은 뭔가 조치를 취해주지 않으면 원하는 대로 쓸 수 없다.

데이터의 생성/수정 시각을 기록하는 JPA Auditing을 대상으로 그 조치 방법을 알아보자. 모든 엔티티가 상속해야 하는 BaseEntity라는 추상 클래스를 만들어서 이 클래스에 JPA Auditing을 적용하는 상황이다.

Jsr310JpaConverters.class를 활용하는 방법

Spring Data JPA 1.8 이상부터 사용가능한 방법으로, 아마 가장 간단한 방법일 것 같다.

 

아래와 같이 @EntityScanJsr310JpaConverters.class를 지정해주기만 하면 된다. 다만, Jsr310JpaConverters.class를 사용하지 않았다면 굳이 지정해 주지 않아도 자동 설정으로 처리될 basePackages도 명시적으로 지정해줘야만 엔티티를 로딩할 수 있다는 단점이 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing
@EntityScan(
        basePackageClasses = {Jsr310JpaConverters.class},  // basePackageClasses에 지정
        basePackages = {"homo.efficio.toy.member.domain"}) // basePackages도 추가로 반드시 지정해줘야 한다
@SpringBootApplication
public class MemberApplication {

    public static void main(String[] args) {
        SpringApplication.run(MemberApplication.class, args);
    }
}

 

엔티티에도 날짜/시간형 필드에 @Temporal(TemporalType.TIMESTAMP)를 붙여주지 않아도 된다. 사실은 붙이고 싶어도 붙일 수가 없다. Java EE API 문서에 보면 @Temporaljava.util.Date이나 java.util.Calendar에만 붙일 수 있게 되어 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.io.Serializable;
import java.time.LocalDateTime;

@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
public abstract class BaseEntity implements Serializable {

    @CreatedDate
    @Column(name = "created_at", updatable = false)
    private LocalDateTime createdDateTime;

    @LastModifiedDate
    @Column(name = "last_modified_at", updatable = true)
    private LocalDateTime lastModifiedDateTime;
}

위의 코드를 보면 BaseEntity에는 따로 무슨 조치를 취한 것 없이 깔끔하다.

Jsr310JpaConverters 클래스는 사실 다음에 설명할 Attribute Converter를 활용하는 방법을 Spring에서 구현해서 쓰기 편하게 Wrapping 해준 Jsr310Converters 클래스를 JPA에서 사용할 수 있게 해주는 클래스다.

Attribute Converter를 활용하는 방법

JPA 2.1 부터 Attribute Converter라는 기능이 도입되었다. AttributeConverter 클래스를 상속받는 자체 Converter를 만들면, Java8의 날짜/시간 데이터 타입을 JPA에서 인식할 수 있는 타입으로 자동으로 변환되게 할 수 있다.
아래는 java.time.LocalDateTime에 대한 Converter다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.Date;

import static java.time.Instant.ofEpochMilli;
import static java.time.LocalDateTime.ofInstant;
import static java.time.ZoneId.systemDefault;

@Converter
public class LocalDateTimePersistenceConverter implements AttributeConverter<LocalDateTime, Date> {

    @Override
    public Date convertToDatabaseColumn(LocalDateTime localDateTime) {
        return Date.from(localDateTime.atZone(systemDefault()).toInstant());
    }

    @Override
    public LocalDateTime convertToEntityAttribute(Date date) {
        return ofInstant(ofEpochMilli(date.getTime()), systemDefault());
    }
}

위의 코드는 LocalDateTime에 대한 구현체만 들어있는데, 앞에서 언급한 org.springframework.data.convert.Jsr310Converters 클래스는 LocalDate, LocalTime, LocalDateTime, Instant, ZoneId 모두에 대한 변환 기능을 구현해서 제공해주며, Spring Data JPA 1.8 이상이라면 앞에서 살펴본 것처럼 Jsr310JpaConverters를 통해 Spring Data JPA에서 사용할 수 있다.

자체 Converter를 만들었다고 끝난 것이 아니다. 어느 데이터에 이 Converter를 적용할지 지정해줘야 한다. 따라서 아래와 같이 BaseEntity에서 Converter에 의한 자동변환이 필요한 데이터에 @Convert 애노테이션을 지정해준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.io.Serializable;
import java.time.LocalDateTime;

@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
public abstract class BaseEntity implements Serializable {

    @CreatedDate
    @Convert(converter = LocalDateTimePersistenceConverter.class)  // <- @Converter를 지정 해줘야 한다.
    @Column(name = "created_at", updatable = false)
    private LocalDateTime createdDateTime;

    @LastModifiedDate
    @Convert(converter = LocalDateTimePersistenceConverter.class)  // <- @Converter를 지정 해줘야 한다.
    @Column(name = "last_modified_at", updatable = true)
    private LocalDateTime lastModifiedDateTime;
}

getter, setter를 변형해서 활용하는 방법

이 방법은 Jsr310Converters이나 AttributeConverter 등에 포함된 변환 로직을 그냥 getter, setter에 직접 심어버리는 방법으로 가장 직관적이고 간단하며, 외부 의존성도 적다. Spring을 사용하지 않는다면 이 방법으로 해결하면 된다. 다만, 타이핑 양은 좀 되지만 복붙신공이면 될 일이고.. ㅋㅋ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Date;

import static java.time.Instant.ofEpochMilli;
import static java.time.LocalDateTime.ofInstant;
import static java.time.ZoneId.systemDefault;
import static java.util.Objects.isNull;

@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
public abstract class BaseEntity implements Serializable {

    @CreatedDate
    @Temporal(TemporalType.TIMESTAMP)  // java.util.Date이므로 @Temporal을 붙여준다.
    @Column(name = "created_at", updatable = false)
    private Date createdDateTime;

    @LastModifiedDate
    @Temporal(TemporalType.TIMESTAMP)  // java.util.Date이므로 @Temporal을 붙여준다.
    @Column(name = "last_modified_at", updatable = true)
    private Date lastModifiedDateTime;



    // 변환 로직을 직접 구현

    public LocalDateTime getCreatedDateTime() {
        return getLocalDateTimeFrom(createdDateTime);
    }

    public void setCreatedDateTime(final LocalDateTime createdDateTime) {
        this.createdDateTime = getDateFrom(createdDateTime);
    }

    public LocalDateTime getLastModifiedDateTime() {
        return getLocalDateTimeFrom(lastModifiedDateTime);
    }

    public void setLastModifiedDateTime(final LocalDateTime lastModifiedDateTime) {
        this.lastModifiedDateTime = getDateFrom(lastModifiedDateTime);
    }

    private LocalDateTime getLocalDateTimeFrom(Date date) {
        return isNull(date) ? null : ofInstant(ofEpochMilli(date.getTime()), systemDefault());
    }

    private Date getDateFrom(LocalDateTime localDateTime) {
        return isNull(localDateTime) ? null : Date.from(localDateTime.atZone(systemDefault()).toInstant());
    }
}

정리

Java8의 java.time (JSR-310) 데이터를 DB에 저장해야 한다면,

  • Spring Data JPA 1.8 이상이라면 Jsr310JpaConverters 클래스를 사용하자.

  • Spring Data JPA 1.8 미만이지만, JPA 2.1 이상이라면 Jsr310Converters 클래스를 사용해서 AttributeConverter를 상속하고 변환 로직을 구현해서 사용하자.

  • 이도 저도 아니라면 java.time (JSR-310)java.util.Date를 상호 변환할 수 있는 로직을 getter/setter 안에 직접 구현해서 사용하자.

더 읽을 거리

[출처] http://blog.eomdev.com/java/2016/01/11/jpa_04_entityMapping.html

 

\x00xpw\x07\x03\x00\x00\x07\xE0\x05\x1Fx'
for column 'start_date' at row 1
...

또는 날짜/시간을 나타내는 컬럼이 MySQL에서 datetime 타입이 아니라 tinyblob 타입으로 생성되어서 아래와 같이 원치 않는 형식으로 저장되기도 한다.

어느 경우든, JPA와 Java8 Date/Time은 뭔가 조치를 취해주지 않으면 원하는 대로 쓸 수 없다.

데이터의 생성/수정 시각을 기록하는 JPA Auditing을 대상으로 그 조치 방법을 알아보자. 모든 엔티티가 상속해야 하는 BaseEntity라는 추상 클래스를 만들어서 이 클래스에 JPA Auditing을 적용하는 상황이다.

Jsr310JpaConverters.class를 활용하는 방법

Spring Data JPA 1.8 이상부터 사용가능한 방법으로, 아마 가장 간단한 방법일 것 같다.

 

아래와 같이 @EntityScanJsr310JpaConverters.class를 지정해주기만 하면 된다. 다만, Jsr310JpaConverters.class를 사용하지 않았다면 굳이 지정해 주지 않아도 자동 설정으로 처리될 basePackages도 명시적으로 지정해줘야만 엔티티를 로딩할 수 있다는 단점이 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.data.jpa.convert.threeten.Jsr310JpaConverters;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;

@EnableJpaAuditing
@EntityScan(
        basePackageClasses = {Jsr310JpaConverters.class},  // basePackageClasses에 지정
        basePackages = {"homo.efficio.toy.member.domain"}) // basePackages도 추가로 반드시 지정해줘야 한다
@SpringBootApplication
public class MemberApplication {

    public static void main(String[] args) {
        SpringApplication.run(MemberApplication.class, args);
    }
}

 

엔티티에도 날짜/시간형 필드에 @Temporal(TemporalType.TIMESTAMP)를 붙여주지 않아도 된다. 사실은 붙이고 싶어도 붙일 수가 없다. Java EE API 문서에 보면 @Temporaljava.util.Date이나 java.util.Calendar에만 붙일 수 있게 되어 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.io.Serializable;
import java.time.LocalDateTime;

@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
public abstract class BaseEntity implements Serializable {

    @CreatedDate
    @Column(name = "created_at", updatable = false)
    private LocalDateTime createdDateTime;

    @LastModifiedDate
    @Column(name = "last_modified_at", updatable = true)
    private LocalDateTime lastModifiedDateTime;
}

위의 코드를 보면 BaseEntity에는 따로 무슨 조치를 취한 것 없이 깔끔하다.

경축! 아무것도 안하여 에스천사게임즈가 새로운 모습으로 재오픈 하였습니다.
어린이용이며, 설치가 필요없는 브라우저 게임입니다.
https://s1004games.com

Jsr310JpaConverters 클래스는 사실 다음에 설명할 Attribute Converter를 활용하는 방법을 Spring에서 구현해서 쓰기 편하게 Wrapping 해준 Jsr310Converters 클래스를 JPA에서 사용할 수 있게 해주는 클래스다.

Attribute Converter를 활용하는 방법

JPA 2.1 부터 Attribute Converter라는 기능이 도입되었다. AttributeConverter 클래스를 상속받는 자체 Converter를 만들면, Java8의 날짜/시간 데이터 타입을 JPA에서 인식할 수 있는 타입으로 자동으로 변환되게 할 수 있다.
아래는 java.time.LocalDateTime에 대한 Converter다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import java.time.Instant;
import java.time.LocalDateTime;
import java.util.Date;

import static java.time.Instant.ofEpochMilli;
import static java.time.LocalDateTime.ofInstant;
import static java.time.ZoneId.systemDefault;

@Converter
public class LocalDateTimePersistenceConverter implements AttributeConverter<LocalDateTime, Date> {

    @Override
    public Date convertToDatabaseColumn(LocalDateTime localDateTime) {
        return Date.from(localDateTime.atZone(systemDefault()).toInstant());
    }

    @Override
    public LocalDateTime convertToEntityAttribute(Date date) {
        return ofInstant(ofEpochMilli(date.getTime()), systemDefault());
    }
}

위의 코드는 LocalDateTime에 대한 구현체만 들어있는데, 앞에서 언급한 org.springframework.data.convert.Jsr310Converters 클래스는 LocalDate, LocalTime, LocalDateTime, Instant, ZoneId 모두에 대한 변환 기능을 구현해서 제공해주며, Spring Data JPA 1.8 이상이라면 앞에서 살펴본 것처럼 Jsr310JpaConverters를 통해 Spring Data JPA에서 사용할 수 있다.

자체 Converter를 만들었다고 끝난 것이 아니다. 어느 데이터에 이 Converter를 적용할지 지정해줘야 한다. 따라서 아래와 같이 BaseEntity에서 Converter에 의한 자동변환이 필요한 데이터에 @Convert 애노테이션을 지정해준다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.io.Serializable;
import java.time.LocalDateTime;

@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
public abstract class BaseEntity implements Serializable {

    @CreatedDate
    @Convert(converter = LocalDateTimePersistenceConverter.class)  // <- @Converter를 지정 해줘야 한다.
    @Column(name = "created_at", updatable = false)
    private LocalDateTime createdDateTime;

    @LastModifiedDate
    @Convert(converter = LocalDateTimePersistenceConverter.class)  // <- @Converter를 지정 해줘야 한다.
    @Column(name = "last_modified_at", updatable = true)
    private LocalDateTime lastModifiedDateTime;
}

getter, setter를 변형해서 활용하는 방법

이 방법은 Jsr310Converters이나 AttributeConverter 등에 포함된 변환 로직을 그냥 getter, setter에 직접 심어버리는 방법으로 가장 직관적이고 간단하며, 외부 의존성도 적다. Spring을 사용하지 않는다면 이 방법으로 해결하면 된다. 다만, 타이핑 양은 좀 되지만 복붙신공이면 될 일이고.. ㅋㅋ

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import javax.persistence.*;
import java.io.Serializable;
import java.time.LocalDateTime;
import java.util.Date;

import static java.time.Instant.ofEpochMilli;
import static java.time.LocalDateTime.ofInstant;
import static java.time.ZoneId.systemDefault;
import static java.util.Objects.isNull;

@EntityListeners(AuditingEntityListener.class)
@MappedSuperclass
public abstract class BaseEntity implements Serializable {

    @CreatedDate
    @Temporal(TemporalType.TIMESTAMP)  // java.util.Date이므로 @Temporal을 붙여준다.
    @Column(name = "created_at", updatable = false)
    private Date createdDateTime;

    @LastModifiedDate
    @Temporal(TemporalType.TIMESTAMP)  // java.util.Date이므로 @Temporal을 붙여준다.
    @Column(name = "last_modified_at", updatable = true)
    private Date lastModifiedDateTime;



    // 변환 로직을 직접 구현

    public LocalDateTime getCreatedDateTime() {
        return getLocalDateTimeFrom(createdDateTime);
    }

    public void setCreatedDateTime(final LocalDateTime createdDateTime) {
        this.createdDateTime = getDateFrom(createdDateTime);
    }

    public LocalDateTime getLastModifiedDateTime() {
        return getLocalDateTimeFrom(lastModifiedDateTime);
    }

    public void setLastModifiedDateTime(final LocalDateTime lastModifiedDateTime) {
        this.lastModifiedDateTime = getDateFrom(lastModifiedDateTime);
    }

    private LocalDateTime getLocalDateTimeFrom(Date date) {
        return isNull(date) ? null : ofInstant(ofEpochMilli(date.getTime()), systemDefault());
    }

    private Date getDateFrom(LocalDateTime localDateTime) {
        return isNull(localDateTime) ? null : Date.from(localDateTime.atZone(systemDefault()).toInstant());
    }
}

정리

Java8의 java.time (JSR-310) 데이터를 DB에 저장해야 한다면,

  • Spring Data JPA 1.8 이상이라면 Jsr310JpaConverters 클래스를 사용하자.

  • Spring Data JPA 1.8 미만이지만, JPA 2.1 이상이라면 Jsr310Converters 클래스를 사용해서 AttributeConverter를 상속하고 변환 로직을 구현해서 사용하자.

  • 이도 저도 아니라면 java.time (JSR-310)java.util.Date를 상호 변환할 수 있는 로직을 getter/setter 안에 직접 구현해서 사용하자.

더 읽을 거리

[출처] http://blog.eomdev.com/java/2016/01/11/jpa_04_entityMapping.html

 

본 웹사이트는 광고를 포함하고 있습니다.
광고 클릭에서 발생하는 수익금은 모두 웹사이트 서버의 유지 및 관리, 그리고 기술 콘텐츠 향상을 위해 쓰여집니다.
번호 제목 글쓴이 날짜 조회 수
35 [EMF] EMF Tutorial EMF 튜터리얼 file 졸리운_곰 2023.08.23 283
34 Eclipse RAP Tutorial for Beginners - Workbench Application (OLD) file 졸리운_곰 2021.01.30 323
33 Learn Eclipse GMF in 15 minutes file 졸리운_곰 2019.11.27 253
32 [Eclipse] GEF entry series (10, an implementation of the form) file 졸리운_곰 2019.11.25 212
31 GEF Programmer Guide 번역 졸리운_곰 2019.11.25 227
30 Learn Eclipse GMF in 15 minutes file 졸리운_곰 2019.11.20 224
29 RCP 에디터 정리 졸리운_곰 2019.11.20 333
28 다른 그림과 관련하여 GEF 편집기 레이아웃에서 그림의 위치 제한 조건을 동적으로 계산 Dynamically calculating the position constraints for a figure in a GEF editor layout in relation to another figure file 졸리운_곰 2019.11.20 277
27 RCP 등에서 .mf 파일로 다른 프로젝트 익스포트 포함시 라이브러리(메소드)를 찾지 못할 때, Eclipse RCP - cant resolve importing libraries in final build file 졸리운_곰 2019.10.15 410
26 ESE2006-EclipseModelingSymposium15_GMF.pdf file 졸리운_곰 2019.09.21 302
25 GMF_Creation_Review.pdf file 졸리운_곰 2019.09.21 366
24 Eclipse EMF and GMF Tutorial file 졸리운_곰 2019.09.21 222
23 GMF Tutorial/ko file 졸리운_곰 2019.09.20 347
22 Model Driven Architecture approach to domain of graphical editors file 졸리운_곰 2019.09.20 268
21 Single_Sourcing_RAP_RCP_en.pdf file 졸리운_곰 2019.05.15 311
20 Rich client platform 설명 및 배우기 참고 졸리운_곰 2019.05.15 309
19 Rich Ajax Platform, Part 1: 소개 file 졸리운_곰 2019.05.15 320
18 또 하나의 크로스 플랫폼: Eclipse RAP file 졸리운_곰 2019.05.15 319
17 Eclipse 4 RCP 튜토리얼(완료) file 졸리운_곰 2019.05.14 977
16 Updating UI in Eclipse RCP 졸리운_곰 2015.11.07 408
대표 김성준 주소 : 경기 용인 분당수지 U타워 등록번호 : 142-07-27414
통신판매업 신고 : 제2012-용인수지-0185호 출판업 신고 : 수지구청 제 123호 개인정보보호최고책임자 : 김성준 sjkim70@stechstar.com
대표전화 : 010-4589-2193 [fax] 02-6280-1294 COPYRIGHT(C) stechstar.com ALL RIGHTS RESERVED