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
13 changes: 13 additions & 0 deletions buildSrc/src/main/kotlin/framefork.java-public.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,19 @@ java {
withSourcesJar()
}

project.pluginManager.withPlugin("java-test-fixtures") {
// Since this code runs only when the plugin is present, it's safe to access the component and configurations created by it.
val javaComponent = project.components["java"] as AdhocComponentWithVariants
// Disable publication of the test fixtures API variant
javaComponent.withVariantsFromConfiguration(project.configurations.named("testFixturesApiElements").get()) {
skip()
}
// Disable publication of the test fixtures runtime variant
javaComponent.withVariantsFromConfiguration(project.configurations.named("testFixturesRuntimeElements").get()) {
skip()
}
}

publishing {
publications {
create<MavenPublication>("mavenStaging") {
Expand Down
52 changes: 52 additions & 0 deletions buildSrc/src/main/kotlin/framefork.java.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -230,3 +230,55 @@ configurations.all {
)
}
}

// This is the custom task to unpack the source JARs
tasks.register("unpackAllDependencySources") {
group = "Build Setup"
description = "Unpacks the source JAR for each dependency into build/dependencies/"

doLast {
// Define the target directory for all unpacked sources and ensure it's clean.
val outputDir = project.layout.buildDirectory.dir("dependencies")
delete(outputDir)
mkdir(outputDir)

// 1. Get the component identifiers for all dependencies in the 'runtimeClasspath' configuration.
// This gives us a list of all libraries we need to find sources for.
val componentIds: List<ComponentIdentifier> = configurations.runtimeClasspath.get()
.incoming
.resolutionResult
.allComponents
.map { it.id }

// 2. Create and execute an artifact query.
// We ask Gradle to find all artifacts of type 'SourcesArtifact' for Java libraries.
val result = dependencies.createArtifactResolutionQuery()
.forComponents(componentIds)
.withArtifacts(JvmLibrary::class.java, SourcesArtifact::class.java)
.execute()

// 3. Iterate over the results and unpack each source JAR found.
println("Unpacking dependency sources...")
result.resolvedComponents.forEach { component ->
component.getArtifacts(SourcesArtifact::class.java).forEach { artifact ->
// Ensure the artifact was successfully resolved and is the type we expect.
if (artifact is ResolvedArtifactResult) {
val sourceJarFile = artifact.file

// Create a unique directory name from the source JAR's filename.
// e.g., 'guava-31.1-jre-sources.jar' -> 'guava-31.1-jre'
val artifactDirName = sourceJarFile.name.removeSuffix("-sources.jar")
val targetDir = outputDir.get().dir(artifactDirName)

// Perform the unzip operation.
copy {
from(zipTree(sourceJarFile))
into(targetDir)
}
println(" -> Unpacked ${sourceJarFile.name} to ${targetDir}")
}
}
}
println("Finished unpacking all dependency sources.")
}
}
1 change: 1 addition & 0 deletions modules/typed-ids-hibernate-61-testing/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ plugins {

dependencies {
api(project(":typed-ids-testing"))
api(testFixtures(project(":typed-ids-hibernate-61")))

api(libs.hibernate.orm.v61)
api(libs.hypersistence.utils.hibernate61)
Expand Down
1 change: 1 addition & 0 deletions modules/typed-ids-hibernate-61/build.gradle.kts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
plugins {
id("framefork.java-public")
id("java-test-fixtures")
}

dependencies {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
package org.framefork.typedIds.bigint.hibernate;

import org.framefork.typedIds.bigint.ObjectBigIntId;
import org.framefork.typedIds.bigint.hibernate.basic.BigIntDbSequenceGeneratedExplicitMappingEntity;
import org.framefork.typedIds.bigint.hibernate.basic.BigIntPooledLoOptimizerEntity;
import org.framefork.typedIds.hibernate.tests.AbstractPostgreSQLIntegrationTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Properties;

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

final class AdvancedGenerationStrategyPostgreSQLTest extends AbstractPostgreSQLIntegrationTest
{

@Override
protected Class<?>[] entities()
{
return new Class<?>[]{
BigIntPooledLoOptimizerEntity.class,
BigIntDbSequenceGeneratedExplicitMappingEntity.class,
};
}

@Override
protected void additionalProperties(Properties properties)
{
// Enable JDBC batching for testing batching behavior
properties.setProperty("hibernate.jdbc.batch_size", "20");
properties.setProperty("hibernate.order_inserts", "true");
properties.setProperty("hibernate.order_updates", "true");
}

@Test
public void testPooledLoOptimizerPerformance()
{
// This test validates that the pooled-lo optimizer reduces database calls
doInJPA(em -> {
// Create 21 entities (more than allocation size of 20)
List<BigIntPooledLoOptimizerEntity> entities = new ArrayList<>();
for (int i = 1; i <= 21; i++) {
entities.add(new BigIntPooledLoOptimizerEntity("data-" + i));
}

// Persist all entities
entities.forEach(em::persist);
em.flush();

// Verify all entities have IDs assigned
entities.forEach(entity -> {
Assertions.assertNotNull(entity.getId(), "ID must not be null after persist");
});

// With pooled-lo optimizer and allocationSize=20:
// - First 20 entities should get IDs from first sequence call
// - 21st entity should get ID from second sequence call
List<Long> idValues = entities.stream()
.map(BigIntPooledLoOptimizerEntity::getId)
.filter(Objects::nonNull)
.map(ObjectBigIntId::toLong)
.sorted()
.toList();

assertThat(idValues.get(0)).as("First ID should be 1").isEqualTo(1L);
assertThat(idValues.get(20)).as("21st ID should be 21").isEqualTo(21L);
});
}

@Test
public void testSequenceStrategyBasicFunctionality()
{
doInJPA(em -> {
// Create multiple entities with SEQUENCE generation
List<BigIntDbSequenceGeneratedExplicitMappingEntity> entities = new ArrayList<>();
for (int i = 1; i <= 5; i++) {
entities.add(new BigIntDbSequenceGeneratedExplicitMappingEntity("sequence-" + i));
}

// Verify IDs are null before persist
entities.forEach(entity -> {
Assertions.assertNull(entity.getId(), "ID should be null before persist");
});

// Persist all entities
entities.forEach(em::persist);
em.flush();

// Verify all entities have IDs assigned from sequence
entities.forEach(entity -> {
Assertions.assertNotNull(entity.getId(), "ID must not be null after persist");
});

// All IDs should be unique and positive
List<Long> idValues = entities.stream()
.map(BigIntDbSequenceGeneratedExplicitMappingEntity::getId)
.filter(Objects::nonNull)
.map(ObjectBigIntId::toLong)
.toList();

assertThat(idValues).as("All IDs should be unique")
.doesNotHaveDuplicates();

assertThat(idValues).as("All IDs should be positive")
.allMatch(id -> id > 0);
});
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,165 @@
package org.framefork.typedIds.bigint.hibernate;

import jakarta.persistence.PersistenceException;
import org.framefork.typedIds.bigint.ObjectBigIntId;
import org.framefork.typedIds.bigint.hibernate.basic.BigIntDbIdentityGeneratedExplicitMappingEntity;
import org.framefork.typedIds.bigint.hibernate.basic.BigIntNullIdEntity;
import org.framefork.typedIds.bigint.hibernate.basic.BigIntPooledLoOptimizerEntity;
import org.framefork.typedIds.hibernate.tests.AbstractMySQLIntegrationTest;
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;

import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Properties;

import static org.assertj.core.api.Assertions.assertThat;
import static org.junit.jupiter.api.Assertions.assertThrows;

final class AdvancedGenerationStrategyTest extends AbstractMySQLIntegrationTest
{

@Override
protected Class<?>[] entities()
{
return new Class<?>[]{
BigIntPooledLoOptimizerEntity.class,
BigIntDbIdentityGeneratedExplicitMappingEntity.class,
BigIntNullIdEntity.class,
};
}

@Override
protected void additionalProperties(Properties properties)
{
// Enable JDBC batching for testing batching behavior
properties.setProperty("hibernate.jdbc.batch_size", "20");
properties.setProperty("hibernate.order_inserts", "true");
properties.setProperty("hibernate.order_updates", "true");
}

@Test
public void testPooledLoOptimizerPerformance()
{
doInJPA(em -> {
// Create 21 entities (more than allocation size of 20)
List<BigIntPooledLoOptimizerEntity> entities = new ArrayList<>();
for (int i = 1; i <= 21; i++) {
entities.add(new BigIntPooledLoOptimizerEntity("data-" + i));
}

// Persist all entities
entities.forEach(em::persist);
em.flush();

// Verify all entities have IDs assigned
entities.forEach(entity -> {
Assertions.assertNotNull(entity.getId(), "ID must not be null after persist");
});

// With pooled-lo optimizer and allocationSize=20:
// - First 20 entities should get IDs 1-20 from first sequence call
// - 21st entity should get ID 21 from second sequence call
// Validate that IDs are sequential and within expected ranges
List<Long> idValues = entities.stream()
.map(BigIntPooledLoOptimizerEntity::getId)
.filter(Objects::nonNull)
.map(ObjectBigIntId::toLong)
.sorted()
.toList();

assertThat(idValues.get(0)).as("First ID should be 1").isEqualTo(1L);
assertThat(idValues.get(20)).as("21st ID should be 21").isEqualTo(21L);
});
}


@Test
public void testIdentityStrategyBasicFunctionality()
{
doInJPA(em -> {
// Create multiple entities with IDENTITY generation
List<BigIntDbIdentityGeneratedExplicitMappingEntity> entities = new ArrayList<>();
for (int i = 1; i <= 5; i++) {
entities.add(new BigIntDbIdentityGeneratedExplicitMappingEntity("identity-" + i));
}

// Verify IDs are null before persist
entities.forEach(entity -> {
Assertions.assertNull(entity.getId(), "ID should be null before persist");
});

// Persist all entities
entities.forEach(em::persist);
em.flush();

// Verify all entities have IDs assigned by database
entities.forEach(entity -> {
Assertions.assertNotNull(entity.getId(), "ID must not be null after persist");
});

// IDs should be sequential (MySQL AUTO_INCREMENT behavior)
List<Long> idValues = entities.stream()
.map(BigIntDbIdentityGeneratedExplicitMappingEntity::getId)
.filter(Objects::nonNull)
.map(ObjectBigIntId::toLong)
.sorted()
.toList();

// Verify IDs are sequential starting from 1
for (int i = 0; i < idValues.size(); i++) {
assertThat(idValues.get(i)).as("ID should be sequential")
.isEqualTo((long) (i + 1));
}
});
}


@Test
public void testNullIdRejectionForAssignedStrategy()
{
// Test that attempting to persist an entity with null ID throws appropriate exception
var exception = assertThrows(
PersistenceException.class,
() -> doInJPA(em -> {
var entity = new BigIntNullIdEntity("test-data");
// ID is null by default
Assertions.assertNull(entity.getId());

em.persist(entity);
em.flush(); // This should trigger the exception
})
);

// Verify the exception message indicates the problem
assertThat(exception.getMessage()).containsAnyOf("id", "identifier", "manually assigned");
}

@Test
public void testAssignedStrategyWithValidId()
{
// Test successful persistence when ID is properly assigned
BigIntNullIdEntity.Id assignedId = BigIntNullIdEntity.Id.random();

doInJPA(em -> {
var entity = new BigIntNullIdEntity("test-data");
entity.setId(assignedId);

em.persist(entity);
em.flush();

// Verify ID was preserved
Assertions.assertEquals(assignedId, entity.getId());
});

// Verify entity can be retrieved by assigned ID
doInJPA(em -> {
var retrieved = em.find(BigIntNullIdEntity.class, assignedId);
Assertions.assertNotNull(retrieved);
Assertions.assertEquals("test-data", retrieved.getData());
Assertions.assertEquals(assignedId, retrieved.getId());
});
}

}
Loading