I am trying to execute the following JPA query:
public static final String UPDATE_INVENTORY_CUSTOMER_FOR_AMS_MAPPING = "UPDATE Inventory inventory SET"
+ " inventory.customer.id = :" + DataAccessConstants.PARAM_CUSTOMER_ID
+ " ,inventory.lastUpdateUserId = :" + DataAccessConstants.PARAM_USER_ID
+ " where inventory.amsConsignorName = :" + DataAccessConstants.PARAM_AMS_CONSIGNOR_NAME
+ " and inventory.amsConsignorOrgCd = :" + DataAccessConstants.PARAM_AMS_CONSIGNOR_ORG_CD
+ " and inventory.amsConsignorTypeName = :" + DataAccessConstants.PARAM_AMS_CONSIGNOR_TYPE
+ " and inventory.status.code in (:" + DataAccessConstants.PARAM_STATUS + ")";
but it is seeing the following:
update ATL_INVENTORY, set CONSIGNOR_ID=?, LAST_UPDATE_USER_ID=? where AMS_CONSIGNOR_NAME=? and AMS_CONSIGNOR_ORG_CD=? and AMS_CONSIGNOR_TYPE_NAME=? and (CODE in (? , ? , ? , ?))
Any ideal as to why there is a comma after the table name?
Solution
I had to change the original query to the following:
update Inventory inv set "
+ "inv.customer.id = :" + DataAccessConstants.PARAM_CUSTOMER_ID + " "
+ "where inv.amsConsignorName =:" + DataAccessConstants.PARAM_AMS_CONSIGNOR_NAME + " "
+ "and inv.amsConsignorOrgCd =:" + DataAccessConstants.PARAM_AMS_CONSIGNOR_ORG_CD + " "
+ "and inv.amsConsignorTypeName =:" + DataAccessConstants.PARAM_AMS_CONSIGNOR_TYPE + " "
+ "and exists(select 1 from Code code where inv.status = code and code.code in (:" + DataAccessConstants.PARAM_STATUS + "))
Which then produced this:
update ATL_INVENTORY set CONSIGNOR_ID=? where AMS_CONSIGNOR_NAME=? and AMS_CONSIGNOR_ORG_CD=? and AMS_CONSIGNOR_TYPE_NAME=? and (exists (select 1 from ATL_CODE code1_ where ATL_INVENTORY.STATUS=CODE_ID and (code1_.CODE in (? , ? , ? , ?))))
Based on a clarification located here: Incorrect SQL generated for JPA QL Update statement involving multiple entities
Your query is code as UPDATE Inventory inventory SET, but the generated SQL says update ATL_INVENTORY, set. Why is the literal SQL string not what you coded? When I encounter mysteries like this, they're usually caused by assuming that one thing is being done when in fact another is in play.
This suggests that the SQL you coded isn't being used to generate that SQL the way you're assuming. See where else this query might be coming from. I'd bet that the real source has a misplaced comma in it.
Which JPA implementation are you using? If I'm incorrect about a bad assumption, it says that there's a bug in the implementation. Have you used it before? Have you had success with UPDATE? If yes, it's definitely buried somewhere in your code base.
You have an interface with a bunch of constants in it. Personally, I don't care for a design like that. It's an anti-pattern with a name.
A bug in the JPA provider is very unlikely in my opinion so, as #duffymo said, are you sure you're using the right constant, that the code or maybe dependencies are up-to-date? I'd dig in that direction.
That being said, I really wonder why you're not using named queries (that are most of time pre-compiled by the persistence implementation at deployment time), either in the Java code or in meta-data mapping files (the fun part is that people didn't find having EJB-QL queries externalized in XML very manageable in EJB 2.x, hence the #NamedQuery annotation of JPA).
Related
Is there any fluent "create trigger" builder? It's weird but I can't find any trigger builder example (also searched in the jOOQ manual but without success).
I would like to transform hard-coded statement:
"CREATE TRIGGER " + TRIGGER_DELETE_TAB + " " +
"BEFORE DELETE ON " + TABLE_TABS + " " +
"BEGIN " +
"DELETE FROM " + TABLE_CHORDNAMES + " " +
"WHERE " + CHORDNAME_TAB + " = " + "OLD."+TAB_ID +"; " +
"END;"
in something like that:
SQLiteDSL.createTrigger(TRIGGER_DELETE_TAB)
.beforeDeleteOn(TABLE_TABS)
.begin()
.deleteFrom(TABLE_CHORDNAMES)
.where(column(CHORDNAME_TAB).eq("OLD."+TAB_ID))
.end()
.getSQL();
jOOQ could contain an API to implement a really trivial trigger like yours seems to be, and chances are, it will in some future version (#6956).
But in order to fully support triggers, jOOQ needs a runtime model abstracting over all sorts of procedural languages first, before going into the details of vendor specific trigger features. There's a feature request "Add procedural language abstraction API", in fact: #6475
This is being worked on for the upcoming version jOOQ 3.11, which will definitely support BEGIN .. END style blocks: #6474.
For jOOQ 3.10 and less, you will need to build your own jOOQ extension API based on the plain SQL templating mechanism documented here:
https://www.jooq.org/doc/latest/manual/sql-building/plain-sql-templating
You don't need too much plumbing to get that functionality working...
I don't think you can do that, but you can use instead ExecuteListeners, which can be considered as triggers and you can do something similar that what you build with your SQL.
You can check out their documentation regarding ExecuteListeners, they also provide a few example, like query statistics listener, logging listener and so forth.
They recommend extending DefaultExecuteListener and start from their with Javadoc, they have quite a variety of methods that you can override. I am pretty sure you will find what you need.
I have below name query
#NamedQuery(name="ScInstantTrack.getCustomerDetails",
query="select b.cardDetail.mstCustomer.customerId, last_day(b.endDate), " +
"LISTAGG(b.txnId,'|') WITHIN GROUP (ORDER BY b.endDate), " +
"count(b.txnId), sum(b.amount), sum(b.balanceAmt), sum(b.redemptionAmt) " +
"from ScInstantTrack b " +
"where b.cardNo = b.cardDetail.cardBarcode " +
"AND b.cardDetail.mstCustomer.customerId = :customerId " +
"and b.startDate <= trunc(:todayDate) " +
"and b.endDate >= trunc(:todayDate) " +
"and b.cardDetail.mstStatus.statusId = 3003 group by b.cardDetail.mstCustomer.customerId, last_day(b.endDate)")
When I am executing this query then getting below error :
unexpected token: WITHIN
I am using Oracle Database.
Why I am getting this error? How to solve this issue?
Try to use #NamedNativeQuery instead of #NamedQuery.
Also check this explanation of difference between them.
Basically you are using expressions that are exclusive in Oracle DB. In other words - you want to execute native query (query in native for Oracle DB language). Named queries use Java Persistence Query Language (HQL i.e.).
The error happen because LISTAGG is an oracle specific function.
That function is not avaliable in HQL and there is nothing you can use instead for HQL.
In order to get the result you have to use a SQLQuery wich perform native SQL queryes. This way You have to implement a version of thw query for each database, but it will work.
It is quite usual to find myself writing unit tests against database calls and I always hit the same issue: How to validate if a good query is being sent to the database?
Example, I have this class that will send a final update to the database in the following form:
update credential set password_hash = ?, password_crypt = ?, password_plain = ? where id = ?
(this is a password migration tool, please dont mind the security issues with the password_plain field)
Writing the test class for this class, I have mocked the database access class (in this case I am using Spring JDBCTemplate) and captured the issued sql. After I have the sql, I do the following checks:
String space = "\\s+";
String optSpace = "\\s*";
String something = ".+";
String optSomething = ".*";
sql = sql.toLowerCase();
assertTrue(sql.matches(optSpace + "update" + space + "credential" + space + "set" + space + something));
assertTrue(sql.matches(something + space + "set" + space + optSomething + "password_hash" + optSpace + "=" + optSpace + "\\?" + something + "where" + something));
assertTrue(sql.matches(something + space + "set" + space + optSomething + "password_crypt" + optSpace + "=" + optSpace + "\\?" + something + "where" + something));
assertTrue(sql.matches(something + space + "set" + space + optSomething + "password_plain" + optSpace + "=" + optSpace + "\\?" + something + "where" + something));
assertTrue(sql.matches(something + space + "where" + space + optSomething + "id" + optSpace + "=" + optSpace + "\\?" + optSomething));
With those checks I am indeed validating if the issued SQL contains the most important parts of the update like:
correct table is being updated
all the 3 fields are being updated to values passed as parameters
the id is being used in the where statement, with its value as a parameter
I could simply validate if the issued query is exactly the expected query above, but that would make the test too restrict for future changes and would force a failure if any part of the query was changed, even if the update stays correct. As I think that tests are written to be used mostly in the future (when you are changing software and need more reassurance for that) and not in the present, this option would make the test kinda useless.
Well, finally, I declare my question: Which better options do we have for validating the issued SQL?
I see a lot of projects that create small embedded databases with a small amount of data for testing classes that deal with the database, but I wanted to write a more pure unit test alternative (if I can call that)
I don't think there's a good alternative to testing against a real database (even if it's embedded etc). At the moment you're testing that your SQL is syntactically valid, but will it actually work. e.g. do you know if it would violate constraints etc...
Mocking etc is all well and good but at some stage you have to test against the database. I would ensure that where possible you don't test against the database, and then bite the bullet and construct tests around a small database (with suitable rollback/rebuilds etc) to actually confirm correct db functionality.
I'd implore you to reconsider the benefits of these tests because
it is verifying implementation not behavior
when you change the SQL query in the future (or someone just adds a innocuous space by mistake), you'd have a failing test even if you have preserved behavior.
For the final DataAccessLayer, I'd recommend writing an integration test. One that runs against a real but minimal DB. Sure these tests would be slow but the confidence that they offer is worth it.
So write tests against GetCustomers() and verify the returned DTO contains the right data vs verifying that the SQL query you issued is X.
don't assert your sql. it's pointless. you will end up comparing passed sql string to another string (also created by you so there is no validation) or you'll have to implement your own database. instead just use existing one. check is query returned correct data or correctly changed data in database. use dbunit or sth similar.
For this Java code:
stmt.addBatch(
"INSERT INTO Bills (BillDateTime, Table, Item, NoAttended, Service, Payment, Total) " +
"VALUES('" + billDateTime + "', " + Integer.parseInt(createTableNumberOutput.toString()) + ", '" + null + "', '"
+ Integer.parseInt(createGuestNumberOutput.toString()) + "', " + "5" + ", '" +
createPaymentTypeOutput.toString() + "', '" + "')");
I get the following error:
java.sql.BatchUpdateException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near 'Table, Item, NoAttended, Service, Payment, Total) VALUES('2012-03-26 11:15:8', 1' at line 1
The issue is not apparent to me, as MySql requires the format 'YYYY-MM-DD HH:MM:SS' for dateTime, which I have, right?
Table is reserved keyword in mysql . use backticks(`) around it.
Like below:
stmt.addBatch("INSERT INTO Bills (BillDateTime,`Table`, Item,NoAttended,Service,Payment,Total..........")
Even when you fix the reserved keyword table issue, it still won't work. You are trying the insert date.toString() in the date field. (when concatenated, toString() gets called all non-string objects)
Instead, you should use a PreparedStatement and call stm.setDate(..). Get more familiar with the prepared statement, as it is most often the better way to go. (it also has addBatch(), which works in a slightly different way - you set all parameters, then add, then set again.
I have this Java code (JPA):
String queryString = "SELECT b , sum(v.votedPoints) as votedPoint " +
" FROM Bookmarks b " +
" LEFT OUTER JOIN Votes v " +
" on (v.organizationId = b.organizationId) " +
"WHERE b.userId = 101 " +
"GROUP BY b.organizationId " +
"ORDER BY votedPoint ascending ";
EntityManager em = getEntityManager();
Query query = em.createQuery(queryString);
query.setFirstResult(start);
query.setMaxResults(numRecords);
List results = query.getResultList();
I don't know what is wrong with my query because it gives me this error:
java.lang.NoSuchMethodError: org.hibernate.hql.antlr.HqlBaseParser.recover(Lantlr/RecognitionException;Lantlr/collections/impl/BitSet;)V
at org.hibernate.hql.antlr.HqlBaseParser.fromJoin(HqlBaseParser.java:1802)
at org.hibernate.hql.antlr.HqlBaseParser.fromClause(HqlBaseParser.java:1420)
at org.hibernate.hql.antlr.HqlBaseParser.selectFrom(HqlBaseParser.java:1130)
at org.hibernate.hql.antlr.HqlBaseParser.queryRule(HqlBaseParser.java:702)
at org.hibernate.hql.antlr.HqlBaseParser.selectStatement(HqlBaseParser.java:296)
at org.hibernate.hql.antlr.HqlBaseParser.statement(HqlBaseParser.java:159)
at org.hibernate.hql.ast.QueryTranslatorImpl.parse(QueryTranslatorImpl.java:271)
at org.hibernate.hql.ast.QueryTranslatorImpl.doCompile(QueryTranslatorImpl.java:180)
at org.hibernate.hql.ast.QueryTranslatorImpl.compile(QueryTranslatorImpl.java:134)
at org.hibernate.engine.query.HQLQueryPlan.(HQLQueryPlan.java:101)
at org.hibernate.engine.query.HQLQueryPlan.(HQLQueryPlan.java:80)
at org.hibernate.engine.query.QueryPlanCache.getHQLQueryPlan(QueryPlanCache.java:94)
at org.hibernate.impl.AbstractSessionImpl.getHQLQueryPlan(AbstractSessionImpl.java:156)
at org.hibernate.impl.AbstractSessionImpl.createQuery(AbstractSessionImpl.java:135)
at org.hibernate.impl.SessionImpl.createQuery(SessionImpl.java:1650)
Thanks.
You definitely have an issue with the version of hibernate and ANTLR jars that you are using. The recover method wasn't present in the ANTLR Parser class until version 2.7.6? If you are using an earlier version of ANTLR, such as 2.7.2, then you will see this problem.
Using maven can cause this sort of situation, where you depend on Hibernate and its transitive dependencies, but something 'closer'; e.g. Struts; providers a different, earlier version of ANTLR and that earlier version gets resolved in your application.
If you can provide the version of jars involved, we would be able to help some more. Once you have fixed the issue with the jar versions, you should get a more revealing error message which shows what is wrong with your HQL expression.
Stab in the dark - Are you sure you have a consistent set of jars - perhaps you need to get the antlr jar that comes with the hibernate distribution you are using...
I've found the problem:
because this is a native query, java classes for this 2 tables must have some special attributes:
in Bookmarks.java class
#OneToMany(mappedBy = "bookmarkId")
private Collection votesCollection;
and in Votes.java class
#JoinColumn(name = "bookmark_id", referencedColumnName = "bookmark_id")
#ManyToOne
[private Bookmarks bookmarkId;
and i have also changed the query to work
tring queryString = "SELECT b, sum(v.votedPoints) " +
"FROM Bookmarks b " +
"LEFT OUTER JOIN b.votesCollection v " +
"WHERE b.userId = 101 " +
"GROUP BY b.organizationId " +
"ORDER BY sum(v.votedPoints) asc ";
thanks for the help
May be you have some double-quotes " missing or which should be doubled in your HQL.
Illustration here.
Or you miss some simple quotes as illustrated there
The query seems to be invalid unless it's an artifact of formatting.
I think you meant this:
Select b, ...
to be:
Select b.organizationId, ...
??
I have the consistent set of jars because simple queries like this one
"SELECT b FROM table_name b WHERE b.userId = 102 "
are working. I have verified all double quotes and everything is alright.
My database is: mysql, and I use jpa to connect to it. I don't know what is causing the problem. Maybe this type of join, i don't know
Er, isnt your query trying to select b which is a table alias and thats not allowed as far as I know.
I'd probably guess that something is going wrong with your query, because the method HqlBaseParser fails to lookup is called recover(RecognitionException, Bitset). Perhaps this query fails for some reason the other simpler queries don't (and the NoSuchMethod exception is thrown when attempting to recover from that error).
java.lang.NoSuchMethodError: org.hibernate.hql.antlr.HqlBaseParser.recover(Lantlr/RecognitionException;Lantlr/collections/impl/BitSet;)V
Your query is still wrong. Maybe it works with your driver/db but it isn't standard SQL. You should be selecting b.* or b.organizationId.