我有一个字符串ID为
的实体@Table
@Entity
public class Stock {
@Id
@Column(nullable = false, length = 64)
private String index;
@Column(nullable = false)
private Integer price;
}
和JpaRepository:
public interface StockRepository extends JpaRepository<Stock, String> {
}
当我致电stockRepository::findAll
时,我遇到N + 1
问题:
日志已简化
从股票中选择s.index,s.price 从股票s中选择s.index,s.price,其中s.index =?
引用的最后一行调用约5K次(表的大小)。此外,当我更新价格时,我会做下一步:
stockRepository.save(listOfStocksWithUpdatedPrices);
在日志中,我有N
个插页
当id是数字时,我还没有看到类似的行为
附:将id的类型设置为numeric不是我个案中的最佳解决方案。
UPDATE1:
我忘了提及还有Trade
类与Stock
有多对多的关系:
@Table
@Entity
public class Trade {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
@Column
@Enumerated(EnumType.STRING)
private TradeType type;
@Column
@Enumerated(EnumType.STRING)
private TradeState state;
@MapKey(name = "index")
@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "trade_stock",
joinColumns = { @JoinColumn(name = "id", referencedColumnName = "id") },
inverseJoinColumns = { @JoinColumn(name = "stock_index", referencedColumnName = "index") })
private Map<String, Stock> stocks = new HashMap<>();
}
UPDATE2:
我为Stock
方添加了多对多关系:
@ManyToMany(cascade = CascadeType.ALL, mappedBy = "stocks") //lazy by default
Set<Trade> trades = new HashSet<>();
但现在它离开了加入交易(但他们很懒),以及所有交易的收藏品(他们也很懒)。但是,生成的Stock::toString
方法会抛出LazyInitializationException
异常。
答案 0 :(得分:5)
相关答案:JPA eager fetch does not join
你基本上需要设置 @Fetch(FetchMode.JOIN),因为 fetch = FetchType.EAGER 只是指定将加载关系,而不是如何加载。
对您的问题可能有帮助的是 @BatchSize 注释,指定在请求第一个时,将加载多少个延迟集合。例如,如果您在内存中有100笔交易(股票未初始化) @BatchSize(size = 50)将确保仅使用2个查询。有效地将n + 1改为(n + 1)/ 50。 https://docs.jboss.org/hibernate/orm/4.3/javadocs/org/hibernate/annotations/BatchSize.html
关于插入,您可能想要设置 hibernate.jdbc.batch_size 属性,并将order_inserts和 order_updates 设置为true。 https://vladmihalcea.com/how-to-batch-insert-and-update-statements-with-hibernate/
答案 1 :(得分:0)
但是,生成的Stock :: toString方法抛出 LazyInitializationException异常。
好的,我假设您已经使用基于类的所有字段的Lombok或IDE生成器生成了toString()
(并且很可能是equals()
和hashcode()
方法)。
请勿在JPA环境中以这种方式覆盖equals()
hashcode()
和toString()
,因为它有可能(a)触发您拥有的异常看看toString()是否在事务之外访问延迟加载的集合,以及(b)在事务中使用时触发极大数据量的加载。写一个不涉及关联的String,并使用(a)一些商业密钥(如果有的话)实现equals()和hashcode(),(b)ID(知道这种方法可能出现的问题或(c)做完全没有覆盖它们。
首先,删除这些生成的方法,看看是否有所改善。
答案 2 :(得分:0)
关于插入,我注意到有一件事在JPA中经常被忽略。我不知道你使用什么数据库,但你必须小心
@GeneratedValue(strategy = GenerationType.AUTO)
对于MySQL,我认为所有JPA实现都映射到auto_incremented字段,一旦你知道JPA是如何工作的,这就有两个含义。
如果您插入了大量对象,并且想要获得良好的性能,我建议您使用表生成的序列,让JPA在大块中预先分配ID,这也允许SQL驱动程序批量插入(。 ..)VALUES(...)优化。
另一个建议(不是每个人都同意我这一点)。我个人从不使用ManyToMany,我总是将它分解为OneToMany和ManyToOne,并将连接表作为一个真实的实体。我喜欢它对级联和提取的附加控制,你可以避免使用双向关系中存在的一些ManyToMany陷阱。