我有一个小例子,在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;
}
我有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方法时:
stockitem.getStock().getValue()
时,当我期望LazyInitializationException
时,我得到一个空指针。
如果我在类外部的映射中调用get()
,它会成功加载关联的对象。
我甚至从get中删除了@Transaction
注释,以及
从我的查询中再次获取联接,如果我从类外部调用它,那么它会从NullPointerException
崩溃。
我已将get
放在TransactionTemplate.execute()
内,我在课堂内打电话时仍然会得到NullPointerException
。
所以主要问题是:
NullPointerException
而不是LazyInitializationException
? 答案 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 = 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;
}
}