How to make long queries more readable?
For example I have this one:
String query = "SELECT CASE WHEN EXISTS (SELECT * FROM users WHERE username = 'username' AND user_password = crypt('password', user_password)) THEN 'match' ELSE 'differ' END";
And it's completely unreadable, are there any ways to beautify it?
Since Java 15, you can use text blocks:
String query = """
SELECT CASE
WHEN
EXISTS (
SELECT *
FROM users
WHERE
username = 'username'
AND user_password = crypt('password', user_password)
)
THEN 'match'
ELSE 'differ'
END
""";
In cases when you don't wont to blend SQL and JAVA you can put SQL queries in an .sql file. And get this text when needed.
public class QueryUtil {
static public String getQuery(String fileName) throws IOException {
Path path = Paths.get("src/test/resources//" + fileName + ".sql");
return Files.readAllLines(path).get(0);
}
}
If you can mix SQL and JAVA then starting from JDK15 you can use text blocks for this.
Also you can generates Java code from your database by using JOOQ, it gives many benefits.
Assuming that you can't move to a newer-than-8 version of Java (or even if you can), by far the best solution is to use an ORM. For Java it pretty much comes down to Hibernate, or jOOQ. jOOQ (and possibly Hibernate, I haven't used it so can't say, sorry) allows you to use a fluent programming interface, which is very much in keeping with existing Java code style and patterns.
Another specific advantage of using an ORM is that you can very easily change which DB engine you use without having to change the Java code that you've written beyond changing the SQL dialect in your setup functions. See https://www.jooq.org/javadoc/latest/org.jooq/org/jooq/SQLDialect.html.
You can use JOOQ and get multiple other benefits like type safety, auto-complete, easy mapping and great support.
Have used it for several projects so far and also competition like Kotlin Exposed but always came back to JOOQ.
Move to Java 13+. There are Text Blocks for this.
Or use some ORM library.
Related
I have a query that returns a Postgres array of UUIDs:
SELECT e.id, e.date,
ARRAY
(
SELECT cs.api_id FROM event_condition_set ecs
JOIN condition_set cs on cs.id = ecs.condition_set_id
WHERE ecs.event_id = e.id
) AS condition_set_ids,
...
And then create and run this query as a native query: Query query = entityManager.createNativeQuery(queryString);
Since Hibernate can normally not deal with these Postgres arrays, I use Vlad's Hibernate Types library.
However, currently I need to register this UUIDArrayType globally in my application:
public class PostgreSQL95CustomDialect extends PostgreSQL95Dialect {
public PostgreSQL95CustomDialect() {
super();
this.registerHibernateType(Types.ARRAY, UUIDArrayType.class.getName());
}
}
Aside from the fact this is a bit ugly, it also leaves no room for other types of arrays.
(Note I also tried registering a generic ListArrayType but this throws a NullPointerException during execution of the query.)
I have also tried registering it as a scalar type:
query.unwrap(org.hibernate.query.NativeQuery.class)
.addScalar("condition_set_ids", UUIDArrayType.INSTANCE);
But this makes the entire query only return a single UUID, which is very strange and seems bugged.
Is there a way to ONLY use this UUIDArrayType specifically in this query?
(Please don't suggest using array_agg because the performance is terrible for this case)
you can call native queries using custom Hibernate types as follows:
String myJsonbData = ...;
String[] myStringArr = ...;
final String queryStr = "select your_function(?, ?, ...)"; // ? for each param
entityManager
.createNativeQuery(queryStr)
.setParameter(1, new TypedParameterValue(JsonBinaryType.INSTANCE, myJsonbData))
.setParameter(2, new TypedParameterValue(StringArrayType.INSTANCE, myStringArr));
This is just an example, but as a rule of thumb, you need to instantiate a new TypedParameterValue.
Answering my own question here. After waiting for a while and updating to the most recent library version (I'm on 2.19.2 right now) I don't have any issues anymore with the scalar types registration as I mentioned in my question, i.e.:
query.unwrap(org.hibernate.query.NativeQuery.class)
.addScalar("condition_set_ids", UUIDArrayType.INSTANCE);
So it appears to just have been a bug and I can now avoid the global registration in favor of using scalars.
we have Spring Hibernate code which generates a Spring JPA data Specification object. This works well (we can use the Specification object to do things like get count). Is there a way to get a plain SQL where-clause from the specification object somehow?
The current code is:
// The line below builds the spec based on business logic. Won't go in the details here, but it is working.
Specification<Crash> querySpec = buildQuerySpec(query);
long count = myDataRepository.count(querySpec);
// Here is what I need: a simple plain T-SQL / Microsoft SQL Server where-clause to be used on other disconnected systems. So something like this:
String whereClause = query.Spec.getPlainSQLWhereClause(...); // e.g. weather in ("raining", "cold") and year in (2014, 2015)
Reason: the specification object (and its related repository) are used in the main system. Now we have other completely disconnected/separate enterprise systems (Esri GIS system), where its APIs can only use plain SQL where-clause.
P.S. I know there's not much code to work with, so some pointers/guides will be much appreciated.
I am currently using spring-data-jpa version 1.9.4.
I have a MySql table with columns project(integer), summary(varchar), and description(varchar).
I have a regex that I would like to use to search the summary and/or description field meaning that if it finds it in summary does not need to apply regex to description.
The repository method I am attempting to use is:
List<Issue> findByProjectAndSummaryOrDescriptionRegex(long project, String regex)
The error I am receiving is:
java.lang.IllegalArgumentException: Unsupported keyword REGEX (1):
[MatchesRegex, Matches, Regex]
It is difficult in my company environment to update/upgrade versions, so if the issue is NOT my syntax but rather the then if someone knows which version now supports 'Regex' for query derivation or where I could find that specific information I would be grateful. I have looked at the Changelog and it appears that 1.9.4 should support but it appears not.
Thanks for your help!
JD
EDIT 1: I am aware of the #Query annotation but have been asked by my lead to only use that as a last resort if I cannot find the correct version which supports keyword REGEX [MatchesRegex, Matches, Regex]
I would recommend using native query (with #Query annotation) if the Spring data syntax does not work, e.g.:
#Query(nativeQuery=true, value="SELECT * FROM table WHERE project = ?1 AND (summary regexp ?2 OR description regexp ?2)")
List<Issue> findByProjectAndSummaryOrDescription(long project, String regex);
Update
If native query is not an option then (a) could you try it with single column and see if that works and (b) could you try by appending regex to both the columns, e.g.:
List<Issue> findByProjectAndDescriptionRegex(long project, String regex);
List<Issue> findByProjectAndSummaryRegexOrDescriptionRegex(long project, String regex, String regex);
In a followup, I discovered by doing some digging that the authoratative list will reside in the org.springframework.data.jpa.repository.query.JpaQueryCreator class. So for future folks that want to know which keywords from the 'Documented' list are ACTUALLY implemented, look inside JpaQueryCreator and you will the keywords supported as case arguments inside a switch!
Hope this helps!
PS - as suspected, REGEX was not supported in my version
try tu use #Query with param nativeQuery = true inside You can use database regexp_like function :
#Query(value = "select t.* from TABLE_NAME t where regexp_like(t.column, ?1)", nativeQuery = true)
Documentation :
https://www.techonthenet.com/oracle/regexp_like.php
I need to parse a query which a user enters, say in a text box, and then what I need, is that I want to encrypt all the values in query leaving the query keywords. To convert it into an equivalent query that can be performed on an encrypted database.
Such as,
select name from employee where salary = 10000
I need an equivalent query as,
select name_enc from employee_enc where salary_enc = 10000_enc
where name_enc,employee_enc, salary_enc and 10000_enc are the encrypted values of name, employee, salary and 10000. I need to do this in java and the the database I'm using is MySQL Server where the table Employee is already encrypted.
Please provide any necessary help. Thanks in advance.
You may want to consider using code from Alibaba's Druid project. Although designed as a sophisticated connection pooling library, this project supports a very advanced parser and AST for ANSI SQL and non-ANSI dialects such as MySQL, Oracle, SQL Server, etc. The project is open source and bears the very liberal Apache License Version 2.0.
The main entry points into this part of the library is SQLUtils.java. You can use values returned from SQLUtils.parseStatements to access a typed model of the statements:
List<SQLStatement> statements = SQLUtils.parseStatements(sql, JdbcConstants.MYSQL);
for (SQLStatement statement : statements) {
if (statement instanceof SQLSelectStatement) {
SQLSelectStatement createTable = (SQLSelectStatement) statement;
// Use methods like: createTable.getSelect().getQuery().
}
}
If you don't need to do it manually use SQL's included encryption and encoding operations.
If you need to do it manually split your SQL query string by spaces and ignore SQL key words as you loop to encrypt. Remember to encode your cipher results with base 64 or hex to ensure data integrity.
private String encryptSQLQuery(String plainSQLQuery){
StringBuilder cipherQuery = new StringBuilder();
String plainQuery = plainSQLQuery;
String[] splitQuery = plainQuery.split("\\s+");
for(String queryWord : splitQuery){
if(!isSQLKeyWord(queryWord))
queryWord = cryptoObject.encryptAndEncode(queryWord);
cipherQuery.append(queryWord);
cipherQuery.append(" ");
}
return cipherQuery.toString();
}
Note that you will have to implement the isSQLKeyWord() and CryptoObject.encryptAndEncode() methods.
My question is about
using registered functions for date/time manipulations in Hibernate Query Language and
IntelliJ IDEA's code inspection for these registered functions in HQL.
I'm using Hibernate 4.2.5 with Java 7, and SQL Server 2008 R2 as the database, and IntelliJ IDEA 12.1.6.
In an HQL query I need to perform the TSQL DATEADD function - or the equivalent HQL date operation. This doesn't seem to exist.
Here's what I'd like to achieve:
update MyTable set startTime = GETDATE(), targetTime = DATEADD(HOUR, allocatedTime, GETDATE()), endTime = null where faultReport.faultReportId = :faultReportId and slaTypeId = :slaTypeId
Searching for answers online has been disappointingly no help, and the most common advice (like the comment seen here: https://stackoverflow.com/a/18150333/2753571) seems to be "don't use date manipulation in hql." I don't see how I can get around performing the operation in the SQL statement in the general case (e.g. when you want to update one column based on the value in another column in multiple rows).
In a similar fashion to the advice in this post: Date operations in HQL, I've subclassed a SQLServerDialect implementation and registered new functions:
registerFunction("get_date", new NoArgSQLFunction("GETDATE", StandardBasicTypes.TIMESTAMP)); // this function is a duplication of "current_timestamp" but is here for testing / illustration
registerFunction("add_hours", new VarArgsSQLFunction(TimestampType.INSTANCE, "DATEADD(HOUR,", ",", ")"));
and added this property to my persistence.xml:
<property name="hibernate.dialect" value="my.project.dialect.SqlServerDialectExtended" />
and then I'm testing with a simple (meaningless, admitted) query like this:
select x, get_date(), add_hours(1, get_date()) from MyTable x
The functions appear to be successfully registered, and that query seems to be working because the following SQL is generated and the results are correct:
select
faultrepor0_.FaultReportSLATrackingId as col_0_0_,
GETDATE() as col_1_0_,
DATEADD(HOUR,
1,
GETDATE()) as col_2_0_,
... etc.
But I now have this problem with IntelliJ IDEA: where get_date() is used in the HQL, the code inspection complains "<expression> expected, got ')'". This is marked as an error and the file is marked in red as a compilation failure.
Can someone can explain how to deal with this, please, or explain what a better approach is? Am I using the incorrect SQLFunction template (VarArgsSQLFunction)? If yes, which is the best one to use?
I'd like the usage of the registered function to not be marked as invalid in my IDE. Ideally, if someone can suggest a better way altogether than creating a new dialect subclass, that would be awesome.