{"id":22220557,"url":"https://github.com/buckelieg/jdbc-fn","last_synced_at":"2025-12-16T05:02:55.034Z","repository":{"id":144518297,"uuid":"72570007","full_name":"buckelieg/jdbc-fn","owner":"buckelieg","description":"A functional layer to simplify a usage of JDBC with Stream API for Java 8+","archived":false,"fork":false,"pushed_at":"2024-07-17T20:38:30.000Z","size":620,"stargazers_count":4,"open_issues_count":0,"forks_count":1,"subscribers_count":2,"default_branch":"master","last_synced_at":"2024-07-18T12:50:29.569Z","etag":null,"topics":["database","functional","java","java-8","java8","jdbc","jdbc-stream","sql","sql-query","sql-script","sql-stream","stream","streams"],"latest_commit_sha":null,"homepage":"https://buckelieg.github.io/jdbc-fn/","language":"Java","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/buckelieg.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.md","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":"2016-11-01T19:37:44.000Z","updated_at":"2024-07-18T11:47:37.000Z","dependencies_parsed_at":null,"dependency_job_id":"a9071810-afe5-4c58-b64b-a5eae052a42c","html_url":"https://github.com/buckelieg/jdbc-fn","commit_stats":null,"previous_names":["buckelieg/simple-tools","buckelieg/db-fn"],"tags_count":16,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/buckelieg%2Fjdbc-fn","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/buckelieg%2Fjdbc-fn/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/buckelieg%2Fjdbc-fn/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/buckelieg%2Fjdbc-fn/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/buckelieg","download_url":"https://codeload.github.com/buckelieg/jdbc-fn/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":227817177,"owners_count":17824199,"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":["database","functional","java","java-8","java8","jdbc","jdbc-stream","sql","sql-query","sql-script","sql-stream","stream","streams"],"created_at":"2024-12-02T23:09:04.875Z","updated_at":"2025-12-16T05:02:49.979Z","avatar_url":"https://github.com/buckelieg.png","language":"Java","funding_links":[],"categories":[],"sub_categories":[],"readme":"[![build](https://github.com/buckelieg/jdbc-fn/actions/workflows/ci.yml/badge.svg)](https://github.com/buckelieg/jdbc-fn/actions/workflows/ci.yml)\n[![license](https://img.shields.io/github/license/buckelieg/jdbc-fn.svg)](./LICENSE.md)\n[![dist](https://img.shields.io/maven-central/v/com.github.buckelieg/jdbc-fn.svg)](http://mvnrepository.com/artifact/com.github.buckelieg/jdbc-fn)\n[![javadoc](https://javadoc.io/badge2/com.github.buckelieg/jdbc-fn/javadoc.svg)](https://javadoc.io/doc/com.github.buckelieg/jdbc-fn)\n[![codecov](https://codecov.io/github/buckelieg/jdbc-fn/graph/badge.svg?token=4uHlR7gi8v)](https://codecov.io/github/buckelieg/jdbc-fn)\n# jdbc-fn\nFunctional style programming over plain JDBC.\n+ Execute SQL [SELECT](#select) query and process results with Java Stream API.\n+ Fire SQL [UPDATE(S)](#update) [batch(es)](#batch-mode) in a single line of code.\n+ Invoke [stored procedures](#stored-procedures) and retrieve its results without dealing with SQLExceptions\n+ Execute parameterized [scripts](#scripts).\n\nAnd more...\n\n## Getting Started\nAdd maven dependency:\n```\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.github.buckelieg\u003c/groupId\u003e\n  \u003cartifactId\u003ejdbc-fn\u003c/artifactId\u003e\n  \u003cversion\u003e1.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\n### Setup database\nThere are a couple of ways to set up the things:\n```java\nDataSource ds = ... // obtain ds (e.g. via JNDI or other way)\nDB db = DB.create(ds::getConnection); // shortcut for DB.builder().build(ds::getConnection)\n// or\nDB db = DB.builder()\n          .withMaxConnections(10) // defaults to Runtime.getRuntime().availableProcessors()\n          .build(() -\u003e DriverManager.getConnection(\"vendor-specific-string\"));\n// do things...\ndb.close(); // cleaning used resources: closes underlying connection pool, executor service (if configured to do so) etc...\n```\n\n### Select\nUse question marks:\n```java\nCollection\u003cString\u003e names = db.select(\"SELECT name FROM TEST WHERE ID IN (?, ?)\", 1, 2).execute(rs -\u003e rs.getString(\"name\")).collect(Collectors.toList());\n```\nor use named parameters:\n```java\n// in java9+\nimport static java.util.Map.of;\nCollection\u003cString\u003e names = db.select(\n\t\t\"SELECT name FROM TEST WHERE 1=1 AND ID IN (:ID) OR NAME=:name\", \n                of(\":ID\", new Object[]{1, 2}, \":name\", \"name_5\")\n        ).execute(rs -\u003e rs.getString(\"name\")).reduce(\n                new LinkedList\u003c\u003e(),\n                (list, name) -\u003e {\n                    list.add(name);\n                    return list;\n                },\n                (l1, l2) -\u003e {\n                  l1.addAll(l2);\n                  return l1;\n                }\n        );\n```\nParameter names are CASE SENSITIVE! 'Name' and 'name' are considered different parameter names.\n\u003cbr/\u003e Parameters may be provided with or without leading colon.\n\n##### The N+1 problem resolution\nFor the cases when it is needed to process (say - enrich) each mapped row with an additional data the `Select.ForBatch` can be used\n```java\nStream\u003cEntity\u003e entities = db.select(\"SELECT * FROM HUGE_TABLE\")\n        .forBatch(/* map resultSet here to needed type*/)\n        .size(1000)\n        .execute(batchOfObjects -\u003e {\n\t\t  // list of mapped rows with size not more than 1000\n\t\t  batchOfObjects.forEach(obj -\u003e obj.setSomethingElse());\n        });\n```\nFor cases where it is needed to issue any additional queries to database use:\n```java\n// suppose the USERS table contains thousands of records\nStream\u003cUser\u003e users = db.select(\"SELECT * FROM USERS\")\n    .forBatch(rs -\u003e new User(rs.getLong(\"id\"), rs.getString(\"name\")))\n    .size(1000)\n    .execute((batchOfUsers, session) -\u003e {\n\t  Map\u003cLong, UserAttr\u003e attrs = session.select(\n\t\t\"SELECT * FROM USER_ATTR WHERE id IN (:ids)\",\n                entry(\"ids\", batchOfUsers.stream().map(User::getId).collect(Collectors.toList()))\n          ).execute(rs -\u003e {\n\t\t\tUserAttr attr = new UserAttr();\n\t\t\tattr.setId(rs.getLong(\"attr_id\"));\n\t\t\tattr.setUserId(rs.getLong(\"user_id\"));\n\t\t\tattr.setName(rs.getString(\"attr_name\"));\n\t\t\t// etc...\n\t\t\treturn attr;\n\t\t  })\n          .groupingBy(UserAttr::userId, Function.identity());\n\t  batchOfUsers.forEach(user -\u003e user.addAttrs(attrs.getOrDefault(user.getId(), Collections.emptyList())));\n\t});\n// stream of users objects will consist of updated (enriched) objects\n```\nUsing this to process batches you must keep some things in mind:\n+ Executor service is used internally to power parallel processing\u003c/li\u003e\n+ All batches are processed regardless any possible short circuits\u003c/li\u003e\n+ \u003ccode\u003eSelect.fetchSize\u003c/code\u003e and \u003ccode\u003eSelect.ForBatch.size\u003c/code\u003e are not the same but connected\u003c/li\u003e\n\n##### Metadata processing\nFor the special cases when only a metadata of the query is needed `Select.forMeta` can be used:\n```java\n// suppose we want to collect information of which column of the provided query is a primary key\nMap\u003cString, Boolean\u003e processedMeta = db.select(\"SELECT * FROM TEST\").forMeta(metadata -\u003e {\n  Map\u003cString, Boolean\u003e map = new HashMap\u003c\u003e();\n  metadata.forEachColumn(columnIndex -\u003e map.put(metadata.getName(columnIndex), metadata.isPrimaryKey(columnIndex)));\n  return map;\n});\n```\n\n### Insert \nwith question marks:\n```java\n// res is an affected rows count\nlong res = db.update(\"INSERT INTO TEST(name) VALUES(?)\", \"New_Name\").execute();\n```\nOr with named parameters:\n```java\nlong res = db.update(\"INSERT INTO TEST(name) VALUES(:name)\", new SimpleImmutableEntry\u003c\u003e(\"name\",\"New_Name\")).execute();\n// in java9+\nlong res = db.update(\"INSERT INTO TEST(name) VALUES(:name)\", Map.entry(\"name\",\"New_Name\")).execute();\n```\n##### Getting generated keys\nTo retrieve possible generated keys provide a mapping function to `execute` method:\n```java\nCollection\u003cLong\u003e generatedIds = db.update(\"INSERT INTO TEST(name) VALUES(?)\", \"New_Name\").execute(rs -\u003e rs.getLong(1));\n```\nSee docs for more options.\n### Update\n```java\nlong res = db.update(\"UPDATE TEST SET NAME=? WHERE NAME=?\", \"new_name_2\", \"name_2\").execute();\n```\nor\n```java\nlong res = db.update(\"UPDATE TEST SET NAME=:name WHERE NAME=:new_name\", \n  new SimpleImmutableEntry\u003c\u003e(\"name\", \"new_name_2\"), \n  new SimpleImmutableEntry\u003c\u003e(\"new_name\", \"name_2\")\n).execute();\n// in java9+\nlong res = db.update(\"UPDATE TEST SET NAME=:name WHERE NAME=:new_name\", Map.entry(\":name\", \"new_name_2\"), Map.entry(\":new_name\", \"name_2\")).execute();\n```\n##### Batch mode\nFor batch operation use:\n```java\nlong res = db.update(\"INSERT INTO TEST(name) VALUES(?)\", new Object[][]{ {\"name1\"}, {\"name2\"} }).batch(2).execute();\n```  \n### Delete\n```java\nlong res = db.update(\"DELETE FROM TEST WHERE name=?\", \"name_2\").execute();\n```\n\n### Stored Procedures\nInvoking stored procedures is also quite simple:\n```java\nString name = db.procedure(\"{call GETNAMEBYID(?,?)}\", P.in(12), P.out(JDBCType.VARCHAR)).call(cs -\u003e cs.getString(2)).orElse(\"Unknown\");\n```\nNote that in the latter case stored procedure must not return any result sets.\n\u003cbr/\u003eIf stored procedure is considered to return result sets it is handled similar to regular selects (see above).\n\n### Scripts\nThere are two options to run an arbitrary SQL scripts:\n\n+ Provide a script itself\n```java\ndb.script(\"CREATE TABLE TEST (id INTEGER NOT NULL, name VARCHAR(255));INSERT INTO TEST(id, name) VALUES(1, 'whatever');UPDATE TEST SET name = 'whatever_new' WHERE name = 'whatever';DROP TABLE TEST;\").execute();\n```\n+ Provide a file with an SQL script\n```java\ndb.script(new File(\"path/to/script.sql\")).timeout(60).execute();\n```\nScript:\n+ Can contain single- and multiline comments. \n+ Each statement must be separated by a semicolon (\";\").\n+ Execution results ignored and not handled after all.\n+ Support named parameters\n+ Support escaped syntax, so it is possible to include JDBC-like procedure call statements.\n\n### Transactions\nLong story short - an example:\n\n```java\n// suppose we have to insert a bunch of new users by name and get the latest one filled with its attributes....\n\nLogger LOG = getLogger(); //... logger used in application \nUser latestUser = db.transaction()\n  .isolation(Transaction.Isolation.SERIALIZABLE)\n  .execute(session -\u003e\n      session.update(\"INSERT INTO users(name) VALUES(?)\", new Object[][]{ {\"name1\"}, {\"name2\"}, {\"name3\"} })\n        .skipWarnings(false)\n        .timeout(1, TimeUnit.MINUTES)\n        .print(LOG::debug)\n        .execute(rs -\u003e rs.getLong(1))\n        .stream()\n        .peek(id -\u003e session.procedure(\"{call PROCESS_USER_CREATED_EVENT(?)}\", id).call())\n        .max(Comparator.comparing(i -\u003e i))\n        .flatMap(id -\u003e session.select(\"SELECT * FROM users WHERE id=?\", id).print(LOG::debug).single(rs -\u003e {\n\t\t  User u = new User();\n\t\t  u.setId(rs.getLong(\"id\"));\n\t\t  u.setName(rs.getString(\"name\"));\n\t\t  // ...fill other user's attributes...\n\t\t  return user;\n        }))\n        .orElse(null)\n);\n```\n##### Nested transactions and deadlocks\nProviding connection supplier function with plain connection\n\u003cbr/\u003elike this: ```DB db = DB.create(() -\u003e connection));```\n\u003cbr/\u003eor this: \u0026nbsp;\u0026nbsp;```DB db = DB.builder().withMaxConnections(1).build(() -\u003e DriverManager.getConnection(\"vendor-specific-string\"));```\n\u003cbr/\u003e e.g - if supplier function always return the same connection\n\u003cbr/\u003ethe concept of transactions will be partially broken.\n\nThe simplest case:\n```java\nDB db = DB.create(() -\u003e connection); // or DB.builder().withMaxConnections(1).build(ds::getConnection)\ndb.transaction().run(session1 -\u003e db.transaction().run(session2 -\u003e {}))\n// runs forever since each transaction tries to obtain new connection and the second one cannot be provided with new one\n```\n\n### Logging \u0026 Debugging\nConvenient logging methods provided.\n```java\nLogger LOG = // ... \ndb.select(\"SELECT * FROM TEST WHERE id=?\", 7).print(LOG::debug).single(rs -\u003e {/*map rs here*/});\n```\nThe above will print a current query to provided logger with debug method.\n\u003cbr/\u003eAll provided parameters will be substituted with corresponding values so this case will output:\n\u003cbr/\u003e\u003ccode\u003eSELECT * FROM TEST WHERE id=7\u003c/code\u003e\n\u003cbr/\u003eCalling \u003ccode\u003eprint()\u003c/code\u003e without arguments will do the same with standard output.\n\n##### Scripts logging\nFor \u003ccode\u003eScript\u003c/code\u003e query \u003ccode\u003everbose()\u003c/code\u003e method can be used to track current script step execution.\n```java\ndb.script(\"SELECT * FROM TEST WHERE id=:id;DROP TABLE TEST\", new SimpleImmutableEntry\u003c\u003e(\"id\", 5)).verbose().execute();\n```\nThis will print out to standard output two lines:\n\u003cbr/\u003e```SELECT * FROM TEST WHERE id=5```\n\u003cbr/\u003e```DROP TABLE TEST```\n\u003cbr/\u003eEach line will be appended to output at the moment of execution.\n\u003cbr/\u003eCalling ```print()``` on ```Script``` will print out the whole sql script with parameters substituted.\n\u003cbr/\u003eCustom logging handler may also be provided for both cases.\n\n### Built-in mappers\nAll ```Select``` query methods which takes a ```mapper``` function has a companion one without.\n\u003cbr/\u003e Calling that ```mapper```-less methods will imply mapping to a tuple as ```String``` alias to ```Object``` value:\n```java\nList\u003cMap\u003cString, Object\u003e\u003e = db.select(\"SELECT name FROM TEST\").execute().collect(Collectors.toList());\n```\n\n### Prerequisites\nJava8, Maven, Appropriate JDBC driver.\n\n## License\nThis project licensed under Apache License, Version 2.0 - see the [LICENSE.md](LICENSE.md) file for details\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbuckelieg%2Fjdbc-fn","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fbuckelieg%2Fjdbc-fn","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fbuckelieg%2Fjdbc-fn/lists"}