{"id":37902256,"url":"https://github.com/jenetics/facilejdbc","last_synced_at":"2026-01-16T17:05:29.542Z","repository":{"id":45873277,"uuid":"216821656","full_name":"jenetics/facilejdbc","owner":"jenetics","description":"FacileJDBC - Fast, simple and lightweight JDBC wrapper","archived":false,"fork":false,"pushed_at":"2025-08-25T22:10:03.000Z","size":1612,"stargazers_count":40,"open_issues_count":5,"forks_count":2,"subscribers_count":4,"default_branch":"master","last_synced_at":"2025-08-26T00:13:35.775Z","etag":null,"topics":["database","db","dbms","java","java-sql-library","jdbc","jdbc-wrapper","query","sql"],"latest_commit_sha":null,"homepage":"","language":"Java","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/jenetics.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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,"zenodo":null}},"created_at":"2019-10-22T13:32:34.000Z","updated_at":"2025-08-25T22:10:07.000Z","dependencies_parsed_at":"2025-07-11T22:15:40.688Z","dependency_job_id":"33ce10d8-f8cf-445f-a66e-5484533e83da","html_url":"https://github.com/jenetics/facilejdbc","commit_stats":null,"previous_names":[],"tags_count":11,"template":false,"template_full_name":null,"purl":"pkg:github/jenetics/facilejdbc","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jenetics%2Ffacilejdbc","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jenetics%2Ffacilejdbc/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jenetics%2Ffacilejdbc/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jenetics%2Ffacilejdbc/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/jenetics","download_url":"https://codeload.github.com/jenetics/facilejdbc/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/jenetics%2Ffacilejdbc/sbom","scorecard":{"id":514879,"data":{"date":"2025-08-11","repo":{"name":"github.com/jenetics/facilejdbc","commit":"50767cbafe46ab588d0700c4812aadf482c59ab7"},"scorecard":{"version":"v5.2.1-40-gf6ed084d","commit":"f6ed084d17c9236477efd66e5b258b9d4cc7b389"},"score":3.9,"checks":[{"name":"Code-Review","score":0,"reason":"Found 0/9 approved changesets -- score normalized to 0","details":null,"documentation":{"short":"Determines if the project requires human code review before pull requests (aka merge requests) are merged.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#code-review"}},{"name":"Dangerous-Workflow","score":10,"reason":"no dangerous workflow patterns detected","details":null,"documentation":{"short":"Determines if the project's GitHub Action workflows avoid dangerous patterns.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#dangerous-workflow"}},{"name":"Packaging","score":-1,"reason":"packaging workflow not detected","details":["Warn: no GitHub/GitLab publishing workflow detected."],"documentation":{"short":"Determines if the project is published as a package that others can easily download, install, easily update, and uninstall.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#packaging"}},{"name":"Maintained","score":10,"reason":"11 commit(s) and 9 issue activity found in the last 90 days -- score normalized to 10","details":null,"documentation":{"short":"Determines if the project is \"actively maintained\".","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#maintained"}},{"name":"Token-Permissions","score":0,"reason":"detected GitHub workflow tokens with excessive permissions","details":["Warn: no topLevel permission defined: .github/workflows/gradle.yml:1","Info: no jobLevel write permissions found"],"documentation":{"short":"Determines if the project's workflows follow the principle of least privilege.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#token-permissions"}},{"name":"Binary-Artifacts","score":8,"reason":"binaries present in source code","details":["Warn: binary detected: buildSrc/lib/java2html.jar:1","Warn: binary detected: gradle/wrapper/gradle-wrapper.jar:1"],"documentation":{"short":"Determines if the project has generated executable (binary) artifacts in the source repository.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#binary-artifacts"}},{"name":"Pinned-Dependencies","score":0,"reason":"dependency not pinned by hash detected -- score normalized to 0","details":["Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/gradle.yml:18: update your workflow using https://app.stepsecurity.io/secureworkflow/jenetics/facilejdbc/gradle.yml/master?enable=pin","Warn: GitHub-owned GitHubAction not pinned by hash: .github/workflows/gradle.yml:21: update your workflow using https://app.stepsecurity.io/secureworkflow/jenetics/facilejdbc/gradle.yml/master?enable=pin","Info:   0 out of   2 GitHub-owned GitHubAction dependencies pinned"],"documentation":{"short":"Determines if the project has declared and pinned the dependencies of its build process.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#pinned-dependencies"}},{"name":"CII-Best-Practices","score":0,"reason":"no effort to earn an OpenSSF best practices badge detected","details":null,"documentation":{"short":"Determines if the project has an OpenSSF (formerly CII) Best Practices Badge.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#cii-best-practices"}},{"name":"Security-Policy","score":0,"reason":"security policy file not detected","details":["Warn: no security policy file detected","Warn: no security file to analyze","Warn: no security file to analyze","Warn: no security file to analyze"],"documentation":{"short":"Determines if the project has published a security policy.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#security-policy"}},{"name":"Fuzzing","score":0,"reason":"project is not fuzzed","details":["Warn: no fuzzer integrations found"],"documentation":{"short":"Determines if the project uses fuzzing.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#fuzzing"}},{"name":"Vulnerabilities","score":10,"reason":"0 existing vulnerabilities detected","details":null,"documentation":{"short":"Determines if the project has open, known unfixed vulnerabilities.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#vulnerabilities"}},{"name":"License","score":0,"reason":"license file not detected","details":["Warn: project does not have a license file"],"documentation":{"short":"Determines if the project has defined a license.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#license"}},{"name":"Signed-Releases","score":-1,"reason":"no releases found","details":null,"documentation":{"short":"Determines if the project cryptographically signs release artifacts.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#signed-releases"}},{"name":"Branch-Protection","score":0,"reason":"branch protection not enabled on development/release branches","details":["Warn: branch protection not enabled for branch 'master'"],"documentation":{"short":"Determines if the default and release branches are protected with GitHub's branch protection settings.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#branch-protection"}},{"name":"SAST","score":0,"reason":"SAST tool is not run on all commits -- score normalized to 0","details":["Warn: 0 commits out of 28 are checked with a SAST tool"],"documentation":{"short":"Determines if the project uses static code analysis.","url":"https://github.com/ossf/scorecard/blob/f6ed084d17c9236477efd66e5b258b9d4cc7b389/docs/checks.md#sast"}}]},"last_synced_at":"2025-08-20T01:33:51.704Z","repository_id":45873277,"created_at":"2025-08-20T01:33:51.704Z","updated_at":"2025-08-20T01:33:51.704Z"},"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":28480081,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-01-16T11:59:17.896Z","status":"ssl_error","status_checked_at":"2026-01-16T11:55:55.838Z","response_time":107,"last_error":"SSL_read: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"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":["database","db","dbms","java","java-sql-library","jdbc","jdbc-wrapper","query","sql"],"created_at":"2026-01-16T17:05:28.779Z","updated_at":"2026-01-16T17:05:29.535Z","avatar_url":"https://github.com/jenetics.png","language":"Java","readme":"[![Build Status](https://github.com/jenetics/facilejdbc/actions/workflows/gradle.yml/badge.svg)](https://github.com/jenetics/facilejdbc/actions/workflows/gradle.yml?query=branch%3Amaster)\n[![Maven Central Version](https://img.shields.io/maven-central/v/io.jenetics/facilejdbc?color=green)](https://central.sonatype.com/artifact/io.jenetics/facilejdbc)\n[![Javadoc](https://www.javadoc.io/badge/io.jenetics/facilejdbc.svg)](http://www.javadoc.io/doc/io.jenetics/facilejdbc)\n\n**_For building and running the library, Java 17 (or above) is required._**\n\n# Facile JDBC\n\n_Making the JDBC usage simpler and less verbose._\n\n## Overview\n\nJDBC is the basic API for accessing relational databases. Being basic makes it quite tedious to use directly. This leads to higher level abstractions like [JPA](https://docs.oracle.com/javaee/7/tutorial/partpersist.htm). Using a full-grown _Object Relational Mapper_ on the other side might be to heavy weight for many uses cases. _FacileJDBC_ tries to fill the gap by making the low-level JDBC access less verbose and tedious. SQL is still used as a query language.\n\n\u003e The API of this library has been heavily influenced by the Scala [Anorm](https://playframework.github.io/anorm/) library.\n\n#### _FacileJDBC_ gives you\n\n\u003e * A lightweight wrapper around the JDBC API.\n\u003e * The possibility to fill query parameters by _name_ instead of its position: Available via the `Param` interface.\n\u003e * Functions for creating (parsing) _entity_ objects from query `ResultSet`s: Available via the `RowParser` interface\n\u003e * Functions for splitting (deconstructing) _entity_ objects to DB columns: Available via the `Dctor` interface.\n\u003e * A `Query` object to putting all things together.\n\u003e * Lightweight transaction handling support.\n\n#### _FacileJDBC_ is not\n\n\u003e * An OR-Mapper\n\u003e * A type safe query language\n\u003e * An SQL query builder\n\n\n#### _FacileJDBC_ has no\n\n\u003e * DB-vendor specific code, uses 100% pure JDBC.\n\u003e * Query generation capabilities. The user is responsible for creating the proper SQL string.\n\u003e * Generated classes or dynamically generated proxies.\n\u003e * No connection pooling.\n\n\n**Other Java DB libraries**\n* [jOOQ](https://www.jooq.org/): Excellent library for accessing databases in a type safe way. Because of the different scope, it is more than a thin wrapper around the JDBC API. \n* [Jdbi](http://jdbi.org/): Similar scope, but with a different approach. \n\n## Examples\n\nThe following example show how to use _FacileJDBC_ for different use cases. For a detailed description of the API, also have a look at the [Javadoc](https://www.javadoc.io/doc/io.jenetics/facilejdbc). \n\n### Executing queries\n\nSQL queries are defined via the `Query` class. Since the `Query` class is immutable, it is safe to use it in a multi-threaded environment or define it as _static_ class member. The `Query` class is the main entry point of the _FacileJDBC_ library. For executing a query, all what it needs is a JDBC `Connection`.\n\n```java\nfinal Query query = Query.of(\"SELECT 1\");\nfinal boolean result = query.execute(conn)\n```\n\nThe `execute` method returns a `boolean` value as specified in the [`PreparedStatement.execute()`](https://docs.oracle.com/en/java/javase/11/docs/api/java.sql/java/sql/PreparedStatement.html#execute()) method. The [Javadoc](https://www.javadoc.io/doc/io.jenetics/facilejdbc) documents the used methods of the `PreparedStatement` for every _execute_ method.\n\n### Selecting objects\n\nUsually, your selected rows will be stored in [DTO](https://en.wikipedia.org/wiki/Data_transfer_object) objects. For the _simple_ examples the following `Person` DTO will be used.\n\n```java\npublic record Person( \n    String name,\n    String email,\n    String link\n){}\n```\n\nThe following query will select all persons which matches a given name _pattern_.\n\n```java\nstatic final Query SELECT = Query.of(\"\"\"\n    SELECT name, email, link\n    FROM person\n    WHERE name like :name\n    \"\"\"\n);\n```\n\nExecuting the select query is shown in the following code snippet. It also shows how query parameter are set and how the result rows are parsed. \n\n```java\nfinal List\u003cPerson\u003e persons = SELECT\n    .on(value(\"name\", \"M%\"))\n    .as(PARSER.list(), conn);\n```\n\nThe `Query.on` method is used for filling the query parameters. The variables uses the usual syntax for SQL bind variables. With the `Param.value` factory method it is possible to fill the defined variables. The `Query.as` method executes the query and returns the parsed result rows. For converting the query result to a _DTO_, a `RowParser` is needed. With the `RowParser.list()` method you will tell the query that you expect 0 to _n_ result rows. If only one result is expected, you need to use the `RowParser.single()` or `RowParser.singleOp()` method.\n\nThe following code snippet shows the `RowParser` implementation for our `Person` DTO.\n\n```java\nstatic final RowParser\u003cPerson\u003e PARSER = (row, conn) -\u003e new Person(\n    row.getString(\"name\"),\n    row.getString(\"email\"),\n    row.getString(\"link\")\n);\n```\n\nSince the `RowParser` is a _functional_ interface it can be written as shown. The first function parameter represents the actual selected row and second parameter the JDBC `Connection` used for executing the query. In most cases the `conn` will not be used, but it is quite helpful for fetching dependent DTOs in a sub-query.\n\nWhe you are useing Java `record`s as entity objects, you can create the `RowParser` with a simple factory method.\n```java\nstatic final RowParser\u003cPerson\u003e PARSER = RowParser.of(Person.class);\n```\n\n### Lazy (stream) select\n\nThe _FacileJdbc_ library allows you to lazily fetch results from the DB. This might be useful when the selected data can cause an `OutOfMemoryError`.\n\n```java\nfinal var select = Query.of(\"SELECT * FROM person;\");\n\n// Make sure to close the returned stream.\ntry (var persons = select.as(PARSER.stream(), conn)) {\n    // Do some processing with the person stream.\n    persons.forEach(person -\u003e ...);\n}\n```\n\nIt's important to _close_ the returned `Stream`, which will close the underlying `ResultSet` and `PreparedStatement`. Every `SQLException`, thrown while fetching the data from the DB, will be wrapped in an `UncheckedSQLException`.\n\n_By setting the fetch-size, with the `Query.withFetchSize(int)` method, it is possible to control the amount of data fetched at once by the JDBC driver._\n\n### Export selection result to CSV\n\nSometime it is convenient to export the whole selection result as CSV line.\n\n```java\nfinal var select = Query.of(\"SELECT * FROM book;\");\nfinal var csv = select.as(ResultSetParser.csvLine(), conn);\nSystem.out.println(csv);\n```\n\nThe printed CSV string will look like the following example.\n\n```\n\"ID\",\"PUBLISHED_AT\",\"TITLE\",\"ISBN\",\"PAGES\"\n\"0\",\"1987-02-04\",\"Auf der Suche nach der verlorenen Zeit\",\"978-3518061756\",\"5100\"\n\"1\",\"1945-01-04\",\"Database Design for Mere Mortals\",\"978-0321884497\",\"654\"\n\"2\",\"1887-02-04\",\"Der alte Mann und das Meer\",\"B00JM4RD2S\",\"142\"\n```\n\nFor a big result set it is possible to lazily stream the selected rows into a file.\n\n```java\nfinal var select = Query.of(\"SELECT * FROM book ORDER BY id;\");\ntry (Stream\u003cString\u003e lines = select.as(RowParser.csvLine().stream(), conn);\n    Writer out = Files.newBufferedWriter(Path.of(\"out.csv\")))\n{\n    lines.forEach(line -\u003e {\n        try {\n            out.write(line);\n            out.write(\"\\r\\n\");\n        } catch (IOException e) {\n            throw new UncheckedIOException(e);\n        }\n    });\n}\n```\n\nThe rows are written _without_ a header into the CSV file.\n\n```\n\"0\",\"1987-02-04\",\"Auf der Suche nach der verlorenen Zeit\",\"978-3518061756\",\"5100\"\n\"1\",\"1945-01-04\",\"Database Design for Mere Mortals\",\"978-0321884497\",\"654\"\n\"2\",\"1887-02-04\",\"Der alte Mann und das Meer\",\"B00JM4RD2S\",\"142\"\n```\n\n### Inserting objects\n\nFor inserting one new `Person` into the DB an _insert_ query have to be defined. \n\n```java\nstatic final Query INSERT = Query.of(\"\"\"\n    INSERT INTO person(name, email, link)\n    VALUES(:name, :email, :link);\n    \"\"\"\n);\n```\n\nWhen all bind variable has been set, it can be inserted by calling the `execute` method.\n\n```java\nfinal boolean inserted = INSERT\n    .on(\n        value(\"name\", \"foo\"),\n        value(\"email\", \"foo@gmail.com\"),\n        value(\"link\", \"http://google.com\"))\n    .execute(conn);\n```\n\nSetting the bind variables this way is quite tedious, if you already have a filled `Person` DTO. Inserting the `Person` DTO directly you need to define the variable-field mapping. This is done via the `Dctor` (deconstructor) interface. The `Dctor` can be seen as the inverse function of the `RowParser`.\n\n```java\nprivate static final Dctor\u003cPerson\u003e DCTOR = Dctor.of(\n    field(\"name\", Person::name),\n    field(\"email\", Person::email),\n    field(\"link\", Person::link)\n);\n```\n\nThe `Dctor` interface also comes with a simple factory method for records.\n\n```java\nprivate static final Dctor\u003cPerson\u003e DCTOR = Dctor.of(Person.class);\n```\n\nOnce a deconstructor is defined for your DTO, you can easily insert single `Person` objects.\n\n```java\nfinal Person person = ...;\nfinal boolean inserted = INSERT\n    .on(person, DCTOR)\n    .execute(conn);\n```\n\nIf you are interested in the _automatically_ generated primary key of the insertion, you have to use the `executeInsert` method. This method returns an `Optional` in the case no primary key could be generated.\n\n```java\nfinal Optional\u003cLong\u003e inserted = INSERT\n    .on(...)\n    .executeInsert(conn);\n```\n\nor\n\n```java\nfinal Optional\u003cInteger\u003e inserted = INSERT\n    .on(...)\n    .executeInsert(RowParser.int32(1), conn);\n```\n\nif you are needed to control the parsing of the generated primary key.\n\n### Batch insertion\n\nIf you have a collection of `Person`s, you can insert it in one batch.\n\n```java\nfinal List\u003cPerson\u003e persons = ...;\nfinal Batch batch = Batch.of(persons, DCTOR);\nfinal int[] counts = INSERT.executeUpdate(batch, conn);\n```\n\nFor simple insertions, you can also do some kind of ad-hoc batch insertions.\n\n```java\nQuery.of(\"INSERT INTO person(id, name) VALUES(:id, :name)\")\n    .execute(\n        Batch.of(\n            List.of(value(\"id\", 1), value(\"name\", \"Peter\")),\n            List.of(value(\"id\", 2), value(\"name\", \"Jack\")),\n            List.of(value(\"id\", 3), value(\"name\", \"John\"))\n        ),\n        conn\n    );\n``` \n\n### Multi-value parameter\n\nA parameter can be multi-value, like a sequence of IDs. In such case, values will be prepared to be passed appropriately in JDBC.\n\n```java\nfinal List\u003cBook\u003e results = Query.of(\"SELECT * FROM book WHERE id IN(:ids);\")\n    .on(Param.values(\"ids\", 1, 2, 3, 4))\n    .as(PARSER.list(), conn);\n```\n\nThe created JDBC query string will look like this\n\n```sql\nSELECT * FROM book WHERE id IN(?,?,?,?);\n```\n\nfilled with the value `1`, `2`, `3` and `4`.\n\n### Custom parameter type\n\nSometimes it is not possible to use the available object conversions, available in the library. E.g. if you want to insert some _raw_ byte content via an `InputStream`.\n\n```java\nfinal var query = Query.of(\"INSERT INTO book(name, pdf) VALUES(:name, :pdf)\");\ntry (var in = Files.newInputStream(Path.of(\"./book.pdf\"))) {\n    final long id = query\n        .on(\n            Param.value(\"name\", \"For Whom the Bell Tolls\"),\n            // Call a \"special\" statement set-method, when setting the parameter.\n            Param.of(\"pdf\", (index, stmt) -\u003e stmt.setBinaryStream(index, in)))\n        .executeInsert(conn)\n        .orElseThrow();\n\n    System.out.println(\"Inserted book with ID: \" + id);\n}\n```\n\n### Custom parameter conversion\n\nIt is possible to create automatic parameter value conversion via the SPI `SqlTypeMapper` class. Usually, it is not possible to insert an `URI` field directly into the DB. You have to convert it into a string object first.\n\n```java\npublic record Person(\n    String name,\n    URI link\n){}\n\nstatic final Dctor\u003cPerson\u003e DCTOR = Dctor.of(\n    Person.class, \n    Dctor.field(\"email\", p -\u003e p.link().toString())\n);\n```\n\nIf a mapper for the `URI` class is defined, it is possible to write the deconstructor more concise.\n\n```java\nstatic final Dctor\u003cPerson\u003e DCTOR = Dctor.of(Person.class);\n```\n\nThe implementation of such a mapping is quite simple and will look like showed\nin the following code snippet.\n\n```java\npublic class URIMapper extends SqlTypeMapper {\n    public Object convert(final Object value) {\n        if (value instanceof URI) {\n            return value.toString();\n        } else {\n            return value;\n        }\n    }\n}\n```\n\nAdd the following line\n\n```java\norg.acme.URIMapper\n```\n\nto the service definition file\n\n```java\nMETA-INF/services/io.jenetics.facilejdbc.spi.SqlTypeMapper\n```\n\nand you are done.\n\n### Selecting/inserting object _graphs_\n\nThe previous examples shows the basic usage of the library. It is possible to use this for all needed select and insert queries, as you will do it with plain JDBC. If you need to select or insert _small_ object graphs, this becomes fast tedious as well. \n\nLets extend our initial example an convert the _link_ of the `Person` into an object\n\n```java\npublic record Person( \n    String name,\n    String email,\n    Link link\n){}\n```\nand with a `Link` class, which will look like the following.\n```java\npublic record Link( \n    String name,\n    URI link\n){}\n```\n\nIt is now possible to create one `RowParser\u003cPerson\u003e` and one `Dctor\u003cPerson\u003e` which automatically takes care about the linked `Link` object. The new parser will look like the following code snippet.\n\n```java\nstatic final RowParser\u003cPerson\u003e PERSON_PARSER = (row, conn) -\u003e new Person(\n    row.getString(\"name\"),\n    row.getString(\"email\"),\n    selectLink(row.getLong(\"link_id\"), conn)\n);\n```\nWith the shown deconstructor.\n```java\nstatic final Dctor\u003cPerson\u003e PERSON_DCTOR = Dctor.of(\n    Person.class,\n    field(\"link_id\", (p, c) -\u003e insertLink(p.link(), c))\n);\n```\n\nThe needed helper methods are responsible for selecting/inserting the `Link` object.\n\n```java\nstatic final RowParser\u003cLink\u003e LINK_PARSER = (row, conn) -\u003e new Link(\n    row.getString(\"name\"),\n    URI.create(row.getString(\"url\"))\n);\n\nstatic final Dctor\u003cLink\u003e LINK_DCTOR = Dctor.of(\n    field(\"name\", Link::name),\n    field(\"url\", l -\u003e l.url.toString())\n);\n\nstatic Link selectLink(final Long linkId, final Connection conn)\n    throws SQLException\n{\n    return Query.of(\"SELECT * FROM link WHERE id = :id\")\n        .on(value(\"id\", linkId))\n        .as(LINK_PARSER.singleNull(), conn);\n}\n\nstatic Long insertLink(final Link link, final Connection conn)\n    throws SQLException\n{\n    return Query.of(\"INSERT INTO link(name, url) VALUES(:name, :url\")\n        .on(link, LINK_DCTOR)\n        .executeInsert(conn)\n        .orElseThrow();\n}\n```\n\nIt is still necessary to implement the sub-inserts and sub-selects, but this can be re-used in other queries, where inserting and selecting of `Link`s is needed. Note that this is not an automatic OR-mapping mechanism. The user is still in charge for the concrete implementation.\n\n\u003e **Note**\n\u003e\n\u003e Although the described feature is quite expressive and may solve some selection/insertion task in an elegant way, does not mean you have to use it. Just treat it as additional possibility.\n\n### Transaction handling\n\n_FacileJDBC_ also contains two interfaces for simple transaction handling. The `Transaction` interface defines methods for executing one or more queries in a transactional context.\n\n```java\nfinal Transaction transaction = ...;\n// Inserting a new link into the DB and returning \n// the  primary key of the newly inserted row.\nfinal long id = transaction.apply(conn -\u003e insertLink(link, conn));\n```\n\nIf you are not interested in the return value of the SQL execution, you can use the `accept` method instead. In the case of an error, the connection is rolled back. If everything works fine, the connection is committed.\n\n```java\ntransaction.accept(conn -\u003e insertLink(link, conn));\n```\n\nThe second interface is the `Transactional` interface, which represents the _transactional_ capability, typically exposed by a database. In this sense, it can be seen as a minimal database  interface, just by exposing a `Connection` factory method, `Transactional::connection`. Since `Transactional` is a functional interface, it can easily created by defining the `Connection` factory method.\n\n```java\nfinal Transactional db = () -\u003e DriverManager.getConnection(\n    \"jdbc:hsqldb:mem:testdb\",\n    \"SA\",\n    \"\"\n);\n```\n\nThe example above shows how to create a `Transactional` instance for a HSQLDB in-memory database, perfectly usable for testing purposes. Then it can be used for performing some SQL inserts.\n\n```java\nfinal long bookId = db.transaction().apply(conn -\u003e\n    Book.insert(book, conn)\n);\n``` \n\nFor production code you usually have a `DataSource`, which represents the connection to the DB. It's equally easy to create a `Transactional` object from a given `DataSource` instance.\n\n```java\nfinal DataSource ds = ...;\nfinal Transactional db = ds::getConnection;\n```\n\n## Release notes\n\n### [2.1.1](https://github.com/jenetics/facilejdbc/releases/tag/v2.1.1)\n\n#### Bug\n\n* [#53](https://github.com/jenetics/facilejdbc/issues/53): RTimeout not being set on the `Query` object.\n\n_[All Release Notes](RELEASE_NOTES.md)_\n\n\n## License\n\nThe library is licensed under the [Apache License, Version 2.0](http://www.apache.org/licenses/LICENSE-2.0.html).\n\n    Copyright 2019-2025 Franz Wilhelmstötter\n\n    Licensed under the Apache License, Version 2.0 (the \"License\");\n    you may not use this file except in compliance with the License.\n    You may obtain a copy of the License at\n\n    http://www.apache.org/licenses/LICENSE-2.0\n\n    Unless required by applicable law or agreed to in writing, software\n    distributed under the License is distributed on an \"AS IS\" BASIS,\n    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n    See the License for the specific language governing permissions and\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjenetics%2Ffacilejdbc","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fjenetics%2Ffacilejdbc","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fjenetics%2Ffacilejdbc/lists"}