0

I am using currently Spring Data JPA and I would like to map an attribute

@Entity
public class Outer {
    ...
    Map<String, List<String>> typesToCategories;
}

Let's assume I have a tables outer and outer_type_category. The first one is trivial: only column outer_id is relevant from it

CREATE TABLE outer_types_categories ( 
    id        uuid                        NOT NULL,
    outer_id  uuid                        NOT NULL,
    type      character varying(128)      NOT NULL,
    category  character varying(128)      NOT NULL,
    ...
 )

Which annotations should I use (if it is possible in general) to map this table to the map?

I have tried to use this

    @ElementCollection
    @CollectionTable(name = "outer_type_category", joinColumns = [JoinColumn(name = "outer_id")])
    @MapKeyColumn(name = "type")
    @Column(name = "category")
    Map<String, List<String>> typesToCategories;

but in the end I see an exception:

Caused by: org.hibernate.MappingException: Could not determine type for: java.util.List, at table: outer_type_category, for columns: [org.hibernate.mapping.Column(category)]

Did I forget anything?

youngDev
  • 329
  • 3
  • 16
  • That would be an `ElementCollection` inside another `ElementCollection` and I don't think, that's feasible. You could try with an extra `Embeddable` class for your List, as was done here: https://stackoverflow.com/questions/28858024/can-i-use-elementcollection-inside-an-embedded – Sascha Apr 18 '19 at 14:45
  • You have overlooked that the element type of the mapped collection must be either a basic type or an embeddable class. `List` is neither. – John Bollinger Apr 18 '19 at 14:46
  • More generally, you seem to have an incorrect expectation that `@ElementColelction` will cause JPA to map the multiple elements of each `List` value in the `Map` to multiple rows of the collection table and back, but it absolutely will not do this. With respect to your particular table structure, it will expect (`outer_id`, `type`) to be a candidate key for table `outer_type_category`, so that there is at most one row for each combination of `outer_id` and `type`. – John Bollinger Apr 18 '19 at 15:00

1 Answers1

0

JPA specifies that

A persistent field or property of an entity or embeddable class may correspond to a collection of a basic type or embeddable class (“element collection”).

(JPA 2.2 Specification, section 2.6; emphasis added)

java.util.List is neither a basic type nor an embeddable class, so it is not among the allowed element types of an element collection. Moreover, JPA goes on to say

An embeddable class [...] that is contained within an element collection must not contain an element collection

, so even replacing List with an embeddable class does not give you a suitable mechanism for mapping the structure you've described. If you're unwilling or unable to change the DB structure at all, then I don't think you can map your table in a manner that is in any way analogous to what you describe.

If you can at least add a new table to the DB then you can introduce a new entity representing an entry in your map, say OuterType, which has an element collection mapped to your outer_types_categories table. It would probably need to have a composite ID corresponding to (outer_id, type). Even then, the DB side would need to be set up to automatically assign values to the id column of the collection table (unless you can drop that column, which in truth does not appear to be useful for your apparent purposes), because members of an element collection are not entities, and therefore JPA does not ascribe IDs to them. Moreover, it is messy (on the JPA side) to have a column that is both part of a composite primary key and a foreign key for a related entity, as this would require.

If you have more freedom to modify the DB structure then I'd set up the aforementioned OuterType entity with a standard, surrogate ID and a bidirectional one-to-one relationship with Outer, represented on the Outer side as a map. Set up an element collection containing the category strings in OuterType using the default mapping strategy, which would use OuterType's (surrogate) ID and neither its "type" nor its "outer_id" in the collection table.

John Bollinger
  • 160,171
  • 8
  • 81
  • 157