본문 바로가기

카테고리 없음

MySQL - ON UPDATE CURRENT_TIMESTAMP

운영중인 데이터가 잘못 들어가 있는 것이 있어서 보정을 필요로 했다.

개발 환경에서 테스트를 하는데 업데이트 시간을 담고있는 컬럼이 업데이트가 되고 있음을 알았다.

 

테이블의 스키마를 확인해보니 아래와 같이 on update 문이 붙어있었다.

updatedAt timestamp default CURRENT_TIMESTAMP not null on update CURRENT_TIMESTAMP,

11.2.5 Automatic Initialization and Updating for TIMESTAMP and DATETIME 문서에 설명하고 있다.

 

이전 회사의 경우 defaulton update 같은 DB 지향적인 것을 지양했다. 목적은 데이터 일관성적인 측면이었다.

왜냐하면 분산 DB 시스템을 사용해서 애플리케이션의 단일 데이터가 여러 DB 시스템에 나누어있게 되었는데, DB 시스템마다 미묘한 시스템 시간이 다르게 입력될 수 있기 때문이다.

 

단일 DBMS를 사용한다고 하면 굳이 생각할 필요가 없을 수 있다. 하지만, 운영을 하다보면 데이터 보정 등의 작업을 수행해야 한다.

비즈니스적인 목적이 아닌 데이터 보정적인 측면에서 보정하는 경우 업데이트 시간이 바뀌게 되면 이력 등을 확인할 때 문제가 될 수 있다.

고객이나 사용자가 값을 수정한 것이 아닌 업데이트 수행 날짜가 바뀌어져 버리게 된 것이다.

해결책

JPA 사용시

JPA를 사용하고 있다면 @CreationTimestamp 이나 @UpdateTimestamp 애너테이션이 붙어있을 가능성이 있다.

JPA에서 UPDATE를 위해서는 일단 엔티티를 가져와서 수정을 하고 저장을 한다. 애플리케이션 코드상에서 업데이트를 할 수도 있지만, DB에 이런 조건을 걸어서 사용을 하고 있다면 @UpdateTimestamp 애너테이션이 붙어있을 가능성도 높다.

Query 사용시

JdbcTemplate 이나 MyBatis, Jdbc 등으로 쿼리를 직접적으로 사용하고 있다면 JPA 처럼 수행하면 된다.

저장된 값을 읽어와서 UPDATE 시에 해당 값을 명시적으로 넣어주면 된다.

 

위에 언급된 MySQL 레퍼런스에도 해당 내용이 언급되어 있다.

To prevent an auto-updated column from updating when other columns change, explicitly set it to its current value.
자동 업데이트 컬럼이 다른 컬럼이 바뀔 때 업데이트를 막기위해서는 현재의 값을 명시적으로 설정을 한다.

JPA에서 @CreationTimestamp 이나 @UpdateTimestamp 애너테이션은 값 생성에 대한 구현은 아래와 같다.

  • @CreationTimestamp -> CreationTimestampGeneration
  • @UpdateTimestamp -> UpdateTimestampGeneration

CreationTimestampGeneration, UpdateTimestampGeneration 는 애플리케이션이 가동되면 부트스트랩 과정에서 메타데이타를 빌드하는 과정에서 생성이 된다.

package org.hibernate.boot.model.process.spi;

public class MetadataBuildingProcess {

	public static MetadataImplementor complete(
			final ManagedResources managedResources,
			final BootstrapContext bootstrapContext,
			final MetadataBuildingOptions options) {
		// ..            
		processor.prepare();

		processor.processTypeDefinitions();
		processor.processQueryRenames();
		processor.processAuxiliaryDatabaseObjectDefinitions();

		processor.processIdentifierGenerators();
		processor.processFilterDefinitions();
		processor.processFetchProfiles();

		final Set<String> processedEntityNames = new HashSet<>();
		processor.prepareForEntityHierarchyProcessing();
		processor.processEntityHierarchies( processedEntityNames );
		processor.postProcessEntityHierarchies();

		processor.processResultSetMappings();
		processor.processNamedQueries();

		processor.finishUp();

@Entity 들에 붙어있는 애너테이션들을 분석해서 처리하게 되는데 @CreationTimestamp 이나 @UpdateTimestamp 도 그 중에 일부이다.

AnnotationBinder 클래스의 processElementAnnotations 에서 수행하는데 꽤 라인수가 길다.

package org.hibernate.cfg;

public final class AnnotationBinder {
	// ..
	private static void processElementAnnotations(
			PropertyHolder propertyHolder,
			Nullability nullability,
			PropertyData inferredData,
			HashMap<String, IdentifierGeneratorDefinition> classGenerators,
			EntityBinder entityBinder,
			boolean isIdentifierMapper,
			boolean isComponentEmbedded,
			boolean inSecondPass,
			MetadataBuildingContext context,
			Map<XClass, InheritanceState> inheritanceStatePerClass) throws MappingException {

		// ..
					propertyBinder.makePropertyValueAndBind();