Skip to content

Hibernate-48 investigation #105

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

Draft
wants to merge 5 commits into
base: main
Choose a base branch
from
Draft
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
4 changes: 1 addition & 3 deletions build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -35,9 +35,7 @@ java {
withSourcesJar()
}

tasks.withType<Javadoc> {
exclude("/com/mongodb/hibernate/internal/**")
}
tasks.withType<Javadoc> { exclude("/com/mongodb/hibernate/internal/**") }

// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
// Integration Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,16 @@
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import org.assertj.core.api.InstanceOfAssertFactories;
import org.bson.BsonDocument;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.ServiceRegistryScope;
import org.hibernate.testing.orm.junit.ServiceRegistryScopeAware;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.SessionFactoryScopeAware;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
Expand All @@ -40,18 +44,30 @@
annotatedClasses = {BasicCrudIntegrationTests.Book.class, BasicCrudIntegrationTests.BookDynamicallyUpdated.class
})
@ExtendWith(MongoExtension.class)
class BasicCrudIntegrationTests implements SessionFactoryScopeAware {
class BasicCrudIntegrationTests implements SessionFactoryScopeAware, ServiceRegistryScopeAware {

@InjectMongoCollection("books")
private static MongoCollection<BsonDocument> mongoCollection;

private SessionFactoryScope sessionFactoryScope;

private TestCommandListener testCommandListener;

@Override
public void injectServiceRegistryScope(ServiceRegistryScope serviceRegistryScope) {
this.testCommandListener = serviceRegistryScope.getRegistry().requireService(TestCommandListener.class);
}

@Override
public void injectSessionFactoryScope(SessionFactoryScope sessionFactoryScope) {
this.sessionFactoryScope = sessionFactoryScope;
}

@BeforeEach
void beforeEach() {
testCommandListener.clear();
}

@Nested
class InsertTests {
@Test
Expand Down Expand Up @@ -101,6 +117,48 @@ void testEntityWithNullFieldValueInsertion() {
.formatted(author));
assertCollectionContainsExactly(expectedDocument);
}

@Test
void testIgnoreFieldWhenNull() {
sessionFactoryScope.inTransaction(session -> {
var book = new Book();
book.id = 1;
book.title = "War and Peace";
book.author = null; // This field should be ignored when null
book.publishYear = 1867;
session.persist(book);

session.flush();

var capturedCommands = testCommandListener.getStartedCommands();

assertThat(capturedCommands)
.singleElement()
.asInstanceOf(InstanceOfAssertFactories.MAP)
.containsAllEntriesOf(
BsonDocument.parse(
"""
{
"insert": "books",
"documents": [
{
"_id": 1,
"title": "War and Peace",
"publishYear": 1867
}
]
}
"""));
});
var expectedDocument = BsonDocument.parse(
"""
{
_id: 1,
title: "War and Peace",
publishYear: 1867
}""");
assertCollectionContainsExactly(expectedDocument);
}
}

@Nested
Expand Down Expand Up @@ -174,6 +232,55 @@ void testDynamicUpdate() {
{"_id": 1, "author": "Leo Tolstoy", "publishYear": 1867, "title": "War and Peace"}\
"""));
}

@Test
void testDeleteFieldWhenSetToNull() {
sessionFactoryScope.inTransaction(session -> {
var book = new Book();
book.id = 1;
book.title = "War and Peace";
book.author = "Leo Tolstoy";
book.publishYear = 1869;
session.persist(book);
session.flush();

book.publishYear = 1867; // Ensure the field is set before deletion
book.author = null; // This field should be deleted when set to null

session.flush();

var capturedCommands = testCommandListener.getStartedCommands();

var lastCommand = capturedCommands.get(capturedCommands.size() - 1);
assertThat(lastCommand)
.asInstanceOf(InstanceOfAssertFactories.MAP)
.containsAllEntriesOf(
BsonDocument.parse(
"""
{
"update": "books",
"updates": [
{
"q": {"_id": {"$eq": 1}},
"u": {
"$set": {
"publishYear": 1867,
"title": "War and Peace"
}
"$unset": {"author": ""}},
"multi": true
}
]
}
"""));
});

assertCollectionContainsExactly(
BsonDocument.parse(
"""
{"_id": 1, "publishYear": 1867, "title": "War and Peace"}\
"""));
}
}

@Nested
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
/*
* Copyright 2025-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.mongodb.hibernate.embeddable;

import static com.mongodb.hibernate.MongoTestAssertions.assertEq;
import static org.assertj.core.api.Assertions.assertThat;

import com.mongodb.client.MongoCollection;
import com.mongodb.hibernate.junit.InjectMongoCollection;
import com.mongodb.hibernate.junit.MongoExtension;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import org.bson.BsonDocument;
import org.bson.BsonInt32;
import org.bson.BsonString;
import org.hibernate.annotations.Struct;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

@SessionFactory(exportSchema = false)
@DomainModel(
annotatedClasses = {
CompactStructIntegrationTests.StructHolder.class,
CompactStructIntegrationTests.CompactStruct.class
})
@ExtendWith(MongoExtension.class)
class CompactStructIntegrationTests {

@InjectMongoCollection("items")
private static MongoCollection<BsonDocument> mongoCollection;

@Test
void test(SessionFactoryScope scope) {
var holder = new StructHolder();
holder.id = 1;
holder.compactStruct = new CompactStruct();
holder.compactStruct.field1 = null;
holder.compactStruct.field2 = "value2";
scope.inTransaction(session -> session.persist(holder));

assertThat(mongoCollection.find())
.containsExactly(new BsonDocument("_id", new BsonInt32(1))
.append("compactStruct", new BsonDocument("field2", new BsonString("value2"))));

var loadedHolder = scope.fromTransaction(session -> session.find(StructHolder.class, 1));
assertEq(holder, loadedHolder);
}

@Entity(name = "StructHolder")
@Table(name = "items")
static class StructHolder {
@Id
int id;

CompactStruct compactStruct;
}

@Embeddable
@Struct(name = "CompactStruct")
static class CompactStruct {
String field1;
String field2;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright 2025-present MongoDB, Inc.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.mongodb.hibernate.embeddable;

import static org.assertj.core.api.Assertions.assertThat;

import com.mongodb.client.MongoCollection;
import com.mongodb.hibernate.junit.InjectMongoCollection;
import com.mongodb.hibernate.junit.MongoExtension;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import org.bson.BsonDocument;
import org.hibernate.annotations.DynamicInsert;
import org.hibernate.annotations.Struct;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

@SessionFactory(exportSchema = false)
@DomainModel(
annotatedClasses = {
DynamicInsertWithStructWithNullValuesIntegrationTests.Book.class,
DynamicInsertWithStructWithNullValuesIntegrationTests.Author.class
})
@ExtendWith(MongoExtension.class)
class DynamicInsertWithStructWithNullValuesIntegrationTests {

@InjectMongoCollection("books")
private static MongoCollection<BsonDocument> mongoCollection;

@Test
void test(SessionFactoryScope scope) {
scope.inTransaction(session -> {
var book = new Book();
book.id = 1;
book.author = new Author();
session.persist(book);
});
assertThat(mongoCollection.find())
.containsExactly(
BsonDocument.parse(
"""
{
_id: 1,
author: {
firstName: null,
lastName: null
}
}
"""));
}

@Entity
@DynamicInsert
@Table(name = "books")
static class Book {
@Id
int id;

Author author;
}

@Embeddable
@Struct(name = "Author")
static class Author {
String firstName;
String lastName;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package com.mongodb.hibernate.embeddable;


import com.mongodb.hibernate.junit.MongoExtension;
import jakarta.persistence.Embeddable;
import jakarta.persistence.Entity;
import jakarta.persistence.Id;
import jakarta.persistence.Table;
import org.hibernate.annotations.Struct;
import org.hibernate.testing.orm.junit.DomainModel;
import org.hibernate.testing.orm.junit.ServiceRegistry;
import org.hibernate.testing.orm.junit.SessionFactory;
import org.hibernate.testing.orm.junit.SessionFactoryScope;
import org.hibernate.testing.orm.junit.Setting;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;

import static com.mongodb.hibernate.MongoTestAssertions.assertEq;

@SessionFactory(exportSchema = false)
@DomainModel(annotatedClasses = {
EmptyStructAggregateRetrievalIntegrationTests.Book.class,
EmptyStructAggregateRetrievalIntegrationTests.Author.class
})
@ExtendWith(MongoExtension.class)
class EmptyStructAggregateRetrievalIntegrationTests {

@Test
void testEmptyStructAggregateRetriedAsNonNull(SessionFactoryScope scope) {
var book = new Book();
book.id = 1;
book.author = new Author();

scope.inTransaction(session -> session.persist(book));

var retrievedBook = scope.fromTransaction(session -> session.get(Book.class, 1));
assertEq(book, retrievedBook);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My understanding is that this test requires hibernate.create_empty_composites.enabled=true. If that's true, then it does not "showcase empty struct could be fetched intact (not null)", as we won't use this configuration property knowing it does not exist in Hibernate ORM 7.0.

We should stop bringing up hibernate.create_empty_composites.enabled to avoid confusion.

}

@Entity
@Table(name = "books")
static class Book {
@Id int id;
Author author;
}

@Embeddable
@Struct(name = "Author")
static class Author {
String firstName;
String lastName;
}
}
Loading