티스토리 뷰

728x90
반응형

DATE_ADD, CURRENT_DATE , NOW() 를 insert 하려고 했는데 null 이 나와버렸다.

[예제코드]

 async createOwnedCoupon(user: Users, newCoupon: CreateOwnedCouponDto) {
    try {
      // 1. 등록할 쿠폰이 존재하는지 확인
      const coupon = await this.couponsRepository
        .createQueryBuilder('coupons')
        .where('couponId = :couponId', { couponId: newCoupon.couponId })
        .getOne();

      if (!coupon) {
        throw new NotFoundException('해당 쿠폰은 존재하지 않습니다.');
      }


      // 쿠폰(Coupon)을 ownedCoupon에 등록
      await await this.userOwnCouponsRepository
      .createQueryBuilder('ownedCoupons')
      .insert(OwnedCoupons)
      .values({
        expirationDate: `DATE_ADD(CURRENT_DATE, INTERVAL  %{coupon.validPeriod} DAY)`,
        UserId: user.userId,
        CouponId: newCoupon.couponId,
      });


      // 새로운 쿠폰정보를 추가한 후 현재유저의 보유쿠폰 리스트를 리스폰스합니다.
      const couponList = await this.userOwnCouponsRepository
        .createQueryBuilder('ownedCoupons')
        .where('UserId = :userId', { userId: user.userId })
        .andWhere('usedDate is null') // 아직 사용 안한 쿠폰
        .getMany();

      return couponList;
    } catch (error) {
      // 에러가 발생하면 롤백
      throw error;
    }
  }

 

typeorm에서 날짜데이터를 insert 하려고했더니...

expirationDate 에 null 값으로 매핑된다...

values({
	expirationDate: `DATE_ADD(CURRENT_DATE, INTERVAL  %{coupon.validPeriod} DAY)`,
});

 

DATE_ADD 나, CURRENT_DATE 는 select 문에서는 조회할 수 있어도, insert문에서는 값이 안나온다...

SELECT CURRENT_DATE(); // 2022-11-03

SELECT CURRENT_DATE; // 2022-11-03

SELECT NOW(); // 2022-11-03 21:00:46

SELECT DATE_ADD( CURRENT_DATE(), INTERVAL 14 DAY ); // 2022-11-17

SELECT DATE_ADD( CURRENT_DATE, INTERVAL 14 DAY ); // 2022-11-17

SELECT DATE_ADD( NOW(), INTERVAL 14 DAY ); // 2022-11-17 21:02:16

 

[참고]

 

여기에 대한 이유를 구글링을 해봐도 못찾아서 다른 방법을 모색했다.

그러면 new Date() 로 하면 되지 않느냐 하지만

Date() 객체를 얻어서 계산을 해야하고 타임포맷을 맞춰야하는 작업이 있어서 이방법을 선택하지 않았다.


js-joda 가 무엇?

- javascript 의 Date는 불변성 보장을 하지 않는다.

- 백엔드에서 날짜타입은 불변성 보장을 필수로 해야한다.

- Js-joda 는 날짜데이터에 있어서 불변성을 보장한다.

- 타임존도 지원한다.

https://jojoldu.tistory.com/610

 

NestJS에서 응답/요청 객체 직렬화 (Serialization) 하기

저 같은 경우에 최대한 Dto를 불변으로 만들기 위해 setter나 public 필드는 배제하는데요. 어쩔수 없이 public 필드 (혹은 public setter)를 써야하는 경우 (TypeORM의 Entity 등)를 제외하고는 무조건이다 싶

jojoldu.tistory.com

https://jojoldu.tistory.com/600

 

js-joda 로 TypeORM Date 타입 대체하기 (with NestJS)

JavaScript 의 Date Type은 JavaScript의 단점을 이야기할때 항상 거론되는 점인데요. javascript-date-type-is-horribly-broken 위 글에서 언급한 연산에 관한 문제도 있지만, 단순히 +1 Day를 해야하는데도 아래와 같

jojoldu.tistory.com


 async createOwnedCoupon(user: Users, newCoupon: CreateOwnedCouponDto) {
    const queryRunner = this.dataSource.createQueryRunner();
    await queryRunner.connect();
    await queryRunner.startTransaction();

    try {
      // 1. 등록할 쿠폰이 존재하는지 확인
      const coupon = await this.couponsRepository
        .createQueryBuilder('coupons')
        .where('couponId = :couponId', { couponId: newCoupon.couponId })
        .getOne();

      if (!coupon) {
        throw new NotFoundException('해당 쿠폰은 존재하지 않습니다.');
      }

      // 2. 쿠폰발행날짜(issuedDate): 쿠폰 등록 요청 시기로 default값 으로 세팅
      const issuedDate = LocalDateTime.now();

      // 3. 쿠폰만료날짜(expirationDate): null -> "쿠폰발행날짜 + 쿠폰기간" 으로 변경
      const expirationDate = issuedDate.plusDays(coupon.validPeriod);

      // 4. 쿠폰(Coupon)을 ownedCoupon에 등록
      await queryRunner.manager.getRepository(OwnedCoupons).save({
        issuedDate,
        expirationDate,
        UserId: user.userId,
        CouponId: newCoupon.couponId,
      });

      // 데이터베이스에 저장
      queryRunner.commitTransaction();

      // 5. 새로운 쿠폰정보를 추가한 후 현재유저의 보유쿠폰 리스트를 리스폰스합니다.
      const couponList = await this.userOwnCouponsRepository
        .createQueryBuilder('ownedCoupons')
        .where('UserId = :userId', { userId: user.userId })
        .andWhere('usedDate is null') // 아직 사용 안한 쿠폰
        .getMany();

      return couponList;
    } catch (error) {
      // 에러가 발생하면 롤백
      await queryRunner.rollbackTransaction();
      throw error;
    }
  }
}

 

일단 그냥 불러서 해봤더니... insert에서 에러가 발생하여 데이터추가가 안된다.

디버깅을 해보니 js-joda로

쿠폰발급날짜(issuedDate) 와 쿠폰만료날짜(expirationDate) 값이 나왔다.

그런데 신기하게도 겉으로는 {YYYY}-{mm}-{dd}T{h}:{m}:{s}.{ms} 구조로 보이지만

_date필드와 _time 필드가 존재했다.

 

디버그 콘솔에서는 다음과 같은 에러가 떴다.

ERROR [ExceptionsHandler] Column count doesn't match value count at row 1

 

위 에러의 원인은 insert 하려는 데이터필드는 8개인데, 값은 10개인것이다!

데이터를 등록할때 _date, _time 도 같이 포함해서 등록하는 거다. 

INSERT INTO `ownedCoupons`(
	`ownedCouponId`, 
	`issuedDate`,
	`usedDate`,
	`expirationDate`,
	`isExtendDate`,	
	`UserId`,
	`CouponId`,
	`OrderId`
) VALUES (
	DEFAULT, 
	`_date` = '2022-11-03', 
	`_time` = '21:08:16.130', 
	DEFAULT, 
	`_date` = '2022-11-10', 
	`_time` = '21:08:16.130', 
	DEFAULT,
	5, 
	1, 
	DEFAULT
)"

 

그러면  요구하는 데이터 필드의 개수에 맞추기 위해서는

_date , _time 으로 분리된 것들을 다시 하나로 합쳐서 issuedDate 값으로 나타내야한다.

 

여기서는 객체를 직렬화 시켜야한다.

그럴려면 ValueTransformer 가 필요하다. 참고 블로그를 보면서 따라해봤다..

 

[ LocalDateTimeTransformer ]

LocalDateTimeTransformer을 만들었는데...

DateTimeUtil 은 참고 블로그 작성자가 만든 객체이다. 

import { LocalDateTime } from 'js-joda';
import { ValueTransformer } from 'typeorm';

export class LocalDateTimeTransformer implements ValueTransformer {
  // entity -> db 로 넣을 때
  to(entityValue: LocalDateTime): Date {
    return DateTimeUtil.toDate(entityValue);
  }

  // db -> entity 로 가져올 때
  from(databaseValue: Date): LocalDateTime {
    return DateTimeUtil.toLocalDateTime(databaseValue);
  }
}

 

[ DateTimeUtil ]

참고 블로그에도 toLocalDateTimeBy() 만 정의되어있지..

toDate(), toLocalDateTime() 는 뒤져봐도 안보인다.  어떻게 만들어야될지 생각이 나지 않았다 ㅠㅠ

import { DateTimeFormatter, LocalDateTime } from 'js-joda';

export class DateTimeUtil {
  private static DATE_FORMATTER = DateTimeFormatter.ofPattern('yyyy-MM-dd');
  private static DATE_TIME_FORMATTER = DateTimeFormatter.ofPattern('yyyy-MM-dd HH:mm:ss');

  static toLocalDateTimeBy(strDate: string): LocalDateTime {
    if (!strDate) {
      return null;
    }

    return LocalDateTime.parse(strDate, DateTimeUtil.DATE_TIME_FORMATTER);
  }
}

 

 

js-joda 공식 다큐먼트
 

Manual | js-joda

Formatting / Parsing Formatting To format a date and/or time, create a DateTimeFormatter and pass it to the .format method of a LocalDate, LocalTime, LocalDateTime, or ZonedDateTime instance. js-joda built-in DateTimeFormatter parses and formats dates and

js-joda.github.io

 

GitHub - js-joda/js-joda: :clock2: Immutable date and time library for javascript

:clock2: Immutable date and time library for javascript - GitHub - js-joda/js-joda: :clock2: Immutable date and time library for javascript

github.com

 

 

js-joda timezone
 

GitHub - js-joda/js-joda: :clock2: Immutable date and time library for javascript

:clock2: Immutable date and time library for javascript - GitHub - js-joda/js-joda: :clock2: Immutable date and time library for javascript

github.com

 

 

convert js-joda -> Date
 

js-joda object to native Date object? · Issue #122 · js-joda/js-joda

Hopefully I'm only overlooking it in the documentation. How do you get a native Date from a js-joda object? For example: var jd = joda.ZonedDateTime.now().plusMinutes(30) var d = jd.toDate() //...

github.com

 

일단은 검색은 해봤는데.. 그래도 감이 오지않았고 과제가 쌓이고 시간이 부족해서 시도해보지 못했다.

다른방법으로 찾아봐야겠다...

현재 내 수준에 있어서 js-joda가 너무 어려웠고.. 나중에 리팩토링할때 도전해봐야겠다...ㅜㅜ!

 

번외지만 mysql datetime issue도 있다..

https://github.com/typeorm/typeorm/issues/7430

 

MySQL DateTime issue · Issue #7430 · typeorm/typeorm

Issue Description MySQL Datetime is not saving ISO8601 Date. Expected Behavior Save time without issues. Actual Behavior Time get changed when it saved to the database. Since it had issues saving I...

github.com


[보충설명] 직렬화 와 역직렬화?

직렬화(Serialization)

  • 객체를 직렬화하여 전송가능한 파일 형태로 만드는 것.
  • 객체들의 데이터를 연속적인 데이터로 변형하여 Stream을 통해 데이터를 읽도록 한다.
  • 객체들을 통째로 파일로 저장하거나 전송할 때 주로 사용.

 

역직렬화(Deserialization)

  • 직렬화된 파일 등을 다시 객체의 형태로 만드는 것
  • 저장된 파일을 읽거나 전송된 스트림 데이터를 읽어 원래 객체의 형태로 복원한다.

 

[참고]


momentjs를 사용

상황1: moment() 부분에서 에러발생

import moment from 'moment';

async createOwnedCoupon(user: Users, newCoupon: CreateOwnedCouponDto) {
	...
    
	// 2. 쿠폰발행날짜(issuedDate): 쿠폰 등록 요청 시기로 default값 으로 세팅
	const now = moment();
	const issuedDate = now.format('yyyy-MM-dd HH:mm:ss');

	// 3. 쿠폰만료날짜(expirationDate): null -> "쿠폰발행날짜 + 쿠폰기간" 으로 변경
	const expirationDate = now.add(coupon.validPeriod, 'day').format('yyyy-MM-dd HH:mm:ss');
	console.log(issuedDate);
	console.log(expirationDate);
    
    ...
}

 

발생에러:  ERROR [ExceptionsHandler] (0 , moment_1.default) is not a function

해결방안

import * as moment from 'moment-timezone';

 

상황2 : 타입포맷이 이상한 현상

moment().format('yyyy-MM-dd HH:mm:ss') 로 타임포맷을 정했으나

디버깅 해보니까 issuedDate와 , expirationDate 모두 '2022-11-Fr 01:40:47' 로 출력된다.

내가 원하는 타임포맷은 '2022-11-04 01:44:05' 와 같은 형식이다.

현재 mysql 8.0의 타임스탬프도 내가 원하는 형식으로 되어있기 때문이다.

해결방안: 타임포맷을 'YYYY-MM-DD HH:mm:ss' 로 고치면된다 :)

 

[참고]

 

 

728x90
반응형
반응형
공지사항
최근에 올라온 글
최근에 달린 댓글
Total
Today
Yesterday
링크
«   2024/05   »
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
글 보관함