DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Please enter at least three characters to search
Refcards Trend Reports
Events Video Library
Refcards
Trend Reports

Events

View Events Video Library

Zones

Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks

Modernize your data layer. Learn how to design cloud-native database architectures to meet the evolving demands of AI and GenAI workkloads.

Secure your stack and shape the future! Help dev teams across the globe navigate their software supply chain security challenges.

Releasing software shouldn't be stressful or risky. Learn how to leverage progressive delivery techniques to ensure safer deployments.

Avoid machine learning mistakes and boost model performance! Discover key ML patterns, anti-patterns, data strategies, and more.

Related

  • Instant Integrations With API and Logic Automation
  • A Guide to Enhanced Debugging and Record-Keeping
  • How to Build a URL Shortener Web App With Flask Framework
  • Commonly Occurring Errors in Microsoft Graph Integrations and How To Troubleshoot Them (Part 4)

Trending

  • 5 Best Node.js Practices to Develop Scalable and Robust Applications
  • Solid Testing Strategies for Salesforce Releases
  • How to Build Scalable Mobile Apps With React Native: A Step-by-Step Guide
  • The Transformative Power of Artificial Intelligence in Cloud Security
  1. DZone
  2. Coding
  3. Frameworks
  4. New ORM Framework for Kotlin

New ORM Framework for Kotlin

The article introduces a Kotlin API for some ORMs to simplify database operations by providing a lightweight and intuitive interface.

By 
Pavel Ponec user avatar
Pavel Ponec
·
Jun. 05, 23 · Presentation
Likes (1)
Comment
Save
Tweet
Share
3.6K Views

Join the DZone community and get the full member experience.

Join For Free

If you have an aversion to new frameworks, don't even read this. For other kind readers, please note that here I'm going to present a proposal for an API for modeling database queries in a declarative style with strong Kotlin type checking primarily. Only some classes around entities are implemented; the database connection is missing for now. In the project, I tried to evaluate my experience and vision so far. However, not all ideas presented in this paper are completely new. Some of them I drew from the Ujorm framework, and the entity concept was inspired by the Ktorm framework. But the code is new. The prototype results from a mosaic that has been slowly pieced together and has now taken a form that is hopefully worth presenting.

If you are not discouraged by the introduction, I will skip the general talk about ORM and let you get to the code samples. The demonstration examples use two relational database tables. This is an employee/department (unspecified organization) relationship where each employee may (or may not) have a supervisor. Both tables are described by entities from the following class diagram:

 Both tables are described by entities from the following class diagram.

Suppose we want to create a report containing the unique employee number, the employee's name, the department name, and the supervisor's name (if any). We are only interested in departments with a positive identifier and a department name starting with the letter "D." We want to sort the report by department name (descending) and then by employee name (ascending). How could a query (SELECT) based on these entities look in the presented API?

Kotlin
 
val employees: Employees = MyDatabase.employees // Employee metamodel
val departments: Departments = MyDatabase.departments // Department metamodel

val result: List<Employee> = MyDatabase.select(
    employees.id,
    employees.name,
    employees.department + departments.name, // DB relation by the inner join
    employees.superior + employees.name) // DB relation by the left outer join
.where((employees.department + departments.id GE 1)
    AND (employees.department + departments.name STARTS "D"))
.orderBy(
    employees.department + departments.name ASCENDING false,
    employees.name ASCENDING true)
.toList()


The use of a DSL in a database query probably doesn't surprise anyone today. However, the chaining of the entity attribute model (hereafter, property-descriptor) is worthy of attention. Combining them creates a new composite property descriptor that implements the same interface as its atomic parts. The query filter (WHERE) is described by an object constructed from elementary conditions into a single binary tree.  Composite property descriptors provide information from which SQL query sessions between database tables are also derived. This approach can cover perhaps the most common SQL queries, including recursive queries. But certainly not all of them. For the remaining ones, an alternative solution must be used. A rough design is in the project tests.

Let's focus next on the employee entity:

Kotlin
 
@Entity
interface Employee {
    var id: Int
    var name: String
    var higherEducation: Boolean
    var contractDay: LocalDate
    var department: Department
    var superior: Employee?
}


Entity is an interface with no other dependencies. The advantage is that the interface can get by (in ORM) without binary code modification. However, to get the object, you must use a factory method that supplies the implementation. An alternative would be to extend some generic class (provided by the framework), which I found more invasive. The metamodel pair object provides the factory method for creating new objects.

Each entity here needs a metamodel that contains information about its type and attributes that provides some services. The attributes of the metamodel are the property mentioned above descriptors of the pair entities. Note that the same property() method is used to create the property descriptors it doesn't matter if it is a session description (an attribute on another entity). The only exception is where the attribute (entity) type accepts NULL. The positive news is that the compiler will report the misuse (of the shorter method name) at compile time. An example of the employee entity metamodel is attached:

Kotlin
 
open class Employees : EntityModel<Employee>(Employee::class) {
    val id = property { it.id }
    val name = property { it.name }
    val higherEducation = property { it.higherEducation }
    val contractDay = property { it.contractDay }
    val department = property { it.department }
    val superior = propertyNullable { it.superior }
}


Annotations will declare the specific properties of the columns (database tables) on the entities so that the metamodel classes can be generated once according to their entity. The entity data is stored (internally) in an object array. The advantage is fewer memory requirements compared to an implementation based on the HashMap class.

The next example demonstrates the creation of new objects and their storage in the database (INSERT).

Kotlin
 
val development: Department = MyDatabase.departments.new {
    name = "Development"
    created = LocalDate.of(2020, 10, 1)
}
val lucy: Employee = MyDatabase.employees.new {
    name = "Lucy"
    contractDay = LocalDate.of(2022, 1, 1)
    superior = null
    department = development
}
val joe: Employee = MyDatabase.employees.new {
    name = "Joe"
    contractDay = LocalDate.of(2022, 2, 1)
    superior = lucy
    department = development
}
MyDatabase.save(development, lucy, joe)


The MyDatabase class (which provides the metamodel) is the only one here (the singleton design pattern). Still, it can generally be any object our application context provides (for example). If we wanted to use a service (cloning an entity object, for example), we could extend (that provider) with the AbstractEntityProvider class and use its resources. An example of the recommended registration procedure (metamodel classes), along with other examples, can be found in the project tests.

Conditions

A condition (or also a criterion) is an object we encountered when presenting a SELECT statement. However, you can also use a condition on its own, for example, to validate entity values or filter collections. If the library provided support for serializing it to JSON text format (and back), the range of uses would probably be even more expansive. To build the following conditions, we start from the metamodel already stored in the employees variable.

Kotlin
 
val crn1 = employees.name EQ "Lucy"
val crn2 = employees.id GT 1
val crn3 = (employees.department + departments.id) LT 99
val crn4 = crn1 OR (crn2 AND crn3)
val crn5 = crn1.not() OR (crn2 AND crn3)


If we have an employee object in the employee variable, the employee criterion can be tested with the following code:

Kotlin
 
expect(crn4(employee)).equals(true)  // Valid employee
expect(crn5(employee)).equals(false) // Invalid employee


On the first line, the employee met the criterion; on the second line, it did not. If needed (during debugging or logging), the content of the conditions can be visualized in the text; examples are attached:

Kotlin
 
expect(crn1.toString())
    .toEqual("""Employee: name EQ "Lucy"""")
expect(crn2.toString())
    .toEqual("""Employee: id GT 1""")
expect(crn3.toString())
    .toEqual("""Employee: department.id LT 99""")
expect(crn4.toString())
    .toEqual("""Employee: (name EQ "Lucy") OR (id GT 1) AND (department.id LT 99)""")
expect(crn5.toString())
    .toEqual("""Employee: (NOT (name EQ "Lucy")) OR (id GT 1) AND (department.id LT 99)""")


Other Interesting Things

The property descriptor may not only be used to model SQL queries but can also participate in reading and writing values to the object. The simplest way is to extend the entity interface with the PropertyAccessor interface. If we have an employee object, code can be used to read it:

Kotlin
 
val id: Int = employee[employees.id]
val name: String = employee[employees.name]
val contractDay: LocalDate = employee[employees.contractDay]
val department: Department = employee[employees.department]
val superior: Employee? = employee[employees.superior]
val departmentName: String = employee[employees.department + departments.name]


The explicit declaration of variable data types is for presentation purposes only, but in practice, they are redundant and can be removed. Writing variables to an object is similar:

Kotlin
 
employee[employees.id] = id
employee[employees.name] = name
employee[employees.contractDay] = contractDay
employee[employees.department] = department
employee[employees.superior] = superior
employee[employees.department + departments.name] = departmentName


Please note that reading and writing values are done without overriding also for NULLABLE values. Another interesting feature is the support for reading and writing values using composite property descriptors. Just for the sake of argument, I assure you that for normal use of the object, it will be more convenient to use the standard entity API declared by the interface.

The sample above copies its attributes to variables and back. If we wanted to clone an object, we could use the following construct (shallow copy):

Kotlin
 
val target: Employee = MyDatabase.utils().clone(source)


No reflection methods are called during data copying, which is allowed by the class architecture used. More functional usage examples can be found in the project tests on GitHub.

Why?

Why was this project created? In the beginning, it was just an effort to learn the basics of Kotlin. Gradually a pile of disorganized notes in the form of source code was created, and it was only a matter of time before I came across language resources that would also lead to a simplified Ujorm framework API. Finding ready-made ORM libraries in Kotlin made me happy. However, of the two popular ones, I couldn't pick one that suited me better. I missed interesting features of one with the other and vice versa. In some places, I found the API not intuitive enough to use; in others, I ran into complications with database table recursion. A common handicap was (for my taste) the increased error rate when manually building the entity metamodel. Here one can certainly counter that entities can be generated from database tables. In the end, I organized my original notes into a project, cleaned up the code, and added this article. That is perhaps all that is relevant.

Conclusion

I like the integration with the core of a ready-made ORM framework; probably the fastest would be the integration with Ujorm. However, I am aware of the risks associated with any integration, and I can't rule out that this project won't eventually find any real use in the ORM field. The prototype is freely available under the Apache Commons 2 license. Thank you for your constructive comments.

Database Framework Kotlin (programming language) Property (programming) Data Types API

Opinions expressed by DZone contributors are their own.

Related

  • Instant Integrations With API and Logic Automation
  • A Guide to Enhanced Debugging and Record-Keeping
  • How to Build a URL Shortener Web App With Flask Framework
  • Commonly Occurring Errors in Microsoft Graph Integrations and How To Troubleshoot Them (Part 4)

Partner Resources

×

Comments
Oops! Something Went Wrong

The likes didn't load as expected. Please refresh the page and try again.

ABOUT US

  • About DZone
  • Support and feedback
  • Community research
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Core Program
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 3343 Perimeter Hill Drive
  • Suite 100
  • Nashville, TN 37211
  • support@dzone.com

Let's be friends:

Likes
There are no likes...yet! 👀
Be the first to like this post!
It looks like you're not logged in.
Sign in to see who liked this post!