Идентификатор UUID равен нулю после сохранения объекта с помощью spring-data-jdbc на сервере sql

У меня есть следующая таблица (фрагмент):

create table users
(
    user_id uniqueidentifier default newid() not null primary key,
    name    varchar(192),
    ...
)

и сущность:

import org.springframework.data.annotation.Id;
import org.springframework.data.relational.core.mapping.Column;
import org.springframework.data.relational.core.mapping.Table;

@Table(value = "users")
public class User {
   
    @Id
    @Column("user_id")
    private UUID userId;
}

Запуск сохранения в базе данных h2 с конфигурациями sql-сервера работает нормально, однако, если запустить сохранение в реальной базе данных sql-сервера, я получаю

java.lang.IllegalArgumentException: After saving the identifier must not be null!
    at org.springframework.util.Assert.notNull(Assert.java:201)
    at org.springframework.data.jdbc.core.JdbcAggregateTemplate.store(JdbcAggregateTemplate.java:364)
    at org.springframework.data.jdbc.core.JdbcAggregateTemplate.save(JdbcAggregateTemplate.java:161)

Я видел различные другие сообщения, посвященные аналогичным проблемам, однако большинство из них используют подходы через спящий режим/JPA, чего я хочу избежать, если это возможно.

Я пробовал разные strategy настройки @GeneratedValue аннотации, но ни один из них не сработал.

Есть ли способ решить эту проблему исключительно с помощью spring-data-jdbc? Как мне правильно аннотировать свойство id, чтобы sql-server создавал uuid и передал его в spring-data-jdbc?

Я использую SQL Server 2019 с настройками по умолчанию.

редактировать:

Чтобы сохранить объект, я использую save из UserRepository extends CrudRepository<User, UUID>

редактировать-2:

С помощью этого обходного пути я смог заставить его работать:

 @Bean
 BeforeConvertCallback<AlertUser> beforeConvertCallback() {
     return (user) -> {
         if (user.getId() == null) {
             user.setId(UUID.randomUUID());
         }
         return user;
     };
 }

Но это все еще не желаемое решение, поскольку оно зависит от генерации uuid на стороне клиента.

Я копнул немного дальше и обнаружил, что проблема возникает, когда Spring пытается получить сгенерированные идентификаторы. Afaics ожидает ResultSet, содержащую строку со свойством GENERATED_KEYS, но на sql-сервере это свойство никогда или по-другому не устанавливается СУБД, как кажется.

Можете ли вы показать, как вы сохраняете свою сущность?

Harry Coder 02.07.2023 07:27
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
1
64
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Используйте аннотацию @GeneratedValue со стратегией GenerationTime.ALWAYS.

    @Id
    @GeneratedValue(strategy = GenerationTime.ALWAYS, jdbcType = JdbcType.UNIQUEIDENTIFIER)
    @Column("user_id")
    private UUID userId;

Не могли бы вы указать, какую версию javax.persistence вы используете? Не удалось найти недвижимость jdbcType или GenerationType.ALWAYS на @GeneratedValue,.. я на javax.persistence:javax.persistence-api:2.2

lunatikz 02.07.2023 16:11
Ответ принят как подходящий

@GeneratedValue — это аннотация JPA. Это совершенно не связано с Spring Data JDBC и поэтому не повлияет на вашу настройку Spring Data JDBC.

Насколько я могу судить, Microsoft SqlServer (или его драйвер JDBC) не возвращает идентификаторы, сгенерированные так, как вы настраиваете свою таблицу. Поэтому Spring Data JDBC не может их подобрать.

Правильный способ обойти это - использовать конструкцию, которую вы уже обнаружили:

 @Bean
 BeforeConvertCallback<AlertUser> beforeConvertCallback() {
     return (user) -> {
         if (user.getId() == null) {
             user.setId(UUID.randomUUID());
         }
         return user;
     };
 }

Вы, вероятно, захотите удалить предложение default newid() на стороне базы данных, чтобы не путать людей, где генерируется UUID. Это "правильный" подход, потому что

  • UUID уникальны по своей конструкции, даже если они генерируются на разных устройствах.
  • Таким образом нагрузка на базу данных сводится к минимуму, что хорошо, поскольку в сценариях с высокой нагрузкой легко добавить 100 дополнительных серверов приложений. Не так просто масштабировать базу данных, если она больше не помещается на одной машине.

Конечно, всегда есть сценарий, когда кто-то выше в пищевой цепочке сказал вам сгенерировать UUID в базе данных. В таком случае есть пара вариантов:

  1. Исправьте Spring Data JDBC, чтобы использовать этот подход для вставки. Если вы сделаете это таким образом, чтобы он был совместим со всем остальным, Spring Data JDBC, вероятно, примет PR.
  2. Убедите Microsoft улучшить драйвер JDBC, чтобы он также возвращал идентификаторы, сгенерированные так, как вы предпочитаете. Это должно автоматически подбирать этот идентификатор Spring Data JDBC.
  3. Используйте Springs NamedParameterJdbcTemplate или JdbcTemplate, чтобы вставить свою сущность, и используйте подход, указанный выше, для получения сгенерированного идентификатора. Вы можете поместить этот код в реализацию пользовательского метода в вашем репозитории Spring Data JDBC.

Я был немного обеспокоен тем, что генерация uuid на стороне клиента может привести к конфликту uuid, но, переосмыслив это, я предполагаю, что вероятность будет довольно низкой. На самом деле не требуется, чтобы я использовал генерацию uuid на стороне сервера, это было больше похоже на то, что у меня было ощущение, что это такой базовый сценарий, я не могу представить, что нет «весеннего» способа настроить/исправить это.

lunatikz 03.07.2023 16:33

Другие вопросы по теме