{"id":13591552,"url":"https://github.com/michael-simons/bootiful-databases","last_synced_at":"2025-07-16T23:36:28.551Z","repository":{"id":60576467,"uuid":"90707802","full_name":"michael-simons/bootiful-databases","owner":"michael-simons","description":null,"archived":false,"fork":false,"pushed_at":"2022-10-01T13:53:08.000Z","size":16211,"stargazers_count":23,"open_issues_count":0,"forks_count":2,"subscribers_count":2,"default_branch":"master","last_synced_at":"2025-05-03T08:25:37.662Z","etag":null,"topics":["demo","jooq","spring-boot","sql","talk"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"other","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/michael-simons.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE.txt","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-05-09T05:57:44.000Z","updated_at":"2025-05-01T15:46:12.000Z","dependencies_parsed_at":"2022-10-01T16:10:28.738Z","dependency_job_id":null,"html_url":"https://github.com/michael-simons/bootiful-databases","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michael-simons%2Fbootiful-databases","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michael-simons%2Fbootiful-databases/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michael-simons%2Fbootiful-databases/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/michael-simons%2Fbootiful-databases/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/michael-simons","download_url":"https://codeload.github.com/michael-simons/bootiful-databases/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":252535159,"owners_count":21763907,"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":["demo","jooq","spring-boot","sql","talk"],"created_at":"2024-08-01T16:00:59.041Z","updated_at":"2025-05-05T16:42:18.568Z","avatar_url":"https://github.com/michael-simons.png","language":"JavaScript","funding_links":[],"categories":["JavaScript"],"sub_categories":[],"readme":"# Demos for the talk \"Bootiful database-centric applications with jOOQ\"\n\n**Important:** _The commits and commit messages have been written very carefully to guide you through reading this project and understanding the talk. Please take your time to study them if you don't understand something_.\n\n## Slides\n\nSlides are on speaker deck: [Michael Simons](https://speakerdeck.com/michaelsimons)\n\n## Abstract\n\nThe basic idea of this demo is to create a \"cloud native\" app based on [Spring Boot](http://projects.spring.io/spring-boot) and then using [jOOQ](http://www.jooq.org) as \"database first\", SQL-centric approach to the database.\n\nFunctional wise the application deals with a simple database model storing the names, artists, genres and albums of tracks I listened to the last years. Those data in full comes from my daily foto project [Daily Fratze](https://dailyfratze.de) that I've been running this year for more than 12 years.\n\nThe application demonstrates the value of jOOQ when it comes to analysis of data, an area for which ORMs like hibernate weren't designed (see comment by Gavin King on [\"What ORMs have taught me: just learn SQL\"](https://www.reddit.com/r/programming/comments/2cnw8x/what_orms_have_taught_me_just_learn_sql/cjheyec). If you just deal with simple inserts, updates and deletes during OLTP, you're mostly fine using ORMs like JPA, even problems like the _n+1_ query problems are known and often solved.\n\nBut if you want to use powerful, analytic functions or have to deal with a database model that is a less than optimal fit for an ORM, than jOOQ will help you.\n\njOOQ is one of several quite different technologies to access relational data from Java based applications. \n\njOOQ is short for \"Java object oriented querying\" and describes a query builder framework that takes a look at your database schema, independent wether you use an Open Source database like PostgreSQL or an commercial product like Oracle Database., and provides you with a domain specific language (DSL) for generating statements.\n\njOOQs goal is explicitly not to shield the user from SQL but providing a type safe way to use it.\n\nLearn in this session who you can facilitate the \"magic\" of Spring Boot to provide jOOQ with needed resources and then use it to publish advanced analytic queries as HTTP apis.\n\nAlong the way you learn how automatic database migrations help you to do continuous delivery even with database centric applications.\n\n## Sample requests\n\nPart of the application is in actual use (the schema and some of the queries) at [Daily Fratze](https://dailyfratze.de). You can implement your own scrobble application. This version supports the following requests for now\n\n```\n## Get all artists\ncurl -X \"GET\" \"http://127.0.0.1:8080/api/artists\"\n```\n\n```\n## Get top n albums by some artists\ncurl -X \"GET\" \"http://127.0.0.1:8080/api/artists/54,86/topNAlbums\"\n```\n\n```\n## Get top n tracks by some artists Duplicate\ncurl -X \"GET\" \"http://127.0.0.1:8080/api/artists/54,86/topNTracks\"\n```\n\n```\n## Get cumulative plays by some artists\ncurl -X \"GET\" \"http://127.0.0.1:8080/api/artists/54,86/cumulativePlays\"\n```\n\n```\n## Get charts for a month\ncurl -X \"GET\" \"http://127.0.0.1:8080/api/charts/2016/5?n=40\"\n```\n\n## Creating and running the demo\n\nThe project needs a running PostgreSQL database providing a user `bootiful-databases` with the same password.\n\nThe project provides the docker-maven-plugin, that creates a container providing PostgreSQL database. If you have Docker installed for your system, you can run everything with\n\n```\n./mvnw docker:start spring-boot:run\n```\n\nand access the application at [http://localhost:8080](http://localhost:8080).\n\n## Ressourcen\n\n* [Spring Initializr](http://start.spring.io)\n* [jOOQ](http://www.jooq.org)\n* [Modern SQL](https://modern-sql.com)\n* [Java, SQL and jOOQ](https://blog.jooq.org)\n* [Vlad on Hibernate](https://vladmihalcea.com/tutorials/hibernate/)\n* [Thoughts on Java](http://www.thoughts-on-java.org/persistence/)\n* [Flyway by Boxfuse](https://flywaydb.org)\n* [Spring Data JPA](http://projects.spring.io/spring-data-jpa/)\n* [Repository Pattern](http://martinfowler.com/eaaCatalog/repository.html)\n\n\n## Fun\n\nA work of art, done by a madmen, this post by [Lukas Eder](https://twitter.com/lukaseder) [\"How to Plot an ASCII Bar Chart with SQL\"](https://blog.jooq.org/how-to-plot-an-ascii-bar-chart-with-sql/).\nWith the dataset available in this repository, you can plot a chart of how often I listened to Queen in 2016:\n\n```\n                                          chart                                           \n------------------------------------------------------------------------------------------\n 45.0000000 |                                              ##                           \n 42.0000000 |                                              ##                           \n 39.0000000 |                                              ##                           \n 36.0000000 |                                              ##          ##               \n 33.0000000 |                                              ##          ##               \n 30.0000000 |                                              ##          ##               \n 27.0000000 |                                              ##          ##               \n 24.0000000 |                                              ##          ##               \n 21.0000000 |                   ##                         ##          ##               \n 18.0000000 |                   ##                         ##          ##               \n 15.0000000 | ##    ##    ##    ##             ##          ##          ##               \n 12.0000000 | ##    ##    ##    ##             ##          ##          ##               \n 9.00000000 | ##    ##    ##    ##             ##       ## ##          ##               \n 6.00000000 | ##    ##    ##    ##          ## ##       ## ## ##       ##               \n 3.00000000 | ##    ##    ##    ##          ## ##       ## ## ##    ## ##               \n -----------+----------------------------------------------------------------------------\n            | 2016-01-13 00:00:00                                     2016-06-30 00:00:00\n(17 rows)\n```\n\nwith this query:\n\n\n```\n-- The example uses https://www.jooq.org/sakila, but you can just replace\n-- the \"source\" table with anything else\nwith\n \n  -- This part is what you can modify to adapt to your own needs\n  --------------------------------------------------------------\n \n  -- Your data producing query here \n  source (key, value) as (\n    select p.played_on::date::timestamp, count(*)\n    from plays p join tracks t on t.id = p.track_id join artists a on a.id = t.artist_id\n    where a.artist = 'Queen'\n    group by p.played_on::date::timestamp\n  ),\n   \n  -- Some configuration items:\n  constants as (\n    select\n     \n      -- the height of the y axis\n      15 as height, \n \n      -- the width of the x axis, if normalise_x, otherwise, ignored\n      25 as width, \n \n      -- the bar characters\n      '##' as characters,\n \n      -- the characters between bars\n      ' ' as separator,\n       \n      -- the padding of the labels on the y axis\n      10 as label_pad, \n       \n      -- whether to normalise the data on the x axis by\n      -- - filling gaps (if int, bigint, numeric, timestamp, \n      --   timestamptz)\n      -- - scaling the x axis to \"width\"\n      true as normalise_x\n  ),\n   \n  -- The rest doesn't need to be touched\n  --------------------------------------\n   \n  -- Pre-calculated dimensions of the source data\n  source_dimensions (kmin, kmax, kstep, vmin, vmax) as (\n    select\n      min(key), max(key), \n      (max(key) - min(key)) / max(width), \n      min(value), max(value)\n    from source, constants\n  ),\n   \n  -- Normalised data, which fills the gaps in case the key data\n  -- type can be generated with generate_series (int, bigint, \n  -- numeric, timestamp, timestamptz)\n  source_normalised (key, value) as (\n    select k, coalesce(sum(source.value), 0)\n    from source_dimensions\n      cross join constants\n      cross join lateral \n        generate_series(kmin, kmax, kstep) as t (k)\n      left join source \n        on source.key \u003e= t.k and source.key \u003c t.k + kstep\n    group by k\n  ),\n \n  -- Replace source_normalised by source if you don't like the \n  -- normalised version\n  actual_source (i, key, value) as (\n    select row_number() over (order by key), key, value \n    from source_normalised, constants\n    where normalise_x\n    union all\n    select row_number() over (order by key), key, value\n    from source, constants\n    where not normalise_x\n  ),\n     \n  -- Pre-calculated dimensions of the actual data\n  actual_dimensions (\n    kmin, kmax, kstep, vmin, vmax, width_or_count\n  ) as (\n    select\n      min(key), max(key), \n      (max(key) - min(key)) / max(width), \n      min(value), max(value), \n      case\n        when every(normalise_x) then least(max(width), count(*)::int) \n        else count(*)::int\n      end\n    from actual_source, constants\n  ),\n   \n  -- Additional convenience\n  dims_and_consts as (\n    with\n      temp as (\n        select *, \n        (length(characters) + length(separator)) \n          * width_or_count as bar_width\n      from actual_dimensions, constants\n    )\n    select *,\n      (bar_width - length(kmin::text) - length(kmax::text)) \n        as x_label_pad\n    from temp\n  ),\n   \n  -- A cartesian product for all (x, y) data points\n  x (x) as (\n    select generate_series(1, width_or_count) from dims_and_consts\n  ),\n  y (y) as (\n    select generate_series(1, height) from dims_and_consts\n  ),\n \n  -- Rendering the ASCII chart\n  chart (rn, chart) as (\n    select\n      y,\n      lpad(y * (vmax - vmin) / height || '', label_pad) \n        || ' | '\n        || string_agg(\n             case\n               when height * actual_source.value / (vmax - vmin) \n                 \u003e= y then characters \n               else repeat(' ', length(characters)) \n             end, separator \n             order by x\n           )\n    from\n      x left join actual_source on actual_source.i = x, \n      y, dims_and_consts\n    group by y, vmin, vmax, height, label_pad\n    union all\n    select\n      0, \n      repeat('-', label_pad) \n        || '-+-'\n        || repeat('-', bar_width)\n    from dims_and_consts\n    union all\n    select\n      -1, \n      repeat(' ', label_pad) \n        || ' | '\n        || case\n             when x_label_pad \u003c 1 then ''\n             else kmin || repeat(' ', x_label_pad) || kmax \n           end\n    from dims_and_consts\n  )\nselect chart\nfrom chart\norder by rn desc\n;\n```\n\n\n## License\n\n\u003ca rel=\"license\" href=\"http://creativecommons.org/licenses/by-nc-sa/4.0/\"\u003e\u003cimg alt=\"Creative Commons Lizenzvertrag\" style=\"border-width:0\" src=\"https://i.creativecommons.org/l/by-nc-sa/4.0/88x31.png\" /\u003e\u003c/a\u003e\u003cbr /\u003e\u003cspan xmlns:dct=\"http://purl.org/dc/terms/\" property=\"dct:title\"\u003e\"Bootiful database-centric applications with jOOQ\"\u003c/span\u003e von \u003ca xmlns:cc=\"http://creativecommons.org/ns#\" href=\"https://github.com/michael-simons/DOAG2016\" property=\"cc:attributionName\" rel=\"cc:attributionURL\"\u003eMichael J. Simons\u003c/a\u003e ist lizenziert unter einer \u003ca rel=\"license\" href=\"http://creativecommons.org/licenses/by-nc-sa/4.0/\"\u003eCreative Commons Namensnennung - Nicht-kommerziell - Weitergabe unter gleichen Bedingungen 4.0 International Lizenz\u003c/a\u003e.","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmichael-simons%2Fbootiful-databases","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fmichael-simons%2Fbootiful-databases","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fmichael-simons%2Fbootiful-databases/lists"}