Fetch only some attributes from table using jpa findall java - java

I want to select just a few columns from a table.. The catch is that I'm using a specification and pagination from the front end filter, and I don't think I can concatenate those with criteriabuilder. My original idea was to create a #MappedSuperClass with the attributes I wanted (in this case, just the id and date), and fetch using a dao repository from an empty subclass. I have done something similar to this before and it worked, but the subclasses used different tables so it's a different ball game. In this case, since both subclasses use the same table, and there's nothing to differentiate between the classes other than one doesn't have any attributes, it keeps fetching the original bigger class. I want to avoid creating a view with just the columns I want or processing the data in the backend after the fetching, but I think that's the only possible solution.
Superclass
#MappedSupperClass
public class Superclass
{
#Column( name = "id" )
private Integer id;
#Column( name = "date" )
private Date date;
}
Original Subclass
#Entity
#Table( name = "table" )
public class OriginalSubclass
extends Superclass
{
#Column( name = "code" )
private Integer code;
#Column( name = "name" )
private String name;
}
New Subclass
#Entity
#Table( name = "table" )
public class NewSubclass
extends Superclass
{
}
I created a new dao for the new subclass
#Repository
public interface NewSubclassDao
extends JpaRepository<NewSubclass, Integer>, JpaSpecificationExecutor<NewSubclass>
{
}
Is there a way to get only the attributes I want with something similar to my idea?
Or is it possible to do it with criteriabuilder?
If none of the options are viable, would you prefer to use a view or process the data?
EDIT
To make it perfectly clear, I want Spring to bring me only the id and date attributes, using JPA findAll or something very similar, without messing the pagination or filter from the Specification.

You should be able to use #Query to do something like:
#Repository
#Transactional(readOnly = true)
public interface NewSubclassDao
extends JpaRepository<NewSubclass, Integer>, JpaSpecificationExecutor<NewSubclass>
{
#Query("SELECT table.code FROM #{#entityName} table")
public Set<Integer> findAllCodes();
}

There are many ways to do this, but I think this is a perfect use case for Blaze-Persistence Entity Views.
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(User.class)
public interface UserDto {
#IdMapping
Long getId();
String getName();
Set<RoleDto> getRoles();
#EntityView(Role.class)
interface RoleDto {
#IdMapping
Long getId();
String getName();
}
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
UserDto a = entityViewManager.find(entityManager, UserDto.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features
Page<UserDto> findAll(Pageable pageable);
The best part is, it will only fetch the state that is actually necessary!

Related

How to create an EntityView with field that is not mapped to Entity

I have a scenario where i have to fine multiple queries to get the result.
Since EntityView is interface and if i want to send API response with some custom attributes that is not defined in Entity, how can i do that?
#EntityView(DealListing.class)
public abstract class DealListingBuyerView {
abstract Long getId();
abstract Long getDealTypeId();
abstract DealRelatedType getDealRelatedType();
abstract String getRelatedCode();
abstract DealStatus getDealListingStatus();
abstract String getDealListingCompletionRate();
abstract DealRefDataBuyerView getDealRefData();
}
The last attribute DealRefDataBuyerView is not a field in the Entity
I don't know where the value comes from, so I can only guess. You have various mapping annotations that you can use to access values or use expressions to model what you need like e.g. #Mapping. See the documentation for more details. Here is an example where you would join the data based on some custom join condition DealRef.deal.id = DealListing.id:
#EntityView(DealListing.class)
public interface DealListingBuyerView {
#IdMapping
Long getId();
Long getDealTypeId();
DealRelatedType getDealRelatedType();
String getRelatedCode();
DealStatus getDealListingStatus();
String getDealListingCompletionRate();
#Mapping("DealRef[deal.id = VIEW(id)]")
DealRefDataBuyerView getDealRefData();
}

Select with join into custom type

I have entities that look like the following:
#Entity
#Data
#NoArgsConstructor
#AllArgsConstructor
public class MyEntity {
#Id
private UUID id;
#OneToMany(targetEntity = Relation.class, fetch = FetchType.EAGER)
private List<Relation> relatedList;
}
#Entity
#Data
public class Relation {
#Id
private UUID id;
}
In addition, I have another type:
#Data
public class OtherType extends MyEntity {
private String otherField;
public OtherType(UUID id, List<Relation> relations, String otherField) {
super(id, relations);
this.otherField = otherField;
}
}
What I want to do now is to select the objects in the table of MyEntity together with some additional info (otherField) into an object of type OtherType:
select e.id, e.relatedList, 'otherStuff' as otherField from MyEntity e
If I use this query with HQL, it converts e.relatedList to . as col_x_x_, which obviously is a syntax error. I was trying to use a native query, but that just says that OtherType is not an Entity. If I use a NamedNativeQuery with a resultSetMapping, it can't map a list of values to a Collection (No dialect mapping for JDBC type 1111). What I also tried is use the postgres array_agg function to get only an array of IDs for my relation, but that can't be mapped either. Is there any way to achieve this except defining a constructor in OtherType that accepts a single value instead of a list, doing an actual real SQL join (where every instance of Relation adds another MyEntity row), and mapping that afterwards?
This is a perfect use case for Blaze-Persistence Entity Views.
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. Since the attribute name is used as default mapping, you mostly don't need explicit mappings as 80% of the use cases is to have DTOs that are a subset of the entity model.
The interesting part for you is, that it supports collections. A sample model could look like the following:
#EntityView(MyEntity.class)
public interface MyEntityView {
#IdMapping
UUID getId();
String getOtherField();
List<RelationView> getRelations();
}
#EntityView(Relation.class)
public interface RelationView {
#IdMapping
UUID getId();
}
Querying is a matter of applying the entity view to a query, the simplest being just a query by id.
MyEntityView p = entityViewManager.find(entityManager, MyEntityView.class, id);
The Spring Data integration allows you to use it almost like Spring Data Projections: https://persistence.blazebit.com/documentation/entity-view/manual/en_US/index.html#spring-data-features

Spring Data REST QueryDSL arbitrary binding

I'm experimenting with Spring Data REST and so far it's going relatively well. I'm able to query and manipulate the entities, and I have reached a point where I'd like to filter the retrieved data by a variable number of parameters. For this purpose I've been reading and decided on QueryDSL which is integrated nicely with Spring, and it works (almost) flawlessly when using fields from the entities.
However, my filtering form contains some parameters which have no direct mapping to the entity, leading to this question. For the sake of brevity, I'll be using an over-simplified example, hence my using of a persons's age instead of birth-date & etc.
Supposing we have the following Person entity:
#Data
#NoArgsConstructor
#Entity
public class Person {
#Id
#GeneratedValue
private UUID id;
private String name;
private String lastName;
private Integer age;
}
... and the appropriate repo
#RepositoryRestResource
public interface PersonRepository extends CrudRepository<Person, UUID>, QuerydslPredicateExecutor<Person>, QuerydslBinderCustomizer<QPerson> {
#RestResource
Page<Person> findAll(#QuerydslPredicate Predicate predicate, Pageable pageable);
#Override
default void customize(QuerydslBindings bindings, QPerson person) {
bindings.bind(String.class).first((SingleValueBinding<StringPath, String>) StringExpression::containsIgnoreCase);
}
}
... one can access and filter persons by name or last name (case insensitive) via http://<server>/persons?name=whatever, so far so good.
Next step, I would like to see only the people that are "pensionable", let's say over 65 years old, so the URL would look like http://<server>/persons?pensionable=true. However, pensionable is not an attribute in the Person entity, so adding it as a request param doesn't do anything.
I've been trying to figure out how this can be achieved or if this is currently a limitation of the framework(s), but my searches haven't been successful so far. Eventually via trial and error, I've come up with something that seems to work but feels more like a hack:
Create a different PersonExtendedFilter bean (not entity) which includes the extra/arbitrary params:
#Data
#NoArgsConstructor
public class PersonExtendedFilter{
private Boolean pensionable;
}
... create a BooleanPath using the above, and use it to define a binding inside the repo's customize method:
#Override
default void customize(QuerydslBindings bindings, QPerson person) {
bindings.bind(String.class).first((SingleValueBinding<StringPath, String>) StringExpression::containsIgnoreCase);
BooleanPath pensionable = new PathBuilder<>(PersonExtendedFilter.class, "personExtendedFilter").getBoolean("pensionable");
bindings.bind(pensionable).first((path, value) -> new BooleanBuilder().and(value ? person.age.gt(65) : person.age.loe(65)));
}
Bottom line, I'm wondering whether there is an elegant way of doing this or if I missing something, be it from a logical POV, a RTFM one, or something else.

Filter child object in Spring Data Query

I have a following domain model:
Playlist -> List<PlaylistItem> -> Video
#Entity
class Playlist{
// id, name, etc
List<PlaylistItem> playlistItems;
// getters and setters
}
#Entity
class PlaylistItem{
// id, name, etc.
Video video;
// getters and setters
}
#Entity
class Video{
// id, name, etc.
boolean isDeleted;
// getters and setters
}
And my repository:
public interface PlaylistRepository extends JpaRepository<Playlist, Long> {
List<Playlist> findAll();
}
Now, how do I return a playlist with only existing videos, ie, if there are three videos in the database assigned to that playlist item and one of those videos has isDeleted set to true, then I need to get only two items instead.
All you have to do is declare this method on your PlaylistRepository interface:
List<Playlist> findByPlaylistItemsVideoIsDeleted(boolean isDeleted);
And call it like this:
playListRepository.findByPlaylistItemsVideoIsDeleted(false);
That will return all playlist with videos that are not removed.
You may have already resolved this issue, but I thought I would contribute this in hopes it might help you, or anyone else visiting this page.
Using Spring JPA Specifications, you would:
Enable your PlaylistRepository to use JPA Specifications
Write the Specification as a reusable method
Make use of the Specification as the query
Here are the details.
1. Implement JpaSpecificationExecutor
Update PlaylistRepository to implement JpaSpecificationExecutor. This adds find* methods that accept Specification<T> parameters to your PlaylistRepository.
public interface PlaylistRepository extends JpaRepository<Playlist, Long>,
JpaSpecificationExecutor<Playlist> {
}
2. Create the Specification
Create a class with a static method for use in creating a reusable Specification.
public final class PlaylistSpecifications {
private PlaylistSpecifications() {}
public static Specification<Playlist> hasExistingVideos() {
return (root, query, cb) -> {
return cb.equal(root.join("playlistItems").join("video")
.get("isDeleted"), false);
};
}
}
Using root.join (and subsequent joins) is similar to using JOIN in SQL. Here, we are joining on the fields of classes, instead of on columns of tables.
3. Issue the Query
I don't know how you plan to issue your query, but below is an example of how it could be done in a "service" class:
#Service
public class PlaylistService {
#Autowired
private PlaylistRepository playlistRepository;
public List<Playlist> findPlaylistsWithExistingVideos() {
Specification<Playlist> spec = PlaylistSpecifications.hasExistingVideos();
return playlistRepository.findAll(spec);
}
}
Hope this helps!
Maksim, you could use the #query annotation like this :
public interface PlaylistRepository extends JpaRepository<Playlist, Long> {
#Query("select playlist from Playlist playlist
fetch join playlist.playlistItems itens
fetch join itens.video as video
where video.isDeleted = false")
List<Playlist> findAll();
}
Or even better way :
public interface PlaylistRepository extends JpaRepository<Playlist, Long> {
#Query("select playlist from Playlist playlist
fetch join playlist.playlistItems itens
fetch join itens.video as video
where video.isDeleted = :hasVideo ")
List<Playlist> findPlayList(#Param("hasVideo") boolean hasVideo);
}
You can look into Spring Data Specifications. You use them by calling repository.findAll(s);
Specifications allow you add on arbitrary conditions to your query, including the filter you want to add. Another nice thing about Specifications is that they can be type-safe. See here:
http://docs.spring.io/spring-data/jpa/docs/current/reference/html/#specifications

EclipseLink JPA inheritance without discriminator column

I have a Client and Affiliate class, inheriting from Person class. Joined inheritance strategy type is being used - each of them sharing primary key with the parent class. As there's no discriminator column we chose to use DescriptorCustomizer and ClassExtractor. But it doesn't really give any idea how it works, also, the code doesnt seem to compile. It would be nice if someone gives a nice example with code snippet for understanding.
According to the mentioned documentation:
If you are mapping to an existing database, and the tables do not have
a discriminator column you can still define inheritance using the
#ClassExtractor annotation or <class-extractor> element. The class
extractor takes a class that implements the ClassExtractor
interface. An instance of this class is used to determine the class
type to use for a database row. The class extractor must define a
extractClassFromRow() method that takes the database Record and
Session.
we need to annotate the root entity in a hierarchy with user defined using the class extractor:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
#ClassExtractor(PersonClassExtractor.class)
public abstract class Person {
#Id #GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
private int age;
// ...
}
Notice that we don't use #Customizer annotations since as this is not required in case of JOINED inheritance strategy:
If a class extractor is used with SINGLE_TABLE inheritance, the rows
of the class type must be able to be filtered in queries. This can be
accomplished by setting an onlyInstancesExpression() or
withAllSubclassesExpression() for branch classes. These can be set
to Expression objects using a DescriptorCustomizer.
The class extractor must be able to determine and return the class type from the database row.
In general we need a replacement of a discriminator column, i.e.
column name unique for a given entity type among others
criteria based on values of a given column of the root entity
Suppose that each of inherited entity type in a hierarchy has a column with unique name:
#Entity
public class Client extends Person {
#Column(name = "CLIENT_SPECIFIC")
private String clientSpecific;
// ...
}
#Entity
public class Affiliate extends Person {
#Column(name = "AFFILIATE_SPECIFIC")
private float affiliateSpecific;
// ...
}
then class extractor may look as follows:
public class PersonClassExtractor extends ClassExtractor {
#Override
public Class<?> extractClassFromRow(Record databaseRow, Session session) {
if (databaseRow.containsKey("CLIENT_SPECIFIC")) {
return Client.class;
} else if (databaseRow.containsKey("AFFILIATE_SPECIFIC")) {
return Affiliate.class;
} else {
return Person.class; // this should never happen
}
}
}
retrieve a list of clients and affiliates
List<Person> polymorphicResults = em.createQuery("SELECT p FROM Person p")
.getResultList();
retrieve a list of affiliates or clients respectively
List<Affiliate> concreteResults = em.createQuery("SELECT a FROM Affiliate a")
.getResultList();
List<Client> concreteResults = em.createQuery("SELECT c FROM Client c")
.getResultList();

Categories

Resources