Module org.jooq
Package org.jooq.impl

Class DefaultRecordMapper<R extends Record,​E>

  • All Implemented Interfaces:
    RecordMapper<R,​E>

    public class DefaultRecordMapper<R extends Record,​E>
    extends Object
    implements RecordMapper<R,​E>
    This is the default implementation for RecordMapper types.

    The mapping algorithm is this:

    If <E> is an array type:

    The resulting array is of the nature described in Record.intoArray(). Arrays more specific than Object[] can be specified as well, e.g. String[]. If conversion to the element type of more specific arrays fails, a MappingException is thrown, wrapping conversion exceptions.

    If <E> is a field "value type" and <R> has exactly one column:

    Any Java type available from SQLDataType qualifies as a well-known "value type" that can be converted from a single-field Record1. The following rules apply:

    • If <E> is a reference type like String, Integer, Long, Timestamp, etc., then converting from <R> to <E> is mere convenience for calling Record.getValue(int, Class) with fieldIndex = 0
    • If <E> is a primitive type, the mapping result will be the corresponding wrapper type. null will map to the primitive type's initialisation value, e.g. 0 for int, 0.0 for double, false for boolean.
    If a default constructor is available and any JPA Column annotations are found on the provided <E>, only those are used:

    • If <E> contains single-argument instance methods of any visibility annotated with Column, those methods are invoked
    • If <E> contains no-argument instance methods of any visibility starting with getXXX or isXXX, annotated with Column, then matching setXXX() instance methods of any visibility are invoked
    • If <E> contains instance member fields of any visibility annotated with Column, those members are set
    Additional rules:
    • The same annotation can be re-used for several methods/members
    • Column.name() must match Field.getName(). All other annotation attributes are ignored
    • Static methods / member fields are ignored
    • Final member fields are ignored

    If a default constructor is available and if there are no JPA Column annotations, or jOOQ can't find the javax.persistence API on the classpath, jOOQ will map Record values by naming convention:

    If Field.getName() is MY_field (case-sensitive!), then this field's value will be set on all of these (regardless of visibility):

    • Single-argument instance method MY_field(...)
    • Single-argument instance method myField(...)
    • Single-argument instance method setMY_field(...)
    • Single-argument instance method setMyField(...)
    • Non-final instance member field MY_field
    • Non-final instance member field myField

    If Field.getName() is MY_field.MY_nested_field (case-sensitive!), then this field's value will be considered a nested value MY_nested_field, which is set on a nested POJO that is passed to all of these (regardless of visibility):

    • Single-argument instance method MY_field(...)
    • Single-argument instance method myField(...)
    • Single-argument instance method setMY_field(...)
    • Single-argument instance method setMyField(...)
    • Non-final instance member field MY_field
    • Non-final instance member field myField

    If no default constructor is available, but at least one constructor annotated with ConstructorProperties is available, that one is used

    • The standard JavaBeans ConstructorProperties annotation is used to match constructor arguments against POJO members or getters.
    • If the property names provided to the constructor match the record's columns via the aforementioned naming conventions, that information is used.
    • If those POJO members or getters have JPA annotations, those will be used according to the aforementioned rules, in order to map Record values onto constructor arguments.
    • If those POJO members or getters don't have JPA annotations, the aforementioned naming conventions will be used, in order to map Record values onto constructor arguments.
    • When several annotated constructors are found, the first one is chosen, randomly.
    • When invoking the annotated constructor, values are converted onto constructor argument types

    If no default constructor is available, but at least one "matching" constructor is available, that one is used

    • A "matching" constructor is one with exactly as many arguments as this record holds fields
    • When several "matching" constructors are found, the first one is chosen (as reported by Class.getDeclaredConstructors()). This choice is non-deterministic as neither the JVM nor the JDK guarantee any order of methods or constructors.
    • When Settings.isMapConstructorParameterNames() is turned on, and parameter names are available through reflection on Executable.getParameters(), then values are mapped by name, otherwise by index. (see #4627)
    • When invoking the "matching" constructor, values are converted onto constructor argument types

    If no default constructor is available, no "matching" constructor is available, but Settings.isMapConstructorParameterNames() is turned on, and parameter names are available through reflection on Executable.getParameters(), the first constructor is used

    • The first constructor is chosen (as reported by Class.getDeclaredConstructors()). This choice is non-deterministic as neither the JVM nor the JDK guarantee any order of methods or constructors.
    • When invoking that constructor, values are converted onto constructor argument types

    If the supplied type is an interface or an abstract class

    Abstract types are instantiated using Java reflection Proxy mechanisms. The returned proxy will wrap a HashMap containing properties mapped by getters and setters of the supplied type. Methods (even JPA-annotated ones) other than standard POJO getters and setters are not supported. Details can be seen in Reflect.as(Class).

    Other restrictions

    • <E> must provide a default or a "matching" constructor. Non-public default constructors are made accessible using Constructor.setAccessible(boolean)
    • primitive types are supported. If a value is null, this will result in setting the primitive type's default value (zero for numbers, or false for booleans). Hence, there is no way of distinguishing null and 0 in that case.

    This mapper is returned by the DefaultRecordMapperProvider. You can override this behaviour by specifying your own custom RecordMapperProvider in Configuration.recordMapperProvider()

    Author:
    Lukas Eder
    See Also:
    RecordMapper, DefaultRecordMapperProvider, Configuration