簡體   English   中英

JPA AttributeConverter 使休眠在事務中的整個表上生成更新語句

[英]JPA AttributeConverter makes hibernate generate update statements on whole table in transaction

這篇文章中的所有代碼都可以在這里找到: https : //github.com/cuipengfei/gs-accessing-data-jpa/tree/master/complete

您可以運行此測試來重現問題: https : //github.com/cuipengfei/gs-accessing-data-jpa/blob/master/complete/src/test/java/hello/T1ServiceTest.java

我有一個域模型:

@Entity
@Table(name = "t1", schema = "test")
public class T1 extends BaseEntity {

  @Column(nullable = false)
  private UUID someField;

  @Column()
  private ZonedDateTime date;

  public T1(UUID someField, ZonedDateTime date) {
    this.someField = someField;
    this.date = date;
  }
}

它有一個類型為 ZonedDateTime 的字段,所以我有一個轉換器將其轉換為 sql 時間戳:

@Converter(autoApply = true)
public class ZonedDateTimeAttributeConverter
    implements AttributeConverter<ZonedDateTime, Timestamp> {

  @Override
  public Timestamp convertToDatabaseColumn(ZonedDateTime entityValue) {
    return (entityValue == null) ? null :
        valueOf(entityValue.withZoneSameInstant(of("UTC")).toLocalDateTime());
  }

  @Override
  public ZonedDateTime convertToEntityAttribute(Timestamp databaseValue) {
    return (databaseValue == null) ? null : databaseValue.toLocalDateTime().atZone(
        of("UTC"));
  }
}

當我嘗試在這樣的事務中創建大量 T1 時:

@Service
public class T1Service {
  private static final Logger log = LoggerFactory.getLogger(T1Service.class);

  @Autowired
  T1Repository t1Repository;

  @Transactional
  public void insertMany() {
    for (int i = 0; i < 1000; i++) {
      log.info("!!! " + (i + 1) + "th item start");

      UUID randomUUID = UUID.randomUUID();
      T1 foundT1 = tryToFindExistingT1(randomUUID);//certainly won't find

      if (foundT1 == null) {
        log.info("t1 not found");
        ZonedDateTime date = now();
        //date = null;
        //if you enable the line above, there won't be any update statements anymore
        //and find will also become faster
        T1 t1 = new T1(randomUUID, date);
        saveT1(t1);
      }

      log.info("!!! " + (i + 1) + "th item finished");
      log.info("====================================");
    }
  }

  private T1 tryToFindExistingT1(UUID someField) {
    long start = currentTimeMillis();
    T1 t1Id = t1Repository.findBySomeField(someField);
    //as nth item increases, the line above will become very very slow
    //and also, there will be more and more update statements
    //but if you set date of t1 to null, update statement will disappear and it'll not be slow
    log.info("find took: " + (currentTimeMillis() - start) + " milliseconds");
    return t1Id;
  }

  private T1 saveT1(T1 t1) {
    long start = currentTimeMillis();
    T1 savedT1 = t1Repository.save(t1);
    log.info("save took: " + (currentTimeMillis() - start) + " milliseconds");
    return savedT1;
  }

}

hibernate 會為整個 t1 表生成更新語句。

這是 for 循環前幾輪的日志:

2017-03-20 17:51:54.039  INFO 74789 --- [           main] hello.T1Service                          : !!! 1th item start
2017-03-20 17:51:54.052  INFO 74789 --- [           main] o.h.h.i.QueryTranslatorFactoryInitiator  : HHH000397: Using ASTQueryTranslatorFactory
Hibernate: select t1x0_.id as id1_0_, t1x0_.date as date2_0_, t1x0_.some_field as some_fie3_0_ from test.t1 t1x0_ where t1x0_.some_field=?
2017-03-20 17:51:54.154  INFO 74789 --- [           main] hello.T1Service                          : find took: 114 milliseconds
2017-03-20 17:51:54.154  INFO 74789 --- [           main] hello.T1Service                          : t1 not found
2017-03-20 17:51:54.177  INFO 74789 --- [           main] hello.T1Service                          : save took: 15 milliseconds
2017-03-20 17:51:54.177  INFO 74789 --- [           main] hello.T1Service                          : !!! 1th item finished
2017-03-20 17:51:54.177  INFO 74789 --- [           main] hello.T1Service                          : ====================================
2017-03-20 17:51:54.177  INFO 74789 --- [           main] hello.T1Service                          : !!! 2th item start
Hibernate: insert into test.t1 (date, some_field, id) values (?, ?, ?)
Hibernate: update test.t1 set date=?, some_field=? where id=?
Hibernate: select t1x0_.id as id1_0_, t1x0_.date as date2_0_, t1x0_.some_field as some_fie3_0_ from test.t1 t1x0_ where t1x0_.some_field=?
2017-03-20 17:51:54.194  INFO 74789 --- [           main] hello.T1Service                          : find took: 17 milliseconds
2017-03-20 17:51:54.194  INFO 74789 --- [           main] hello.T1Service                          : t1 not found
2017-03-20 17:51:54.195  INFO 74789 --- [           main] hello.T1Service                          : save took: 1 milliseconds
2017-03-20 17:51:54.195  INFO 74789 --- [           main] hello.T1Service                          : !!! 2th item finished
2017-03-20 17:51:54.195  INFO 74789 --- [           main] hello.T1Service                          : ====================================
2017-03-20 17:51:54.195  INFO 74789 --- [           main] hello.T1Service                          : !!! 3th item start
Hibernate: insert into test.t1 (date, some_field, id) values (?, ?, ?)
Hibernate: update test.t1 set date=?, some_field=? where id=?
Hibernate: update test.t1 set date=?, some_field=? where id=?
Hibernate: select t1x0_.id as id1_0_, t1x0_.date as date2_0_, t1x0_.some_field as some_fie3_0_ from test.t1 t1x0_ where t1x0_.some_field=?
2017-03-20 17:51:54.200  INFO 74789 --- [           main] hello.T1Service                          : find took: 4 milliseconds
2017-03-20 17:51:54.200  INFO 74789 --- [           main] hello.T1Service                          : t1 not found
2017-03-20 17:51:54.200  INFO 74789 --- [           main] hello.T1Service                          : save took: 0 milliseconds
2017-03-20 17:51:54.200  INFO 74789 --- [           main] hello.T1Service                          : !!! 3th item finished
2017-03-20 17:51:54.200  INFO 74789 --- [           main] hello.T1Service                          : ====================================
2017-03-20 17:51:54.200  INFO 74789 --- [           main] hello.T1Service                          : !!! 4th item start
Hibernate: insert into test.t1 (date, some_field, id) values (?, ?, ?)
Hibernate: update test.t1 set date=?, some_field=? where id=?
Hibernate: update test.t1 set date=?, some_field=? where id=?
Hibernate: update test.t1 set date=?, some_field=? where id=?
Hibernate: select t1x0_.id as id1_0_, t1x0_.date as date2_0_, t1x0_.some_field as some_fie3_0_ from test.t1 t1x0_ where t1x0_.some_field=?
2017-03-20 17:51:54.209  INFO 74789 --- [           main] hello.T1Service                          : find took: 9 milliseconds
2017-03-20 17:51:54.209  INFO 74789 --- [           main] hello.T1Service                          : t1 not found
2017-03-20 17:51:54.210  INFO 74789 --- [           main] hello.T1Service                          : save took: 1 milliseconds
2017-03-20 17:51:54.210  INFO 74789 --- [           main] hello.T1Service                          : !!! 4th item finished
2017-03-20 17:51:54.210  INFO 74789 --- [           main] hello.T1Service                          : ====================================

在日志中可以看到,hibernate 會生成越來越多的更新語句。

看起來更新語句的數量總是與等待提交的 T1 的行數相同。

現在如果我移除轉換器,這個問題就會消失。

我可以用 hibernate-java8 lib 代替這個轉換器來達到同樣的效果,但是為什么會這樣呢?

為什么 JPA AttributeConverter 使 hibernate 在事務中的整個表上生成更新語句?

我通過在 getter 和 setter 方法中應用轉換器而不是作為注釋來解決這個問題。 像這樣:

@Entity
@Table(name = "t1", schema = "test")
public class T1 extends BaseEntity {

  @Column(nullable = false)
  private UUID someField;

  @Column()
  private Timestamp date;

  public T1(UUID someField, ZonedDateTime date) {
    this.someField = someField;
    this.date = date;
  }

    public void setDate(ZonedDateTime date) {
        this.date = new ZonedDateTimeAttributeConverter().convertToDatabaseColumn(date);
    }

    public ZonedDateTime getDate() {
        return new ZonedDateTimeAttributeConverter().convertToEntityAttribute(date);
    }
}

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM