创建后,事务中的Lazy属性为null

时间:2018-05-30 14:11:18

标签: spring-boot spring-data-jpa spring-transactions

我有一个小例子,在Spring Boot中有一些get / post映射和JpaRepository调用。

首先,我有两个实体类:

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

我有StockItemStock的多对一关联。 我插入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方法时:

  • 它会创建一个新项目
  • 设置关联属性的ID
  • 保存并结束交易
事情变得奇怪...... 我在帖子之后调用get并进行上面的存储库调用,它有一个连接提取,当我调用stockitem.getStock().getValue()时,当我期望LazyInitializationException时,我得到一个空指针。

如果我在类外部的映射中调用get(),它会成功加载关联的对象。

我甚至从get中删除了@Transaction注释,以及 从我的查询中再次获取联接,如果我从类外部调用它,那么它会从NullPointerException崩溃。

我已将get放在TransactionTemplate.execute()内,我在课堂内打电话时仍然会得到NullPointerException

所以主要问题是:

  • 为什么我会收到NullPointerException而不是LazyInitializationException
  • 什么是没有事务但成功获取延迟属性的交易魔法?

2 个答案:

答案 0 :(得分:1)

这里的问题是你在滥用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的id并返回该实例,即post方法中使用的完全相同,引用仍为{{1 }}

  

没有事务但成功获取延迟属性后面的事务魔法是什么?

这里没有魔法。 null规范仅用于对象遍历。 JPQL查询不承认这些。

尚未解决的问题仍然存在:如何解决问题?

明显的解决方法是丢失简单列,只使用JPA预期的实体引用。

您不想这样做以避免在某处访问数据库。但只要您只访问引用的fetch的{​​{1}},就不应该对其进行初始化。因此,只有 Lazy Fetching 才能实现这一点。

或者,我建议删除多对一关系并为id创建存储库,并在需要时手动加载。

答案 1 :(得分:0)

@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 = falseupdatable = 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;
    }
}