JPA @Convert проблема с отметкой времени - PullRequest
0 голосов
/ 04 мая 2018

Я хочу зашифровать некоторые данные, хранящиеся в базе данных MySQL, используя параметры JPA @Convert с алгоритмом AES. В целом, работает нормально со всеми полями, но у меня есть проблемы с одним из них, который является меткой времени. Моя версия Hibernate - 4.3.8.Final.

Поскольку я впервые работаю с конвертерами, я следую этому примеру GiT . Для этого теста шифрование AES отключено , я включу его позже, и это причина, по которой я хочу преобразовать некоторые поля в строку. Поэтому проблема должна быть в конвертере.

Сущность хранит пользователя с несколькими типичными данными (имя, фамилия, ...) и датой рождения, которая хранится в виде метки времени. Кроме того, поскольку я хочу выполнить поиск по 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() + "";
}

Это очень простой код. И я могу правильно сохранить объект в базе данных и правильно извлечь его из базы данных, например, если я получу пользователя по идентификатору. Следовательно, конвертер должен быть правильным.

Сохраненные данные в 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 при использовании даты рождения в качестве критерия?

EDIT

Кажется, что метод protected String entityAttributeToString(Timestamp attribute); не используется при вызове get('test@email.com', 2018-05-04 12:09:05.862).

Ответы [ 2 ]

0 голосов
/ 11 мая 2018

После некоторых усилий я изменил версию Hibernate на 5.2.17.Final и метки времени обрабатываются правильно с аннотацией @Convert. Это означает, что это действительно ошибка Hibernate с версией 4.X, и эта функция лучше реализована в Hibernate 5.X

0 голосов
/ 07 мая 2018

После некоторые исследования . Возможно, что Hibernate по-прежнему имеет некоторые ошибки при фильтрации по критерию с атрибутом @Convert. Я провел несколько тестов с разными вариантами, но безуспешно.

В качестве обходного пути я изменил атрибут birthdate на Long.

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

Обновление сеттеров и геттеров для преобразования метки времени в Long с помощью getTime()

И создание 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();
}

Это работает, как ожидалось, и фильтр критериев теперь работает нормально. Тем не менее, я не уверен, почему версия с отметкой времени не работает нормально.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...