Skip to content

Persisting a JMolecules Aggregate Association does not work when using EntityManager but works when using a Spring Data Repository #397

@MartinAhrer

Description

@MartinAhrer

I have multiple JMolecules managed aggregate roots that use associations. When persisting them with a Spring Data JPA repository the associations are converted properly.

However when persisting the same object and its associations using an entity manager instance, this fails.

The full scenario has also been posted to https://stackoverflow.com/questions/79862584/persisting-a-jmolecules-aggregate-association-does-not-work-when-using-entityman.

A test showing both scenarios is:

@DataJpaTest()
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
@TestPropertySource(properties = """
    spring.jpa.show-sql=true
""")
@Import([IntegrationTestConfiguration, CatalogIntegrationTestConfiguration])
class AssociationWithEntityManagerSpec extends AbstractRepositorySpecification {

    @Autowired
    ArticleRepository repository

    @Autowired
    MarketDataRepository marketDataRepository

    @Autowired
    RicRateRepository ricRateRepository

    ArticleFactory entityFactory = new ArticleFactory()
    RicRateFactory ricRateFactory = new RicRateFactory()
    MarketDataFactory marketDataFactory = new MarketDataFactory()


    def "find by id with repository"() {
        given:
        RicRate rate = ricRateFactory.newObject(id: new RicRateIdentifier('HKD'))
        rate = ricRateRepository.save(rate)

        MarketData marketData = marketDataFactory.newObject(rate: Association.forAggregate(rate))
        marketData = marketDataRepository.save(marketData)

        Article article = repository.save(entityFactory.newObject(marketData: Association.forAggregate(marketData)))

        when:
        Optional<AggregateRoot> result = repository.findById(article.id)

        then:
        result.isPresent()
    }

    def "find by id with entity manager"() {
        given:
        RicRate rate = ricRateFactory.newObject(id: new RicRateIdentifier('HKD'))
        rate = entityManager.merge(rate)

        MarketData marketData = marketDataFactory.newObject(rate: Association.forAggregate(rate))
        marketData = entityManager.merge(marketData)

        Article article = repository.save(entityFactory.newObject(marketData: Association.forAggregate(marketData)))

        when:
        Optional<AggregateRoot> result = repository.findById(article.id)

        then:
        result.isPresent()
    }
}

The test using the entity manager is showing the following exception which looks like the database key value HKD for the rate association is used to construct a MarketDataIdentifer which is a UUID. It really should create a RicRateIdentifier

So I assume, without the repository some meta data is missing to read the association properly? Since the byte buddy plugin is enhancing the entity with attribute converters I would expect that it wouldn't matter whether to use the repository or the entity manager.

jakarta.persistence.PersistenceException: Error attempting to apply AttributeConverter

    at org.hibernate.type.descriptor.converter.internal.AttributeConverterBean.toDomainValue(AttributeConverterBean.java:103)
    at org.hibernate.type.descriptor.converter.internal.AttributeConverterMutabilityPlan.deepCopyNotNull(AttributeConverterMutabilityPlan.java:59)
    at org.hibernate.type.descriptor.java.MutableMutabilityPlan.deepCopy(MutableMutabilityPlan.java:48)
    at org.hibernate.type.descriptor.java.JavaType.getReplacement(JavaType.java:140)
    at org.hibernate.type.internal.ConvertedBasicTypeImpl.replace(ConvertedBasicTypeImpl.java:337)
    at org.hibernate.type.TypeHelper.replace(TypeHelper.java:152)
    at org.hibernate.event.internal.DefaultMergeEventListener.entityIsTransient(DefaultMergeEventListener.java:292)
    at org.hibernate.event.internal.DefaultMergeEventListener.entityIsDetached(DefaultMergeEventListener.java:440)
    at org.hibernate.event.internal.DefaultMergeEventListener.merge(DefaultMergeEventListener.java:192)
    at org.hibernate.event.internal.DefaultMergeEventListener.doMerge(DefaultMergeEventListener.java:136)
    at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:120)
    at org.hibernate.event.internal.DefaultMergeEventListener.onMerge(DefaultMergeEventListener.java:77)
    at org.hibernate.event.service.internal.EventListenerGroupImpl.fireEventOnEachListener(EventListenerGroupImpl.java:138)
    at org.hibernate.internal.SessionImpl.fireMerge(SessionImpl.java:797)
    at org.hibernate.internal.SessionImpl.merge(SessionImpl.java:782)
    at org.springframework.orm.jpa.SharedEntityManagerCreator$SharedEntityManagerInvocationHandler.invoke(SharedEntityManagerCreator.java:317)
    at at.dig.wertekasse.catalog.internal.AssociationWithEntityManagerSpec.find by id with entity manager(AssociationWithEntityManagerSpec.groovy:67)
Caused by: org.springframework.core.convert.ConversionFailedException: Failed to convert from type [java.lang.String] to type [java.util.UUID] for value [HKD]
    at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:47)
    at org.springframework.core.convert.support.GenericConversionService.convert(GenericConversionService.java:181)
    at org.jmolecules.spring.PrimitivesToIdentifierConverter.prepareSource(PrimitivesToIdentifierConverter.java:188)
    at org.jmolecules.spring.PrimitivesToIdentifierConverter.lambda$detectConstructor$14(PrimitivesToIdentifierConverter.java:174)
    at org.jmolecules.spring.PrimitivesToIdentifierConverter.convert(PrimitivesToIdentifierConverter.java:128)
    at org.jmolecules.spring.PrimitivesToAssociationConverter.resolveIdentifier(PrimitivesToAssociationConverter.java:123)
    at org.jmolecules.spring.PrimitivesToAssociationConverter.convert(PrimitivesToAssociationConverter.java:101)
    at org.jmolecules.spring.jpa.JakartaPersistenceAssociationAttributeConverter.convertToEntityAttribute(JakartaPersistenceAssociationAttributeConverter.java:60)
    at org.jmolecules.spring.jpa.JakartaPersistenceAssociationAttributeConverter.convertToEntityAttribute(JakartaPersistenceAssociationAttributeConverter.java:30)
    at org.hibernate.type.descriptor.converter.internal.AttributeConverterBean.toDomainValue(AttributeConverterBean.java:97)
    ... 16 more
Caused by: java.lang.IllegalArgumentException: Invalid UUID string: HKD
    at java.base/java.util.UUID.fromString1(UUID.java:282)
    at java.base/java.util.UUID.fromString(UUID.java:260)
    at org.springframework.core.convert.support.StringToUUIDConverter.convert(StringToUUIDConverter.java:37)
    at org.springframework.core.convert.support.StringToUUIDConverter.convert(StringToUUIDConverter.java:33)
    at org.springframework.core.convert.support.GenericConversionService$ConverterAdapter.convert(GenericConversionService.java:356)
    at org.springframework.core.convert.support.ConversionUtils.invokeConverter(ConversionUtils.java:41)
    ... 25 more
@NoArgsConstructor
@Getter
public class MarketData implements AggregateRoot<MarketData, MarketDataIdentifier> {
    private MarketDataIdentifier id;

    @Column(name = "rate_id")
    private Association<RicRate, RicRate.RicRateIdentifier> rate;

    public record MarketDataIdentifier(UUID id) implements Identifier {
        public MarketDataIdentifier() {
            this(UUID.randomUUID());
        }
    }
}

@Getter
@NoArgsConstructor
public class Article implements AggregateRoot<Article, ArticleIdentifier> {
    private ArticleIdentifier id;

    @Column(name = "market_data_id")
    private Association<MarketData, MarketDataIdentifier> marketData;

    public record ArticleIdentifier(UUID id) implements Identifier {
        public ArticleIdentifier() {
            this(UUID.randomUUID());
        }
    }
}

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions