select binary(16) in jooq, but show hex string - java

In the table is a pk with uuid's stored as binary(16).
I'm able to retrieve the hexadecimal using plain sql:
select hex(UUID) as uuid from tbl ;
But the jooq equivalent does not know a hex function.
Result<Record1<byte[]>> result = ctx
.select(tbl.UUID)
.from(tbl)
.fetch();
Casting to String gives the id of the java object.
Any idea's?
Result<Record1<byte[]>> result = ctx
.select(tbl.UUID.cast(String.class))
.from(tbl)
.fetch();
Same problem using ip's (ipv4, ipv6):
select inet_ntoa(conv(hex(IP), 16, 10)) as ip from tbl ;

jOOQ doesn't support all vendor-specific functions out of the box. Whenever you are missing such a function, you can create it yourself using plain SQL:
public class DSLExtensions {
public static Field<String> hex(Field<byte[]> field) {
return DSL.field("hex({0})", String.class, field);
}
}
This obviously holds true for your other functions, too, such as inet_ntoa() and conv()

#Lukas Eder, you can simplify that even more.
public static Field<String> hex(Field<byte[]> field) {
return DSL.function("hex", String.class, field);
}

Related

How to correctly call a stored function with return value in Oracle via Java JPA (Hibernate)?

We have to refracgtor some code used for integration with another application which runs in Oracle DB and exposes to us some stored procedures or functions. Actually the execution of those stored procedures is done via Session of Hibernate but for some security issues we are required to strictly use the generic JPA interfaces and EntityManager.
Unfortunately, we are facing some problems because we are not able to understand which is the right way to proceed. This is the signature of the Oracle stored function which is inside a package
CREATE OR REPLACE package body DEVELOPERS.INTEGRATION_OPERATIONS is
...
function doOperation(arg1_in in number,
arg2_in in varchar2,
arg3_in in date,
arg4_in in number,
arg5_in in number,
arg6_in in date,
arg7_in in varchar2 default null,
arg8_in in varchar2 default null,
arg9_in in varchar2 default null,
arg10_in in varchar2,
arg11_in in number default null,
arg12_in in number
) return number is
...
end doOperation;
begin
...
end INTEGRATION_OPERATIONS;
The first trial I did was to use JPA StoredProcedureQuery by reading what I found here and other similar posts. But it does not seem to work: it fails with an error saying that the procedure is not defined. My guess is that it expects a really stored procedure while mine is a function.
The only way which seems to work is the following:
public enum OPERATION_PARAMATERS {
PARAM_ARG1(1,"arg1_in", Long.class, ParameterMode.IN),
PARAM_ARG2(2, "arg2_in", String.class, ParameterMode.IN),
PARAM_ARG3(3, "arg3_in", Timestamp.class, ParameterMode.IN),
PARAM_ARG4(4, "arg4_in", Long.class, ParameterMode.IN),
PARAM_ARG5(5, "arg5_in", Long.class, ParameterMode.IN),
PARAM_ARG6(6, "arg6_in", Timestamp.class, ParameterMode.IN),
PARAM_ARG7(7, "arg7_in", String.class, ParameterMode.IN),
PARAM_ARG8(8, "arg8_in", String.class, ParameterMode.IN),
PARAM_ARG9(9, "arg9_in", String.class, ParameterMode.IN),
PARAM_ARG10(10, "arg10", String.class, ParameterMode.IN),
PARAM_ARG11(11, "arg11_in", Long.class, ParameterMode.IN),
PARAM_ARG12(12, "arg12_in", Long.class, ParameterMode.IN);
private final int paramPosition;
private final String paramName;
private final Class<?> paramType;
private final ParameterMode paramMode;
<T> OPERATION_PARAMATERS(int paramPosition, String paramName, Class<T> clazz, ParameterMode paramMode) {
this.paramPosition = paramPosition;
this.paramName = paramName;
this.paramType = clazz;
this.paramMode = paramMode;
}
public int getParamPosition() {
return paramPosition;
}
public String getParamName() {
return paramName;
}
public Class<?> getParamType() {
return paramType;
}
public ParameterMode getParamMode() {
return paramMode;
}
}
#Override
public void invokeOperation(final DoOperation operation) {
Query nativeQuery = this.entityManager.createNativeQuery(DoOperation.STORED_FUNCTION_CALL);
for(OPERATION_PARAMATERS p : OPERATION_PARAMATERS.values()) {
nativeQuery.setParameter(p.getParamName(), operation.getParamValue(p));
}
nativeQuery.executeUpdate();
}
where DoOperation is a sort of DTO containing all values for parameters and the native query is
public static final String STORED_FUNCTION_CALL = "DECLARE res NUMBER; begin res \\:= INTEGRATION_OPERATIONS.DOOPERATION(:arg1_in, :arg2_in, :arg3_in, :arg4_in, :arg5_in, :arg6_in, :arg7_in, :arg8_in, :arg9_in, :arg10_in, :arg11_in, :arg12_in); END;";
This seems to be executed without errors, but supposing this is the right way to do, how can I recover the returned value in res variable? I cannot ask the other developers to refactor this function as a stored procedure with in/out paramaters as it is a legacy integration.
How can I do? Is there a way to use JPA StoredProcedureQuery or is it reserved to pure stored procedures?
Our application is a Java EE 7 application running under Wildfly 10 with EJB 3.1. The implementation of JPA is provided by Wildfly with hibernate-core-4.1.2.Final.jar

How to select unix timestamp as date in Jooq?

I am working in with a database in which dates are stored as unix time (seconds since 1970). I have the following sql which works as expected:
select CONVERT_TZ(FROM_UNIXTIME(creation_date), ##session.time_zone, "Europe/Berlin")
from transaction;
This is how I tried to do it in Jooq:
dsl.select(DSL.date(TRANSACTION.CREATION_DATE) // This does not work
.from(TRANSACTION)
.fetch();
You're using quite a few vendor specific functions there, which are not supported out of the box in jOOQ. As always, when you run into this situation, you can roll your own support by using plain SQL templating, ideally by building your own library:
public static Field<Timestamp> fromUnixtime(Field<? extends Number> field) {
return field("from_unixtime({0})", SQLDataType.TIMESTAMP, field);
}
public static Field<Timestamp> convertTz(
Field<Timestamp> field,
Field<String> fromTz,
Field<String> toTz
) {
return field("convert_tz({0}, {1}, {2})", SQLDataType.TIMESTAMP, field, fromTz, toTz);
}
public static Field<String> sessionTimeZone() {
return field("##session.time_zone", SQLDataType.VARCHAR);
}
Now, you can use it like this:
dsl.select(convertTz(
fromUnixtime(TRANSACTION.CREATION_DATE),
sessionTimeZone(),
inline("Europe/Berlin"))
)
.from(TRANSACTION)
.fetch();
Or, take it a step further and wrap all of these expressions in yet another auxiliary function, like this one:
public static Field<Timestamp> fromUnixtimeToBerlinTZ(Field<? extends Number> field) {
return convertTz(
fromUnixtime(TRANSACTION.CREATION_DATE),
sessionTimeZone(),
inline("Europe/Berlin")
);
}
All of these examples are assuming the usual static import:
import static org.jooq.impl.DSL.*;

Query from user defined function JOOQ

I'm looking to query from user defined function. All the example I've seen are querying the function as select parameter
myFunction.setMyParam("plapla");
create.select(myFunction.asField()).fetch();
This return the result as one column although the result is actually multiple columns.
What I would like to do is
myFunction.setMyParam("plapla");
create.select().from(myFunction).fetch();
But I have not found a way to do so...
Currently I'm using
DSL.using(create.configuration())
.select()
.from("myFunction('" + myparam + "')")
.fetch();
and this does not seem like a good solution (unescaped, untreated etc.)
How to do it using JOOQ generated function?
jOOQ generates function calls for PostgreSQL table valued functions. If your function looks like this:
CREATE FUNCTION my_function(text, integer, text)
RETURNS TABLE (myParams text) AS ...
Then jOOQ produces the following method:
public class Tables {
...
public static MyFunction myFunction(String param1, Integer param2, String param3)
{ ... }
}
Which you can then call as such:
Result<MyFunctionRecord> result =
DSL.using(create.configuration())
.selectFrom(myFunction("a", 1, "b")
.fetch();
The resulting record will be of the form:
public class MyFunctionRecord {
...
public String getMyParams() { ... }
}
I had to turn off generating table valued functions for now because of the infamous
java: variable XYZ is already defined in class
I was able to accomplish the above like
create
.selectFrom( Routines.myFunction(myparam).toString() )
I hope to get the table valued functions generating properly, because the above answer is better, but this will work in the meantime.

How to implement order by field value with jOOQ's dynamic queries

I am trying to convert something like this in jOOQ:
select foo from bar
order by field(foo, 'value1', 'something-else', 'value3')
on a select query, like:
SelectQuery<Record> query = ...
query.addSelect(BAR.FOO);
query.addFrom(BAR);
query.addOrderBy( ... ? ... )
How does one add the last bit?
Background
What I am trying to accomplish is basically described here: MySQL - ORDER BY values within IN(). In my case, I have a generic batch load function that uses 'where field in(..)' and I want to preserve order. This works as I need it to using plain SQL, but I need to add this bit to a dynamically constructed query with jOOQ.
Whenever you hit jOOQ's limits, resort to plain SQL. You can write your own field function like this:
class MyDSL {
public static Field<Integer> field(Field<String> search, String in1) {
return field(search, DSL.val(in1));
}
public static Field<Integer> field(Field<String> search, Field<String> in1) {
return DSL.field("field({0}, {1})", Integer.class, search, in1);
}
public static Field<Integer> field(Field<String> search,
String in1,
String in2) {
return field(search, val(in1), val(in2));
}
public static Field<Integer> field(Field<String> search,
Field<String> in1,
Field<String> in2) {
return DSL.field("field({0}, {1}, {2})", Integer.class, search, in1, in2);
}
// ... or, support a varargs function variant, too
}
And now use that in all your statements:
query.addOrderBy( MyDSL.field(BAR.FOO, "value1", "something-else", "value3") );
This seems to do the trick. Not sure if there is a better answer,
Field[] args = new Field[]{DSL.field("foo"),
DSL.val("value1"), DSL.val("something-else"), DSL.val("value3")}
query.addOrderBy(DSL.function("field", SQLDataType.INTEGER, args));
You can use something like to convert following sql to jooq. Here 'sortAsc' is used to sort according to the given value order.
SQL
select foo from bar order by field(foo, 'value1', 'something-else', 'value3')
JOOQ
DSL()
.select(BAR.FOO)
.from(BAR)
.orderBy(BAR.FOO.sortAsc('value11', 'something-else', 'value3'))
.fetch()

How to use Annotations with iBatis (myBatis) for an IN query?

We'd like to use only annotations with MyBatis; we're really trying to avoid xml. We're trying to use an "IN" clause:
#Select("SELECT * FROM blog WHERE id IN (#{ids})")
List<Blog> selectBlogs(int[] ids);
MyBatis doesn't seem able to pick out the array of ints and put those into the resulting query. It seems to "fail softly" and we get no results back.
It looks like we could accomplish this using XML mappings, but we'd really like to avoid that. Is there a correct annotation syntax for this?
I believe the answer is the same as is given in this question. You can use myBatis Dynamic SQL in your annotations by doing the following:
#Select({"<script>",
"SELECT *",
"FROM blog",
"WHERE id IN",
"<foreach item='item' index='index' collection='list'",
"open='(' separator=',' close=')'>",
"#{item}",
"</foreach>",
"</script>"})
List<Blog> selectBlogs(#Param("list") int[] ids);
The <script> element enables dynamic SQL parsing and execution for the annotation. It must be very first content of the query string. Nothing must be in front of it, not even white space.
Note that the variables that you can use in the various XML script tags follow the same naming conventions as regular queries, so if you want to refer to your method arguments using names other than "param1", "param2", etc... you need to prefix each argument with an #Param annotation.
I believe this is a nuance of jdbc's prepared statements and not MyBatis. There is a link here that explains this problem and offers various solutions. Unfortunately, none of these solutions are viable for your application, however, its still a good read to understand the limitations of prepared statements with regards to an "IN" clause. A solution (maybe suboptimal) can be found on the DB-specific side of things. For example, in postgresql, one could use:
"SELECT * FROM blog WHERE id=ANY(#{blogIds}::int[])"
"ANY" is the same as "IN" and "::int[]" is type casting the argument into an array of ints. The argument that is fed into the statement should look something like:
"{1,2,3,4}"
Had some research on this topic.
one of official solution from mybatis is to put your dynamic sql in #Select("<script>...</script>"). However, writing xml in java annotation is quite ungraceful. think about this #Select("<script>select name from sometable where id in <foreach collection=\"items\" item=\"item\" seperator=\",\" open=\"(\" close=\")\">${item}</script>")
#SelectProvider works fine. But it's a little complicated to read.
PreparedStatement not allow you set list of integer. pstm.setString(index, "1,2,3,4") will let your SQL like this select name from sometable where id in ('1,2,3,4'). Mysql will convert chars '1,2,3,4' to number 1.
FIND_IN_SET don't works with mysql index.
Look in to mybatis dynamic sql mechanism, it has been implemented by SqlNode.apply(DynamicContext). However, #Select without <script></script> annotation will not pass parameter via DynamicContext
see also
org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
org.apache.ibatis.scripting.xmltags.DynamicSqlSource
org.apache.ibatis.scripting.xmltags.RawSqlSource
So,
Solution 1: Use #SelectProvider
Solution 2: Extend LanguageDriver which will always compile sql to DynamicSqlSource. However, you still have to write \" everywhere.
Solution 3: Extend LanguageDriver which can convert your own grammar to mybatis one.
Solution 4: Write your own LanguageDriver which compile SQL with some template renderer, just like mybatis-velocity project does. In this way, you can even integrate groovy.
My project take solution 3 and here's the code:
public class MybatisExtendedLanguageDriver extends XMLLanguageDriver
implements LanguageDriver {
private final Pattern inPattern = Pattern.compile("\\(#\\{(\\w+)\\}\\)");
public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
Matcher matcher = inPattern.matcher(script);
if (matcher.find()) {
script = matcher.replaceAll("(<foreach collection=\"$1\" item=\"__item\" separator=\",\" >#{__item}</foreach>)");
}
script = "<script>" + script + "</script>";
return super.createSqlSource(configuration, script, parameterType);
}
}
And the usage:
#Lang(MybatisExtendedLanguageDriver.class)
#Select("SELECT " + COLUMNS + " FROM sometable where id IN (#{ids})")
List<SomeItem> loadByIds(#Param("ids") List<Integer> ids);
I've made a small trick in my code.
public class MyHandler implements TypeHandler {
public void setParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
Integer[] arrParam = (Integer[]) parameter;
String inString = "";
for(Integer element : arrParam){
inString = "," + element;
}
inString = inString.substring(1);
ps.setString(i,inString);
}
And I used this MyHandler in SqlMapper :
#Select("select id from tmo where id_parent in (#{ids, typeHandler=ru.transsys.test.MyHandler})")
public List<Double> getSubObjects(#Param("ids") Integer[] ids) throws SQLException;
It works now :)
I hope this will help someone.
Evgeny
Other option can be
public class Test
{
#SuppressWarnings("unchecked")
public static String getTestQuery(Map<String, Object> params)
{
List<String> idList = (List<String>) params.get("idList");
StringBuilder sql = new StringBuilder();
sql.append("SELECT * FROM blog WHERE id in (");
for (String id : idList)
{
if (idList.indexOf(id) > 0)
sql.append(",");
sql.append("'").append(id).append("'");
}
sql.append(")");
return sql.toString();
}
public interface TestMapper
{
#SelectProvider(type = Test.class, method = "getTestQuery")
List<Blog> selectBlogs(#Param("idList") int[] ids);
}
}
In my project, we are already using Google Guava, so a quick shortcut is.
public class ListTypeHandler implements TypeHandler {
#Override
public void setParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException {
ps.setString(i, Joiner.on(",").join((Collection) parameter));
}
}
In Oracle, I use a variant of Tom Kyte's tokenizer to handle unknown list sizes (given Oracle's 1k limit on an IN clause and the aggravation of doing multiple INs to get around it). This is for varchar2, but it can be tailored for numbers (or you could just rely on Oracle knowing that '1' = 1 /shudder).
Assuming you pass or perform myBatis incantations to get ids as a String, to use it:
select #Select("SELECT * FROM blog WHERE id IN (select * from table(string_tokenizer(#{ids}))")
The code:
create or replace function string_tokenizer(p_string in varchar2, p_separator in varchar2 := ',') return sys.dbms_debug_vc2coll is
return_value SYS.DBMS_DEBUG_VC2COLL;
pattern varchar2(250);
begin
pattern := '[^(''' || p_separator || ''')]+' ;
select
trim(regexp_substr(p_string, pattern, 1, level)) token
bulk collect into
return_value
from
dual
where
regexp_substr(p_string, pattern, 1, level) is not null
connect by
regexp_instr(p_string, pattern, 1, level) > 0;
return return_value;
end string_tokenizer;
You could use a custom type handler to do this. For example:
public class InClauseParams extends ArrayList<String> {
//...
// marker class for easier type handling, and avoid potential conflict with other list handlers
}
Register the following type handler in your MyBatis config (or specify in your annotation):
public class InClauseTypeHandler extends BaseTypeHandler<InClauseParams> {
#Override
public void setNonNullParameter(final PreparedStatement ps, final int i, final InClauseParams parameter, final JdbcType jdbcType) throws SQLException {
// MySQL driver does not support this :/
Array array = ps.getConnection().createArrayOf( "VARCHAR", parameter.toArray() );
ps.setArray( i, array );
}
// other required methods omitted for brevity, just add a NOOP implementation
}
You can then use them like this
#Select("SELECT * FROM foo WHERE id IN (#{list})"
List<Bar> select(#Param("list") InClauseParams params)
However, this will not work for MySQL, because the MySQL connector does not support setArray() for prepared statements.
A possible workaround for MySQL is to use FIND_IN_SET instead of IN:
#Select("SELECT * FROM foo WHERE FIND_IN_SET(id, #{list}) > 0")
List<Bar> select(#Param("list") InClauseParams params)
And your type handler becomes:
#Override
public void setNonNullParameter(final PreparedStatement ps, final int i, final InClauseParams parameter, final JdbcType jdbcType) throws SQLException {
// note: using Guava Joiner!
ps.setString( i, Joiner.on( ',' ).join( parameter ) );
}
Note: I don't know the performance of FIND_IN_SET, so test this if it is important
I had done this with postgresql.
#Update('''
UPDATE sample_table
SET start = null, finish = null
WHERE id=ANY(#{id});
''')
int resetData(#Param("id") String[] id)
ANY works like the IN.
Code above is using groovy but can be converted into java by replacing the single quotes into double.

Categories

Resources