У меня есть небольшой пример с некоторыми сопоставлениями get / post и вызовами JpaRepository в Spring Boot.
Во-первых, у меня есть два класса сущностей:
@Entity
@Table(name = "stock")
public class Stock extends BaseEntity
{
@Column(name = "value")
public String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
@Entity
@Table(name = "stock_item")
public class StockItem extends BaseEntity
{
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "stock_id", insertable = false, updatable = false)
public Stock stock;
@Column(name = "stock_id")
public Long stockId;
@Column(name = "value")
public String value;
}
У меня связь "многие к одному" от StockItem к Stock.
Я вставляю Stock и имею контроллер, как показано ниже:
@Autowired
public Controller(StockItemRepository stockItemRepository) {
this.stockItemRepository = stockItemRepository;
}
@RequestMapping("/")
@Transactional(readOnly = true)
public String get() {
List<StockItem> stockItemList = stockItemRepository.getItemsById(1L);
System.out.println("TX MANAGER: " + TransactionSynchronizationManager.isActualTransactionActive());
for (StockItem stockItem : stockItemList) {
System.out.println(stockItem.getStock().getValue());
}
return "get";
}
@RequestMapping("/fromSave")
@Transactional
public String post() {
StockItem stockItem = new StockItem();
stockItem.setStockId(1L);
stockItemRepository.saveAndFlush(stockItem);
System.out.println("saveCalled");
return get();
}
а getItemsById в репозитории определяется следующим образом:
@Query("FROM StockItem si " +
"JOIN FETCH si.stock stk " +
"WHERE si.stockId = :id")
List<StockItem> getItemsById(@Param("id") Long id);
Насколько я понимаю, когда я вызываю метод post:
Вот где все становится странно ...
Я вызываю get после публикации и делаю вышеуказанный вызов репозитория, который имеет объединенную выборку, и когда я вызываю stockitem.getStock().getValue(), я получаю нулевой указатель, когда я ожидаю LazyInitializationException.
Если я вызываю get() из сопоставления вне класса, он успешно загружает связанный объект.
Я даже удалил аннотацию @Transaction из get, а также
объединение-выборка из моего запроса и снова, если я вызываю извне класса, он работает и из сообщения, он вылетает с NullPointerException.
Я поместил get внутрь TransactionTemplate.execute(), и я все еще получаю NullPointerException при вызове изнутри класса.
Итак, основные вопросы:
NullPointerException вместо LazyInitializationException?



@Entity
@Table(name = "stock_item")
public class StockItem extends BaseEntity
{
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "stock_id", insertable = false, updatable = false) //here is your problem
public Stock stock;
@Column(name = "stock_id")
public Long stockId; // why explicitly define a separate column for foreign key after mapping it above
@Column(name = "value")
public String value;
}
с insertable = false и updatable = false он не будет вставлен в вашу БД и не разрешит обновление, поэтому вы получаете NullPointerException. Вы должны хотя бы разрешить вставку, чтобы выполнить запрос на основе внешнего ключа stock_id
ОБНОВИТЬ
Измените свой класс Entity с помощью доступ на основе собственности:
@Entity
@Table(name = "stock_item")
public class StockItem extends BaseEntity
{
private Stock stock; // variables should always be private since you have getters and setters
private String value;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "stock_id", updatable = false)
public Stock getStock() {
return stock;
}
public void setStock(Stock stock) {
this.stock = stock;
}
@Basic
@Column(name = "value")
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
Да, вы обязательно получите эту ошибку, поскольку вы использовали доступ на основе полей вместо доступа на основе свойств. дождитесь обновления вашего ответа.
смотри мое обновление. Посмотрим, решится ли ваша проблема или нет
да, это работает, однако это удаляет stockID из объекта .. который мне нужен.
также почему get () разрешается правильно при доступе за пределами класса vs из внутреннего вызова? действительно ищу объяснение больше, чем ответ
Престижность .. Вы можете нажать на галочку, чтобы принять этот ответ как решение вашего вопроса. Удачного кодирования !!!
Это старая ошибка.
См. Этот сайт для получения дополнительных ссылок мысли-on-java.org/…
Мне нужно сохранить атрибут Long id, потому что это сокращает ненужные вызовы db позже в моей системе. Я хочу, чтобы вся ассоциация оставалась ленивой, если она мне явно не нужна. Так что я не чувствую, что ты ответил на мой вопрос
проблема не в сохранении, экономия работает. Когда я отлаживаю идентификатор, сохраняется, но проблема связана с извлечением, почему он не извлекается правильно?
также вставляемый и обновляемый работают только с включением атрибута в операторы вставки и обновления
Проблема здесь в том, что вы неправильно используете JPA. Как вы, похоже, знаете, судя по комментариям к другому ответу, вы дважды сопоставили столбец stock_id. Когда-то как отношения многие-к-одному
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "stock_id", insertable = false, updatable = false)
public Stock stock;
и однажды как простой столбец
@Column(name = "stock_id")
public Long stockId;
Когда вы устанавливаете простой столбец и сбрасываете изменения, как в вашем методе post(), происходит следующее:
null.null.Вызов репозитория найдет идентификатор StockItem в Контекст постоянства и вернет этот экземпляр, то есть тот же самый, что использовался в методе post, со ссылкой все еще null.
What is the transaction magic behind having no transaction but successfully fetching a lazy attribute??
Здесь нет никакой магии. Спецификации fetch используются только для обхода объекта. Запросы JPQL этого не учитывают.
Остается незаданный вопрос: как исправить ситуацию?
Очевидное решение - потерять простой столбец и просто использовать ссылки на сущности, как задумано JPA.
Вы не хотите этого делать, чтобы где-то избежать доступа к БД. Но пока вы обращаетесь только к id упомянутого Stock, он не должен инициализироваться. Кажется, что это должно быть возможно только с Ленивая загрузка.
В качестве альтернативы я бы предложил удалить связь «многие к одному» и создать репозиторий для Stock и вручную загружать его при необходимости.
тогда почему инициализируется «Stock», если я вызываю get () через сопоставление запросов, а не вызываю get () из возврата post ()? Соединение выборки, похоже, не работает в последнем сценарии
это дает мне «невозможно построить Hibernate SessionFactory» при сборке. потому что это повторяющийся столбец