I'm working on modifying an existing application and I've decided to work with these 2 things.
My unmapped object is a simple object that consists of 2 integer properties:
public class EmployeeScore {
private int id;
private int score;
}
and I have a DAO which does the following:
public List<EmployeeScore> findEmployeeTotals(int regionId, int periodId) {
DataVerify.greaterThan(regionId, 0, "Invalid Region id: Region Id cannot be zero");
DataVerify.lessThan(regionId, 4, "Invalid Region id: Region id cannot be greater than 3");
List<EmployeeScore> results = (List<EmployeeScore>) currentSession().createSQLQuery(
"select n.EMP_ID, SUM(DISTINCT(nom.TOTAL_POINT)) from" +
" NOMINEE n join NOMINATION nom on nom.NOM_ID = n.NOM_ID" +
" join EMPLOYEE e on n.EMP_ID = e.EMP_ID" +
" join COMPANY c on c.COMPANY_CODE = e.COMPANY_CODE" +
" join REGION r on r.REGION_ID = c.REGION_ID" +
" where nom.PERIOD_ID = :periodId" +
" AND nom.STATUS_ID = 2" +
" AND e.ISACTIVE = 1" +
" AND nom.CATEGORY_CODE != 'H'" +
" AND r.REGION_ID = :regionId" +
" group by n.EMP_ID")
.setParameter("regionId", regionId)
.setParameter("periodId", periodId)
.list();
return results;
}
It's a huge query i know. I'm having problems on my tests and I assume because I'm not understanding how to apply these 2 correctly.
My test goes as follows:
#Test
#Transactional(isolation = Isolation.SERIALIZABLE)
public void testEmpScore() {
NomPeriod period = nomPeriodHibernateDAO.findById(6);
Region region = regionHibernateDAO.findById(1);
List<EmployeeScore> results = winnerHibernateDAO.findEmployeeTotals(region.getId(), period.getId());
results.toString();
Assert.assertEquals(13, results.size());
}
It should return 13 objects type EmployeeScore but instead it returns 0 so the test fails.
Can you point me in the right direction of what I'm doing wrong? I know it has to be something with my object seeing as it is not mapped but I have no way of mapping the score value or the id value since they reference different tables or aggregates.
Thanks.
The problem is that you are querying for two integers and trying to interpret them as EmployeeScores. Hibernate can do it but it will take a bit more work than that.
Assuming EmployeeScore has a constructor that takes two integers, you can try
select new my.package.EmployeeScore(n.EMP_ID, SUM(DISTINCT(nom.TOTAL_POINT))) ...
You need to give it the full package path to your object.
Alternatively, by default, I think the query will return a List<Object[]>. So you could iterate through these and form your employee scores manually.
List<Object[]> results = query.list();
List<EmployeeScore> scores = new LinkedList<EmployeeScore>();
for (Object[] arr : results)
{
int id = (int) arr[0];
int total = (int) arr[1];
scores.add(new EmployeeScore(id, total));
}
Related
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;
}
}
This is caused by different timezone between your application and your DB.
Either:
Use absolute time like Epoch millisecond to represent time (Work regardless timezone) OR
Check timezone of your application and sql server, choose and convert your Date to a common timezone
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.
try to use HQL
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 :beginDate and :endDate
and store_id = :storeID
group by transaction_type, payment_type
create a string from the query and then:
Query query = session.createQuery(hql);
query.setParameter("beginDate ", start);
query.setParameter("endDate", end != null ? end : start);
query.setParameter("storeID", storeID);
return query.list();
also another good advice is to always check your connection string, that you are connecting to the same database
If you examine the content of the ArrayList (the one holding Hibernate resultset), you will notice the 1st and 3rd row have the exact same address (#7427).
This means the same database row is repeated twice.
This also indicates that Hibernate is considering that the 3rd read record is identical to the 1st one (which is already present in current Session).
So IMHO, you have to investigate why these 2 AmountRow instances are evaluated as equals (> check equalsTo(), compareTo() and hashCode() implementation).
NB: To validate my diagnostic, you can return a Set (and not List) and check its size. I expect a size of 2...
im using a for with a query to obtain some rows and finally add all of these rows into one Arraylist of one class. But i receive the following error:
java.lang.ClassCastException: java.util.Vector cannot be cast to com.dominion.procop.agr.util.AGRSalvaguardasInforme
at (pathofmyclass).mostrarInformeActivosAGR(AGRInformes.java:1130)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(Unknown Source)
at java.lang.reflect.Method.invoke(Unknown Source)
the lines are these (The marked with $ is the AGRInformes.java:1130) :
for (int i = 0; i < InformeAmenazasAGR.size()-1; i++) {
String Amenaza = InformeAmenazasAGR.get(i).toString();
Amenaza = Amenaza.substring(1,3);
List<AGRSalvaguardasInforme> resultadoQuery = (List<AGRSalvaguardasInforme>) manager.SalvaguardaPorAmenaza(Amenaza);
if(!resultadoQuery.isEmpty() ){
$ for (AGRSalvaguardasInforme salvaguardaExtraida : resultadoQuery) {
InformeSalvaguardasAGR.add(salvaguardaExtraida);
}
}
}
And this is the manager.SalvaguardaPorAmenaza() method (Query and return the objects):
public List<AGRSalvaguardasInforme> SalvaguardaPorAmenaza(String idAmenaza) {
// SALVAGUARDAS POR ID_AMENAZA
int dimension = 0;
String consulta = "";
if(dimension!=0){
consulta = "SELECT COALESCE(MFT.ID_AMENAZA, MFA.ID_AMENAZA) as ID_AMENAZA, SAL.ID_SALVAGUARDA, SAL.DENOMINACION, SAL.DESCRIPCION, SAL.EFICACIA FROM AGR_SALVAGUARDAS SAL LEFT JOIN AGR_MIT_FREC_TIPO MFT "
+ " ON SAL.ID_SALVAGUARDA = MFT.ID_SALVAGUARDA AND MFT.ID_AMENAZA = "+idAmenaza+" "
+ " LEFT JOIN AGR_MIT_FREC_ACT MFA "
+ " ON SAL.ID_SALVAGUARDA = MFA.ID_SALVAGUARDA AND MFA.ID_AMENAZA = "+idAmenaza+" "
+ " WHERE MFT.ID_SALVAGUARDA IS NOT NULL OR MFA.ID_SALVAGUARDA IS NOT NULL GROUP BY SAL.ID_SALVAGUARDA, SAL.DENOMINACION, SAL.DESCRIPCION, SAL.EFICACIA, MFT.ID_AMENAZA, MFA.ID_AMENAZA";
}else{
consulta = "SELECT COALESCE(MFT.ID_AMENAZA, MFA.ID_AMENAZA) as ID_AMENAZA, SAL.ID_SALVAGUARDA, SAL.DENOMINACION, SAL.DESCRIPCION, SAL.EFICACIA FROM AGR_SALVAGUARDAS SAL LEFT JOIN AGR_MIT_FREC_TIPO MFT "
+ " ON SAL.ID_SALVAGUARDA = MFT.ID_SALVAGUARDA AND MFT.ID_AMENAZA = "+idAmenaza+" "
+ " LEFT JOIN AGR_MIT_FREC_ACT MFA "
+ " ON SAL.ID_SALVAGUARDA = MFA.ID_SALVAGUARDA AND MFA.ID_AMENAZA = "+idAmenaza+" "
+ " WHERE MFT.ID_SALVAGUARDA IS NOT NULL OR MFA.ID_SALVAGUARDA IS NOT NULL GROUP BY SAL.ID_SALVAGUARDA, SAL.DENOMINACION, SAL.DESCRIPCION, SAL.EFICACIA, MFT.ID_AMENAZA, MFA.ID_AMENAZA";
}
Query q = dao.createNativeQuery(consulta);
List<AGRSalvaguardasInforme> resultado = q.getResultList();
return resultado;
}
Why appears this error and how i can manage correctly?.
Thank you in advance.
Problem 1
Naming is mess, please follow java naming convention.
Problem 2
for (int i = 0; i < InformeAmenazasAGR.size()-1; i++) {
in this way, the last element of List:InformeAmenazasAGR would never be read.
Problem 3
for (int i = 0; i < InformeAmenazasAGR.size()-1; i++) {
...
InformeSalvaguardasAGR.add(salvaguardaExtraida);
...
}
You are adding element to a list within the for looping. It will lead to unpredictable consequence. If you need do that, use Iterator.
Problem 4
If you used "NativeQuery" (I guess jpa?) .getResultList(), you got a List<Object[]>, You cannot expect the query to give you a List<YourClass>. You have to go into the result, and do the work by yourself.
To me it looks like
Query q = dao.createNativeQuery(consulta);
List<AGRSalvaguardasInforme> resultado = q.getResultList();
it is not really returning a List of AGRSalvaguardasInforme but a List of Vector. Is Query class yours? You can use debugger to see what is the real type of the elements inside the "resultado" list at runtime.
Note: Generic information is lost at runtime due erasure. So there is no runtime check of the types of the elements contained in the collection. The issue is when you start iterating it and expecting each element to be a concrete type they are not.
This:
for (AGRSalvaguardasInforme salvaguardaExtraida : resultadoQuery) {
InformeSalvaguardasAGR.add(salvaguardaExtraida);
}
Is just a code sugar for :
for (Iterator it : resultadoQuery.iterator(); it.hasNext();) {
AGRSalvaguardasInforme salvaguardaExtraida = (AGRSalvaguardasInforme) it.next();
InformeSalvaguardasAGR.add(salvaguardaExtraida);
}
So the cast is implicitly there (and that cast is the one throwing ClassCastException at runtime for the reasons I mentioned above)
I want to create a query that query only rows that have an empty list.
The list in my model :
public static final Finder<Long, BankDebit> find = new Finder<>(Long.class, BankDebit.class);
#ManyToMany(cascade = CascadeType.ALL)
public List<Mandate> mandates;
The function that do the query :
public static ExpressionList<BankDebit> findFilter(sepaId, mandat, day ....) {
ExpressionList<BankDebit> exp = find
.fetch("creditor")
.fetch("debitor")
.fetch("mandates")
.where()
.eq("sepa.id", sepaId);
if (day > 0) {
dateMax = dateMax.withDayOfMonth(day);
exp.eq("executionDate", dateMax.toDate());
}
if (!mandat.isEmpty())
exp.eq("mandates.id", 0); // here is the problem
return exp
}
I want to query only the BankDebit that have an empty list of mandates. I tried to do it with .isNull("mandates"), .isNull("mandates.id"), .lt("mandates.id", 1), .eq("mandates.id", null) and a lot more, nothing ever worked...
I don't understund how I'm supposed to do. Do a rawSql would be very painful (I didnt paste the whole code of the function)
I tried a lot of things and reached many 4th page on google (never a good sign). I just ran out of ideas.
Huh, you were faster actually I wanted to suggest you similar solution, probably lighter as doesn't require object mapping:
List<Integer> idsWithoutMandates = new ArrayList<>();
List<SqlRow> rowList = Ebean.createSqlQuery("SELECT debits.id id " +
"FROM bank_debit AS debits " +
"LEFT JOIN bank_debit_mandate AS jointable ON (debits.id = jointable.bank_debit_id) " +
"WHERE (jointable.mandate_id IS NULL OR jointable.mandate_id = 0)").findList();
for (SqlRow sqlRow : rowList) idsWithoutMandates.add(sqlRow.getInteger("id"));
List<BankDebit> debitsWithoutMandates = BankDebit.find.where().in("id", idsWithoutMandates).findList();
I found out that although .isNull() doesn't work, .isNotNull() did work. So I made a little ugly modification to use the existing ones to find the others...
if (!mandat.isEmpty()) {
List<BankDebit> tmp = find.fetch("mandates").where().eq("sepa.id", sepaId).isNotNull("mandates.id").findList();
List<Long> ids = Lists.newArrayList();
for (BankDebit bd : tmp) {
ids.add(bd.id);
}
exp.not(Expr.in("id", ids));
}
I have an issue that is making me a little nuts. Here is the Java method.
public List<FtpActiveMerchantDTO> getFtpActiveMerchants() {
String sql = "select m.merchantId, ma.merchantAcctId, m.domain, f.fetchUrl, ma.acctActive, " +
"f.fieldDelimiter, f.feedType " +
"from merchant_account ma " +
"join merchant_ftp_account f on f.merchantAcctId = ma.merchantAcctId " +
"join merchant m on m.merchantAcctId = ma.merchantAcctId " +
"where f.fetchUrl is not null and ma.acctActive = 1";
Query query = currentSession().createSQLQuery(sql);
List<FtpActiveMerchantDTO> ftpActiveMerchantDTOList = new ArrayList<FtpActiveMerchantDTO>();
int merchantId, merchantAcctId;
byte acctActive;
for (Object rowObject : query.list()) {
Object[] row = (Object []) rowObject;
merchantId = ((BigDecimal) row[0]).intValue();
merchantAcctId = ((BigDecimal) row[1]).intValue();
acctActive = ((BigDecimal) row[4]).byteValue();
ftpActiveMerchantDTOList.add(new FtpActiveMerchantDTOBuilder().withMerchantId(merchantId)
.withMerchantAcctId(merchantAcctId).withDomain((String) row[2])
.withFetchUrl((String) row[3]).withAcctActive(acctActive > 0)
.withFieldDelimiter(row[5].toString()).withFeedType((String) row[6]).build());
}
return ftpActiveMerchantDTOList;
}
When I run my service with the code as it is shown here, I get
$ curl -X GET http://localhost:8080/merchants/ftpActive
{"responseData":null,"errorData":[{"code":500,"detailMessage":"","message":"java.lang.Byte cannot be cast to java.math.BigDecimal"}],"debugData":null}
The error is occurring at the line where acctActive is assigned. When I fix that line to this:
acctActive = (Byte) row[4];
then the service works as expected. But then my integration test (run from within IntelliJ)
private void whenFetchingFtpActiveMerchants() {
openAndBindSession();
ftpActiveMerchantDTOList = merchantDAO.getFtpActiveMerchants();
flushAndCloseSession();
}
fails, with this error:
java.lang.ClassCastException: java.math.BigDecimal cannot be cast to java.lang.Byte
at com.pronto.mpds.dal.MerchantDAOImpl.getFtpActiveMerchants(MerchantDAOImpl.java:143)
at com.pronto.mpds.dal.MerchantDAOIT.whenFetchingFtpActiveMerchants(MerchantDAOIT.java:96)
at com.pronto.mpds.dal.MerchantDAOIT.testFtpActiveMerchants(MerchantDAOIT.java:44)
...
The field in the db table is a tinyint(4). Why would the results from the db query be "expecting" to be a BigDecimal? Is there some kind of default data type? I know I am not configuring one anywhere.
At a first glance it looks like the database schema for the integration test is different from the production one, thus the type mismatch.
I have a M-to-M relation going from Nomination to User mapped on a "Nominee" table. I have the following method to encapsulate results in a paging class called "ResultPage":
protected ResultPage<T> findPageByCriteria(Criteria criteria, int page,
int pageSize) {
DataVerify.notNull(criteria);
DataVerify.greaterThan(page, 0, "Invalid page number");
DataVerify.isTrue(pageSize >= 0, "Invalid page size");
if (logger.isDebugEnabled()) {
logger.debug("Arguments: ");
logger.debug("Page: " + page);
logger.debug("Page size: " + pageSize);
}
int totalItems = 0;
List<T> results = null;
if (pageSize != 0) {
totalItems = ((Number) criteria.setProjection(Projections.rowCount()).
uniqueResult()).intValue();
criteria.setProjection(null);
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
criteria.addOrder(Order.desc("id"));
results = criteria.setFirstResult((page-1) * pageSize).
setMaxResults(pageSize).list();
} else {
results = criteria.setFirstResult((page-1) * pageSize).
list();
totalItems = results.size();
}
ResultPage<T> resultsPage = new ResultPage<T>(results, page,
totalItems,
(pageSize != 0) ? pageSize :
totalItems);
if (logger.isDebugEnabled()){
logger.debug("Total Results: " + resultsPage.getTotalItems());
}
return resultsPage;
}
Now fetching is done right. However my results count is not being consistent. This of course only happens when a "Nomination" has more than 1 user assigned to it. It then counts the users instead of the root entity and thus I get totals of "1 to 22" per page instead of "1 to 25" like I have specified - as if there are 22 nominations but 25 users total.
Can I get some help for this? Let me know if I have to clarify.
if anything this is the question that comes as closest as my problem: how to retrieve distinct root entity row count in hibernate?
The solution I use for this problem is to have a first query to only load the IDs of the root entities that satisfy the criteria (i.e. the IDs of your 25 nominations), and then issue a second query which loads the data of these 25 IDs, by doing a query like the following
select n from Nomination n
[... joins and fetches]
where n.id in (:ids)