0

given the entity model below:

@Entity(name = "Accounts")
open class AccountEntity(
    @field:Id
    @field:GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "accounts_sequence_generator")
    @field:SequenceGenerator(name = "accounts_sequence_generator", sequenceName = "sequence_accounts")
    @field:Column(name = "id", nullable = false, unique = true)
    open var id: Long? = null,

    @field:[NotBlank Size(min = 2, max = 255, message = "username must be between 2 and 255 characters long")]
    @field:Column(name = "username", nullable = false, unique = true)
    open var username: String,

    @field:Embedded
    open var address: Address?
)

@Embeddable
data class Address(
    @field:Embedded
    val geolocation: Geolocation
)

@Embeddable
data class Geolocation(
    @field:Column(name = "geolocation", columnDefinition = "geography(POINT,4326)")
    val geolocation: Point
)

I would like to execute a query using a DTO projection with a constructor expression:

val query = entityManager.createQuery(
            "select new org.example.dto.AccountSummary(acc.address.geolocation.geolocation, acc.id, acc.username) from Accounts acc" +
                    "",
            AccountSummary::class.java
        )
        return query.resultList

where the AccountSummary class is given below:

@Introspected
data class AccountSummary(
    val point: Location,
    val id: Long,
    val username: String
)

However, I would also like to perform a type conversion on a geolocation property (type Point) to a custom Location data class, so I've registered a custom TypeConverter from a Geometry to Location:

@Singleton
class GeometryLocationConverter : TypeConverter<Geometry, Location> {
    override fun convert(`object`: Geometry?, targetType: Class<Location>?, context: ConversionContext?): Optional<Location> {
        return when (`object`) {
            is Point -> Optional.of(Location(`object`.y, `object`.x))
            else -> throw Exception("unknown geometry type")
        }
    }
}

However, an exception gets thrown with the error: unable to locate appropriate constructor on class AccountSummary. Is something like this possible? I haven't found any examples that showcase this use case.

1 Answers1

0

Not sure if Micronaut is capable to do this, but Blaze-Persistence Entity Views has type converters and will also make it easier to write queries.

I created the library to allow easy mapping between JPA models and custom interface or abstract class defined models, something like Spring Data Projections on steroids. The idea is that you define your target structure(domain model) the way you like and map attributes(getters) via JPQL expressions to the entity model.

A DTO model for your use case could look like the following with Blaze-Persistence Entity-Views:

@EntityView(AccountEntity.class)
public interface AccountSummary {
    @IdMapping
    Long getId();
    String getUsername();
    @Mapping("address.geolocation.geolocation")
    Location getLocation();
}

Querying is a matter of applying the entity view to a query, the simplest being just a query by id.

AccountSummary a = entityViewManager.find(entityManager, AccountSummary.class, id);

The best part is, it will only fetch the state that is actually necessary!

Christian Beikov
  • 15,141
  • 2
  • 32
  • 58
  • thanks for your answer; I am aware of blaze-persistence framework, but officially that framework is not yet supported by micronaut, so I decided not to use it. There is a potential solution to this issue by introducing a custom hibernate type converter. – JerryThePineapple Dec 08 '22 at 12:36
  • You should be able to use it in Micronaut anyway though, at least other people do: https://github.com/Blazebit/blaze-persistence/issues/1088 – Christian Beikov Dec 08 '22 at 14:49