带有时间戳的JPA @Convert问题

时间:2018-05-04 10:49:10

标签: java hibernate jpa

我想使用带有AES算法的JPA @Convert选项加密存储在MySQL数据库中的一些数据。一般来说,所有字段都可以正常工作,但我遇到的问题是其中一个是时间戳。我的Hibernate版本是4.3.8.Final。

由于这是我第一次使用转换器,我正在关注此GiT example。对于此测试,AES加密已禁用,我稍后将启用它,这是我想将某些字段转换为String的原因。因此问题必须在转换器中。

实体为用户存储几个典型信息(名称,姓氏,...)和存储为时间戳的生日。另外,由于我想通过birthdate执行一些搜索,我会删除setter中实体中的birthdate的所有小时,秒和毫秒。

public class User {
  @Column(length = 100, nullable = false)
  @Convert(converter = StringCryptoConverter.class)
  private String firstname;

  ....

  @Column(nullable = false)
  @Convert(converter = TimestampCryptoConverter.class)
  private Timestamp birthdate;

  public void setBirthdate(Timestamp birthdate) {
    // Remove time from birthdate. Is useless and can cause troubles when
    // using in SQL queries.
    if (birthdate != null) {
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(birthdate.getTime());
        calendar.set(Calendar.HOUR_OF_DAY, 0); // set hour to midnight
        calendar.set(Calendar.MINUTE, 0); // set minute in hour
        calendar.set(Calendar.SECOND, 0); // set second in minute
        calendar.set(Calendar.MILLISECOND, 0); // set millis in second
        this.birthdate = new Timestamp(calendar.getTime().getTime());
    } else {
        this.birthdate = null;
    }
  }

....
}

TimestampCryptoConverter.class有很少的方法非常简单。一般来说,范围是将时间戳转换为字符串以便稍后应用AES算法,我将时间戳的时间作为getTime()的长整数,并将它们转换为字符串:

@Override
protected Timestamp stringToEntityAttribute(String dbData) {
    try {
        return (dbData == null || dbData.isEmpty()) ? null : new Timestamp(Long.parseLong(dbData));
    } catch (NumberFormatException nfe) {
        Logger.errorMessage("Invalid long value in database.");
        return null;
    }
}

@Override
protected String entityAttributeToString(Timestamp attribute) {
    return attribute == null ? null : attribute.getTime() + "";
}

这是一个非常简单的代码。我可以正确地将实体存储到数据库中,并正确地从数据库中检索它,例如,如果我通过ID获取用户。因此,转换器必须正确。

存储到MySQL中的数据类似于:

# id, birthdate, firstname, ...
'1', '1525384800000', 'TEST', ...

如果我通过任何字段搜索用户,我将检索具有正确转换的所有数据的实体。当我想从出生日期执行搜索时,会出现此问题。例如,在我的DAO中,我有一个方法是:

public List<User> get(String email, Timestamp birthdate) {
    // Get the criteria builder instance from entity manager
    CriteriaBuilder criteriaBuilder = getEntityManager().getCriteriaBuilder();
    CriteriaQuery<User> criteriaQuery = criteriaBuilder.createQuery(getEntityClass());
    // Tell to criteria query which tables/entities you want to fetch
    Root<User> typesRoot = criteriaQuery.from(getEntityClass());

    List<Predicate> predicates = new ArrayList<Predicate>();
    predicates.add(criteriaBuilder.equal(typesRoot.get("email"), email));

    if (birthdate != null) {
        // Remove hours and seconds.
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(birthdate.getTime());
        calendar.set(Calendar.HOUR_OF_DAY, 0); // set hour to midnight
        calendar.set(Calendar.MINUTE, 0); // set minute in hour
        calendar.set(Calendar.SECOND, 0); // set second in minute
        calendar.set(Calendar.MILLISECOND, 0); // set millis in second
        birthdate = new Timestamp(calendar.getTime().getTime());
        predicates.add(criteriaBuilder.equal(typesRoot.<Timestamp> get("birthdate"), birthdate));
    }

    criteriaQuery.where(criteriaBuilder.and(predicates.toArray(new Predicate[] {})));

    return getEntityManager().createQuery(criteriaQuery).getResultList();
}

如您所见,我还从搜索查询中删除小时,秒和毫秒以匹配数据库上的值。

如果我仅使用电子邮件get('test@email.com', null)调用此方法,则它可以正常工作,因为在检索用户之前,用户的生日是正确的。

但如果我用生日get('test@email.com', 2018-05-04 12:09:05.862)调用此方法,则获得的结果为null。在某些单一测试中,调用中使用的时间戳与用于创建用户的参数完全相同,因此必须与数据库上的值匹配。例如,我有这个统一的测试:

@Test(dependsOnMethods = { "storeUser" })
@Rollback(value = false)
@Transactional(value = TxType.NEVER)
public void searchByMailUser() {
    Assert.assertEquals(userDao.getRowCount(), 1);
    List<User> dbUsers = userDao.get(EMAIL, null);
    Assert.assertTrue(!dbUsers.isEmpty());

    User dbUser = dbUsers.iterator().next();
    Assert.assertEquals(dbUser.getFirstname(), FIRSTNAME);
    Assert.assertEquals(dbUser.getEmail(), EMAIL);
    ....        

    Calendar calendar = Calendar.getInstance();
    calendar.setTimeInMillis(BIRTHDATE.getTime());
    calendar.set(Calendar.HOUR_OF_DAY, 0); // set hour to midnight
    calendar.set(Calendar.MINUTE, 0); // set minute in hour
    calendar.set(Calendar.SECOND, 0); // set second in minute
    calendar.set(Calendar.MILLISECOND, 0); // set millis in second
    Timestamp birthdate = new Timestamp(calendar.getTime().getTime());
    Assert.assertEquals(dbUser.getBirthdate(), birthdate);
}

执行得很好,最后一个断言告诉我生日和正确检索生日。但是在这个测试中:

@Test(dependsOnMethods = { "storeUser" })
public void searchByMailAndBirthdateUser() {
    Assert.assertEquals(userDao.getRowCount(), 1);
    Assert.assertTrue(!userDao.get(EMAIL, BIRTHDATE).isEmpty());
}

由于没有找到用户,此测试失败,但如果更改为:

则传递
@Test(dependsOnMethods = { "storeUser" })
public void searchByMailAndBirthdateUser() {
    Assert.assertEquals(userDao.getRowCount(), 1);
    Assert.assertTrue(!userDao.get(EMAIL, null).isEmpty());
}

但是,如果我禁用转换器,则会传递两个测试。

如果从数据库中正确检索了生日。为什么我在使用birthdate作为标准时具有null值?

修改

似乎在调用protected String entityAttributeToString(Timestamp attribute);时未使用方法get('test@email.com', 2018-05-04 12:09:05.862)

1 个答案:

答案 0 :(得分:0)

some research之后。当使用@Convert属性过滤条件时,Hibernate仍有可能存在一些错误。我已经用不同的选项进行了几次测试,但没有成功。

作为一种解决方法,我将birthdate属性更改为Long。

@Column(nullable = false)
@Convert(converter = LongCryptoConverter.class)
private Long birthdate;

更新setter和getter,使用getTime()

将时间戳转换为Long

使用非常简单的方法创建Long CryptoConverter,将Long转换为String,反之亦然:

@Override
protected Long stringToEntityAttribute(String dbData) {
    try {
        return (dbData == null || dbData.isEmpty()) ? null : Long.parseLong(dbData);
    } catch (NumberFormatException nfe) {
        UsmoLogger.errorMessage(this.getClass().getName(), "Invalid long value in database.");
        return null;
    }
}

@Override
protected String entityAttributeToString(Long attribute) {
    return attribute == null ? null : attribute.toString();
}

这是按预期工作的,现在标准过滤器工作正常。我仍然不确定为什么带有Timestamp的版本不能正常工作。