JPA에서는 persistence context 라는 개념이 있어서, 사실상 DB와 Application 사이에 하나의 proxy가 더 존재한다. 캐시나 관리 등의 측면에서 장점도 분명이 있지만, mybatis와 같이 direct로 데이터를 주고 받을 때와 다르게 data의 sync가 맞지 않으면 곤란한 경우가 발생한다. 그 중에 하나가 datetime 문제이다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
@Entity @Table(schema = "foo_table", name = "foo") @Data @RequiredArgsConstructor(staticName = "newInstance") public class FooEntity implements Serializable { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Integer idx; @Column @NonNull private String foo; @Column private LocalDateTime createdAt; @PrePersist public void persist() { createdAt = LocalDateTime.now(); } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
@RunWith(SpringRunner.class) @SpringBootTest @Transactional @Rollback public class FooJpaRepositoryTest { @Inject private FooJpaRepository dut; @Test public void test() throws InterruptedException { dut.save(FooEntity.newInstance("Foo1")); dut.save(FooEntity.newInstance("Foo2")); dut.save(FooEntity.newInstance("Foo3")); List result = dut.findByOrderByCreatedAtDesc(); for (FooEntity foo : result) { System.out.println(foo); } } } |
위처럼 시간에 대한 역순을 출력하게 되면, 당연히 foo3, foo2, foo1의 출력을 기대하게 된다. 하지만 결과는 다음과 같다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
Hibernate: /* select generatedAlias0 from FooEntity as generatedAlias0 order by generatedAlias0.createdAt desc */ select fooentity0_.idx as idx1_7_, fooentity0_.created_at as created_2_7_, fooentity0_.foo as foo3_7_ from foo fooentity0_ order by fooentity0_.created_at desc 2016-12-06 14:03:05.447 TRACE 26324 --- [ main] o.h.type.descriptor.sql.BasicExtractor : extracted value ([idx1_7_] : [INTEGER]) - [1] 2016-12-06 14:03:05.447 TRACE 26324 --- [ main] o.h.type.descriptor.sql.BasicExtractor : extracted value ([idx1_7_] : [INTEGER]) - [2] 2016-12-06 14:03:05.447 TRACE 26324 --- [ main] o.h.type.descriptor.sql.BasicExtractor : extracted value ([idx1_7_] : [INTEGER]) - [3] FooEntity(idx=1, foo=Foo1, createdAt=2016-12-06T14:03:05.314) FooEntity(idx=2, foo=Foo2, createdAt=2016-12-06T14:03:05.343) FooEntity(idx=3, foo=Foo3, createdAt=2016-12-06T14:03:05.345) |
분명 쿼리는 created_at desc임에도 불구하고, 데이터는 asc 정렬로 표기된다. 그런데 rollback=false로 설정하여 DB 데이터를 확인해보면,
1 2 3 4 |
1 Foo1 2016-12-06 14:03:05 2 Foo2 2016-12-06 14:03:05 3 Foo3 2016-12-06 14:03:05 |
datetime에 대한 값이 milli-sec 단위는 잘려서 들어간다. 즉, desc ordering을 하더라도, 값이 동일하기 때문에 foo1, foo2, foo3으로 select 결과가 나온다. 다만, jpa에서는 persistence context에 해당 값들이 milli-sec 잘리지 않은 채로 캐시되어 있기 때문에, 정작 결과값은 desc ordering이 되지 않은 것처럼 출력이 된다. 실제로 각 save사이에 1초 sleep을 주게 되면, 원하는 값이 출력된다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 |
Hibernate: /* select generatedAlias0 from FooEntity as generatedAlias0 order by generatedAlias0.createdAt desc */ select fooentity0_.idx as idx1_7_, fooentity0_.created_at as created_2_7_, fooentity0_.foo as foo3_7_ from foo fooentity0_ order by fooentity0_.created_at desc 2016-12-06 14:13:41.820 TRACE 26476 --- [ main] o.h.type.descriptor.sql.BasicExtractor : extracted value ([idx1_7_] : [INTEGER]) - [6] 2016-12-06 14:13:41.821 TRACE 26476 --- [ main] o.h.type.descriptor.sql.BasicExtractor : extracted value ([idx1_7_] : [INTEGER]) - [5] 2016-12-06 14:13:41.821 TRACE 26476 --- [ main] o.h.type.descriptor.sql.BasicExtractor : extracted value ([idx1_7_] : [INTEGER]) - [4] FooEntity(idx=6, foo=Foo3, createdAt=2016-12-06T14:13:41.685) FooEntity(idx=5, foo=Foo2, createdAt=2016-12-06T14:13:40.679) FooEntity(idx=4, foo=Foo1, createdAt=2016-12-06T14:13:39.640) |
mysql에서 datetime(3) 또는 datetime(6) 으로 지정하면 괜찮아지긴한다. 다만, persistence context와 db의 값 sync가 맞지 않는 것만으로도 mybatis에 비해서 버그에 대한 노출은 더 많아졌다고 생각된다.
ref.
http://kwonnam.pe.kr/wiki/java/jpa/datetime
http://stackoverflow.com/questions/13344994/mysql-5-6-datetime-doesnt-accept-milliseconds-microseconds