{"id":18165553,"url":"https://github.com/coolsamson7/dorm","last_synced_at":"2026-05-13T12:34:21.517Z","repository":{"id":256930446,"uuid":"856851180","full_name":"coolsamson7/dorm","owner":"coolsamson7","description":"a complete dynamic object relational mapper that is able to define schemas during runtime while offering complete query possibilities similar to jpa including a GraphQL endpoint with almost identical scope","archived":false,"fork":false,"pushed_at":"2024-10-16T08:48:22.000Z","size":419,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-13T10:34:38.600Z","etag":null,"topics":["graphql","kotlin","orm","orm-framework","orm-library"],"latest_commit_sha":null,"homepage":"","language":"Kotlin","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/coolsamson7.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-09-13T10:24:31.000Z","updated_at":"2024-10-16T08:48:26.000Z","dependencies_parsed_at":"2024-10-18T01:08:11.994Z","dependency_job_id":null,"html_url":"https://github.com/coolsamson7/dorm","commit_stats":null,"previous_names":["coolsamson7/dorm"],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coolsamson7%2Fdorm","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coolsamson7%2Fdorm/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coolsamson7%2Fdorm/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/coolsamson7%2Fdorm/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/coolsamson7","download_url":"https://codeload.github.com/coolsamson7/dorm/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247601446,"owners_count":20964866,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["graphql","kotlin","orm","orm-framework","orm-library"],"created_at":"2024-11-02T12:07:44.328Z","updated_at":"2025-10-29T20:17:50.628Z","avatar_url":"https://github.com/coolsamson7.png","language":"Kotlin","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![Java CI with Maven](https://github.com/coolsamson7/dorm/actions/workflows/maven.yml/badge.svg)](https://github.com/coolsamson7/dorm/actions/workflows/maven.yml)\n\n# dORM\n\n_dOrm_ is a complete dynamic orm ( Object Relational Mapper ), that doesn't require to setup custom table structures per entity but is able to map\ndynamic entities on a database based on a couple of technical tables only. In addition to the server side api a GraphQL interface has been implemented with identical scope.\n\n## What's the problem anyway...\n\nBefore we describe the solution, let's figure out what the problem is....\n\nTypical applications use the normal ORMs that persist entities on table structures that are known at compile time predefined in the underlying dbms. \nWhile this is fine for most cases, there are some situations, where we need to configure entities dynamically.\nThink of a custom workflow definition ( e.g. with Camunda ) that wants to persist some complex internal state, which is not known upfront.\n\nExactly this problem is solved by the current implementation.\n\n## Sample \nLet's look a simple example first:\n\nAssuming we have an injected `ObjectManager` which is responsible for transaction and object lifecycle management, we are able to\nspecify an entity by defining the attributes including type and type constraint information\n\n\n```Kotlin\npersonDescriptor = manager.type(\"person\")\n   .add(attribute(\"name\").type(string().length(100))) // string with length constraint\n   .add(attribute(\"age\").type(int().greaterThan(0)))\n   .add(relation(\"parents\").target(\"person\").multiplicity(Multiplicity.ONE_OR_MANY).inverse(\"children\"))\n   .add(relation(\"children\").target(\"person\").multiplicity(Multiplicity.ZERO_OR_MANY).inverse(\"parents\"))\n   ...\n   .register()\n``` \n\nWith this structural information - which is also persisted in the database - we can create and access `DataObject` instances, that carry the payload information\n\n        \n```Kotlin\nmanager.begin()\ntry {\n    val parent = manager.create(personDescriptor)\n\n    // set some values by the custom get and set operators\n    \n    person[\"name\"] = \"Andi\"\n    person[\"age\"] = 58\n\n   val child = manager.create(personDescriptor)\n\n    // set some values by the custom get and set operators\n    \n    child[\"name\"] = \"Nika\"\n    child[\"age\"] = 14\n\n    // add relation\n\n    parent.relation(\"children\").add(child)\n\n}\nfinally {\n    manager.commit() // will create!\n}\n``` \n\nLet's query all persons persisted so far...\n\n```Kotlin\nval queryManager = manager.queryManager()\nmanager.begin()\ntry {\n    val person = queryManager.from(personDescriptor)\n    val query = queryManager\n        .create()\n        .select(person)\n        .from(person)\n\n    val queryResult = query.execute().getResultList()\n\n    val name = queryResult[0][\"name\"]\n\n    // let's modify values\n\n    queryResult[0][\"age\"] = 30 // better!\n}\nfinally {\n    manager.commit() // will update all dirty objects\n}\n``` \n\nSince the objects are managed by the `ObjectManager` we can update values easily. The manager will know about any changes and will\npersist them at the end of the transaction.\n\nProjections are possible as well, as seen here\n\n```Kotlin\nval queryManager = manager.queryManager()\nmanager.begin()\ntry {\n    val person = queryManager.from(personDescriptor)\n    val children = person.join(\"children\")\n    val query = queryManager\n        .create()\n        .select(person.get(\"age\"), person.get(\"name\")\n        .where(eq(children.get(\"name\"), \"Nika\"))\n\n    val tupleResult = query.execute().getResultList()\n\n    val name = tupleResult[0][1]\n}\nfinally {\n    manager.commit()\n}\n``` \n\nIn addition to a criteria api like query, we are of course able to specify hql like queries as well:\n\n```Kotlin\nval queryManager = manager.queryManager()\n\nmanager.begin()\ntry {\n    val query = manager.query\u003cDataObject\u003e(\"SELECT p.name FROM person AS p JOIN p.children as children WHERE children.name = :name\")\n\n    val queryResult = query.executor()\n        .set(\"name\", \"NIka\")\n        .execute()\n        .getResultList()\n\n        ...\n}\nfinally {\n    manager.commit() // will update\n}\n```\n## Solution design\n\nThe solution is pretty straight forward. Entities are stored as a combination of three technical tables\n* `ENTITY` a table referencing the entity definition and a generated primary key\n* `PROPERTY` a table that will store single attributes of an entity\n* `RELATIONS` a bridge table expressing object relations\n\nThe property table defines the columns\n\n* `TYPE` the id of the entity structure\n* `ENTITY` the id of the corresponding entity\n* `NAME` the property name\n\nand a number of columns that are able to store payload data with respect to the supported low-level data types\n* `STRING_VALUE` a string value\n* `INT_VALUE` a int value ( stores boolean values well )\n* `DOUBLE_VALUE` a floating point value\n\nIn order to model relations, the property table has a reflexive relation that expresses relationships stpored in a bridge table.\n\nAs the definition of an entity is known, the engine will know which attributes are stored in which columns.\n\nLet's look at a simple query, that will read a single person.\n\n```Sql\n  select\n        p.PROPERTY,\n        p.ENTITY,\n        p.DOUBLE_VALUE,\n        p.INT_VALUE,\n        p.STRING_VALUE,\n        p.TYPE \n    from\n        PROPERTY p \n    where\n        p.ENTITY=?\n```\n\nAfter reading the result set, the engine will create the appropriate `DataObject` instance and store the appropriate values in the correct places.\n\nIf we talk about queries, that code gets a little bit more complicated. Querying for an integer attribute \"age\" with the operator \"=\" and value 58 will result in something like\n```Sql\nselect\n        p1_0.ATTRIBUTE,\n        p1_0.ENTITY,\n        p1_0.DOUBLE_VALUE,\n        p1_0.INT_VALUE,\n        p1_0.STRING_VALUE,\n        p1_0.TYPE \n    from\n        PROPERTY p1_0 \n    where\n        p1_0.ENTITY in (\n            (select distinct p2_0.ENTITY \n               from PROPERTY p2_0 \n              where\n                    p2_0.TYPE=\"person\" \n                    and p2_0.NAME=\"age\" \n                    and p2_0.INT_VALUE=58)) \n    order by\n        p1_0.ENTITY\n```\n\n## Performance\n\nOf course, the performance and storage requirements are not as good as if we would map on static tables, since\n* we have a lot a attribute rows\n* indexes are much bigger\n* we require a subselect for every condition\n\nLet's look at a simple benchmark, that\n* creates objects\n* rereads all objects\n* filters objects ( that will match all objects )\n* filters and projects to a single attribute\n* updates a single property flushing all objects\n\nThe test was repeated with 2 scenarios executing 2000 times each.\n* JPA entity with 10 properties\n* DORM object with 1 properties\n* DORM object with 10 properties\n\nThe results ( avg time per object in ms ) are based on a H2 database ( on my old macbook :-) ). \n|Test              |   JPA  | DORM(1) | DORM(10) |\n|------------------|--------|---------|----------|\n| Create           | 0.067  | 0.077   |  1.015   |\n| Read             | 0.0405 | 0.217   |  0.4875  |\n| Filter           | 0.019  | 0.07    |  0.3705  |\n| Filter \u0026 Project | 0.003  | 0.0505  |  0.0655  |\n| Update           | 0.0635 | 0.2575  |  0.4495  |\n\nAs you can see, the biggest difference is the create test, since it has to create a row per property.\nReading is surpisingly fast, even though a lot of rows need to be read and processed.\nUpdate is almost even, since only a single property was changed. The difference would grow again, the more properties are touched.\n\nStill, not bad, huh?\n\n## GraphQL\n\nIn addition to the supplied Kotlin API a GraphQL server is available giving access to all CRUD and Query possibilities. \nThe internal schema is created dynamically based on the internal registry.\n\nFor the already mentioned `Person` object, it will create a schema based on the following technical types and inputs\n\n```GraphQL\ntype OperationResult {\n  count: Int\n}\n\ninput FloatFilter {\n  eq: Float\n  ge: Float\n  gt: Float\n  le: Float\n  lt: Float\n  ne: Float\n}\n\ninput IntFilter {\n  eq: Int\n  ge: Int\n  gt: Int\n  le: Int\n  lt: Int\n  ne: Int\n}\n\ninput StringFilter {\n  eq: String\n  ne: String\n}\n\ninput BooleanFilter {\n  eq: Boolean\n  ne: Boolean\n}\n```\nresulting in\n```GraphQL\ntype Person {\n  id: Int\n  name: String\n  age: Int\n  children: [Person]\n  father: Person\n}\n\ninput PersonInput {\n  id: Int\n  name: String\n  age: Int\n  children: [PersonInput]\n  father: PersonInput\n}\n\ninput PersonFilter {\n  and: [PersonFilter]\n  or: [PersonFilter]\n\n  id: IntFilter\n  name: StringFilter\n  age: IntFilter\n \n  father: PersonFilter\n}\n\ntype Query {\n  Person(where: PersonFilter): [Person]\n}\n\ntype Mutation {\n  createPerson(input: PersonInput): Person\n  deletePersons(where: PersonFilter): OperationResult\n  updatePerson(input: PersonInput): Person\n  updatePersons(input: PersonInput, where: PersonFilter): [Person]\n}\n```\n\nA query - already showing a join - will look like:\n\n```GraphQL\nquery sampleQuery {\n   Person (\n      where: {\n         father: {name: {eq: \"Andi\"}}\n      }\n    ) {    \n      id\n      name\n      father {\n         id\n         name\n      }\n   }\n}\n```\n\nAn bulk update:\n```GraphQL\nmutation {\n    updatePersons(\n      where: {\n        id: {eq: 1}\n      },\n      input: { age: 59, name: \"Andi\" }) {\n        id\n        name\n    }\n}\n```\n\n## Reference\n\nCheck the [Wiki](https://github.com/coolsamson7/dorm/wiki) for detailed information.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoolsamson7%2Fdorm","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fcoolsamson7%2Fdorm","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fcoolsamson7%2Fdorm/lists"}