我想使用 JPA @Convert
选项和 AES 算法对存储在 MySQL 数据库中的一些数据进行加密。一般来说,所有字段都工作得很好,但我对其中一个时间戳有问题。我的 Hibernate 版本是 4.3.8.Final。
由于这是我第一次使用转换器,因此我关注此 GiT example 。对于此测试,AES 加密被禁用,我稍后将启用它,这就是我想要将某些字段转换为字符串的原因。因此问题一定出在转换器上。
该实体存储用户的几个典型信息(姓名、姓氏……)以及存储为时间戳的生日。另外,由于我想按出生日期执行一些搜索,因此我删除了 setter 中实体中出生日期的所有小时、秒和毫秒。
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()
将时间戳的时间作为 long ,并将它们转换为字符串:
@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());
}
但是,如果我禁用转换器,则两个测试都会通过。
如果从数据库中正确检索出生日期。为什么在使用出生日期作为条件时出现 null
值?
编辑
调用 get('test@email.com', 2018-05-04 12:09:05.862)
时,似乎没有使用 protected StringEntityAttributeToString(Timestamp attribute);
方法。
最佳答案
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();
}
这按预期工作,并且标准过滤器现在工作正常。我仍然不确定为什么带有时间戳的版本不能正常工作。
关于java - JPA @Convert 时间戳问题,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/50173169/