Skip to content

Commit 55cdf3e

Browse files
Merge branch 'main' into HIBERNATE-35-batch-processing
# Conflicts: # src/main/java/com/mongodb/hibernate/jdbc/MongoConnectionProvider.java # src/main/java/com/mongodb/hibernate/jdbc/MongoPreparedStatement.java # src/test/java/com/mongodb/hibernate/jdbc/MongoConnectionTests.java # src/test/java/com/mongodb/hibernate/jdbc/MongoPreparedStatementTests.java # src/test/resources/hibernate.properties
2 parents 225aaf6 + 392ac28 commit 55cdf3e

38 files changed

+1114
-388
lines changed

README.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,26 @@
1-
# A MongoDB Dialect for the Hibernate ORM
1+
# MongoDB extension of Hibernate ORM
22

3-
This project aims to provide a library to seamlessly integrate MongoDB with Hibernate ORM. Hibernate _ORM_ is a powerful **O**bject-**r**elational **m**apping tool. Due to the SQL and JDBC standards, Hibernate ORM could centralize each SQL vendor's idiosyncrasies in the so-called _Hibernate Dialect_. This project will include a document database member in the Hibernate's Dialect family.
3+
This product enables applications to use databases managed by the [MongoDB](https://www.mongodb.com/) DBMS
4+
via [Hibernate ORM](https://hibernate.org/orm/).
45

56
## Overview
67

7-
MongoDB speaks _MQL_ (**M**ongoDB **Q**uery **L**anguage in JSON format) instead of SQL. This project creates a MongoDB Hibernate Dialect by:
8+
MongoDB speaks MQL (**M**ongoDB **Q**uery **L**anguage) instead of SQL. This product works by:
89

9-
- Creating a JDBC adapter using [MongoDB Java Driver](https://www.mongodb.com/docs/drivers/java-drivers/)
10-
- Translating Hibernate's internal SQL AST into MQL
10+
- Creating a JDBC adapter using [MongoDB Java Driver](https://www.mongodb.com/docs/drivers/java-drivers/),
11+
which has to be plugged into Hibernate ORM via a custom [`ConnectionProvider`](https://docs.jboss.org/hibernate/orm/6.6/javadocs/org/hibernate/engine/jdbc/connections/spi/ConnectionProvider.html).
12+
- Translating Hibernate's internal SQL AST into MQL by means of a custom [`Dialect`](https://docs.jboss.org/hibernate/orm/6.6/javadocs/org/hibernate/dialect/Dialect.html),
13+
which has to be plugged into Hibernate ORM.
1114

12-
<img src="mongodb_dialect.png" alt="MongoDB Dialect" />
15+
<img src="mongodb_dialect.png" alt="MongoDB extension" />
1316

1417
## Development
1518

1619
Java 17 is the JDK version for development.
1720

1821
Initially Hibernate ORM v6.6 is the dependency version.
1922

20-
MongoDB v6 is the minimal version this dialect supports.
23+
MongoDB v6.0 is the minimal version this product supports.
2124

2225
> [Standalone instance](https://www.mongodb.com/docs/manual/reference/glossary/#std-term-standalone) is not supported. It is recommended to [convert it to a replica set](https://www.mongodb.com/docs/manual/tutorial/convert-standalone-to-replica-set/).
2326

build.gradle.kts

Lines changed: 32 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,10 +18,12 @@ import com.diffplug.spotless.FormatterFunc
1818
import com.diffplug.spotless.FormatterStep
1919
import java.io.Serializable
2020
import net.ltgt.gradle.errorprone.errorprone
21+
import org.gradle.api.tasks.testing.logging.TestLogEvent
2122

2223
version = "1.0.0-SNAPSHOT"
2324

2425
plugins {
26+
idea
2527
`java-library`
2628
alias(libs.plugins.spotless)
2729
alias(libs.plugins.errorprone)
@@ -32,10 +34,8 @@ repositories { mavenCentral() }
3234

3335
java { toolchain { languageVersion = JavaLanguageVersion.of(17) } }
3436

35-
tasks.named<Test>("test") { useJUnitPlatform() }
36-
3737
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
38-
// Integration Tests
38+
// Integration Test
3939

4040
sourceSets {
4141
create("integrationTest") {
@@ -44,29 +44,37 @@ sourceSets {
4444
}
4545
}
4646

47-
val integrationTestImplementation by configurations.getting { extendsFrom(configurations.implementation.get()) }
48-
val integrationTestRuntimeOnly: Configuration by configurations.getting
47+
val integrationTestSourceSet: SourceSet = sourceSets["integrationTest"]
4948

50-
configurations["integrationTestRuntimeOnly"].extendsFrom(configurations.runtimeOnly.get())
49+
val integrationTestImplementation: Configuration by
50+
configurations.getting { extendsFrom(configurations.implementation.get()) }
51+
val integrationTestRuntimeOnly: Configuration by
52+
configurations.getting { extendsFrom(configurations.runtimeOnly.get()) }
5153

52-
val integrationTest =
54+
val integrationTestTask: Task =
5355
task<Test>("integrationTest") {
54-
description = "Runs integration tests requiring external MongoDB deployment"
55-
group = "verification"
56+
group = LifecycleBasePlugin.VERIFICATION_GROUP
57+
testClassesDirs = integrationTestSourceSet.output.classesDirs
58+
classpath = integrationTestSourceSet.runtimeClasspath
59+
}
5660

57-
testClassesDirs = sourceSets["integrationTest"].output.classesDirs
58-
classpath = sourceSets["integrationTest"].runtimeClasspath
59-
shouldRunAfter("test")
61+
tasks.check { dependsOn(integrationTestTask) }
6062

61-
useJUnitPlatform()
63+
tasks.withType<Test>().configureEach {
64+
useJUnitPlatform()
65+
testLogging { events(TestLogEvent.PASSED, TestLogEvent.SKIPPED, TestLogEvent.FAILED) }
66+
}
6267

63-
testLogging { events("passed") }
68+
// https://youtrack.jetbrains.com/issue/IDEA-234382/Gradle-integration-tests-are-not-marked-as-test-sources-resources
69+
idea {
70+
module {
71+
testSources.from(integrationTestSourceSet.allSource.srcDirs)
72+
testResources.from(integrationTestSourceSet.resources.srcDirs)
6473
}
65-
66-
tasks.check { dependsOn(integrationTest) }
74+
}
6775

6876
// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
69-
// Static Analysis Tasks
77+
// Static Analysis
7078

7179
spotless {
7280
java {
@@ -139,7 +147,7 @@ class MultilineFormatter : Serializable {
139147
}
140148

141149
tasks.withType<JavaCompile>().configureEach {
142-
options.compilerArgs.addAll(listOf("-Xlint:deprecation", "-Werror"))
150+
options.compilerArgs.addAll(listOf("-Xlint:all", "-Werror"))
143151
if (name == "compileJava") {
144152
options.errorprone {
145153
disableWarningsInGeneratedCode.set(true)
@@ -165,20 +173,25 @@ buildConfig {
165173

166174
dependencies {
167175
testImplementation(libs.junit.jupiter)
176+
testImplementation(libs.assertj)
168177
testImplementation(libs.logback.classic)
169178
testImplementation(libs.mockito.junit.jupiter)
170179
testRuntimeOnly(libs.junit.platform.launcher)
171180

172181
integrationTestImplementation(libs.junit.jupiter)
182+
integrationTestImplementation(libs.assertj)
173183
integrationTestImplementation(libs.logback.classic)
184+
185+
@Suppress("UnstableApiUsage")
174186
integrationTestImplementation(libs.hibernate.testing) {
175187
exclude(group = "org.apache.logging.log4j", module = "log4j-core")
176188
}
189+
177190
integrationTestRuntimeOnly(libs.junit.platform.launcher)
178191

179-
errorprone(libs.nullaway)
180192
api(libs.jspecify)
181193

194+
errorprone(libs.nullaway)
182195
errorprone(libs.google.errorprone.core)
183196

184197
implementation(libs.hibernate.core)

gradle/libs.versions.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33

44
[versions]
55
junit-jupiter = "5.11.4"
6+
assertj = "3.27.3"
67
spotless = "7.0.2"
78
palantir = "2.50.0"
89
ktfmt = "0.54"
@@ -20,6 +21,7 @@ buildconfig = "5.5.1"
2021
[libraries]
2122
junit-jupiter = { module = "org.junit.jupiter:junit-jupiter", version.ref = "junit-jupiter" }
2223
junit-platform-launcher = { module = "org.junit.platform:junit-platform-launcher" }
24+
assertj = { module = "org.assertj:assertj-core", version.ref = "assertj" }
2325
nullaway = { module = "com.uber.nullaway:nullaway", version.ref = "nullaway" }
2426
jspecify = { module = "org.jspecify:jspecify", version.ref = "jspecify" }
2527
google-errorprone-core = { module = "com.google.errorprone:error_prone_core", version.ref = "google-errorprone-core" }

src/integrationTest/java/com/mongodb/hibernate/BasicInsertionTests.java renamed to src/integrationTest/java/com/mongodb/hibernate/BasicInsertionIntegrationTests.java

Lines changed: 15 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@
1717
package com.mongodb.hibernate;
1818

1919
import static org.assertj.core.api.Assertions.assertThat;
20-
import static org.hibernate.cfg.JdbcSettings.JAKARTA_JDBC_URL;
20+
import static org.assertj.core.api.InstanceOfAssertFactories.LIST;
2121

22-
import com.mongodb.ConnectionString;
23-
import com.mongodb.MongoClientSettings;
2422
import com.mongodb.client.MongoClients;
2523
import com.mongodb.client.MongoCollection;
24+
import com.mongodb.hibernate.internal.cfg.MongoConfiguration;
25+
import com.mongodb.hibernate.internal.cfg.MongoConfigurationBuilder;
2626
import jakarta.persistence.Column;
2727
import jakarta.persistence.Embeddable;
2828
import jakarta.persistence.Entity;
@@ -32,19 +32,24 @@
3232
import java.util.List;
3333
import java.util.function.Consumer;
3434
import org.bson.BsonDocument;
35-
import org.hibernate.cfg.Configuration;
3635
import org.hibernate.testing.orm.junit.DomainModel;
3736
import org.hibernate.testing.orm.junit.SessionFactory;
3837
import org.hibernate.testing.orm.junit.SessionFactoryScope;
3938
import org.junit.jupiter.api.BeforeEach;
4039
import org.junit.jupiter.api.Test;
4140

4241
@SessionFactory(exportSchema = false)
43-
@DomainModel(annotatedClasses = {BasicInsertionTests.Book.class, BasicInsertionTests.BookWithEmbeddedField.class})
44-
class BasicInsertionTests {
42+
@DomainModel(
43+
annotatedClasses = {
44+
BasicInsertionIntegrationTests.Book.class,
45+
BasicInsertionIntegrationTests.BookWithEmbeddedField.class
46+
})
47+
class BasicInsertionIntegrationTests {
48+
private static MongoConfiguration config;
4549

4650
@BeforeEach
47-
void setUp() {
51+
void setUp(SessionFactoryScope scope) {
52+
config = new MongoConfigurationBuilder(scope.getSessionFactory().getProperties()).build();
4853
onMongoCollection(MongoCollection::drop);
4954
}
5055

@@ -115,12 +120,8 @@ void testEntityWithEmbeddedFieldInsertion(SessionFactoryScope scope) {
115120
}
116121

117122
private void onMongoCollection(Consumer<MongoCollection<BsonDocument>> collectionConsumer) {
118-
var connectionString = new ConnectionString(new Configuration().getProperty(JAKARTA_JDBC_URL));
119-
try (var mongoClient = MongoClients.create(MongoClientSettings.builder()
120-
.applyConnectionString(connectionString)
121-
.build())) {
122-
var collection =
123-
mongoClient.getDatabase(connectionString.getDatabase()).getCollection("books", BsonDocument.class);
123+
try (var mongoClient = MongoClients.create(config.mongoClientSettings())) {
124+
var collection = mongoClient.getDatabase(config.databaseName()).getCollection("books", BsonDocument.class);
124125
collectionConsumer.accept(collection);
125126
}
126127
}
@@ -132,7 +133,7 @@ private List<BsonDocument> getCollectionDocuments() {
132133
}
133134

134135
private void assertCollectionContainsOnly(BsonDocument expectedDoc) {
135-
assertThat(getCollectionDocuments()).asList().singleElement().isEqualTo(expectedDoc);
136+
assertThat(getCollectionDocuments()).asInstanceOf(LIST).singleElement().isEqualTo(expectedDoc);
136137
}
137138

138139
@Entity(name = "Book")
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
/*
2+
* Copyright 2025-present MongoDB, Inc.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package com.mongodb.hibernate;
18+
19+
import static org.junit.jupiter.api.Assertions.assertEquals;
20+
21+
import com.mongodb.client.MongoClient;
22+
import com.mongodb.client.MongoClients;
23+
import com.mongodb.hibernate.internal.cfg.MongoConfiguration;
24+
import com.mongodb.hibernate.internal.cfg.MongoConfigurationBuilder;
25+
import jakarta.persistence.Column;
26+
import jakarta.persistence.Entity;
27+
import jakarta.persistence.EntityManagerFactory;
28+
import jakarta.persistence.Id;
29+
import jakarta.persistence.Persistence;
30+
import jakarta.persistence.Table;
31+
import org.junit.jupiter.api.AfterAll;
32+
import org.junit.jupiter.api.BeforeAll;
33+
import org.junit.jupiter.api.Test;
34+
35+
class JakartaPersistenceBootstrappingIntegrationTests {
36+
private static EntityManagerFactory entityManagerFactory;
37+
private static MongoConfiguration config;
38+
private static MongoClient mongoClient;
39+
40+
@BeforeAll
41+
static void beforeAll() {
42+
entityManagerFactory = Persistence.createEntityManagerFactory("test-persistence-unit");
43+
config = new MongoConfigurationBuilder(entityManagerFactory.getProperties()).build();
44+
mongoClient = MongoClients.create(config.mongoClientSettings());
45+
}
46+
47+
@BeforeAll
48+
static void beforeEach() {
49+
mongoClient.getDatabase(config.databaseName()).drop();
50+
}
51+
52+
@AfterAll
53+
@SuppressWarnings("try")
54+
static void afterAll() {
55+
try (var closed1 = entityManagerFactory;
56+
var closed2 = mongoClient) {}
57+
}
58+
59+
@Test
60+
void smoke() {
61+
try (var entityManager = entityManagerFactory.createEntityManager()) {
62+
var transaction = entityManager.getTransaction();
63+
try {
64+
transaction.begin();
65+
var item = new Item();
66+
item.id = 1;
67+
entityManager.persist(item);
68+
} finally {
69+
transaction.commit();
70+
}
71+
assertEquals(
72+
1,
73+
mongoClient
74+
.getDatabase(config.databaseName())
75+
.getCollection("items")
76+
.countDocuments());
77+
}
78+
}
79+
80+
@Entity(name = "Item")
81+
@Table(name = "items")
82+
static class Item {
83+
@Id
84+
@Column(name = "_id")
85+
int id;
86+
}
87+
}

src/integrationTest/java/com/mongodb/hibernate/SessionFactoryTests.java renamed to src/integrationTest/java/com/mongodb/hibernate/SessionFactoryIntegrationTests.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -16,20 +16,18 @@
1616

1717
package com.mongodb.hibernate;
1818

19-
import static org.hibernate.cfg.JdbcSettings.JAKARTA_JDBC_URL;
19+
import static org.hibernate.cfg.AvailableSettings.JAKARTA_JDBC_URL;
2020
import static org.junit.jupiter.api.Assertions.assertDoesNotThrow;
21-
import static org.junit.jupiter.api.Assertions.assertInstanceOf;
2221
import static org.junit.jupiter.api.Assertions.assertThrows;
2322

2423
import java.util.Map;
25-
import org.hibernate.HibernateException;
2624
import org.hibernate.SessionFactory;
2725
import org.hibernate.boot.MetadataSources;
2826
import org.hibernate.boot.registry.StandardServiceRegistryBuilder;
2927
import org.hibernate.service.spi.ServiceException;
3028
import org.junit.jupiter.api.Test;
3129

32-
class SessionFactoryTests {
30+
class SessionFactoryIntegrationTests {
3331

3432
@Test
3533
void testSuccess() {
@@ -38,10 +36,9 @@ void testSuccess() {
3836

3937
@Test
4038
void testInvalidConnectionString() {
41-
var exception = assertThrows(ServiceException.class, () -> buildSessionFactory(
39+
assertThrows(ServiceException.class, () -> buildSessionFactory(
4240
Map.of(JAKARTA_JDBC_URL, "jdbc:postgresql://localhost/test"))
4341
.close());
44-
assertInstanceOf(HibernateException.class, exception.getCause());
4542
}
4643

4744
@Test

src/integrationTest/java/com/mongodb/hibernate/SessionTests.java renamed to src/integrationTest/java/com/mongodb/hibernate/SessionIntegrationTests.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@
2727
import org.junit.jupiter.api.BeforeEach;
2828
import org.junit.jupiter.api.Test;
2929

30-
class SessionTests {
30+
class SessionIntegrationTests {
3131

3232
private static SessionFactory sessionFactory;
3333

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<persistence
2+
xmlns="http://xmlns.jcp.org/xml/ns/persistence"
3+
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4+
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd"
5+
version="2.1">
6+
<persistence-unit name="test-persistence-unit">
7+
<provider>org.hibernate.jpa.HibernatePersistenceProvider</provider>
8+
<class>com.mongodb.hibernate.JakartaPersistenceBootstrappingIntegrationTests$Item</class>
9+
<properties><!-- rely on `hibernate.properties` --></properties>
10+
</persistence-unit>
11+
</persistence>
Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
jakarta.persistence.jdbc.url=mongodb://localhost/mongo-hibernate-test?directConnection=false
21
hibernate.dialect=com.mongodb.hibernate.dialect.MongoDialect
32
hibernate.connection.provider_class=com.mongodb.hibernate.jdbc.MongoConnectionProvider
3+
jakarta.persistence.jdbc.url=mongodb://localhost/mongo-hibernate-test?directConnection=false

0 commit comments

Comments
 (0)