Simple Tagging Implementation with Spring Data JPA/Rest - spring-data-jpa

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?

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)

Related

Is there a way to fetch an #Embedded field lazily?

I'm having some trouble trying to figure out how to set up a class that has an #Embedded field that must be fetched lazily. I tried to annotate the field with #Basic(fetch = FetchType.LAZY), but it causes the persistence API to treat the field as a basic type that implements Serializable, so it maps the field to a BYTEA field in the database (postgresql). I tested it on Derby too, and the same happens.
I also tried to annotate the fields of the #Embeddable class individually with #Basic(fetch = FetchType.LAZY) instead of annotating the #Embedded field of the entity that has it. The generated schema is correct in this case, but the fields are fetched eagerly when I load instances of the entity.
My understanding is that the #Basic annotation is used on basic fields/properties only, so the first case is expected. But why the fields of the #Embeddable class are fetched eagerly even if they are annotated with #Basic(fetch = FetchType.LAZY)? Also, I know that the fetch strategy can be specified by the #Basic and relationship annotations, but is there any other way to specify that fields should be fetched lazily? I'm using EclipseLink 2.6, but let me know if the behaviour is different for other versions of EclipseLink or for another provider.
Directly you cant, because of how #Embedded objects work, but by setting attributes in the object it should work.
#Basic(fetch=FetchType.LAZY)
Remember that lazy should be use only on collections or big objects, and that setting fetch type on lazy is only a clue for provider, it doesn't mean that it will always fetch it lazy rather than eager.

How to find entity with any value of argument

I'm using Spring JPA with hibernate and have an entity with a lot of properties, let's say it has five; as illustrated below:
#Entity
#Table
public class MyEntity{
Object properties1;
Object properties2;
Object properties3;
Object properties4;
Object properties5;
}
Spring provides a very nice feature; it generates JPQL query based on method name in the repository. For example:
List<MyEntity> findByProperties3(Object properties3);
In my situation, users have an html form to search for a MyEntity. This html form has five fields respectively which correspond to the five properties on the MyEntity class. User also can leave any field empty so that the search will include all values of this property in the query.
I have idea of how to implement that but it would break away from the Spring convenience methods and need a lot of coding. My idea is to create a method on the repository interface for all possibilities: user leaving all field empty, filling one field, two fields, etc; up to five fields. Unfortunately, that means that there would be:
possibilities. How can I avoid this path? Ideally, I would create just one method:
List<MyEntity> findByProperties1andProperties2andProperties3andProperties4andProperties5(Object p1,Object p2,Object p3,Object p4,Object p5)
But, if some of the pXs are null, then Spring JPA will explicitly find MyEntitys with propertiesX equal to null, as opposed to all possible values of, say, 1, 2, 3, 4, 5 and null.
===========EDIT===============
I am still hoping to get an answer from someone about a Spring JPA solution, but I've used javax.persistence.criteria.CriteriaBuilder in the mean time for my solution.

JPA 2.0 retrieve entity by business key

I know there have been a number of similar posts about this, but I couldn't find a clear answer to my problem.
To make it as simple as possible, say I have such an entity:
#Entity
public class Person implements Serializable {
#Id
private Long id; // PK
private String name; // business key
/* getters and setters */
/*
override equals() and hashCode()
to use the **name** field
*/
}
So, id is the PK and name is the business key.
Say that I get a list of names, with possible duplicates, which I want to store.
If I simply create one object per name, and let JPA make it persistent, my final table will contain duplicate names - Not acceptable.
My question is what you think is the best approach, considering the alternatives I describe here below and (especially welcome) your own.
Possible solution 1: check the entity manager
Before creating a new person object, check if one with the same person name is already managed.
Problem: The entity manager can only be queried by PK. IS there any workaround Idon't know about?
Possible solution 2: find objects by query
Query query = em.createQuery("SELECT p FROM Person p WHERE p.name = ...");
List<Person> list = query.getResultList();
Questions: Should the objects requested be already loaded in the em, will this still fetch from database? If so, I suppose it would still be not very efficient if done very frequently, due to parsing the query?
Possible solution 3: keep a separate dictionary
This is possible because equals() and hashCode() are overridden to use the field name.
Map<String,Person> personDict = new HashMap<String,Person>();
for(String n : incomingNames) {
Person p = personDict.get(n);
if (p == null) {
p = new Person();
p.setName(n);
em.persist(p);
personDict.put(n,p);
}
// do something with it
}
Problem 1: Wasting memory for large collections, as this is essentially what the entity manager does (not quite though!)
Problem 2: Suppose that I have a more complex schema, and that after the initial writing my application gets closed, started again, and needs to re-load the database. If all tables are loaded explicitly into the em, then I can easily re-populate the dictionaries (one per entity), but if I use lazy fetch and/or cascade read, then it's not so easy.
I started recently with JPA (I use EclipseLink), so perhaps I am missing something fundamental here, because this issue seems to boil down to a very common usage pattern.
Please enlighten me!
The best solution which I can think of is pretty simple, use a Unique Constraint
#Entity
#UniqueConstraint(columnNames="name")
public class Person implements Serializable {
#Id
private Long id; // PK
private String name; // business key
}
The only way to ensure that the field can be used (correctly) as a key is to create a unique constraint on it. You can do this using #UniqueConstraint(columnNames="name") or using #Column(unique = true).
Upon trying to insert a duplicate key the EntityManager (actually, the DB) will throw an exception. This scenario is also true for a manually set primary key.
The only way to prevent the exception is to do a select on the key and check if it exists.

A select JPA query that ignores the related entities of one-to-many relation, is that possible?

I am new in JPA, so excuse me if my question seems basic.
I have an entity called User, which is related to a list of other entities like follow:
#OneToMany(cascade = CascadeType.ALL , mappedBy = "user")
private List<session> sessionList;
In a controller class, I defined a find method in a RESTFull manner like follow:
#GET
#Path("/Users")
#Produces("application/json")
public List<UserDevice> findAllUsers()
{
return em.createQuery("SELECT u FROM User u").getResultList();
}
The returned result contains all the sessions of the users which is normal, but make the result huge though I just want to retrieve the basic information of the users (all the simple columns).
My question is: is it possible to ignore the related entities and just keep the columns of the actual entity? Thank you very much
Unless you explicitely map the association as eagerly loaded (using #OneToMany(fetch = FetchType.EAGER)), the above query should only return the fields of the users, and should not load their sessionList.
If the sessions are loaded, then the association is marked as eagerly loaded, or you load them lazily by calling a method of the List<Session>.

possible to return only one column using JPA

I have an Open JPA entity and it successfully connects a many-to-many relationship. Right now I successfully get the entire table, but I really only want the ID's from that tables. I plan on calling the database later to reconstruct the entities that I need (according to the flow of my program).
I need only the ID's (or one column from that table).
1) Should I try and restrict this in my entity beans, or in the stateless session beans that I will be using to call the entity beans
2) If I try and do this using JPA, how can I specify that I only get back the ID's from the table, instead of the whole table? So far looking online, I don't see a way that you can do this. So I am guessing there is no way to do this.
3) If I simply just manipulate the return values, should I create a separate class that I will be returning to the user that will return only the required id list to the user?
I could be completely wrong here, but from the looks of it, I don't think there is a simple way to do this using JPA and I will have to return a custom object instead of the entity bean to the user (this custom object would only hold the id's as opposed to the whole table as it currently does)
Any thoughts... I don't think this is really relevant, but people are always asking for code, so here you go...
#ManyToMany(fetch=FetchType.EAGER)
#JoinTable(name="QUICK_LAUNCH_DISTLIST",
joinColumns=#JoinColumn(name="QUICK_LAUNCH_ID"),
inverseJoinColumns=#JoinColumn(name="LIST_ID"))
private List<DistributionList> distributionlistList;
Currently how I get the entire collection of records. Remember I only want the id...
try
{
//int daSize = 0;
//System.out.println("Testing 1.2..3...! ");
qlList = emf.createNamedQuery("getQuickLaunch").getResultList();
}
This is how I call the Entity beans. I am thinking this is where I will have to programatically go through and create a custom object similar to the entity bean (but it just has the ID's and not the whole table, and attempt to put the id's in there somewhere.
What are your thoughts?
Thanks
I believe I just figured out the best solution to this problem.
This link would be the answer:
my other stack overflow answer post
But for the sake of those too lazy to click on the link I essentially used the #ElementCollection attribute...
#ElementCollection(fetch=FetchType.EAGER)
#CollectionTable(name="QUICK_LAUNCH_DISTLIST",joinColumns=#JoinColumn(name="QUICK_LAUNCH_ID"))
#Column(name="LIST_ID")
private List<Long> distListIDs;
That did it.
Sounds like you want something like this in your quickLaunch class:
#Transient
public List<Integer> getDistributionListIds () {
List<Integer> distributionListIds = new LinkedList<Integer>();
List<DistributionList> distributionlistList = getDistributionlistList();
if (distributionlistList != null) {
for (DistributionList distributionList : distributionlistList)
distributionListIds.add(distributionList.getId());
}
return distributionListIds;
}
I had to guess a little at the names of your getters/setters and the type of DistributionList's ID. But basically, JPA is already nicely handling all of the relationships for you, so just take the values you want from the related objects.