ID为字符串时为N + 1(JpaRepository)

时间:2016-12-01 15:06:35

标签: java hibernate jpa spring-data spring-data-jpa

我有一个字符串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异常。

3 个答案:

答案 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是如何工作的,这就有两个含义。

  • 每个插入内容都包含两个查询。首先是insert,然后是select查询(MySQL的LAST_INSERT_ID)以获取生成的主键。
  • 它还可以防止任何批量查询优化,因为每个查询都需要在其自己的插入中完成。

如果您插入了大量对象,并且想要获得良好的性能,我建议您使用表生成的序列,让JPA在大块中预先分配ID,这也允许SQL驱动程序批量插入(。 ..)VALUES(...)优化。

另一个建议(不是每个人都同意我这一点)。我个人从不使用ManyToMany,我总是将它分解为OneToMany和ManyToOne,并将连接表作为一个真实的实体。我喜欢它对级联和提取的附加控制,你可以避免使用双向关系中存在的一些ManyToMany陷阱。