jOOQ .fetchMap() with Converters - java

I am trying to execute .fetchMap(key, value) with jOOQ but I want to process the key through a custom converter.
The docs are very clear on how to use converters and how to use .fetchMap() but I can't find anywhere a way of combining both.
Could this feature be missing from my jOOQ version (3.9) ?

Converter (and Binding) implementations are bound to the Field reference by the code generator, or you can do it manually like this:
// Using this static import
import static org.jooq.impl.DSL.*;
// Assuming a VARCHAR column in the database:
DataType<MyType> type = SQLDataType.VARCHAR.asConvertedDataType(
new MyConverter<String, MyType>());
Field<MyType> field = field(name("MY_TABLE", "MY_FIELD"), type);
Now, whenever you fetch this field in your SELECT statements, e.g.
Result<Record1<MyType>> result =
DSL.using(configuration)
.select(field)
.from(...)
.fetch();
jOOQ will automatically apply your converter while fetching results from the underlying JDBC ResultSet. You will never see the original String value in your result.
The ResultQuery.fetchMap(Field, Field) method that you've mentioned is just short for fetch() and then Result.intoMap(Field, Field). In other words, the converter will already have been applied automatically by the time you call fetchMap() or intoMap(), so there is no need to do anything specific. Just use your field as an argument to fetchMap() :
Map<MyType, OtherType> result =
DSL.using(configuration)
.select(field, otherField)
.from(...)
.fetchMap(field, otherField);

Related

Register global converters in jOOQ

I have a snippet of code like:
ctx.select()
.from()
...
.fetchInto(MyAwesomeClass.class)
MyAwesomeClass has a field with a custom type (let's call it MyLong), which can be converted to/from Long. Currently, jOOQ can't convert between Long and MyLong:
org.jooq.exception.DataTypeException: Cannot convert from 1 (class java.lang.Long) to class foo.bar.MyLong
I would like to register a converter in the jOOQ DSL configuration to convert from Long to MyLong. I have the converter, but I'm struggling to get it into the DSL configuration. There is the option to give the configuration a ConverterProvider, which I did - but it didn't seem to work.
The ConverterProvider snippet is something like this:
new DefaultConfiguration()
.set(
new ConverterProvider() {
#Override
public <T, U> Converter<T, U> provide(Class<T> tType, Class<U> uType) {
if (tType == Long.class && uType == MyLong.class) {
return (Converter<T, U>) Converter.of(Long.class, MyLong.class, MyLong::of, MyLong::toLong);
} else {
throw new UnsupportedOperationException();
}
}
});
I know that this API is marked as experimental, but is there some other way to achieve what I want, without declaring a forced binding or some other construct on the code generation side?
The ConverterProvider SPI
The ConverterProvider SPI has been added in jOOQ 3.6, but this addition was not documented, nor has the SPI been implemented completely. The relevant, rejected issue is: https://github.com/jOOQ/jOOQ/issues/3896
The SPI types have not been removed yet due to backwards compatibility issues in the Configuration API (especially relevant when configured with Spring).
Current solution as of jOOQ 3.11
This SPI would have solved your problem indeed. Until a similar SPI is implemented, you need to register your custom Converter types with the fields that are part of your projection / SELECT clause. This can be done in two ways:
Using the code generator
You can register your own custom data types and the relevant converters easily using the <forcedTypes/> configuration in your code generator. That way, whenever you select a converted column, jOOQ will produce your own custom type MyLong rather than the database / JDBC type Long
Using ad-hoc converted types
If you're not using the code generator, you can create your own data types like this:
DataType<MyLong> MY_LONG_TYPE = SQLDataType.BIGINT.asConvertedDataType(new MyLongConverter());
Field<MyLong> MY_LONG_FIELD = field(name("MY_TABLE", "MY_LONG_FIELD"), MY_LONG_TYPE);
You can now use this column reference in any query to get MyLong values:
ctx.select(MY_LONG_FIELD)
.from(...)
.fetchInto(MyAwesomeClass.class);

Automatic data type conversion in jOOQ's code generation

I have the following questions:
eg:
Does jooq provide a conversion mechanism like mybatis type, rather than every time I need to manually convert. For example, int is converted to Byte. Long [] into UInteger and so on. I do not know how to deal with the type of conversion, can give me a detailed solution.
The code generation tool is as follows:
public static void main(String[] args) throws Exception {
Configuration configuration = new Configuration()
.withJdbc(new Jdbc()
.withDriver("com.mysql.jdbc.Driver")
.withUrl("jdbc:mysql://localhost:3306/51unboxdb")
.withUser("root")
.withPassword("root")
)
.withGenerator(
new Generator()
.withName("org.jooq.util.JavaGenerator")
.withGenerate(new Generate()
.withPojos(true)
.withImmutablePojos(true)
.withInterfaces(true)
.withDaos(true)
.withSpringAnnotations(true)
.withJavaTimeTypes(true)
)
.withDatabase(new Database()
.withName("org.jooq.util.mysql.MySQLDatabase")
.withIncludes(".*")
.withExcludes("")
.withDateAsTimestamp(true)
.withInputSchema("51unboxdb")
)
.withTarget(new Target()
.withPackageName("com.chunfytseng.unbox.jooq")
.withDirectory("src/main/java")
)
);
GenerationTool.generate(configuration);
}
There are many tables in the database. When you modify a property of a table and then overwrite the existing code from the newly generated code, it makes me very troublesome. Can I specify to update a table or exclude some tables? Not every time you build it overwrites the existing code.
There are at least two ways to tackle this problem generically
1. By using the code generator's type rewriting feature
The code generator supports a feature called type rewriting, where you can specify a regular expression matching all relevant column names and/or data types, whose generated type should be rewritten to BIGINT in your case (as you'd like to use long rather than UInteger. Just add:
...
.withDatabase(new Database()
...
.withForcedTypes(new ForcedType()
.withName("BIGINT")
.withExpression("(?i:.*\.USER\.ID)")
)
...
)
...
2. By using the built-in auto-conversion API
jOOQ's auto-conversion utility is called org.jooq.tools.Convert. It already covers a vast variety of automatic type conversions, e.g. between different well-known numeric types as you’ve shown. There’s also a lot of convenience API, e.g. you can pass a Class reference of your desired type to the fetch() methods in order to get a result of such type.

jooq: How to configure dialect for static DSL methods?

I've got dsl with POSTGRES_9_4 dialect. I try to use custom selection query with org.jooq.impl.DSL.Condition:
dsl.selectFrom(TAG_JSON).where(condition("translations ??| array[?]", normValues)).fetch(mapper);
It throws an exception:
org.jooq.exception.SQLDialectNotSupportedException: Type class java.util.ArrayList is not supported in dialect DEFAULT
at org.jooq.impl.DefaultDataType.getDataType(DefaultDataType.java:757)
at org.jooq.impl.DefaultDataType.getDataType(DefaultDataType.java:704)
at org.jooq.impl.DSL.getDataType(DSL.java:14371)
at org.jooq.impl.Utils.queryParts(Utils.java:1565)
at org.jooq.impl.SQLImpl.<init>(SQLImpl.java:64)
at org.jooq.impl.DSL.sql(DSL.java:6240)
at org.jooq.impl.DSL.condition(DSL.java:7323)
Why DEFAULT dialect is used? How to configure global one?
The error message is a bit misleading. Your intention seems for array[?] to take a List<String> as a single bind value to pass to an array constructor. This won't work. You have two options:
Binding a single array
condition("translations <op> ?::text[]", normValues.toArray(new String[0]))
Binding the array as a list of bind values
condition("translations <op> {0}", list(
normValues.stream().map(DSL::val).toArray(QueryPart[]::new)
))
This is using DSL.list() to create a list of bind variables.
Note, in both cases, I have avoided your ??| operator because jOOQ parses plain SQL text for bind values (?) and currently doesn't recognise ??| as a non-bind variable sequence (I've registered an issue for this). If that operator has a textual alternative representation, I recommend using that instead.

jOOQ and selecting an enum field into enum object

I'm using jOOQ with Postgresql to select an enum value from a table.
List<my.project.jooq.enums.Color> colors =
dsl.selectDistinct(TABLE.T_COLOR.as("color"))
.from(TABLE).fetch()
.into(my.project.jooq.enums.Color.class);
Anyway I get the exception:
org.jooq.exception.MappingException: No matching constructor found on type class my.project.jooq.enums.Color for record org.jooq.impl.DefaultRecordMapper#7c66447f
I see that fetch() will return Result<Record1<my.project.model.jooq.enums.Color>>, so I wonder if there is a way to immediately fetch the Color enums into a list as I can do with any pojo.
How can I fetch into the enum values?
As of jOOQ 3.7, this is not yet supported out of the box, but will be in jOOQ 3.8 (see #5154).
You can easily map a string column to your Enum type yourself, though, via
List<my.project.jooq.enums.Color> colors =
dsl.selectDistinct(TABLE.T_COLOR)
.from(TABLE)
.fetch()
.map(rec -> my.project.jooq.enums.Color.valueOf(rec.getValue(TABLE.T_COLOR)));
In case, your my.project.jooq.enums.Color was generated by jOOQ from a PostgreSQL enum data type, you don't need to specifically map anything, jOOQ will automatically do that for you:
List<my.project.jooq.enums.Color> colors =
dsl.selectDistinct(TABLE.T_COLOR)
.from(TABLE)
.fetch(TABLE.T_COLOR);

jOOQ : compare uuid(from postgresql) and string(user id in class)

My backend is postgresql. I am trying to write a simple function to fetch data from database, using jooq records. The DAO for this table is written by me. The problem I am facing is comparison of UUID in db and String provided to function.
public Correspondence fetchByExternalId(String externalId) {
CorrespondenceRecord correspondenceRecord =
create.fetchOne(Tables.CORRESPONDENCE,
Tables.CORRESPONDENCE.USERID.eq(externalId));
CORRESPONDENCE.USERID is UUID and externalId is String; The operator eq is the one which i am not able to implement here, which has worked for me on previous occasions.
The error shown is :
Cannot resolve method 'eq(java.lang.String)'
when i referred to the jOOQ site for help it show 'eq' is the operator it suggests to be used!
Can anyone help me with an alternative or tell me where i went wrong?
jOOQ is a very type safe API, so you cannot compare UUID types with String types using eq(), because the eq() method uses the generic <T> type of your CORRESPONDENCE.USERID column, which is UUID:
TableField<..., java.util.UUID> USERID = ...
Generating a UUID bind value in the Java client
You either have to supply an externalId in the form of a UUID type, e.g.:
CORRESPONDENCE.USERID.eq(UUID.fromString(externalId))
... or, you let jOOQ convert that type for you:
CORRESPONDENCE.USERID.eq(CORRESPONDENCE.USERID.getDataType().convert(externalId))
Both of the above are equivalent.
Generating a UUID bind value in the database
You can always also defer the type conversion work to the database by casting (which will result in a CAST(? AS UUID) being rendered:
CORRESPONDENCE.USERID.eq(DSL.cast(externalId, UUID.class));
... or by coercing the variable to adhere to the UUID type (trusting that the database can implicitly convert the VARCHAR type to a UUID type):
CORRESPONDENCE.USERID.eq(DSL.coerce(DSL.val(externalId), UUID.class));

Categories

Resources