Ленивый атрибут равен нулю внутри транзакции после создания - PullRequest
0 голосов
/ 30 мая 2018

У меня есть небольшой пример с некоторыми сопоставлениями 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?
  • В чем заключается магия транзакции, заключающаяся в отсутствии транзакции, но успешной выборке ленивого атрибута ??

Ответы [ 2 ]

0 голосов
/ 31 мая 2018

Проблема в том, что вы неправильно используете 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 в контексте Persistence и вернет этот экземпляр, то есть точно такой же, который использовался вметод post со ссылкой до сих пор null.

В чем заключается магия транзакции, заключающаяся в отсутствии транзакции, но успешном получении ленивого атрибута ??

Магия не задействованаВот.fetch спецификации используются только для обхода объекта.JPQL-запросы не учитывают их.

Остается нерешенным вопрос: как исправить ситуацию?

Очевидное решение - потерять простой столбец и просто использовать сущностьссылки, как задумано JPA.

Вы не хотите делать это, чтобы избежать доступа к БД где-нибудь.Но пока вы получаете доступ только к id из указанных Stock, оно не должно инициализироваться.Таким образом, кажется, что это может быть возможно только с Lazy Fetching .

В качестве альтернативы, я бы предложил удалить отношение многие-к-одному и создать хранилище для Stock и вручную загрузить его при необходимости.

0 голосов
/ 30 мая 2018
@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
@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;
    }
}
...