2

NOTE

Before reading, what I am trying to find is to avoid N+1 queries on Hibernate

END NOTE

I have 3 tables. Customer, Merchant and Transactions.

Customers can have transactions. A customer can have many transaction but each transaction can only have one merchant.

This is how the transaction table looks like:

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;

Table:

`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
 )

I need to a query where the result would be:

merchantName   Trend Holomes
customerName   Paul Florence
amountDue      20000.00

I can do this in a single query. Basically I can create either a subquery

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

or by joining

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

I was able to create the output I want using Spring Data JPA + Hibernate but I noticed something strange in the logs. This is how I retrieve the data:

transactionRepository.findAll();

Two select queries are executed.

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=?

and

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=?

Does that mean that every time every time an inner join is used, a select query is executed for each referenced table? Isn't that a bit expensive? What if there are more entities that are needed to be joined?

Is there a way in hibernate to do a single one execution join just like the subquery or inner join above as to avoid sending one select query per referenced entity?

tl;dr?

Does Hibernate create select statements with the foreign key as the parameter for each joined entity?

More foreign key meaning more select statements sent?

Francis Zabala
  • 1,037
  • 2
  • 11
  • 30
  • Your question is quite confusing. Please post the relevant entities, the code you're executing, and the SQL queries that are generated by that code. – JB Nizet Oct 22 '15 at 06:04
  • hi jb, i have updated the question. hope it's more clear now. – Francis Zabala Oct 22 '15 at 06:28
  • But you still haven't posted the actual code you're executing. You said *I was able to create the output I want using Spring Data JPA + Hibernate*: post that code, and post the actual SQL queries that are executed by Hibernate and that are surprising you: Hibernate would never do a `select *`. – JB Nizet Oct 22 '15 at 06:37
  • Hi JB. I was just trying to make it a short but if you insist. Give me 10 minutes. – Francis Zabala Oct 22 '15 at 06:40
  • Hi JB, care to share your thoughts? – Francis Zabala Oct 23 '15 at 04:43

2 Answers2

1

Please, don't use findAll() in production code. It can be used for testing purposes only. If you want an optimal query — use projections with aliases(in criteria) or joins (in HQL) with a result transformer. You can use a transformer to set values directly in properties of yours Transaction object. And you need a strategy of using associations. See this for a reference.

Updated

You can use fetching strategies. So you can tweak associations to don't use separate selects. And, please, check you hibernate.properties for value of hibernate.max_fetch_depth.

And one thing else — you can have additional subselects when have not lazy bidirectional associations. But looks like it is not yous case.

Community
  • 1
  • 1
v.ladynev
  • 19,275
  • 8
  • 46
  • 67
  • Hi v.ladynev.. Thanks for the comment. No, I am not using findAll() in our code but just for the sake of my question, that what I put in there. So it is normal for hibernate to keep sending select statements per join? – Francis Zabala Oct 22 '15 at 08:03
  • @FrancisZabala You are welcome. I advise to you thinking about the global query association strategy (mentioned by me) because of tweaking associations by fetching annotations is not effective. And one important thing else — querying data and writing data is not the same! – v.ladynev Oct 22 '15 at 09:37
  • @FrancisZabala Hey! I tried to reproduce your database schema with Transaction, Customer and Merchant. There were no additional select queries. [Sources](https://github.com/v-ladynev/fluent-hibernate/tree/master/fluent-hibernate-examples/spring-console/src/main/java/com/github/fluent/hibernate/example/spring/console). Video of the try [video1](https://www.livecoding.tv/video/fluent-hibernate-meets-spring-4/) [video2](https://www.livecoding.tv/video/fluent-hibernate-meets-spring-5/) – v.ladynev Oct 23 '15 at 17:09
  • I think it's because you explicitly set an inner join. I just recently found the name of my problem, it's called N+1. – Francis Zabala Oct 24 '15 at 08:15
  • @FrancisZabala I have two requests in `doSomeDatabaseStuff()`. The first `H. request(Transaction.class).list()` is the same as `session.createCriteria(Transaction.class).list()` it was translated in SQL with left outer joins (you can see it in the second video). The second request has inner joins. It loads only merchantName, customerName and amountDue. So the problem is not with inner joins. – v.ladynev Oct 24 '15 at 10:16
  • It's not with their joins, the outer/inner joins aren't being generated. Even if adding the Fetch strategies. By the way, I am using jackson to unmarshall json from a get request. – Francis Zabala Oct 24 '15 at 10:42
  • Hi v.ladynev. Just wanted to thank you again for your help. :) Anyway, I am going to test out the codes you sent and see if they can easily be integrated with Spring Data, Jesey, Jackson and Hibernate. Otherwise, I'll revert back to MyBatis. I am confident enough to hand code SQL statements. :D – Francis Zabala Oct 24 '15 at 11:04
  • 1
    @FrancisZabala You are welcome. Anyway, you can ask me anything on stream or here. :) – v.ladynev Oct 24 '15 at 17:38
0

Aside from v.ladynev's answer, this is how I solved my issue:

Update

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

NOTE 1

My question is try to solve the N+1 issue on Hibernate because of Lazy Loading.

NOTE 2

Please note that it is important to know that I am using Spring Data + Hibernate + Jersey + Jackson. This is probably why the annotation for Fetch.Eager isn't working as it has a lot of stuff in between Hibernate and Spring Data without Spring Boot.

No, I am not using Spring boot as Jackson doesn't play nice with Spring Boot + Jersey.

Francis Zabala
  • 1,037
  • 2
  • 11
  • 30