Задача загрузки Spring для обновления внешнего ключа в БД

Я работаю над REST API, используя Spring boot, который будет управлять аукционом предметов.

Пользователи, участвующие в аукционе, будут выделены в отдельный Таблица пользователей.

Предметы для аукциона будут в Таблица Auction_items вместе с временем начала и окончания аукциона, победитель будет нулевым, если аукцион все еще продолжается.

Все ставки, сделанные пользователями для соответствующих товаров, находятся в Таблица ставок.

Я уже пробовал разные способы (cron, fixedRate, fixedDelay) для планирования задачи, получил одинаковую ошибку для всех из них, Я подозреваю, что это может быть проблема с БД, из-за которой это происходит.

Ниже приведены таблицы и соответствующий класс модели, а также интерфейс сервиса и репозитория.

ТАБЛИЦА ПОЛЬЗОВАТЕЛЕЙ:

CREATE TABLE USERS
(
USER_ID SERIAL,
NAME VARCHAR(200),
EMAIL VARCHAR(200),
PASSWORD VARCHAR(200),
PRIMARY KEY(USER_ID)
);

ТАБЛИЦА AUCTION_ITEMS:

CREATE TABLE AUCTION_ITEMS
(
ITEM_ID SERIAL,
ITEM_NAME VARCHAR(200),
ITEM_DESCRIPTION TEXT,
START_TIME TIMESTAMP,
END_TIME TIMESTAMP,
STARTING_AMOUNT INT,
WINNER INT,
PRIMARY KEY(ITEM_ID),
FOREIGN KEY(WINNER) REFERENCES USERS(USER_ID) ON DELETE CASCADE
);

ТАБЛИЦА ПРЕДЛОЖЕНИЙ:

CREATE TABLE BIDS
(
BID_ID SERIAL,
ITEM_ID INT,
USER_ID INT,
AMOUNT INT,
PRIMARY KEY(BID_ID),
FOREIGN KEY(ITEM_ID) REFERENCES AUCTION_ITEMS(ITEM_ID) ON DELETE 
CASCADE,
FOREIGN KEY(USER_ID) REFERENCES USERS(USER_ID) ON DELETE CASCADE
);

Ниже приведены классы моделей для приведенных выше таблиц:

Пользователь.java:

@Entity
@Table(name = "USERS")
public class User {

@Id
private Integer user_id;
private String name;
private String email;
private String password;

//getter setter methods
}

Пункт.java:

@Entity
@Table(name = "AUCTION_ITEMS")
public class Item {

@Id
private Integer item_id;
private String item_name;
private String item_description;
private Timestamp start_time;
private Timestamp end_time;
private int starting_amount;
@ManyToOne
@JoinColumn(name = "winner")
private User user;

//getter setter methods
}

Ставка.java:

@Entity
@Table(name = "BIDS")
public class Bid {

@Id
private Integer bid_id;
@ManyToOne
@JoinColumn(name = "item_id")
private Item item;
@ManyToOne
@JoinColumn(name = "user_id")
private User user;
private int amount;
//getter setter nethods
}

Ниже приведен класс обслуживания, в котором я определяю Метод запланированной задачи() для запуска и вычисления победителя предмета, выставленного на аукцион, по истечении времени окончания аукциона:

ItemService.java:

@Service
public class ItemService {

    @Autowired
    private ItemRepository itemRepository;

    @Autowired
    private UserRepository userRepository;


    public List<Item> getAllItems(){
        return (List<Item>) itemRepository.findAll();
    }

    public Object getItem(Integer id) {
        Item i = itemRepository.findById(id).orElse(null);
        if (i.getUser()!=null) {
            return i.getUser();
        }
        else {
            //will be returning highest bid amount for that particular item
            return itemRepository.getMaxBid(id);
        }
    }

    @Scheduled(cron = "20 36 17 * * ?")
    public void scheduledTask() {
        List<Item> listOfItems = (List<Item>)itemRepository.findAll();
        System.out.println("going to update DB");
        for(Item i : listOfItems) {
            Timestamp time = new Timestamp(System.currentTimeMillis());
            if (time.equals(i.getEnd_time()) || time.after(i.getEnd_time())) {
                if (i.getUser() == null) {
                    Integer item_id = i.getItem_id();
                    Integer winner_id = itemRepository.findWinner(item_id);
                    User u= userRepository.findById(winner_id).orElse(null);

                    i.setUser(u);
                    System.out.println("updated");
                }
            }
        }
    }

}

ItemRepository.java:

public interface ItemRepository extends CrudRepository<Item,Integer>{

@Query(value = "select max(b.amount) from bids as b where b.item_id=?1", 
nativeQuery=true )
public Integer getMaxBid(Integer item_id);

@Query(value = "select b.user_id from bids as b where b.item_id=?1 AND 
b.amount = (select max(amount) from bids);", nativeQuery=true)
public Integer findWinner(Integer id);

Ниже ошибка, которую я получаю один раз, приходит время обновить данные в таблице:

2019-04-22 12:55:30.123 INFO 9100 --- [ scheduling-1] >o.h.h.i.QueryTranslatorFactoryInitiator : HHH000397: Using >ASTQueryTranslatorFactory going to update DB 2019-04-22 12:55:30.464 ERROR 9100 --- [ scheduling-1] ?>o.s.s.s.TaskUtils$LoggingErrorHandler : Unexpected error occurred in >scheduled task.

org.springframework.dao.InvalidDataAccessApiUsageException: >org.hibernate.QueryException: JPA-style positional param was not an integral >ordinal; nested exception is java.lang.IllegalArgumentException: >org.hibernate.QueryException: JPA-style positional param was not an integral >ordinal at >org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExcepti>onIfPossible(EntityManagerFactoryUtils.java:373) ~[spring-orm->5.1.6.RELEASE.jar:5.1.6.RELEASE] at >org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPo>ssible(HibernateJpaDialect.java:255) ~[spring-orm->]>5.1.6.RELEASE.jar:5.1.6.RELEASE] at >org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExcepti>onIfPossible(AbstractEntityManagerFactoryBean.java:527) ~[spring-orm->5.1.6.RELEASE.jar:5.1.6.RELEASE] at >org.springframework.dao.support.ChainedPersistenceExceptionTranslator.transla>teExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61) ~ [spring-tx-5.1.6.RELEASE.jar:5.1.6.RELEASE] at >org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAcce>ssUtils.java:242) ~[spring-tx-5.1.6.RELEASE.jar:5.1.6.RELEASE] at >org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.in>voke(PersistenceExceptionTranslationInterceptor.java:153) ~[spring-tx->5.1.6.RELEASE.jar:5.1.6.RELEASE] at >org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(Reflecti>veMethodInvocation.java:186) ~[spring-aop-5.1.6.RELEASE.jar:5.1.6.RELEASE] at >org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcess>or$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPos>tProcessor.java:138) ~[spring-data-jpa-2.1.6.RELEASE.jar:2.1.6.RELEASE] at >org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(Reflecti>veMethodInvocation.java:186) ~[spring-aop-5.1.6.RELEASE.jar:5.1.6.RELEASE] at >org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(Expose>InvocationInterceptor.java:93) ~[spring-aop-5.1.6.RELEASE.jar:5.1.6.RELEASE] at >org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(Reflecti>veMethodInvocation.java:186) ~[spring-aop-5.1.6.RELEASE.jar:5.1.6.RELEASE] at >org.springframework.data.repository.core.support.SurroundingTransactionDetect>orMethodInterceptor.invoke(SurroundingTransactionDetectorMethodInterceptor.ja>va:61) ~[spring-data-commons-2.1.6.RELEASE.jar:2.1.6.RELEASE] at >org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(Reflecti>veMethodInvocation.java:186) ~[spring-aop-5.1.6.RELEASE.jar:5.1.6.RELEASE] at >org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProx>y.java:212) ~[spring-aop-5.1.6.RELEASE.jar:5.1.6.RELEASE] at com.sun.proxy.$Proxy91.findUser(Unknown Source) ~[na:na] at auction.demo.service.ItemService.scheduledTask(ItemService.java:55) >~[classes/:na] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~ [na:1.8.0_121] at >sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) >~[na:1.8.0_121] at >sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.>java:43) ~[na:1.8.0_121] at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_121] at >org.springframework.scheduling.support.ScheduledMethodRunnable.run(ScheduledM>ethodRunnable.java:84) ~[spring-context-5.1.6.RELEASE.jar:5.1.6.RELEASE] at >org.springframework.scheduling.support.DelegatingErrorHandlingRunnable.run(De>legatingErrorHandlingRunnable.java:54) ~[spring-context->5.1.6.RELEASE.jar:5.1.6.RELEASE] at >org.springframework.scheduling.concurrent.ReschedulingRunnable.run(Rescheduli>ngRunnable.java:93) [spring-context-5.1.6.RELEASE.jar:5.1.6.RELEASE] at >java.util.concurrent.Executors$RunnableAdapter.call(Executors.java:511) [na:1.8.0_121] at java.util.concurrent.FutureTask.run(FutureTask.java:266) [na:1.8.0_121] at >java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$2>01(ScheduledThreadPoolExecutor.java:180) [na:1.8.0_121] at >java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Sche>duledThreadPoolExecutor.java:293) [na:1.8.0_121] at >java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:114>2) [na:1.8.0_121] at >java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:61>7) [na:1.8.0_121] at java.lang.Thread.run(Thread.java:745) [na:1.8.0_121]

Вопрос: Создать запланированную задачу, которая запускается в момент окончания аукциона по предмету и определяет победителя этого предмета из таблицы ставок и сохраняет идентификатор этого пользователя в столбце победителя таблицы Auction_items.

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

Даже если это причина, я не уверен, как ее решить, также если это не причина, будет ли приложение автоматически сохранять правильный user_id в столбце победителя, если правильный экземпляр пользователя установлен с помощью setUser().

Обратите внимание, что я использовал @EnabledScheduling в классе с методом main().

Обновлено: Вставлен новый ItemService.java, Item.java В Item.java я использовал тип данных User для переменной-члена, поскольку создание его как Integer не работало.

Пробовали убрать точку с запятой в конце запроса?

mallikarjun 22.04.2019 11:55

Да, это не сработало, возникло другое исключение: org.springframework.dao.InvalidDataAccessResourceUsageExcept‌​ion: не удалось извлечь ResultSet

Akki 22.04.2019 12:01

Я думаю, что тип возвращаемого значения недействителен, поэтому измените его на List<User> вместо пользователя.

mallikarjun 22.04.2019 12:39

возвращаемый тип какого метода, findUser возвращает только один экземпляр пользователя.

Akki 22.04.2019 12:48
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
1
4
3 580
2
Перейти к ответу Данный вопрос помечен как решенный

Ответы 2

  1. предполагается, что репозиторий управляет одним типом домена (объектом), в вашем случае вы используете ItemRepository для получения объектов пользователя, что является неправильным использованием.
  2. вам не нужно реализовывать метод findUser, поскольку в репозиториях уже есть реализация для findById.
  3. Я бы порекомендовал вам попробовать использовать методы запросов JPA для таких простых запросов вместо того, чтобы прибегать к собственным запросам SQL.

Итак, короткий ответ, создайте отдельный UserRepository, переместите findWinner метод туда и используйте существующий findById из UserRepository вместо findUser из ItemRepository

Да, есть отдельный USerRepository, и я использовал только метод findById, я просто включил все сюда, так как оба возвращают одно и то же, решил немного сократить его.

Akki 22.04.2019 12:46

Как видите, CrudRepository принимает два параметра типа: тип идентификатора объекта и сам объект. Это делает репозиторий очень специфичным для одного объекта и, следовательно, его не следует использовать для работы с другими типами доменов.

Ahmed Abdelhady 22.04.2019 13:08

хорошо, даже если у нас есть пользовательский запрос? Но, переходя к настоящему вопросу, я использовал метод userRepository.findById(winner_id), теперь я получаю это исключение, я думаю, что мы сужаемся до основной проблемы: не удалось вызвать метод init; вложенным исключением является javax.persistence.PersistenceException: [PersistenceUnit: по умолчанию] Невозможно построить Hibernate SessionFactory; вложенным исключением является org.hibernate.MappingException: не удалось определить тип для:auction.demo.models.User, в таблице:auction_items, для столбцов: [org.hibernate.mapping.Column(winner)]

Akki 22.04.2019 13:13

Я полагаю, это связано с тем, что внешний ключ для пользователя в таблице аукционов имеет тип INT, однако первичный ключ в таблице пользователей — Serial

Ahmed Abdelhady 22.04.2019 13:18

Да, я тоже это подозреваю, но не знаю, как это убрать.

Akki 22.04.2019 13:21

вам просто нужно изменить тип столбца в AUCTION_ITEMS, либо запустить SQL непосредственно в вашей базе данных, либо, что еще лучше, добавить в качестве сценария миграции, если вы используете какой-либо инструмент миграции, такой как flyway или liquibase

Ahmed Abdelhady 22.04.2019 13:28

но поскольку я использую столбец победителя в качестве внешнего ключа, у него есть user_id, который будет ссылаться на таблицу пользователей, не уверен, что мне следует обновить тип столбца до ....?

Akki 22.04.2019 13:32

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

Ahmed Abdelhady 22.04.2019 13:37

Вы предлагаете изменить серийный номер user_id на Integer в таблице Users?

Akki 22.04.2019 13:44

В качестве обходного пути я изменил тип данных переменной пользователя в классе модели Item на Integer и в методе Service, просто сделав i.setWinner(winner_id); в конце концов. Это устраняет ошибку, выполнение доходит до этой точки и все, но она все еще не обновляется в БД.

Akki 22.04.2019 13:53

нет, я предлагаю вам обновить тип столбца внешнего ключа (winner_id), чтобы он соответствовал типу первичного ключа таблицы пользователей.

Ahmed Abdelhady 22.04.2019 14:11

на самом деле кажется, что int следует принимать еще и потому, что серийный номер - это тип целого числа: |

Ahmed Abdelhady 22.04.2019 14:15

Да, это должно быть принято, хотя я явно изменил тип на целое число, но получил то же исключение.

Akki 22.04.2019 14:20

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

Ahmed Abdelhady 22.04.2019 14:24

Я вставил обновленный ItemService.java, Item.java. Как видите, я добавил 2 системных вывода в метод ScheduleTask, один из которых говорит «собирается обновить базу данных», а другой говорит «обновлено», на этот раз никаких ошибок не выдается, оба системных вывода напечатано, но обновление БД еще не сделано.

Akki 22.04.2019 17:08

да, потому что вам нужно вызвать itemRepository.save(i) после i.setUser(u), чтобы сохранить изменения, внесенные вами в объект

Ahmed Abdelhady 23.04.2019 09:13

Да, теперь это решено, я был очень близок к решению, но двигался по кругу :D

Akki 23.04.2019 11:43

Давайте продолжить обсуждение в чате.

Akki 23.04.2019 15:36
Ответ принят как подходящий

Хорошо, поэтому в методе ScheduleTask() ItemService.java управление достигало каждой части, и экземпляр элемента «i» также обновлялся, я думаю, мне просто нужно было сохранить это в БД с помощью itemRepository.save(i) Ниже приведена полная реализация метода ScheduleTask().

@Scheduled(cron = "0 17 11 * * ?")
    public void scheduledTask() {
        List<Item> listOfItems = (List<Item>)itemRepository.findAll();
        System.out.println("going to update DB");
        for(Item i : listOfItems) {
            Timestamp time = new Timestamp(System.currentTimeMillis());
            if (time.equals(i.getEnd_time()) || time.after(i.getEnd_time())) {
                if (i.getUser() == null) {
                    Integer item_id = i.getItem_id();
                    Integer winner_id = itemRepository.findWinner(item_id);
                    User u= userRepository.findById(winner_id).orElse(null);

                    i.setUser(u);
                    itemRepository.save(i);//change in code, which seems to work now
                    System.out.println("printing item:");
                    System.out.println(i.getItem_description()+", "+i.getItem_name()+", "+i.getStarting_amount()+", "+i.getItem_id()+", "+i.getEnd_time()+", "+i.getStart_time()+", "+i.getUser().getUser_id());
                    System.out.println("updated");
                }
            }
        }
    }

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