Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,19 +14,19 @@ permissions:

jobs:
test:
name: Maven Test (JDK 8)
name: Maven Verify (JDK 21)
runs-on: ubuntu-latest

steps:
- name: Check out repository
uses: actions/checkout@v4

- name: Set up JDK 8
- name: Set up JDK 21
uses: actions/setup-java@v4
with:
distribution: temurin
java-version: '8'
java-version: '21'
cache: maven

- name: Run tests
run: mvn --batch-mode --no-transfer-progress test
- name: Verify
run: mvn --batch-mode --no-transfer-progress verify
62 changes: 46 additions & 16 deletions README.MD
Original file line number Diff line number Diff line change
@@ -1,23 +1,53 @@
# Introduction
# SQL Hierarchy Models
[![CI](https://github.com/Kotryos/sql-hierarchy-models/actions/workflows/ci.yml/badge.svg)](https://github.com/Kotryos/sql-hierarchy-models/actions/workflows/ci.yml)

The Nested Set Model is an approach to storing hierarchical data structures in relational databases.
It is an alternative to more common Adjacency List Model.
The Nested Set Model has a clear advantage over Adjacency List Model for operations like finding path for a node, retrieving the whole tree, finding depth of a node etc.
For example, in Adjacency List it is impossible to find a path for a node with pure SQL without knowing the depth of that node.
On the other hand, create/delete operations in Nested Set Model are much more complicated, since these operations cause the recalculation of "left" and "right" values for the whole tree.
As a consequence to the aforementioned, the write lock on the table is necessary for create/delete, which would require a stored procedure or SERIALIZABLE isolation level (may lead to heavy performance problems).
This project compares relational models for hierarchical data in SQL databases.
The active codebase is being modernized around Java 21, Spring Boot, jOOQ, Flyway, JUnit 5, AssertJ, and Testcontainers.

Summing up, the Nested Set Model is suitable for systems where different reads are frequent while writes are not needed or usually occur in batch at predefined time.
The original 2016 Spring Data JPA nested-set demo is preserved in the [`legacy-2016`](https://github.com/Kotryos/sql-hierarchy-models/tree/legacy-2016) tag.

This project demonstrates Spring Data JPA powered implementation of different reads in the Nested Set Model.
## Current Model

# Implemented operations
The first modernized model is the Nested Set Model.
It stores each node with `lft` and `rgt` interval values and currently implements:

- retrieval of all leaf nodes
- path retrieval
- retrieval of child nodes for a node
- retrieval of a node depth
- retrieval of all leaf nodes
- path retrieval
- flattened subtree retrieval
- node depth retrieval

# How to run
Run `mvn test` from the project root (requires Maven 3).
The current integration tests use this tree:

```text
Vehicles [1, 18]
|-- Motor Vehicles [2, 11]
| |-- Cars [3, 8]
| | |-- Convertibles [4, 5]
| | `-- Minivans [6, 7]
| `-- Buses [9, 10]
`-- Rail Vehicles [12, 17]
|-- Trains [13, 14]
`-- Trams [15, 16]
```

## How To Run

Run the full verification build from the project root:

```shell
mvn verify
```

The current baseline uses Flyway migrations and Testcontainers-backed integration tests for PostgreSQL and MySQL.
Docker must be available to run `mvn verify`.

## jOOQ Code Generation

jOOQ table metadata is generated from the Flyway SQL migrations during the Maven `generate-sources` phase.
Generated files are written to `target/generated-sources/jooq` and are not committed.

Run code generation explicitly with:

```shell
mvn generate-sources
```
168 changes: 91 additions & 77 deletions pom.xml
Original file line number Diff line number Diff line change
@@ -1,114 +1,128 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.github.constantinet</groupId>
<artifactId>nested-set-model-with-spring-data-jpa-example</artifactId>
<version>1.0-SNAPSHOT</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.5.15</version>
<relativePath/>
</parent>

<groupId>dev.kotryos</groupId>
<artifactId>sql-hierarchy-models</artifactId>
<version>0.1.0-SNAPSHOT</version>
<name>sql-hierarchy-models</name>
<description>Comparing SQL hierarchy models with Spring Boot and jOOQ</description>

<properties>
<java.version>1.8</java.version>
<hsqldb.version>2.3.4</hsqldb.version>
<commons-dbcp2.version>2.1.1</commons-dbcp2.version>
<hibernate.version>5.1.2.Final</hibernate.version>
<spring-framework.version>4.3.3.RELEASE</spring-framework.version>
<spring-data-jpa.version>1.10.4.RELEASE</spring-data-jpa.version>
<lombok.version>1.16.8</lombok.version>
<junit.version>4.12</junit.version>
<java-hamcrest.version>2.0.0.0</java-hamcrest.version>
<spring-test-dbunit.version>1.3.0</spring-test-dbunit.version>
<dbunit.version>2.5.3</dbunit.version>
<java.version>21</java.version>
</properties>

<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.5.1</version>
<configuration>
<source>${java.version}</source>
<target>${java.version}</target>
</configuration>
</plugin>
</plugins>
</build>

<dependencies>
<dependency>
<groupId>org.hsqldb</groupId>
<artifactId>hsqldb</artifactId>
<version>${hsqldb.version}</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-dbcp2</artifactId>
<version>${commons-dbcp2.version}</version>
</dependency>
<dependency>
<groupId>org.hibernate</groupId>
<artifactId>hibernate-entitymanager</artifactId>
<version>${hibernate.version}</version>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jooq</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>${spring-framework.version}</version>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-core</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.data</groupId>
<artifactId>spring-data-jpa</artifactId>
<version>${spring-data-jpa.version}</version>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-database-postgresql</artifactId>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>${spring-framework.version}</version>
<groupId>org.flywaydb</groupId>
<artifactId>flyway-mysql</artifactId>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>${lombok.version}</version>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>${junit.version}</version>
<groupId>org.testcontainers</groupId>
<artifactId>junit-jupiter</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>java-hamcrest</artifactId>
<version>${java-hamcrest.version}</version>
<groupId>org.testcontainers</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>${spring-framework.version}</version>
<groupId>org.postgresql</groupId>
<artifactId>postgresql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.github.springtestdbunit</groupId>
<artifactId>spring-test-dbunit</artifactId>
<version>${spring-test-dbunit.version}</version>
<groupId>org.testcontainers</groupId>
<artifactId>mysql</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.dbunit</groupId>
<artifactId>dbunit</artifactId>
<version>${dbunit.version}</version>
<groupId>com.mysql</groupId>
<artifactId>mysql-connector-j</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<artifactId>junit</artifactId>
<groupId>junit</groupId>
</exclusion>
</exclusions>
</dependency>
</dependencies>

</project>
<build>
<plugins>
<plugin>
<groupId>org.jooq</groupId>
<artifactId>jooq-codegen-maven</artifactId>
<version>${jooq.version}</version>
<executions>
<execution>
<id>generate-jooq</id>
<phase>generate-sources</phase>
<goals>
<goal>generate</goal>
</goals>
</execution>
</executions>
<dependencies>
<dependency>
<groupId>org.jooq</groupId>
<artifactId>jooq-meta-extensions</artifactId>
<version>${jooq.version}</version>
</dependency>
</dependencies>
<configuration>
<generator>
<database>
<name>org.jooq.meta.extensions.ddl.DDLDatabase</name>
<properties>
<property>
<key>scripts</key>
<value>src/main/resources/db/migration/*.sql</value>
</property>
<property>
<key>sort</key>
<value>semantic</value>
</property>
<property>
<key>defaultNameCase</key>
<value>lower</value>
</property>
</properties>
</database>
<target>
<packageName>dev.kotryos.sqlhierarchy.jooq</packageName>
<directory>target/generated-sources/jooq</directory>
</target>
</generator>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>

This file was deleted.

Loading
Loading