Persisting a JMolecules Aggregate Association does not work when using EntityManager

2 weeks ago 19
ARTICLE AD BOX

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.

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.

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 converterts 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

These are the aggregate root types:

@NoArgsConstructor @Getter public class RicRate implements AggregateRoot<RicRate, RicRateIdentifier> { private RicRateIdentifier id; private BigDecimal ask; private BigDecimal bid; //@Value(staticConstructor = "of") public static class RicRateIdentifier implements Identifier { private String id; public RicRateIdentifier(String id) { this.id = id; } } } @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()); } } }
Read Entire Article