I have a Java app which interfaces with MySQL via Hibernate.
In my code, I have the app executing the SQL command:
final SQLQuery query = sf.getCurrentSession().createSQLQuery(
"select\n" +
" id, null as store_id, case when transaction_type = 'SALE' then 1 else -1 end as sign, payment_type,\n" +
" sum(cost_before_tax) as amount_before_tax, sum(tax_amount) as tax_amount, sum(cost) as amount,\n" +
" sum(ticket_count) as count\n" +
"from settlement_collection_initial_settlement\n" +
"where\n" +
" business_date between :start and :end\n" +
(storeID != null ? " and store_id = :store\n" : "") +
"group by transaction_type, payment_type"
);
query.addEntity(AmountRow.class);
query.setDate("start", start);
query.setDate("end", end != null ? end : start);
if (storeID != null) {
query.setString("store", new UUIDType().toSQLString(storeID));
}
return query.list();
And query.list()
returns:
However, if I run the exact same query in MySqlWorkbench:
select
id, store_id, case when transaction_type = 'SALE' then 1 else -1 end as sign, payment_type,
sum(cost_before_tax) as amount_before_tax, sum(tax_amount) as tax_amount, sum(cost) as amount,
sum(ticket_count) as count
from settlement_collection_initial_settlement
where
business_date between '2018-07-27' and '2018-07-27'
and store_id = 'xxxxxx'
group by transaction_type, payment_type
I get the results:
Notice that those results are close, but not the same. Have a look at the two CASH
lines, and the direct SQL shows a second CASH
line with a different sign
and other values. So in essense, the Hibernate executed sql is repeating that CASH line.
To me, it looks like both ways should return the exact same results. Does anyone see why my Hibernate SQL is returning different (wrong) results from when I directly execute the SQL?
UPDATE:
Here is my AmountRow
class:
@Entity
public class AmountRow {
static final int SIGN_SALE_OR_DEBIT = 1, SIGN_REFUND_OR_CREDIT = -1;
@Id
private float id;
@ManyToOne
@JoinColumn(name = "store_id", nullable = true)
private Store store;
@Column(nullable = true)
@Enumerated(EnumType.STRING)
private Payment.Type paymentType;
private int sign;
// NOTE: "$" is the name of a custom class
@Column(nullable = false)
@org.hibernate.annotations.Type(type = "com.mycompany.service.$Type")
private $ amountBeforeTax, taxAmount, amount;
@Column(nullable = false)
private int count;
// Hibernate constructor
protected AmountRow() {}
// NOTE: "$" is the name of a custom class
AmountRow(final $ amountBeforeTax, final $ taxAmount, final $ amount, final int count, final int sign) {
Assert.noneNull(amountBeforeTax, "amountBeforeTax", taxAmount, "taxAmount", amount, "amount");
Assert.notNegative(count, "count");
assertValidSign(sign);
this.amountBeforeTax = amountBeforeTax;
this.taxAmount = taxAmount;
this.amount = amount;
this.count = count;
this.sign = sign;
}
// NOTE: "$" is the name of a custom class
AmountRow(final $ amountBeforeTax, final $ taxAmount, final $ amount, final int count, final int sign, final Payment.Type paymentType) {
this(amountBeforeTax, taxAmount, amount, count, sign);
this.paymentType = paymentType;
}
static void assertValidSign(final int sign) {
if (sign != SIGN_SALE_OR_DEBIT && sign != SIGN_REFUND_OR_CREDIT)
throw new IllegalArgumentException("invalid sign " + sign);
}
public String toString() {
return "AmountRow[paymentType=" + paymentType + ", sign=" + sign + ", amountBeforeTax=" + amountBeforeTax + ", taxAmount=" + taxAmount + ", amount=" + amount + ", count=" + count + "]";
}
public Store getStore() {
return store;
}
public Payment.Type getPaymentType() {
return paymentType;
}
public int getSign() {
return sign;
}
public $ getAmountBeforeTax() {
return amountBeforeTax;
}
public $ getTaxAmount() {
return taxAmount;
}
public $ getAmount() {
return amount;
}
public int getCount() {
return count;
}
public $ getAmountBeforeTaxPerCount() {
return count != 0 ? amountBeforeTax.divide(count) : null;
}
public $ getAmountPerCount() {
return count != 0 ? amount.divide(count) : null;
}
}
Hibernate provide option to execute native SQL queries through the use of SQLQuery object. Hibernate SQL Query is very handy when we have to execute database vendor specific queries that are not supported by Hibernate API. For example query hints or the CONNECT keyword in Oracle Database.
Hibernate performs a lot of operations within the database and we have to make sure all things are working as expected, so: Set the org.hibernate.SQL to DEBUG , in order to log the executed SQL statements without the bounded values in the query.
This answer is not wrong but we have more options to do so, we will talk about them, but before, let’s explain why we don’t recommend using the show_sql option: Hibernate avoid using the logging framework when writing all executed SQL statements. Writing in standard out has a direct impact on performance.
Enable the org.hibernate.stat to be able to identify the most critical queries and prevent possible crashes in the prod environment. Once again, logging has a direct impact on performance, so in prod environment, we want to log as little as possible, but with valuable information in order to be able to identify errors fast:
This is caused by different timezone between your application and your DB.
Either:
Your HSQL also expose another problem in your code: There's a duplicated result.
Your should post your AmountRow
class for further investigation.
Update
After reading your AmountRow
class, the problem is usage of float as @Id
key would cause wrong equality check, makes Hibernate not load the data from DB. You should never rely on float/double equality.
@df778899 has an excellent explanation for this issue.
Change the @Id
field to long/int to solve the duplication.
@Manh is right in his earlier comment about the id
as a float. He deserves the answer + bounty - just using an answer to allow room to explain...
The reason is that Hibernate manages objects in its session in a StatefulPersistenceContext
. Entities in there are effectively keyed in a HashMap
by the @Id
value. (The real key class is an org.hibernate.engine.spi.EntityKey
, but the equals()
method on here will, still indirectly, drill down to Float.equals()
against the two auto-boxed float id
values).
Those id
values have too much precision for a float - the limit is typically about 7 digits:
16842421f -> (float) 1.684242E7
16842423f -> (float) 1.6842424E7
16842419f -> (float) 1.684242E7
Clearly rows 0 and 2 will be treated as having equal id
values, and therefore seen as the same entity instance by Hibernate. As @Manh says, an int
or a long
would cope better.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With