Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Clarify documentation of Hibernate ORM dialects wrt database multi-tenancy #41761

Merged
merged 3 commits into from
Jul 10, 2024
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
1 change: 1 addition & 0 deletions docs/src/main/asciidoc/_attributes.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
:quickstarts-tree-url: ${quickstarts-base-url}/tree/main
// .
:hibernate-orm-docs-url: https://docs.jboss.org/hibernate/orm/{hibernate-orm-version-major-minor}/userguide/html_single/Hibernate_User_Guide.html
:hibernate-orm-dialect-docs-url: https://docs.jboss.org/hibernate/orm/{hibernate-orm-version-major-minor}/dialect/dialect.html
:hibernate-search-docs-url: https://docs.jboss.org/hibernate/search/{hibernate-search-version-major-minor}/reference/en-US/html_single/
// .
:amazon-services-guide: https://quarkiverse.github.io/quarkiverse-docs/quarkus-amazon-services/dev/index.html
Expand Down
127 changes: 86 additions & 41 deletions docs/src/main/asciidoc/hibernate-orm.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -84,17 +84,17 @@
[source,properties]
.Example `{config-file}`
----
# datasource configuration
quarkus.datasource.db-kind = postgresql
quarkus.datasource.db-kind = postgresql <1>
quarkus.datasource.username = hibernate
quarkus.datasource.password = hibernate
quarkus.datasource.jdbc.url = jdbc:postgresql://localhost:5432/hibernate_db

# drop and create the database at startup (use `update` to only update the schema)
quarkus.hibernate-orm.database.generation=drop-and-create
quarkus.hibernate-orm.database.generation=drop-and-create <2>
----
<1> xref:datasource.adoc[Configure the datasource].
<2> Drop and create the database at startup (use `update` to only update the schema).

Check warning on line 95 in docs/src/main/asciidoc/hibernate-orm.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Fluff] Depending on the context, consider using 'Be concise: rewrite the sentence to not use' rather than 'Note that'. Raw Output: {"message": "[Quarkus.Fluff] Depending on the context, consider using 'Be concise: rewrite the sentence to not use' rather than 'Note that'.", "location": {"path": "docs/src/main/asciidoc/hibernate-orm.adoc", "range": {"start": {"line": 95, "column": 77}}}, "severity": "INFO"}
yrodiere marked this conversation as resolved.
Show resolved Hide resolved

Note that these configuration properties are not the same ones as in your typical Hibernate ORM configuration file.

Check warning on line 97 in docs/src/main/asciidoc/hibernate-orm.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using 'because' or 'while' rather than 'as'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using 'because' or 'while' rather than 'as'.", "location": {"path": "docs/src/main/asciidoc/hibernate-orm.adoc", "range": {"start": {"line": 97, "column": 53}}}, "severity": "INFO"}
They will often map to Hibernate ORM configuration properties but could have different names and don't necessarily map 1:1 to each other.

Also, Quarkus will set many Hibernate ORM configuration settings automatically, and will often use more modern defaults.
Expand Down Expand Up @@ -227,9 +227,16 @@
quarkus.datasource.password = hibernate
quarkus.datasource.jdbc.url = jdbc:postgresql://localhost:26257/hibernate_db

quarkus.hibernate-orm.dialect=org.hibernate.dialect.CockroachDialect <1>
quarkus.hibernate-orm.dialect=Cockroach <1>
----
<1> Set the Hibernate ORM dialect.
+
For built-in dialects, the expected value is one of the names
in the link:{hibernate-orm-dialect-docs-url}[official list of dialects], *without* the `Dialect` suffix,
for example `Cockroach` for `CockroachDialect`.
+
For third-party dialects, the expected value is the fully-qualified class name,
for example `com.acme.hibernate.AcmeDbDialect`.

[WARNING]
====
Expand All @@ -249,12 +256,29 @@
quarkus.datasource.password = hibernate
quarkus.datasource.jdbc.url = jdbc:postgresql://localhost:26257/hibernate_db

quarkus.hibernate-orm.dialect=org.hibernate.dialect.CockroachDialect <2>
quarkus.hibernate-orm.dialect=Cockroach <2>
----
<1> Set the database version. The Hibernate ORM dialect will target that version.
Since we're targeting CockroachDB here, we're passing the CockroachDB version, not the PostgreSQL version.
<2> Set the Hibernate ORM dialect.

[[hibernate-dialect-varying-database]]
== Varying database

Check warning on line 266 in docs/src/main/asciidoc/hibernate-orm.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.SentenceLength] Try to keep sentences to an average of 32 words or fewer. Raw Output: {"message": "[Quarkus.SentenceLength] Try to keep sentences to an average of 32 words or fewer.", "location": {"path": "docs/src/main/asciidoc/hibernate-orm.adoc", "range": {"start": {"line": 266, "column": 11}}}, "severity": "INFO"}

When enabling <<database-approach,database multi-tenancy>>,
Hibernate ORM will use multiple datasources at runtime for the same persistence unit,
and by default Quarkus cannot tell which datasource is going to be used,

Check warning on line 270 in docs/src/main/asciidoc/hibernate-orm.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsSuggestions] Depending on the context, consider using ', which (non restrictive clause preceded by a comma)' or 'that (restrictive clause without a comma)' rather than 'which'. Raw Output: {"message": "[Quarkus.TermsSuggestions] Depending on the context, consider using ', which (non restrictive clause preceded by a comma)' or 'that (restrictive clause without a comma)' rather than 'which'.", "location": {"path": "docs/src/main/asciidoc/hibernate-orm.adoc", "range": {"start": {"line": 270, "column": 24}}}, "severity": "INFO"}
so it will not be able to detect a dialect to use in Hibernate ORM.

Check warning on line 271 in docs/src/main/asciidoc/hibernate-orm.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.SentenceLength] Try to keep sentences to an average of 32 words or fewer. Raw Output: {"message": "[Quarkus.SentenceLength] Try to keep sentences to an average of 32 words or fewer.", "location": {"path": "docs/src/main/asciidoc/hibernate-orm.adoc", "range": {"start": {"line": 271, "column": 59}}}, "severity": "INFO"}

For that reason, when enabling <<database-approach,database multi-tenancy>>,
it is recommended to explicitly point the Hibernate ORM configuration to one datasource
among those that will be used at runtime, e.g. with `quarkus.hibernate-orm.datasource=base`

Check warning on line 275 in docs/src/main/asciidoc/hibernate-orm.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'for example' rather than 'e.g.' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'for example' rather than 'e.g.' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/hibernate-orm.adoc", "range": {"start": {"line": 275, "column": 32}}}, "severity": "WARNING"}
(`base` being the name of a datasource).

When doing so, Quarkus will infer the database version and (if possible) dialect from that datasource.
For unsupported databases, you may still need to set the Hibernate ORM dialect explicitly,

Check warning on line 279 in docs/src/main/asciidoc/hibernate-orm.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'might (for possiblity)' or 'can (for ability)' rather than 'may' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/hibernate-orm.adoc", "range": {"start": {"line": 279, "column": 21}}}, "severity": "WARNING"}

Check warning on line 279 in docs/src/main/asciidoc/hibernate-orm.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'. Raw Output: {"message": "[Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'.", "location": {"path": "docs/src/main/asciidoc/hibernate-orm.adoc", "range": {"start": {"line": 279, "column": 31}}}, "severity": "INFO"}
as explained in <<hibernate-dialect-other-databases,this section>>.

[[hibernate-configuration-properties]]
=== Hibernate ORM configuration properties

Expand Down Expand Up @@ -1117,35 +1141,40 @@
=== Configuring the application

In general, it is not possible to use the Hibernate ORM database generation feature in conjunction with a multitenancy setup.
Therefore, you have to disable it, and you need to make sure that the tables are created per schema.

Check warning on line 1144 in docs/src/main/asciidoc/hibernate-orm.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'. Raw Output: {"message": "[Quarkus.Fluff] Depending on the context, consider using 'Rewrite the sentence, or use 'must', instead of' rather than 'need to'.", "location": {"path": "docs/src/main/asciidoc/hibernate-orm.adoc", "range": {"start": {"line": 1144, "column": 28}}}, "severity": "INFO"}

Check warning on line 1144 in docs/src/main/asciidoc/hibernate-orm.adoc

View workflow job for this annotation

GitHub Actions / Linting with Vale

[vale] reported by reviewdog 🐶 [Quarkus.TermsWarnings] Consider using 'verify' rather than 'make sure' unless updating existing content that uses the term. Raw Output: {"message": "[Quarkus.TermsWarnings] Consider using 'verify' rather than 'make sure' unless updating existing content that uses the term.", "location": {"path": "docs/src/main/asciidoc/hibernate-orm.adoc", "range": {"start": {"line": 1144, "column": 36}}}, "severity": "WARNING"}
The following setup will use the xref:flyway.adoc[Flyway] extension to achieve this goal.

[[schema-approach]]
==== SCHEMA approach

The same data source will be used for all tenants and a schema has to be created for every tenant inside that data source.
CAUTION: Some databases like MariaDB/MySQL do not support database schemas. In these cases you have to use the DATABASE approach below.

CAUTION: Some databases like MariaDB/MySQL do not support database schemas. In these cases you have to use the <<database-approach,database approach>>.

[source,properties]
----
# Disable generation
quarkus.hibernate-orm.database.generation=none
quarkus.hibernate-orm.database.generation=none <1>

# Enable SCHEMA approach and use default datasource
quarkus.hibernate-orm.multitenant=SCHEMA
# You could use a non-default datasource by using the following setting
# quarkus.hibernate-orm.datasource=other
quarkus.hibernate-orm.multitenant=SCHEMA <2>

# The default data source used for all tenant schemas
quarkus.datasource.db-kind=postgresql
quarkus.datasource.db-kind=postgresql <3>
quarkus.datasource.username=quarkus_test
quarkus.datasource.password=quarkus_test
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/quarkus_test

# Enable Flyway configuration to create schemas
quarkus.flyway.schemas=base,mycompany
quarkus.flyway.schemas=base,mycompany <4>
quarkus.flyway.locations=classpath:schema
quarkus.flyway.migrate-at-start=true
----
<1> Disable schema generation, because it is not supported by Hibernate ORM for schema multi-tenancy.
We'll use Flyway instead, see further down.
<2> Enable schema multi-tenancy.
+
We use the default datasource here, but could use a named datasource if we wanted to,
by following instructions <<multiple-persistence-units,there>>.
<3> xref:datasource.adoc[Configure the datasource].
<4> Configure xref:flyway.adoc[Flyway] for database initialization,
because schema generation by Hibernate ORM is not supported in this case.

Here is an example of the Flyway SQL (`V1.0.0__create_fruits.sql`) to be created in the configured folder `src/main/resources/schema`.

Expand Down Expand Up @@ -1174,44 +1203,62 @@
INSERT INTO mycompany.known_fruits(id, name) VALUES (3, 'Blackberries');
----



[[database-approach]]
==== DATABASE approach

For every tenant you need to create a named data source with the same identifier that is returned by the `TenantResolver`.

[CAUTION]
====
// Related to https://github.com/quarkusio/quarkus/issues/11861
With this approach, all datasources used by the same persistence unit
are assumed to point to a database of the same vendor (same `db-kind`) and version.

Mismatches will not be detected, and may result in unpredictable behavior.
====

[source,properties]
----
# Disable generation
quarkus.hibernate-orm.database.generation=none
quarkus.hibernate-orm.database.generation=none <1>

# Enable DATABASE approach
quarkus.hibernate-orm.multitenant=DATABASE
quarkus.hibernate-orm.multitenant=DATABASE <2>
quarkus.hibernate-orm.datasource=base <3>

# Default tenant 'base'
quarkus.datasource.base.db-kind=postgresql
quarkus.datasource.base.db-kind=postgresql <4>
quarkus.datasource.base.username=quarkus_test
quarkus.datasource.base.password=quarkus_test
quarkus.datasource.base.jdbc.url=jdbc:postgresql://localhost:5432/quarkus_test
quarkus.flyway.base.locations=classpath:database/base <5>
quarkus.flyway.base.migrate-at-start=true

# Tenant 'mycompany'
quarkus.datasource.mycompany.db-kind=postgresql
quarkus.datasource.mycompany.db-kind=postgresql <6>
quarkus.datasource.mycompany.username=mycompany
quarkus.datasource.mycompany.password=mycompany
quarkus.datasource.mycompany.jdbc.url=jdbc:postgresql://localhost:5433/mycompany

# Flyway configuration for the default datasource
quarkus.flyway.locations=classpath:database/default
quarkus.flyway.migrate-at-start=true

# Flyway configuration for the mycompany datasource
quarkus.flyway.mycompany.locations=classpath:database/mycompany
quarkus.flyway.mycompany.locations=classpath:database/mycompany <7>
quarkus.flyway.mycompany.migrate-at-start=true
----
<1> Disable schema generation, because it is not supported by Hibernate ORM for database multi-tenancy.
We'll use Flyway instead, see further down.
<2> Enable database multi-tenancy.
<3> Select a datasource for the persistence unit.
+
This is only to allow Quarkus to determine the Hibernate ORM dialect to use;
see <<hibernate-dialect-varying-database,this section>> for details.
<4> xref:datasource.adoc[Configure the datasource] for one tenant, `base`.
<5> Configure xref:flyway.adoc[Flyway] for database initialization for tenant `base`,
because schema generation by Hibernate ORM is not supported in this case.
<6> xref:datasource.adoc[Configure the datasource] for another tenant.
+
There could be more tenants, but here we're stopping at two.
<7> Configure xref:flyway.adoc[Flyway] for database initialization for tenant `mycompany`,
because schema generation by Hibernate ORM is not supported in this case.

Following are examples of the Flyway SQL files to be created in the configured folder `src/main/resources/database`.

Default schema (`src/main/resources/database/default/V1.0.0__create_fruits.sql`):
Schema for tenant `base` (`src/main/resources/database/base/V1.0.0__create_fruits.sql`):

[source,sql]
----
Expand All @@ -1227,7 +1274,7 @@
INSERT INTO known_fruits(id, name) VALUES (3, 'Banana');
----

Mycompany schema (`src/main/resources/database/mycompany/V1.0.0__create_fruits.sql`):
Schema for tenant `mycompany` (`src/main/resources/database/mycompany/V1.0.0__create_fruits.sql`):

[source,sql]
----
Expand All @@ -1243,25 +1290,23 @@
INSERT INTO known_fruits(id, name) VALUES (3, 'Blackberries');
----



[[discriminator-approach]]
==== DISCRIMINATOR approach

The default data source will be used for all tenants. All entities defining a field annotated with `@TenantId` will have that field populated automatically, and will get filtered automatically in queries.


[source,properties]
----
# Enable DISCRIMINATOR approach
quarkus.hibernate-orm.multitenant=DISCRIMINATOR
quarkus.hibernate-orm.multitenant=DISCRIMINATOR <1>

# The default data source used for all tenant schemas
quarkus.datasource.db-kind=postgresql
quarkus.datasource.db-kind=postgresql <2>
quarkus.datasource.username=quarkus_test
quarkus.datasource.password=quarkus_test
quarkus.datasource.jdbc.url=jdbc:postgresql://localhost:5432/quarkus_test
----

<1> Enable discriminator multi-tenancy.
<2> xref:datasource.adoc[Configure the datasource].

=== Programmatically Resolving Tenants Connections

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -276,26 +276,28 @@ default boolean isAnyPropertySet() {
interface HibernateOrmConfigPersistenceUnitDialect {

/**
* Class name of the Hibernate ORM dialect.
* Name of the Hibernate ORM dialect.
*
* The complete list of bundled dialects is available in the
* https://docs.jboss.org/hibernate/stable/orm/javadocs/org/hibernate/dialect/package-summary.html[Hibernate ORM
* JavaDoc].
*
* Setting the dialect directly is only recommended as a last resort:
* most popular databases have a corresponding Quarkus extension,
* allowing Quarkus to select the dialect automatically,
* in which case you do not need to set the dialect at all,
* though you may want to set
* xref:datasource.adoc#quarkus-datasource_quarkus.datasource.db-version[`quarkus.datasource.db-version`] as
* high as possible
* For xref:datasource.adoc#extensions-and-database-drivers-reference[supported databases],
* this property does not need to be set explicitly:
* it is selected automatically based on the datasource,
* and configured using the xref:datasource.adoc#quarkus-datasource_quarkus.datasource.db-version[DB version set on the
* datasource]
* to benefit from the best performance and latest features.
*
* If your database does not have a corresponding Quarkus extension,
* you will need to set the dialect directly.
* you *will* need to set this property explicitly.
* In that case, keep in mind that the JDBC driver and Hibernate ORM dialect
* may not work properly in GraalVM native executables.
*
* For built-in dialects, the expected value is one of the names
* in the link:{hibernate-orm-dialect-docs-url}[official list of dialects],
* *without* the `Dialect` suffix,
* for example `Cockroach` for `CockroachDialect`.
*
* For third-party dialects, the expected value is the fully-qualified class name,
* for example `com.acme.hibernate.AcmeDbDialect`.
*
* @asciidoclet
*/
@WithParentName
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ quarkus.hibernate-orm.database.generation=none
quarkus.hibernate-orm.multitenant=database
# Necessary because we're creating datasources dynamically,
# which means the extension can't rely on a static datasource to guess the dialect
quarkus.hibernate-orm.dialect=org.hibernate.dialect.MariaDBDialect
quarkus.hibernate-orm.dialect=MariaDB
yrodiere marked this conversation as resolved.
Show resolved Hide resolved
quarkus.hibernate-orm.packages=io.quarkus.it.hibernate.multitenancy.fruit

# We create datasources manually, so a lack of configuration doesn't mean Quarkus should step in with defaults.
Expand All @@ -12,7 +12,7 @@ quarkus.datasource.devservices.enabled=false
# Inventory persistence unit
quarkus.hibernate-orm."inventory".database.generation=none
quarkus.hibernate-orm."inventory".multitenant=database
quarkus.hibernate-orm."inventory".dialect=org.hibernate.dialect.MariaDBDialect
quarkus.hibernate-orm."inventory".dialect=MariaDB
quarkus.hibernate-orm."inventory".packages=io.quarkus.it.hibernate.multitenancy.inventory

#mariadb.base_url is set through Maven config
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ quarkus.hibernate-orm.database.generation=none
quarkus.hibernate-orm.multitenant=database
# Necessary because we're creating datasources dynamically,
# which means the extension can't rely on a static datasource to guess the dialect
quarkus.hibernate-orm.dialect=org.hibernate.dialect.MariaDBDialect
quarkus.hibernate-orm.dialect=MariaDB
quarkus.hibernate-orm.packages=io.quarkus.it.hibernate.multitenancy.fruit

# We create datasources manually, so a lack of configuration doesn't mean Quarkus should step in with defaults.
Expand All @@ -12,7 +12,7 @@ quarkus.datasource.devservices.enabled=false
# Inventory persistence unit
quarkus.hibernate-orm."inventory".database.generation=none
quarkus.hibernate-orm."inventory".multitenant=database
quarkus.hibernate-orm."inventory".dialect=org.hibernate.dialect.MariaDBDialect
quarkus.hibernate-orm."inventory".dialect=MariaDB
quarkus.hibernate-orm."inventory".packages=io.quarkus.it.hibernate.multitenancy.inventory

#mariadb.base_url is set through Maven config
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL5Dialect
spring.jpa.properties.hibernate.dialect=MySQL5
spring.jpa.open-in-view=false
spring.jpa.hibernate.naming.physical-strategy=org.springframework.boot.orm.jpa.hibernate.SpringPhysicalNamingStrategy
spring.jpa.hibernate.naming.implicit-strategy=org.springframework.boot.orm.jpa.hibernate.SpringImplicitNamingStrategy
Expand Down
Loading