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

#java #spring-boot #jpa

#java #весенняя загрузка #jpa

Вопрос:

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

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

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

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

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

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

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

 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
);
  

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

User.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
}
  

Item.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
}
  

Bid.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
}
  

Below is the service class, where I am defining the scheduledTask() method to run and calculate the winner of Item auctioned once the end time of auction item is passed:

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);
  

Below is the error which I get once, the time comes to update the data in table:

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 не был целым > порядковым номером; вложенным исключением является java.lang.Исключение IllegalArgumentException: >org.hibernate.Исключение запроса: позиционный параметр в стиле JPA не был целым > порядковым номером в >org.springframework.orm.jpa.EntityManagerFactoryUtils.convertJpaAccessExcepti>onIfPossible(EntityManagerFactoryUtils.java:373 ) ~[spring-orm->5.1.6.RELEASE.jar:5.1.6.RELEASE] в >org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPo>ssible(HibernateJpaDialect.java:255) ~[spring-orm->]>5.1.6.RELEASE.jar:5.1.6.RELEASE] в >org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExcepti>onIfPossible(AbstractEntityManagerFactoryBean.java:527 ) ~[spring-orm->5.1.6.RELEASE.jar:5.1.6.RELEASE] в >org.springframework.dao.support.ChainedPersistenceExceptionTranslator.transla>teExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:61 ) ~ [spring-tx-5.1.6.RELEASE.jar:5.1.6.RELEASE] в >org.springframework.dao.support.DataAccessUtils.При необходимости переведите(DataAcce>ssUtils.java:242) ~[spring-tx-5.1.6.RELEASE.jar:5.1.6.RELEASE] в >org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.in>voke(PersistenceExceptionTranslationInterceptor.java:153 ) ~[spring-tx->5.1.6.RELEASE.jar:5.1.6.RELEASE] в >org.springframework.aop.framework.ReflectiveMethodInvocation.продолжить (Reflecti>veMethodInvocation.java: 186) ~[spring-aop-5.1.6.RELEASE.jar:5.1.6.RELEASE] в >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] в >org.springframework.aop.framework.ReflectiveMethodInvocation.продолжить (Reflecti>veMethodInvocation.java: 186) ~ [spring-aop-5.1.6.RELEASE.jar:5.1.6.RELEASE] в >org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(Expose>InvocationInterceptor.java:93 ) ~[spring-aop-5.1.6.RELEASE.jar:5.1.6.RELEASE] в >org.springframework.aop.framework.ReflectiveMethodInvocation.продолжить (Reflecti>veMethodInvocation.java: 186) ~[spring-aop-5.1.6.RELEASE.jar:5.1.6.RELEASE] в >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] в > org.springframework.aop.framework.ReflectiveMethodInvocation.продолжить (Reflecti>veMethodInvocation.java:186) ~[spring-aop-5.1.6.RELEASE.jar:5.1.6.RELEASE] в >org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProx>y.java:212) ~[spring-aop-5.1.6.RELEASE.jar:5.1.6.RELEASE] в com.sun.proxy.$Proxy91.findUser(неизвестный источник) ~ [na:na] в auction.demo.service.ItemService.ScheduledTask(ItemService.java: 55) > ~[classes/:na] в sun.reflect.NativeMethodAccessorImpl.invoke0(собственный метод) ~ [na: 1.8.0_121] в > sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) > ~ [na: 1.8.0_121] в > sun.отразить.Делегирование methodaccessorimpl.invoke(делегирование methodaccessorimpl.>java:43) ~[na:1.8.0_121] в java.lang.reflect.Метод.invoke(Method.java:498) ~ [na: 1.8.0_121] в>org.springframework.планирование.поддержка.ScheduledMethodRunnable.run(ScheduledM>ethodRunnable.java:84) ~[spring-context-5.1.6.RELEASE.jar:5.1.6.RELEASE] в >org.springframework.scheduling.support.Делегирование errorhandlingrunnable.run(Де> Легирование errorhandlingrunnable.java:54) ~[spring-context-> 5.1.6.RELEASE.jar:5.1.6.RELEASE] в >org.springframework.scheduling.concurrent.Перепланирование runnable.run(перепланирование> ngRunnable.java:93) [spring-context-5.1.6.RELEASE.jar:5.1.6.RELEASE] в> java.util.concurrent.Исполнители $RunnableAdapter.call(Executors.java:511) [na:1.8.0_121] в java.util.concurrent.FutureTask.run(FutureTask.java: 266) [na: 1.8.0_121] в> java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.access$ 2> 01(ScheduledThreadPoolExecutor.java:180) [na: 1.8.0_121] в> java.util.concurrent.ScheduledThreadPoolExecutor$ScheduledFutureTask.run(Sche> duledThreadPoolExecutor.java:293) [na: 1.8.0_121] в> 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] на java.lang.Thread.run(Thread.java:745) [na: 1.8.0_121]

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

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

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

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

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

Комментарии:

1. Вы пытались удалить точку с запятой в конце запроса?

2. Да, это не сработало, оно выдало другое исключение: org.springframework.dao.InvalidDataAccessResourceUsageException: не удалось извлечь результирующий набор

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

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

Ответ №1:

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

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

Комментарии:

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

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

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

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

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

Ответ №2:

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

 @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");
                }
            }
        }
    }