JSR310 Guide - redutan/redutan.github.io GitHub Wiki

JSR310

java8μ—λŠ” μƒˆλ‘œμš΄ λ‚ μ§œ, μ‹œκ°„ ν˜•μ‹μ΄ 좔가됨.

  • LocalDateTime, LocalDate, LocalTime
  • OffsetDateTime, ZonedDateTime
  • Instant, Duration, Clock, Period, ZonedOffset
  • Chronology, ChronoLocalDate, ChronoLocalDate, ChronoZonedDateTime

μ°Έκ³  : http://www.oracle.com/technetwork/articles/java/jf14-date-time-2125367.html

기쑴의 λ‚ μ§œν˜•μ‹μΈ Date, Calendar 이 잘 μ•Œλ‹€μ‹œν”Ό μ—¬λŸ¬κ°€μ§€ 섀계적인 문제점이 μ‘΄μž¬ν•˜κ³  μžˆμ—ˆλŠ”λ° 그것을 ν•΄κ²°ν•˜κ³ μž 함 그런데 μ‹ κ·œ μŠ€νŒ©μ΄λ‹€ λ³΄λ‹ˆ μ—¬λŸ¬ λΌμ΄λΈŒλŸ¬λ¦¬λ‚˜ ν”„λ ˆμž„μ›Œν¬μ—μ„œ κΈ°λ³Έ 지원이 μ•ˆλ˜λŠ” κ²½μš°κ°€ 많음. 고둜 μ΄λŸ¬ν•œ 뢀뢄을 직접 ν•΄κ²°ν•˜κ±°λ‚˜ λ‹€λ₯Έ λ°©μ‹μœΌλ‘œ ν’€μ–΄μ•Ό 함

JPA & HIbernate

AS-IS

@Entity
@Getter
@EqualsAndHashCode
@ToString
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Entity {
    @Id
    @GeneratedValue
    private Long seq;
 
    @Column(length = 200, nullable = false)
    private String title;
 
    @Lob
    @Column
    private String content;
 
    @Column(nullable = false, insertable = false)
    @ColumnDefault("0")
    private int hit;
 
    @Column(nullable = false, insertable = false)
    @ColumnDefault("CURRENT_TIMESTAMP")
    @Temporal(Date) // Temporal 섀정이 μš”κ΅¬λ¨
    private Date createdAt;
}

TO-BE

μ΅œμ†Œ hibernate 5.x 이상이 μš”κ΅¬

@Entity
@Getter
@EqualsAndHashCode
@ToString
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Entity {
    @Id
    @GeneratedValue
    private Long seq;
 
    @Column(length = 200, nullable = false)
    private String title;
 
    @Lob
    @Column
    private String content;
 
    @ColumnDefault("0")
    @Column(nullable = false, insertable = false)
    private int hit;
 
    @ColumnDefault("CURRENT_TIMESTAMP")
    @Column(nullable = false, insertable = false)
    private ZonedDateTime createdAt;
}

JSON

μƒν˜Έ κ°„ APIλ₯Ό μš”μ²­, 응닡 μ‹œ λ‚΄λΆ€μ μœΌλ‘œ λ‚ μ§œμ‹œκ°„ νƒ€μž…μ„ timestamp-milliseconds(long, μ΄ν•˜ timeMillis) ν˜•μœΌλ‘œ ν†΅μ‹ ν•œλ‹€κ³  κ°€μ •

Server(Serializer)

  • κΈ°λ³Έ 섀정이 timestamp-nanoseconds 이기 λ•Œλ¬Έμ— 변경이 μš”λ§

application.yml

spring:
  jackson:
    serialization:
      write-date-timestamps-as-nanoseconds: false

Client (Deserializer)

역직렬화 ν•˜λŠ” ν΄λΌμ΄μ–ΈνŠΈ μž…μž₯μ—μ„œλŠ” λ‹¨μˆœ μ„€μ •λ§ŒμœΌλ‘œλŠ” 해결이 λΆˆκ°€λŠ₯ κ·Έλž˜μ„œ 직접 jackson Deserializer μΈν„°νŽ˜μ΄μŠ€λ₯Ό κ΅¬ν˜„ν•˜λŠ” μ»€μŠ€ν…€ 역직렬화 μ»΄ν¬λ„ŒνŠΈλ₯Ό μ œκ³΅ν•˜κ³  ν•΄λ‹Ή 섀정을 기본으둜 μ˜¬λ €μ•Όν•¨

ZonedDateTimeDeserizlizer.java

public class ZonedDateTimeDeserializer extends StdDeserializer<ZonedDateTime> {
 
    public ZonedDateTimeDeserializer() {
        super(ZonedDateTime.class);
    }
 
    @SuppressWarnings("DuplicateThrows")
    @Override
    public ZonedDateTime deserialize(JsonParser jp, DeserializationContext ctxt) throws IOException, JsonProcessingException {
        long timeMillis = jp.getLongValue();
        if (timeMillis == 0) {
            return null;
        }
        return ZonedDateTime.ofInstant(Instant.ofEpochMilli(timeMillis), ZoneId.systemDefault());
    }
}

CustomerJsonComponent.java

@JsonComponent
class CustomerJsonComponent {
    public static class DefaultZonedDateTimeDeserializer extends ZonedDateTimeDeserializer {
        private static final long serialVersionUID = 3414058555754703366L;
    }
}

FreeMarker

기본적으둜 FreeMarkerμ—μ„œ JSR310 에 κ΄€λ ¨ν•œ νƒ€μž…μ„ μ§€μ›ν•˜μ§€ μ•ŠμŒ!!! μ˜€ν”ˆμ†ŒμŠ€κ°€ μžˆμ–΄μ„œ 그것을 μ΄μš©ν•˜λŠ” κ²ƒμœΌλ‘œ κ²°μ •

build.gradle

compile "no.api.freemarker:freemarker-java8:$freemarkerJava8Version"

FreeMarkerConfiguration.java

@Configuration("freeMarkerCustomConfiguration")
@Slf4j
public class FreeMarkerConfiguration {
    @Autowired
    private freemarker.template.Configuration freeMarkerConfiguration;
 
    @PostConstruct
    public void postConstruct() {
        log.info("FreeMarkerConfiguration.postConstruct");
        // freeMarker java8 νƒ€μž… 지원 μ„€μ •
        freeMarkerConfiguration.setObjectWrapper(new Java8ObjectWrapper(freemarker.template.Configuration.VERSION_2_3_26));
    }
}

Usage

sample.ftl

<tbody>
    <#list samples as sample>
    <tr>
        <td class="text-right">${sample.seq}</td>
        <td><a href="/samples/${sample.seq}">${sample.title}</a></td>
        <td>${sample.content}</td>
        <td class="text-right">${sample.hit}</td>
        <!-- μ•„λž˜μ™€ 같이 zonedDateTimeObj.format(formatString) ν˜•νƒœλ‘œ μ‚¬μš©ν•˜λ©΄λ¨ -->
        <td class="text-center">${sample.createdAt.format("yyyy.MM.dd HH:mm:ss")}</td>
    </tr>
    </#list>
    <#if !samples?has_content>
    <tr>
        <td colspan="5">ν‘œμ‹œν•  정보가 μ‘΄μž¬ν•˜μ§€ μ•ŠμŠ΅λ‹ˆλ‹€.</td>
    </tr>
    </#if>
</tbody>
⚠️ **GitHub.com Fallback** ⚠️