0
votes

I am trying to come up with a way of implementing tags for my entity that works well for me and need some help in the process. Let me write down some requirements I have in mind:

Firstly, I would like tags to show in entities as a list of strings like this:

{
  "tags": ["foo", "bar"]
}

Secondly, I need to be able to retrieve a set of available tags across all entities so that users can easily choose from existing tags.

The 2nd requirement could be achieved by creating a Tag entity with the value of the Tag as the @Id. But that would make the tags property in my entity a relation that requires an extra GET operation to fetch. I could work with a getter method that resolves all the Tags and returns only a list of strings, but I see two disadvantages in that: 1. The representation as a list of strings suggests you could store tags by POSTing them in that way which is not the case. 2. The process of creating an entity requires to create all the Tags via a /tags endpoint first. That seem rather complicated for such a simple thing.

Also, I think I read somewhere that you shouldn't create a repository for an entity that isn't standalone. Would I create a Tag and only a Tag at any point in time? Nope.

I could store the tags as an @ElementCollection in my entity. In this case I don't know how to fulfill the 2nd requirement, though.

@ElementCollection
private Set<String> tags;

I made a simple test via EntityManager but it looks like I cannot query things that are not an @Entity in a result set.

@RestController
@RequestMapping("/tagList")
@RequiredArgsConstructor(onConstructor = @__(@Autowired))
public class TagListController implements RepresentationModelProcessor<RepositoryLinksResource> {

    @PersistenceContext
    private final @NonNull EntityManager entityManager;
    
    @RequestMapping(method = RequestMethod.GET)
    public ResponseEntity<EntityModel<TagList>> get() {
        System.out.println(entityManager.createQuery("SELECT t.tags FROM Training t").getFirstResult());
    
        EntityModel<TagList> model = EntityModel.of(new TagList(Set.of("foo", "bar")));
        model.add(linkTo(methodOn(TagListController.class).get()).withSelfRel());
        return ResponseEntity.ok(model);
    }

}
org.hibernate.QueryException: not an entity

Does anyone know a smart way?

1

1 Answers

0
votes

The representation as a list of strings suggests you could store tags by POSTing them in that way which is not the case

This is precisely the issue with using entities as REST resource representations. They work fine until it turns out the internal representation (entity) does not match the external representation (the missing DTO).

However, it would probably make most sense performance-wise to simply use an @ElementCollection like you mentioned, because you then don't have the double join with a join table for the many-to-many association (you could also use a one-to-many association where the parent entity and the tag value are both part of the @Id to avoid a join table, but I'm not sure it's convenient to work with. Probably better to just put a UNIQUE(parent_id, TAG) constraint on the collection table, if you need it). Regarding the not an entity error, you would need to use a native query. Assuming that you have @ElementCollection @CollectionTable(name = "TAGS") @Column(name = "TAG") on tags, then SELECT DISTINCT(TAG) FROM TAGS should do the job.

(as a side note, the DISTINCT part of the query will surely introduce some performance penalty, but I would assume the result of that query is a good candidate for caching)