如何使用hibernate(N + 1问题)

时间:2015-10-22 05:55:34

标签: hibernate join

注意

在阅读之前,我想要找到的是避免在Hibernate上进行N + 1查询

结束注释

我有3张桌子。客户,商家和交易。

客户可以进行交易。客户可以有很多交易,但每笔交易只能有一个商家。

这是交易表的样子:

private Long transaction_id;
/** removed other stuff for brevity **/
@ManyToOne(optional=false) 
@JoinColumn(name="merchantId", referencedColumnName="merchantId") 
private Merchant merchant;

@ManyToOne(optional=false) 
@JoinColumn(name="customerId", referencedColumnName="customerId") 
private Customer customer;

表:

`Transaction` (
`sequenceId` bigint(20) NOT NULL AUTO_INCREMENT,
`amountDue` decimal(19,2) DEFAULT NULL,
`merchantId` varchar(255) DEFAULT NULL,
`customerId` varchar(255) NOT NULL
)

`Customer` (
`customerId` bigint(20) NOT NULL AUTO_INCREMENT,
`dateCreated` datetime DEFAULT NULL,
`dateUpdated` datetime DEFAULT NULL,
`email` varchar(255) DEFAULT NULL,
`customerName` varchar(255) DEFAULT NULL,
`subscriberNum` varchar(255) DEFAULT NULL
 )

`Merchant` (
`sequenceId` bigint(20) NOT NULL AUTO_INCREMENT,
`merchantId` varchar(255) DEFAULT NULL,
`merchantName` varchar(255) DEFAULT NULL
 )

我需要查询结果:

merchantName   Trend Holomes
customerName   Paul Florence
amountDue      20000.00

我可以在一个查询中执行此操作。基本上我可以创建一个子查询

SELECT 
    (SELECT m.merchantname  
    FROM merchant m WHERE m.id = t.merchantId) AS merchantname,
    (SELECT c.customername 
    FROM customer c WHERE c.id = t.customerId) AS customername,
    t.amountDue
FROM transaction t

或加入

select m.merchantname, c.customername, t.amountDue
    from transaction t 
    inner join customer c on t.customerId = c.id 
    inner join merchant m on t.merchantId = m.id

我能够使用Spring Data JPA + Hibernate创建我想要的输出,但我注意到日志中有些奇怪。这就是我检索数据的方式:

transactionRepository.findAll();

执行两个选择查询。

select customer0_.customerId as sequence1_0_0_, customer0_.dateCreated as dateCrea2_0_0_, customer0_.dateUpdated as dateUpda3_0_0_, customer0_.email as email4_0_0_, customer0_.customerName as customerNam5_0_0_, customer0_.subscriberNum as subscrib9_0_0_ from Customer customer0_ where customer0_.customerId=?

select merchant0_.sequenceId as sequence1_2_0_, merchant0_.merchantId as merchant2_2_0_, merchant0_.merchantName as merchant3_2_0_, merchant0_.merchantOptInCode as merchant4_2_0_, merchant0_.merchantOptOutCode as merchant5_2_0_ from Merchant merchant0_ where merchant0_.merchantId=?

这是否意味着每次使用内部联接时,都会为每个引用的表执行select查询?这有点贵吗?如果需要加入更多实体怎么办?

在hibernate中是否有办法像上面的子查询或内部联接一样执行单个执行连接,以避免为每个引用的实体发送一个选择查询?

TL;博士

Hibernate是否使用外键创建select语句作为每个连接实体的参数?

更多外键意味着发送更多选择语句?

2 个答案:

答案 0 :(得分:1)

请不要在生产代码中使用findAll()。它仅可用于测试目的。如果您想要一个最佳查询 - 使用具有别名(在条件中)的投影或使用结果转换器连接(在HQL中)。您可以使用转换器直接在您的Transaction对象的属性中设置值。而且你需要一种使用关联的策略。请参阅this以获取参考。

<强>更新

您可以使用fetching strategies。因此,您可以调整关联以不使用单独的选择。并且,请检查hibernate.properties的值hibernate.max_fetch_depth

还有一件事 - 当没有懒惰的双向关联时,你可以有额外的子选择。但看起来不是你的情况。

答案 1 :(得分:0)

除了v​​.ladynev的回答,这就是我解决问题的方法:

<强>更新

@Repository
public interface TransactionRepository extends PagingAndSortingRepository<TransactionRepository, Long>{

@Query("from Transaction AS t inner join "
            + "fetch t.customer "
            + "inner join "
            + "fetch t.merchant "
            + "where t.optStatus = ?1")
public List<TransactionRepository> findByOptStatus(boolean optStatus);

注1

我的问题是尝试解决Hibernate上的N + 1问题,因为Lazy Loading。

注2

请注意,知道我使用Spring Data + Hibernate + Jersey + Jackson非常重要。这可能就是为什么Fetch.Eager的注释没有工作的原因,因为它在没有Spring Boot的Hibernate和Spring Data之间有很多东西。

不,我没有使用Spring启动,因为Jackson与Spring Boot + Jersey不搭配。