{"id":13806552,"url":"https://github.com/susom/database","last_synced_at":"2025-05-13T22:30:22.779Z","repository":{"id":13931102,"uuid":"16630721","full_name":"susom/database","owner":"susom","description":"Relational database access made simpler and safer","archived":false,"fork":false,"pushed_at":"2025-05-01T22:15:29.000Z","size":761,"stargazers_count":41,"open_issues_count":3,"forks_count":25,"subscribers_count":24,"default_branch":"master","last_synced_at":"2025-05-01T23:23:08.406Z","etag":null,"topics":["accessing-databases","database-support","java","open-source","relational-databases","sql"],"latest_commit_sha":null,"homepage":"","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/susom.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,"zenodo":null}},"created_at":"2014-02-07T23:25:13.000Z","updated_at":"2025-05-01T22:15:31.000Z","dependencies_parsed_at":"2024-01-03T02:24:53.298Z","dependency_job_id":"d58b72ca-baea-4aab-b9e3-b85d355c5875","html_url":"https://github.com/susom/database","commit_stats":null,"previous_names":[],"tags_count":15,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/susom%2Fdatabase","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/susom%2Fdatabase/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/susom%2Fdatabase/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/susom%2Fdatabase/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/susom","download_url":"https://codeload.github.com/susom/database/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254036812,"owners_count":22003655,"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":["accessing-databases","database-support","java","open-source","relational-databases","sql"],"created_at":"2024-08-04T01:01:13.192Z","updated_at":"2025-05-13T22:30:22.268Z","avatar_url":"https://github.com/susom.png","language":"Java","readme":"## Easier, Safer Database Access\n\n[![Build Status](https://app.travis-ci.com/susom/database.svg?branch=master)](https://app.travis-ci.com/susom/database)\n[![Maven Central](https://maven-badges.herokuapp.com/maven-central/com.github.susom/database/badge.svg)](https://maven-badges.herokuapp.com/maven-central/com.github.susom/database)\n\n[![Reliability Rating](https://sonarcloud.io/api/project_badges/measure?project=susom_database\u0026metric=reliability_rating)](https://sonarcloud.io/summary/new_code?id=susom_database)\n[![Security Rating](https://sonarcloud.io/api/project_badges/measure?project=susom_database\u0026metric=security_rating)](https://sonarcloud.io/summary/new_code?id=susom_database)\n[![Coverage](https://sonarcloud.io/api/project_badges/measure?project=susom_database\u0026metric=coverage)](https://sonarcloud.io/summary/new_code?id=susom_database)\n\nThe point of this project is to provide a simplified way of accessing databases. It is a\nwrapper around the JDBC driver, and tries to hide some of the more error-prone, unsafe, and non-portable\nparts of the standard API. It uses standard Java types for all operations (as opposed to java.sql.*),\nand acts as a compatibility layer in making every attempt to behave consistently\nwith all supported databases.\n\nThe operations supported are those typical of relational databases, and are expressed as SQL.\nThis is NOT an object-relational mapping layer or an attempt to create a new query language.\n\nIf you are looking for convenient utilities built on top of this library, try https://github.com/susom/database-goodies.\n\n### Features\n\n#### No way to control (mess up) resource handling\n\nConnections, prepared statements, and result sets are hidden so\nthere is no opportunity for client code to make a mistake and\ncause resource leaks. For example, the following code is complete\nand correct with respect to resources, exceptions, and transactions.\n\n```java\n  String url = \"jdbc:hsqldb:file:hsqldb;shutdown=true\";\n  Builder dbb = DatabaseProvider.fromDriverManager(url);\n\n  dbb.transact(db -\u003e {\n      String s = db.get().toSelect(\"select s from t where i=?\")\n          .argInteger(5)\n          .queryOneOrThrow(r -\u003e r.getString());\n      System.out.println(\"Result: \" + s);\n  });\n```\n\nThis style of using callbacks also fits nicely with asynchronous programming models.\nSupport for [Vert.x](http://vertx.io/) is included (use\n[DatabaseProviderVertx](src/main/java/com/github/susom/database/DatabaseProviderVertx.java)).\nThere is also a simple [Vert.x server example](src/test/java/com/github/susom/database/example/VertxServer.java) and\na more sophisticated [example with concurrency](src/test/java/com/github/susom/database/example/VertxServerFastAndSlow.java).\n\n#### Facilitate static analysis\n\nAnnotations are included so you can use the [Checker Framework](http://types.cs.washington.edu/checker-framework/)\nstatic analysis tool to prove there are no SQL Injection vulnerabilities in your application.\nMake your automated build fail immediately when a vulnerability is introduced.\n\n```java\n  // Checker will fail the build for this\n  db.toSelect(\"select a from t where b=\" + userInput).query(...);\n```\n\nOf course, there are times when you need to dynamically construct SQL,\nso there is a safe way to do that as well:\n\n```java\n  Sql sql = new Sql();\n\n  sql.append(\"select a from b where c=?\").argInteger(1);\n\n  if (d) {\n    sql.append(\" and d=?\").argString(\"foo\");\n    // Note the following would also fail with Checker\n    //sql.append(\" and d=\" + userInput);\n  }\n\n  db.toSelect(sql).query(...);\n```\n\n#### Connection pooling\n\nInternal connection pooling is included, leveraging the excellent\n[HikariCP library](https://brettwooldridge.github.io/HikariCP/).\n\n```java\n  String url = \"jdbc:hsqldb:file:hsqldb;shutdown=true\";\n  Config config = Config.from().value(\"database.url\", url).get();\n  Builder dbb = DatabaseProvider.pooledBuilder(config);\n\n  for (...) {\n    dbb.transact(db -\u003e {\n        ...\n    });\n  }\n\n  // ... much later, on shutdown\n  dbb.close();\n```\n\nDue to the normal lifetime of a connection pool, you are obligated to\nexplicitly shutdown the pool (for example, in a JVM shutdown handler)\nwhen it is no longer needed.\n\n#### Type safe with null parameters\n\nThe various `argX()` calls know the type of the object you intend to pass, and can\ntherefore handle null values correctly. No more errors because you pass a null and the JDBC\ndriver can't figure out what type it should be.\n\n```java\n  db.toInsert(\"insert into foo (bar) values (?)\").argLong(maybeNull).insert(1);\n```\n\n#### Indexed or named parameters\n\nYou can use traditional positional parameters in the SQL (the '?' character),\nor you can use named parameters. This can help reduce errors due to counting\nincorrectly.\n\n```java\n  db.toUpdate(\"update foo set bar=?\").argLong(23L).update();\n  db.toUpdate(\"update foo set bar=:baz\").argLong(\"baz\", 23L).update();\n```\n\nYou can use both positional and named within the same SQL statement. The positional\nparameters must be in the correct order, but the `arg*()` calls for the named\nparameters can be mixed anywhere among the positional ones.\n\n```java\n  db.toSelect(\"select c from t where a=:a and b=?\")\n      .argString(\"value for b\")\n      .argString(\":a\", \"value for a\")\n      .queryLongOrNull();\n```\n\n#### No checked exceptions\n\nAll SQLExceptions are wrapped into a DatabaseException that inherits from\nRuntimeException. This makes code much cleaner because in server programming there is usually no\nway to recover from an error (it is handled in a generic way by higher level code).\n\n#### Correct handling of java.util.Date\n\nAs long as your database columns have enough precision, Date\nobjects will round-trip correctly with millisecond precision. No more fiddling with Timestamp\nand dealing with millisecond truncation and nanoseconds.\n\n```java\n  Date now = new Date(); // java.util.Date\n\n  db.toInsert(\"insert into t (pk,d) values (?,?)\")\n      .argInteger(123)\n      .argDate(now)\n      .insert(1);\n  Date sameNow = db.toSelect(\"select d from t where pk=?\")\n      .argInteger(123)\n      .queryDateOrNull();\n\n  if (now.equals(sameNow)) {\n    // don't look so surprised...\n  }\n```\n\nThere is also a convenient way to deal with \"now\", which hides the `new Date()` call\nwithin the configurable `Options`. This is handy for testing because you can explicitly\ncontrol and manipulate the clock.\n\n```java\n  db.toInsert(...).argDateNowPerApp().insert();\n```\n\nSince every database seems to have a different way of dealing with time, this library also\ntries to smooth out some of the syntactic (and semantic) differences in using time according\nto the database server (the operating system time).\n\n```java\n  db.toInsert(...).argDateNowPerDb().insert();\n```\n\nFor Oracle the above code will substitute `systimestamp(3)` for the parameter, while for PostgreSQL\nthe value `date_trunc('milliseconds',localtimestamp)` will be used. To make testing easier,\nthere is also a configuration option to make the above code do exactly the same thing as\n`argDateNowPerApp()` so you can in effect control the database server time as well as that\nof the application server.\n\n#### Support for java.time.LocalDate\n\nThere are use cases where a true database date is more appropriate than a timestamp, \ne.g., for recording date of birth or other fields where time is not really relevant or \nknown. The LocalDate API is stored as a database Date, not a Timestamp, for modern\ndatabases that support a true date type.\n\nLocalDate has been implemented and tested using ISO 8601 format YYYY-MM-DD.  There should be\nno time or timezone associated.\n\nPostgres and MS SQLServer have implemented a fully compliant LocalDate that is consistentently\nreturned as java.sql.Date.   There are no known issues with these databases.\n \nOracle does not support a true date type; the Oracle Date is actually implemented as a \ntimestamp.  Oracle supports LocalDate by using a time of midnight and a timezone of 0. \nAs a result, there are times when the database type returned have been timestamp. \n\nHSQLDB also does not support a true date type - it's date implementation uses a timestamp of \n0 (midnight), but has a bug that sets timezone. For this reason, if a date is stored from one \ntimezone (e.g., system default timezone), and a user attempts to query for that date from \nanother timezone, it may not find the value.  For this reason, HSQLDB is not recommended for \nLocalDate type data if you are working across time zones.\n\nTo work around old database implementations of LocalDate as a Timestamp, when a timestamp is\nreceived, the scale attribute is examined.  A scale of 0 on a Timestamp is always associated \nwith a LocalDate across all tested DB implementations; true timestamps always have a non-zero scale.  \nWhen a scale of zero is found, it is handled as LocalDate.\n\nMore information on specific implementations is available here: \n* Derby: https://db.apache.org/derby/papers/JDBCImplementation.html#Derby+SQL+DATE\n* HSQLDB: http://www.h2database.com/html/datatypes.html\n  * known problem:http://www.h2database.com/html/datatypes.html\n* Oracle: https://docs.oracle.com/javase/8/docs/api/java/sql/Timestamp.html\n\n#### Correct handling of java.math.BigDecimal\n\nSimilarly BigDecimal tries to maintain scale in a more intuitive manner\n(most drivers will revert to \"full precision\" meaning they pad the scale out to what the database column specifies).\n\n#### Simplified handling of CLOBs and BLOBs\n\nDeal with them explicitly as either String/byte[] or streams.\nNo downcasting or driver-specific APIs, and treat them the same as other parameters.\n\n#### Central control of instrumentation and logging\n\nTracks important metrics and logs to SLF4J in a way that is cleaner and\ngives you more control than having database-related logging scattered throughout your code.\nThe logging is customizable so you can choose to see substituted parameters as\nwell.\n\n```\nGet database: 393.282ms(getConn=389.948ms,checkAutoCommit=1.056ms,dbInit=2.273ms)\nDDL: 15.658ms(prep=8.017ms,exec=7.619ms,close=0.021ms) create table dbtest (a numeric)\nInsert: 71.295ms(prep=65.093ms,exec=6.153ms,close=0.048ms) insert into dbtest (a) values (?) ParamSql: insert into dbtest (a) values (23)\nQuery: 38.627ms(prep=27.642ms,exec=9.846ms,read=1.013ms,close=0.125ms) select count(1) from dbtest\n```\n\n#### Allocation of Primary Keys\n\nIt is often a good idea to generate primary keys from a sequence, but this is\nnot always easy to do in a clean and efficient way. This library provides a\nway to do it that will be efficient on advanced databases, and still be able to\ntransparently fall back to multiple database calls when necessary.\n\n```java\n  Long pk = db.toInsert(\"insert into t (pk,s) values (?,?)\")\n      .argPkSeq(\"pk_seq\")\n      .argString(\"Hi\")\n      .insertReturningPkSeq(\"pk\");\n```\n\nThis has a more general form for returning multiple columns. For example, if you\ninserted a database timestamp and need that value as well to update an object in memory:\n\n```java\n  db.toInsert(\"insert into t (pk,d,s) values (?,?,?)\")\n      .argPkSeq(\"pk_seq\")\n      .argDateNowPerDb()\n      .argString(\"Hi\")\n      .insertReturning(\"t\", \"pk\", rs -\u003e {\n          ...\n          if (rs.next()) {\n            ... = rs.getLongOrNull(1); // value of pk\n            ... = rs.getDateOrNull(2); // value of d\n          }\n          ...\n      }, \"d\");\n```\n\n#### Fluent API that is auto-completion friendly\n\nBuilt to make life easier in modern IDEs. Everything you need is accessed from a\nsingle interface (Database).\n\nMethods within the library have also been annotated to help IDEs like IntelliJ\nprovide better support. For example, it can warn you about checking nulls, or\nforgetting to use a return value on a fluent API. Try using the\n[error-prone](http://errorprone.info/) plugin and/or build tools in your project.\n\n#### Schema Definition and Creation\n\nYou can define your database schema using a simple Java API and execute the database specific DDL.\nWhen defining this schema you use the same basic Java types you use when querying, and appropriate\ndatabase-specific column types will be chosen such that data will round-trip correctly. This\nAPI also smooths over some syntax differences like sequence creation.\n\n```java\n  // Observe that this will work across the supported databases, with\n  // specific syntax and SQL types tuned for that database.\n  new Schema()\n      .addTable(\"t\")\n        .addColumn(\"pk\").primaryKey().table()\n        .addColumn(\"d\").asDate().table()\n        .addColumn(\"s\").asString(80).table().schema()\n      .addSequence(\"pk_seq\").schema().execute(db);\n```\n\n### Quick Examples\n\nBasic example including setup:\n\n```java\n  String url = \"jdbc:hsqldb:file:hsqldb;shutdown=true\";\n  DatabaseProvider.fromDriverManager(url).transact(dbs -\u003e {\n      Database db = dbs.get();\n      db.dropTableQuietly(\"t\");\n      new Schema().addTable(\"t\").addColumn(\"a\").asInteger().schema().execute(db);\n      db.toInsert(\"insert into t (a) values (?)\").argInteger(32).insert(1);\n      db.toUpdate(\"update t set a=:val\").argInteger(\"val\", 23).update();\n\n      Long rows = db.toSelect(\"select count(1) from t\").queryLongOrNull();\n      System.out.println(\"Rows: \" + rows);\n  });\n```\n\nNote the lack of error handling, resource management, and transaction calls. This\nis not because it is left as an exercise for the reader, but because it is handled\nautomatically.\n\nFor a more realistic server-side example, a container will usually manage creation\nof the Database or Supplier\u003cDatabase\u003e, and business layer code will declare a\ndependency on this:\n\n```java\npublic class MyBusiness {\n  private Supplier\u003cDatabase\u003e db;\n\n  public MyBusiness(Supplier\u003cDatabase\u003e db) {\n    this.db = db;\n  }\n\n  public Long doStuff(String data) {\n    if (isCached(data)) {\n      // Note how this might never allocate a database connection\n      return cached(data);\n    }\n    \n    return db.get().toSelect(\"select count(*) from a where b=:data\")\n             .argString(\"data\", data).queryLong();\n  }\n\n  // Note we use only java.util.Date, not java.sql.*\n  public List\u003cDate\u003e doMoreStuff(Date after) {\n    return db.get().toSelect(\"select my_date from a where b \u003e ?\")\n        .argDate(after).queryMany(r -\u003e rs.getDateOrNull(\"my_date\"));\n  }\n}\n```\n\nOf course there are also convenience methods for simple cases like\nthis having only one column in the result:\n\n```java\n  public List\u003cDate\u003e doMoreStuff(Date after) {\n    return db.get().toSelect(\"select my_date from a where b \u003e ?\")\n        .argDate(after).queryDates();\n  }\n```\n\n### Getting Started\n\nThe library is available in the public Maven repository:\n\n```\n\u003cdependency\u003e\n  \u003cgroupId\u003ecom.github.susom\u003c/groupId\u003e\n  \u003cartifactId\u003edatabase\u003c/artifactId\u003e\n  \u003cversion\u003e4.0\u003c/version\u003e\n\u003c/dependency\u003e\n```\n\nJust add that to your pom.xml, use one of the static builder methods on \n`com.github.susom.database.DatabaseProvider` (see example above), and enjoy!\n\nTo see more examples of how to use the library, take a look at\nsome of the tests:\n\n[CommonTest.java](https://github.com/susom/database/blob/master/src/test/java/com/github/susom/database/test/CommonTest.java)\n\nThere are also a variety of samples in [the demo directory](https://github.com/susom/database/tree/master/src/test/java/com/github/susom/database/example).\n\n### Database Support and Limitations\n\nThe functionality is currently tested with Oracle, PostgreSQL, HyperSQL (HSQLDB),\nSQL Server, and Derby. It won't work out of the box with other databases right now,\nbut might in the future. If you want pure Java, use HyperSQL rather than Derby. If\nyou are really into MySQL/MariaDB, take a look at the \"mysql\" branch, but be warned\nit is early stages and got a little stuck because of significant feature gaps in\nthat database (e.g. no sequences).  There is basic support for read-only usage\nof BigQuery using the Google-recommended \n[Magnitude Simba JDBC drivers](https://cloud.google.com/bigquery/providers/simba-drivers).\nThe BigQuery driver supports some DDL and DML statements, but BigQuery does not support\ntransactions, sequences, constraints, or indexes.\n\nThe library is compiled and tested with Java 8, so it won't work with Java 7 and earlier.\nIf you really must use Java 7, grab the latest 1.x release of this library.\n\nNo fancy things with results (e.g. scrolling or updating result sets).\n","funding_links":[],"categories":["Database Clients","数据库开发"],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsusom%2Fdatabase","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsusom%2Fdatabase","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsusom%2Fdatabase/lists"}