You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@cayenne.apache.org by Andrus Adamchik <aa...@gmail.com> on 2022/07/14 19:52:05 UTC

Records and result auto-mapping

Java 17 "records" create some interesting possibilities as quick DTOs. Mapping complex SQL is still rather painful in Cayenne. You'd get an Object[] as a result and/or need complex scripting within SQL. I think records and some API tweaks can help:

1. SQL result mapping.

record MyRecord(String lastName, String firstName) {} // non-persistent, not in DataMap

SQLSelect.query("select * from x")

  // new API analogous to "#result" directive but simpler to use
  // also we can implement some implicit name conversions ("AB_C" -> "abC")
  .resultColumn(0, "firstName") 
  .resultColumn("LAST_NAME", "lastName") 

  // new API - auto-mapping the result to a POJO (record in this case)
  .selectAs(MyRecord.class, context);


2. Entity result mapping (e.g. a replacement of column and data row queries).

class MyEntity extends _MyEntity {} // Persistent
record MyEntityDTO(..) {}  // non-persistent, not in DataMap

List<MyEntityDTO> result = ObjectSelect(MyEntity.class)
  .where(MyEntity.NAME.like("Joe"))
  .prefetch(MyEntity.ANOTHER.joint())

  // new API - auto-mapping the result to a POJO (record in this case)
  .selectAs(MyEntityDTO.class, context);

It will be faster than fetching MyEntity (without ObjectContext registration, uniquing, merging), and will be a good replacement of column queries API, requiring no explicit column declarations, and producing a specific type instead of Object[]. Prefetches can be auto-mapped to record hierarchies.

Both examples would work with either records or regular POJOs, it is just that record definitions are so easy to create on the spot, that they can be used in a multitude of very narrow contexts.

Andrus


Re: Records and result auto-mapping

Posted by Andrus Adamchik <aa...@gmail.com>.
Nice! Yeah, I remember we discussed such an explicit mapping, which addresses the POJO mapping case as is. 

So now I am thinking whether we can make it even more transparent - automap property names to column names and JDBC types to Java types. So users won't need "Constructor(Object[])", and generally would not concern themselves with the ordering of properties in the array (mapping by name instead of position).

Andrus


> On Jul 15, 2022, at 5:37 AM, Nikita Timofeev <nt...@objectstyle.com> wrote:
> 
> We already have map(Function) method available in both ColumnSelect
> and SQLSelect that allows to map result from Object[] to anything via
> provided function.
> Here is an example from tests [1]:
> 
> List<ArtistDataWrapper> result = SQLSelect.columnQuery("SELECT * FROM
> ARTIST_CT",
>      Integer.class, String.class, LocalDateTime.class)
>      .map(ArtistDataWrapper::new)
>      .select(context);
> 
> And I experimented with simple PojoMapper that does almost exactly
> what you are suggesting. Not sure if it's available though.
> 
> [1] https://github.com/apache/cayenne/blob/336f0e5297fe6ccdc3b1904ab4f6ee2c6d1ab9dc/cayenne-server/src/test/java/org/apache/cayenne/query/SQLSelectIT.java#L273
> 
> On Thu, Jul 14, 2022 at 10:52 PM Andrus Adamchik <aa...@gmail.com> wrote:
>> 
>> Java 17 "records" create some interesting possibilities as quick DTOs. Mapping complex SQL is still rather painful in Cayenne. You'd get an Object[] as a result and/or need complex scripting within SQL. I think records and some API tweaks can help:
>> 
>> 1. SQL result mapping.
>> 
>> record MyRecord(String lastName, String firstName) {} // non-persistent, not in DataMap
>> 
>> SQLSelect.query("select * from x")
>> 
>>  // new API analogous to "#result" directive but simpler to use
>>  // also we can implement some implicit name conversions ("AB_C" -> "abC")
>>  .resultColumn(0, "firstName")
>>  .resultColumn("LAST_NAME", "lastName")
>> 
>>  // new API - auto-mapping the result to a POJO (record in this case)
>>  .selectAs(MyRecord.class, context);
>> 
>> 
>> 2. Entity result mapping (e.g. a replacement of column and data row queries).
>> 
>> class MyEntity extends _MyEntity {} // Persistent
>> record MyEntityDTO(..) {}  // non-persistent, not in DataMap
>> 
>> List<MyEntityDTO> result = ObjectSelect(MyEntity.class)
>>  .where(MyEntity.NAME.like("Joe"))
>>  .prefetch(MyEntity.ANOTHER.joint())
>> 
>>  // new API - auto-mapping the result to a POJO (record in this case)
>>  .selectAs(MyEntityDTO.class, context);
>> 
>> It will be faster than fetching MyEntity (without ObjectContext registration, uniquing, merging), and will be a good replacement of column queries API, requiring no explicit column declarations, and producing a specific type instead of Object[]. Prefetches can be auto-mapped to record hierarchies.
>> 
>> Both examples would work with either records or regular POJOs, it is just that record definitions are so easy to create on the spot, that they can be used in a multitude of very narrow contexts.
>> 
>> Andrus
>> 
> 
> 
> -- 
> Best regards,
> Nikita Timofeev


Re: Records and result auto-mapping

Posted by Nikita Timofeev <nt...@objectstyle.com>.
We already have map(Function) method available in both ColumnSelect
and SQLSelect that allows to map result from Object[] to anything via
provided function.
Here is an example from tests [1]:

List<ArtistDataWrapper> result = SQLSelect.columnQuery("SELECT * FROM
ARTIST_CT",
      Integer.class, String.class, LocalDateTime.class)
      .map(ArtistDataWrapper::new)
      .select(context);

And I experimented with simple PojoMapper that does almost exactly
what you are suggesting. Not sure if it's available though.

[1] https://github.com/apache/cayenne/blob/336f0e5297fe6ccdc3b1904ab4f6ee2c6d1ab9dc/cayenne-server/src/test/java/org/apache/cayenne/query/SQLSelectIT.java#L273

On Thu, Jul 14, 2022 at 10:52 PM Andrus Adamchik <aa...@gmail.com> wrote:
>
> Java 17 "records" create some interesting possibilities as quick DTOs. Mapping complex SQL is still rather painful in Cayenne. You'd get an Object[] as a result and/or need complex scripting within SQL. I think records and some API tweaks can help:
>
> 1. SQL result mapping.
>
> record MyRecord(String lastName, String firstName) {} // non-persistent, not in DataMap
>
> SQLSelect.query("select * from x")
>
>   // new API analogous to "#result" directive but simpler to use
>   // also we can implement some implicit name conversions ("AB_C" -> "abC")
>   .resultColumn(0, "firstName")
>   .resultColumn("LAST_NAME", "lastName")
>
>   // new API - auto-mapping the result to a POJO (record in this case)
>   .selectAs(MyRecord.class, context);
>
>
> 2. Entity result mapping (e.g. a replacement of column and data row queries).
>
> class MyEntity extends _MyEntity {} // Persistent
> record MyEntityDTO(..) {}  // non-persistent, not in DataMap
>
> List<MyEntityDTO> result = ObjectSelect(MyEntity.class)
>   .where(MyEntity.NAME.like("Joe"))
>   .prefetch(MyEntity.ANOTHER.joint())
>
>   // new API - auto-mapping the result to a POJO (record in this case)
>   .selectAs(MyEntityDTO.class, context);
>
> It will be faster than fetching MyEntity (without ObjectContext registration, uniquing, merging), and will be a good replacement of column queries API, requiring no explicit column declarations, and producing a specific type instead of Object[]. Prefetches can be auto-mapped to record hierarchies.
>
> Both examples would work with either records or regular POJOs, it is just that record definitions are so easy to create on the spot, that they can be used in a multitude of very narrow contexts.
>
> Andrus
>


-- 
Best regards,
Nikita Timofeev