/Zeko-Data-Mapper

A lightweight data mapper library in Kotlin that helps to map the result of queries from your normalized dataset(from RDBMS or any source) back into relational mapped Hash maps

Primary LanguageKotlinApache License 2.0Apache-2.0

Zeko Data Mapper

alt Zeko Data Mapper

Maven Central Apache License 2 Awesome Kotlin Badge

A lightweight, fast and simple data mapper library in Kotlin that helps to map the result of queries from your normalized dataset(from RDBMS, SQLite, CSV or any source) back into relational mapped Hash maps

ps: Zeko turns out to be rabbit in Croatian, thus the logo :D

Show your ❤ with a ★

To use, add these to your maven pom.xml

<dependency>
  <groupId>io.zeko</groupId>
  <artifactId>zeko-data-mapper</artifactId>
  <version>1.6.7</version>
</dependency>

Features

  • No configuration files, no XML, no YAML, no annotations, lightweight, easy to use
  • Fast startup & performance
  • No Reflections. Great for GraalVM and Quarkus

Basic usage

Use this library to denormalized your data from a plaintext CSV, in-memory data, RDBMS tables, etc. Instead of using an ORM, do whatever you need to query & retrieve your normalized data, and mapped the data back into a relational nested object which make sense to your system.

Configuring relationship of the normalized data

Given a MySQL database with tables of user, role and address, where each user should have one or more address and can have multiple roles. Every roles can be assigned to more than one users. So user-role is a many to many (M:M) relationship, user-address is 1:M relationship

alt relationship between tables

val table = linkedMapOf<String, TableInfo>()
table["user"] = TableInfo(key = "id")
table["role"] = TableInfo(key = "id", move_under = "user", foreign_key = "user_id", many_to_many = true)
table["address"] = TableInfo(key = "id", move_under = "user", foreign_key = "user_id", many_to_one = true) 

Using DataMapper class

DataMapper::map accepts the mapper config of your tables which it uses to understand the relationship of the flatten data structure. Pass in the results of your sql query with the field names (with table name as prefix) to mapped the result. More example can be found in DataMapperSpec

Example:

val rawResults = ArrayList<LinkedHashMap<String, Any>>()
rawResults.add(linkedMapOf(
    "user-id" to 1,
    "user-name" to "Leng",
    "role-id" to 2,
    "role-role_name" to "Super Admin",
    "role-user_id" to 1,   //this is selected as alias, not actual field in table. To be used with the mapper
    "address-id" to 128,
    "address-user_id" to 1,
    "address-street1" to "Some block",
    "address-street2" to "in the street"
))

Map the raw results into a denormalized structure

val mapper = DataMapper()
val result = mapper.map(tables, rawResults)
val json = Json.encodePrettily(result)
println(json)

map() method accepts a third argument which is to specify the delimiter of the column name, eg. user-id By default it uses "-" but you can always change it when calling map()

mapper.map(tables, rawResults, "-")

This will give you a list of users with role and address nested under user's role and address field.

[ {
  "id" : 1,
  "name" : "Leng",
  "role" : [ {
    "id" : 2,
    "user_id" : 1,
    "role_name" : "super admin"
  } ],
  "address" : [ {
    "id" : 1,
    "user_id" : 1,
    "street1" : "Jalan 123",
    "street2" : "Taman Tun"
  }, {
    "id" : 3,
    "user_id" : 1,
    "street1" : "Jalan Bunga",
    "street2" : "Taman Negara"
  } ]
} ]

Notice that user_id field is in the role and address data which is quite redundant in many cases. To automatically remove those linked field from the mapped result, set the list of fields in TableInfo remove property.

val table = linkedMapOf<String, TableInfo>()
table["user"] = TableInfo(key = "id")
table["role"] = TableInfo(remove = listOf("user_id"), key = "id", move_under = "user", foreign_key = "user_id", many_to_many = true)
table["address"] = TableInfo(remove = listOf("user_id"), key = "id", move_under = "user", foreign_key = "user_id", many_to_one = true) 

This will give you a result of

[ {
  "id" : 1,
  "name" : "Leng",
  "role" : [ {
    "id" : 2,
    "role_name" : "super admin"
  } ],
  "address" : [ {
    "id" : 1,
    "street1" : "Jalan 123",
    "street2" : "Taman Tun"
  }, {
    "id" : 3,
    "street1" : "Jalan Bunga",
    "street2" : "Taman Negara"
  } ]
} ]

Use MapperConfig class to conveniently set relationship without figuring out the order of nested data and removal of redundant field. The same relationship just now can be defined as the following with MapperConfig

// id is the default primary key of all tables, so you don't have to define for every table
// true auto removes the linked fields such as user_id here

val tables = MapperConfig("id", true)  
    .table("user")
    .table("role").manyToMany(true).moveUnder("user").foreignKey("user_id")
    .table("address").manyToOne(true).moveUnder("user").foreignKey("user_id")

Example usage with Jasync-sql queries

For the DB schema above, you can find the example Zeko usage with Jasync-sql here. The SQL structure and data for the sample app is provided too.

Jasync-sql is a port of mauricio/postgresql-async, to Kotlin.

Example usage with Vert.x Common-sql queries

If you use Vert.x common sql interface, such as the async MySQL and postgresql client which uses Mauricio Linhares async driver, you will find that the result return is typed as List of JsonArray

Example using Zeko with vertx common sql results can be found here