Spring Pagination sorting with cases - spring

I have a problem with Sorting in Spring. I have an enum class
public enum Category {
A,
B,
C,
D,
E,
F;
private static final List<Category> THRILLER;
static {
THRILLER = Arrays.asList(A, F, D);
}
private static final List<Category> DRAMA;
static {
DRAMA = Arrays.asList(C, E, B);
}
}
The Movie Entity:
#Entity
#Table
public class Movie {
...
private Category category;
private Timestamp realizationDate;
....
}
Now I have a rest endpoint that return movies List. I want to Sort this list according to the following order: Movies they belong to THRILLER should be displayed first. When two movies belong to the same category then this should be sorted by realization date.
#GetMapping(value = "/movies")
public ResponseEntity<PagedResources<Movie>> list(#PageableDefault() Pageable pageable) {
.......
}
I tried it with:
#SortDefault.SortDefaults({SortDefault(sort="category", direction=Sort.Direction.ASC), SortDefault(sort="realizationDate", direction=Sort.Direction.ASC)})
But this Sort the enum only alphabetically.
I need a possibility to do something like: Sort.by(Category.THRILLER.contains(movie.getCategory())). It is possible that I achieve that with Pageable or should I do it another way?

Spring Data directly only supports sorting by simple attributes.
So in order for this to work you need to provide an isThriller attribute.
You can do this with the existing object structure by using:
a) a trigger to fill the column in the database
b) use a view to compute the desired value
c) if you use Hibernate you may use the #Formula annotation to instruct it to compute the value.
In either case you are hardcoding the categories that make up thrillers in a String or in the database.
If you don't want that you could make Category an entity with an isThriller attribute which you then can query and sort by in the usual fashion.

AFAIK Sort in spring pagination sorts fields only in lexicographical order (same like in SQL, uneless you use some procedural version of SQL like PSQL) so you can't introduce some custom logic of sort.
You have two options:
introduce custom procedure/function into your DB and call it from code (ugly & bad approach)
Sort list of movies by playing with lambdas and custom comparators, after fetching it from DB (clean & simpler approach)

Related

How to fetch multiple entities by a list of natural ids with Hiberate or JPA repository?

If I have a list of natural Ids, how can I fetch all the records in the DB associated with these natural Ids at once?
All I've seen are methods that let you find an entity by a natural Id, an example of one is shown below.
#Override
public Optional<T> findBySimpleNaturalId(ID naturalId) {
Optional<T> entity = entityManager.unwrap(Session.class)
.bySimpleNaturalId(this.getDomainClass())
.loadOptional(naturalId);
return entity;
}
I am looking for a method that can take a list natural Ids and fetch all the entities with these natural Ids. My current Entity has a autogenerated UUID and a naturalId and I'd like to keep it this way.
Is there something like this below
List<Song> songs = entityManager
.unwrap(Session.class)
.byMultipleSimpleNaturalIds(Song.class)
.multiLoad(songGroup.getSongIds());
// or using the repository
customRepository.findAllByNaturalId(...)
The examples you've seen are showing you how to build them yourself, as spring does not provide individual methods for you; it knows nothing about properties in your entity other than it must have an id. If you want a findAllByNaturalId method, you have to define it in the interface.
Specifying this in your customRepository:
public List<Song> findByNaturalIdIn(List<Int> naturalIds);
Spring should generate an implementation that creates a query similar to "Select s from Song s where s.naturalId In :naturalIds".
If it doesn't, just add that JPQL query string as an annotation and it will execute it for you:
#Query(value = "Select s from Song s where s.naturalId In :naturalIds")
public List<Song> findByNaturalIdIn(List<Int> naturalIds);
Or you can write your own implementation method to execute your loadOptional calls, or any query you wish, but you still must define the method in your repository.

SpringData JPA: Query with collection of entity as parameter

I have a list of entities on which I want to perform an update, I know I could update the table with list of String/Integer.. etc as the parameter with something like
#Query("update tableName i set i.isUpdated = true where i.id in :ids")
void markAsUpdated(#Param("ids") List<Integer> itemIds);
I'm trying to avoid repeated conversion of list of entities to list of Ids for making the query in DB. I know there are deleteAll and deleteInBatch commands which accept parameter as list of entities.
How do I do this in JPA Query, I tried the following but it didn't work yet.
#Modifying(flushAutomatically = true, clearAutomatically = true)
#Query("update tableName i set i.updated = true where i in :items")
void markAsUpdated(#Param("items") List<Item> items)
The query needs ids, it doesn't know how to deal with entities.
You have multiple options:
Just pass ids to the method, the client is responsible for extracting ids.
Pass entities and use SpEL for extracting ids
As suggested in the comments use a default method to offer both APIs and to delegate from one to the other.
As for the question that came up in the comments: You can move the method for extracting the id into a single method by either have relevant entities implement an interface similar to this one:
interface WithId {
Long getId();
}
Or by passing a lambda to the method, doing the conversion for a single entity:
List<ID> extractIds(List<E> entities, Function<E, ID> extractor) {
// ...
}

Is double saving a new entity instance with a Spring data 2 JpaRepository correct?

I have two entities in a bi-directional many to many relationship.
A <-> many to many <-> B
I have an endpoint where a client can create an instance of A, and at the same time add some number of B entities to that A, by passing in an array of B entity id keys. Please keep in mind that these B entities already exist in the database. There is no business or software design case for tightly coupling their creation to the creation of A.
So class A looks like this, and B is the same, but with references to A.
#Entity
class A {
#Id
#GeneratedValue
int id;
#ManyToMany
List<B> bs;
String someValue;
int someValue2;
// With some getters and setters omitted for brevity
}
So at first try my endpoint code looks like this.
public A createA(#RequestBody A aToCreate) {
A savedA = aRepository.save(aToCreate);
savedA.getbs().forEach(b -> Service.callWithBValue(b.getImportantValue());
}
And the client would submit a JSON request like this to create a new A which would contain links to B with id 3, and B with id 4.
{
"bs": [{id:3}, {id:10}],
"someValue": "not important",
"someValue2": 1
}
Okay so everything's working fine, I see all the fields deserializing okay, and then I go to save my new A instance using.
aRepository.save(aToCreate);
And that works great... except for the fact that I need all the data associated with the b entity instances, but the A object returned by aRepository.save() has only populated the autofill fields on A, and done nothing with the B entities. They're still just hollow entities who only have their ids set.
Wut.
So I go looking around, and apparently SimpleJpaRepository does this.
#Transactional
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
And since the A entity is brand new, it only persists the A entity, but it doesn't merge it so I don't get any of the rich B data. So okay, if I modify my code to take this into account I get this.
public A createA(#RequestBody A aToCreate) {
A savedA = aRepository.save(aRepository.save(aToCreate));
savedA.getbs().forEach(b -> Service.callWithBValue(b.getImportantValue());
}
Which works just fine. The second pass through the repository service it merges instead of persists, so the B relationships get hydrated.
My question is: Is this correct, or is there something else I can do that doesn't look so ineloquent and awful?
To be clear this ONLY matters when creating a brand new instance of A, and once A is in the database, this isn't an issue anymore because the SimpleJpaRepository will flow into the em.merge() line of code. Also I have tried different CascadingType annotations on the relationship but none of them are what I want. Cascading is about persisting the state of the parent entity's view of its children, to its children, but what I want to do is hydrate the child entities on new instance creation, instead of having to make two trips to the database.
In the case of a new A, aToCreate and savedA are the same instance because that is what the JPA spec madates:
https://docs.oracle.com/javaee/6/api/javax/persistence/EntityManager.html#persist(java.lang.Object)
Make an instance managed and persistent.
Spring Data simply returns the same instance so persist/merge can be abstracted into one method.
If the B instances you wish to associate with A are existing entities then you need to fetch a reference to these existing instances and set them on A. You can do this without a database hit by using the T getOne(ID id) method of Spring Data's JpaRepository:
https://docs.spring.io/spring-data/jpa/docs/2.1.4.RELEASE/api/
You can do this in your controller or possibly via a custom deserializer.
This is what I ended up going with. This gives the caller the ability to save and hydrate the instance in one call, and explains what the heck is going on. All my Repository instances now extend this base instance.
public interface BaseRepository<T, ID> extends JpaRepository<T, ID> {
/**
* Saves an instance twice so that it's forced to persist AND then merge. This should only be used for new detached entities that need to be saved, and who also have related entities they want data about hydrated into their object.
*/
#Transactional
default T saveAndHydrate(T save) {
return this.save(this.save(save));
}
}

DataTables server-side filtering by objects in a Many to Many relationship

I have a datatables.net DataTable in my app which shows all elements from a table (All entities of class Action). This class has an attribute which is a List of Regions mapped as #ManyToMany using Spring JPA, and I'd like to be able to filter by Regions all the data. So if I choose Regions A, B and D, only show those Actions that apply to at least one of them (For example if Action 1 applies to Regions B and C)
So the controller, which looks like this...
#JsonView(value={DataTablesOutput.View.class})
#ResponseBody
#RequestMapping(value = "/rest/actions_datatable", method = RequestMethod.GET, produces = "application/json")
public DataTablesOutput<Action> getActions(#Valid DataTablesInput input, HttpServletRequest request) {
DataTablesOutput<Action> actions_datatable = actionService.getActions(input);
String[] regionIdsToFilter = request.getParameter("regionfilter").split("[,]");
return actions_datatable;
}
This is my service
#Override
public DataTablesOutput<Action> getActions(DataTablesInput input) {
return actionRepository.findAll(input);
}
And as a note, ActionRepository extends DataTablesRepository
receives the IDs for those regions I'd like to use to filter my actions, as well as the DataTablesInput object sent by the Javascript code.
What would be the best way to achieve this? It would be easier if the field I'm trying to filter was a simple String, or maybe even an object, but a list of them does look a bit harder for me.

how to get one column list in DBFlow

I am using DBflow. I have tried many times to try and make a query as follows:
public class ScoreRepository {
public List<Score> findAllScore() {
return SQLite.select()
.from(Score.class)
.queryList();
}
public List<String> findScore01() {
return SQLite.select()
.from(Score.class)
.queryList();
}
}
In my case findScore01(), this method does not work. I want to make one column String List. I do not want to make an Object List.
queryList will give you list of 'Score' Class. To get custom data,
e.g ( join of two tables, or in your case, just 1 column)
you need to make a class that extends from QueryModel. then use
.queryCustomList(customclass.class);
That will give you
List< customclass > ls;
Check this answer for more details:
Get custom Data from 2 tables
You can use queryCustomList for getting a sub table from an existing table or joining two tables. Like Farhan mentioned above you can create a QueryModel class with your required fields and use it to retrieve the score, but it still be a list of your custom model query class, and not list of strings like you needed.
Only solution I can think of is looping your result list and generate a list of strings like :
List<Score> scoreList = SQLite.select(Score_Table.score01)
.from(Score.class)
.queryList();
List<String> score01List = new ArrayList<>();
foreach(Score score: scoreList) {
score01List.add(score.getScore01());
}

Resources