Есть ли способ создать собственный SQL-запрос поверх репозитория JPA, чтобы иметь BULK UPSERTS?

У меня есть база данных снежинок, и она не поддерживает принудительное применение уникальных ограничений (https://docs.snowflake.com/en/sql-reference/constraints-overview.html).

Я планирую использовать метод в репозитории JPA с настраиваемым SQL-запросом для проверки дубликатов перед вставкой в ​​​​таблицу.

сущность

@Entity
@Table(name = "STUDENTS")
public class Students {

  @Id
  @Column(name = "ID", columnDefinition = "serial")
  @GenericGenerator(name = "id_generator", strategy = "increment")
  @GeneratedValue(generator = "id_generator")
  private Long id;

  @Column(name = "NAME")
  private String studentName;

}

Запрос на создание таблицы Snowflake

CREATE table STUDENTS(
    id int identity(1,1) primary key,
    name VARCHAR NOT NULL,
    UNIQUE(name)
);

Репозиторий

public interface StudentRepository extends JpaRepository<Students, Long> {

//
@Query(value = "???", nativeQuery = true)
    List<Student> bulkUpsertStudents(List<Student> students);

}
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Версия Java на основе версии загрузки
Версия Java на основе версии загрузки
Если вы зайдете на официальный сайт Spring Boot , там представлен start.spring.io , который упрощает создание проектов Spring Boot, как показано ниже.
Документирование API с помощью Swagger на Springboot
Документирование API с помощью Swagger на Springboot
В предыдущей статье мы уже узнали, как создать Rest API с помощью Springboot и MySql .
0
0
83
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

Вы можете использовать запрос SELECT для проверки повторяющихся значений в столбце name перед вставкой новой записи в таблицу. Например:

@Query(value = "SELECT * FROM STUDENTS WHERE name = :name", nativeQuery = true)
List<Student> findByName(@Param("name") String name);

Этот метод вернет список Student записей с указанным name значением. Если список пуст, это означает, что записей с таким значением name нет, и вы можете смело вставлять новую запись с этим значением name.

List<Student> studentList = new ArrayList<>();
for (Student student : students) {
    List<Student> existingStudents = studentRepository.findByName(student.getName());
    if (existingStudents.isEmpty()) {
        studentsToInsert.add(student);
    }
}
studentRepository.bulkUpsertStudents(studentList)

РЕДАКТИРОВАТЬ

Если вышеуказанное решение не работает. Вы можете использовать оператор MERGE для обновления существующих записей в таблице, если данные изменились. Например, если вы хотите обновить имя Student, если оно изменилось, вы можете использовать следующий оператор MERGE:

@Query(value = "MERGE INTO students t USING (SELECT :name AS name, :newName AS newName) s
ON t.name = s.name 
WHEN MATCHED AND t.name <> s.newName THEN UPDATE SET t.name = s.newName
WHEN NOT MATCHED THEN INSERT (name) VALUES (s.name)", nativeQuery = true)
List<Student> bulkUpsertStudents(List<Student> students);

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

Это вызовет запрос выбора для каждой записи в базу данных. Представьте, что если есть 1000 студенческих записей, будет запущено 1000 запросов. Я думал, есть ли какая-то insert on conflict поддержка

Aswath 07.12.2022 09:46

Получение этой ошибки: объект ссылается на несохраненный переходный экземпляр - сохраните переходный экземпляр перед очисткой при втором подходе

Aswath 07.12.2022 16:21

Хорошо, я понимаю, что обновил код, можете ли вы проверить сейчас?

Khalid Saifullah Fuad 07.12.2022 18:28

НА КОНФЛИКТ НИЧЕГО НЕ ДЕЛАТЬ; не поддерживается снежинкой Я искал что-то похожее на это

Aswath 08.12.2022 04:46
Ответ принят как подходящий

Мне удалось преодолеть это, используя приведенный ниже подход, но мне нужно проверить производительность запросов.

  1. Метод репозитория saveAll() для сохранения всех сущностей.

  2. Использование пользовательского nativeQuery, как показано ниже.

       INSERT OVERWRITE INTO STUDENTS
       WITH CTE AS(SELECT ROW_NUMBER() OVER (PARTITION BY NAME ORDER BY ID) AS RNO, ID, NAME FROM STUDENTS)
       SELECT ID, NAME FROM CTE WHERE RNO = 1;
    

Пример кода:

import static io.vavr.collection.List.ofAll;
import static io.vavr.control.Option.of;
import static java.util.function.Predicate.not;

 public Validation<ValidationError, List<Students>> saveAll(List<String> students) {
    return of(students)
        .filter(not(List::isEmpty))
        .map(this::mapToEntities) // maps the list to list of database entities
        .map(repository::saveAll) // save all
        .toValidation(ERROR_SAVING_STUDENTS) // vavr validation in case of error
        .peek(x -> repository.purgeStudents()) // purging to remove duplicates
        .toValidation(ERROR_PURGING_STUDENTS);
  }

Эта проблема возникает только из-за того, что снежинка не может проверить уникальность.

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