I have employee class
#Entity
class Employee {
#Id
Integer id;
String name;
Integer age;
}
and another class EmployeeInfo:
class EmployeeInfo {
Integer id;
String name;
}
Now, I need to build a service to get a paginated list of EmployeeInfo by using findAll(Pageable pageable)
from the repository
#Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
what is the best way to do that? I want to avoid getting the page from findAll and create a new object of EmployeeInfo then adding it to a list in a loop
You can utilize spring-data-jpa projections.
There are many ways to use them (open/closed projections, class or interface based, etc.), but since you already have a EmployeeInfo class, it can be achieved by defining a new method in your repository:
#Repository
public interface EmployeeRepository extends JpaRepository<Employee, Long> {
Page<EmployeeInfo> getAllBy(Pageable pageable);
}
Note that your projection DTO class properties must exactly match properties in the aggregate root (entity class).
Also reference documentation suggest to define .equals() and .hashcode() methods.
Other methods can be found in the official documentation.
Related
Is it possible to use "findAll" for a JPARepository returning a Collection/List of Projections?
Example:
#Entity
public class Login {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "native")
#GenericGenerator(name = "native", strategy = "native")
private Integer id;
private String name;
private String pass;
(...)
}
public interface LoginProjection {
public String getName();
}
#Repository
public interface LoginRepository extends JpaRepository<Login, Long> {
Login findByName(String name);
#Query(value = "SELECT name FROM login", nativeQuery = true)
List<LoginProjection> findAllLoginProjection();
}
Using #Query it works! But it is not possible to use
List<LoginProjection> findAll();
Because LoginProjection it does not extends T (Login).
I was thinking if it is possible to give a "alias" for findAll like findAllXYZ that does the same thing as findAll.
Using filters it works too, but I do not want to use them:
List<LoginProjection> findAllByName(String name);
My main goal would be something like this:
#Repository
public interface LoginRepository extends JpaRepository<Login, Long> {
Login findByName(String name);
List<Login> findAll();
List<LoginProjection> findAllLoginProjection();
}
Which is pretty easy and with "zero #Query"
And add a method to the repository:
List<LoginProjection> findAllProjectedBy();
The method name can be simplified to findBy() to match the documentation example at https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections
I assume you're not using Spring Data REST, so #Projection won't help here. #pavel-molchanov showed one form of expressing the projection, another form is:
List<LoginProjection> findBy();
If you have more than one projection, you can avoid creating a method in the repository for each projection by using dynamic projections like e.g. this:
<T> List<T> findBy(Class<T> projection);
// usage e.g.
... = findBy(LoginProjection.class);
... = findBy(UserSummaryProjection.class);
if you want to be more flexible you can add to the repository this method
<T> List<T> findBy(Class<T> projection);
and then you can call it with the following
List<LoginProjection> rows = loginRepository.findBy(LoginProjection.class);
The advantage of this approach is that you can use all the projections you want using the same query
I can create a repository via defining an interface on the appropriate JPA class A like the following:
public interface ARepository extends CrudRepository<A, Long>
{
}
and I can use that in my Controller (for example) via
#Autowired
private ARepository aRepository;
and just can do things like this:
aRepository.save(..);
aRepository.findAll();
..
No problem so far.
But my problem is that I have ca. 500 JPA classes and need to access each table which means to define 500 Repositories in the style of above.
So does exist an thing to create that either dynamically via some Spring Data "magic" which from my point of view should exist otherwise the above would not be possible. It looks like this is similar to my problem.
Apart from that one more issue related to the above. I can define findBy... methods in the interface and in the background there will be generated a query method for this particular attribute. The question is also if this can be done in a dynamic way related to the previous question, cause I have groups of tables which need supplemental query methods..
There is spring-data-generator which can automatically generate the interfaces for you.
Regarding your 2nd question I don't think you that can be done in a dynamic way. Java is statically compiled and there's no way to add members dynamically. There could be a tool that generates code for those methods but if that tool generates methods for all combinations of columns you will end up with a huge amount of methods.
You can make a base abstract entity for your 500 classes an then create one repo for this class. (I think it's a common practice to have a BaseEntity class with id, version etc. for every entity in the project).
For simple repo methods (like save, findAll etc.) it will work right from the box (note - entities must have the equal id type). For example:
#Entity
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public abstarct class BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
}
#Entity
public class Entity1 extends BaseEntity {
private String name;
}
#Entity
public class Entity2 extends BaseEntity {
private String name;
}
public interface BaseEntityRepo extends JpaRepository<BaseEntity, Long> {
}
Note that BaseEntity must have #Inheritance(strategy = InheritanceType.TABLE_PER_CLASS) to prevent of using singe table base_entity for every entity. And their ids must not intersect (see #GeneratedValue(strategy = GenerationType.SEQUENCE)).
Usage:
#RunWith(SpringRunner.class)
#SpringBootTest
public class BaseEntityRepoTest {
#Autowired private BaseEntityRepo repo;
#Before
public void setUp() throws Exception {
repo.save(asList(
new Entity1("entity1"),
new Entity2("entity2")
));
}
#Test
public void readingTest() throws Exception {
List<BaseEntity> entities = repo.findAll();
assertThat(entities).hasSize(2);
}
}
Related to your second question you can use this approach:
public interface BaseEntityRepo extends JpaRepository<BaseEntity, Long> {
<T> T findById(Long id, Class<T> type);
}
Usage:
#Test
public void findById() {
final Entity1 entity1 = repo.findById(1L, Entity1.class);
final Entity2 entity2 = repo.findById(2L, Entity2.class);
assertThat(entity1).isNotNull();
assertThat(entity2).isNotNull();
}
But you can build repo query methods only for 'common' properties of inherited entities which are present in the base class. To make this method work you must move the name parameter to the BaseEntity:
<T> List<T> findAllByNameLike(String name, Class<T> type);
I have a simple REST service that returns user profile entity that has about 20 fields.
I need to implement a functionality to filter the data where last name is required but all other fields (first name, age, city, state, zip, etc. ) are optional.
Is there a way to do it using JpaRepository without creating a lot of if/else statements for every single combination of patamenters?
It is a use case for JPA criteria (available since JPA2).
In indeed as you want to write a dynamic query, above all, you don't want to hard-coded JPQL queries for each combination and you don't want concatenating chunks of JPQL either as this is error-prone and not checked at compile time.
Note that in any case (Criteria or JPQL) you should check for each possible option if the client has specified it to be able to take them into consideration in the query build.
Now, as you implement the JPARepository interface, you have two ways :
using List<T> findAll(#Nullable Specification<T> spec); provided by the JpaSpecificationExecutor interface that you can also implement in your custom repository.
Enrich the JPARepository with your own interface that defines a method findAll() and that takes as parameter an object containing values for the research. Then create a concrete class to implement JPARepository.
You would have so the ability to inject the EntityManager and to use the Criteria API.
JpaRepository interface also implements QueryByExampleExecutor interface which provides findAll method for getting data using Query by Example (QBE) technique. That method would be really applicable for your scenario and is actually ideal when entity has a lot of fields and you want user to be able to filter entities by some of them.
Let's say the entity is Person and you want to create endpoint for fetching persons whose properties are equal to the ones which are specified. That could be accomplished with the following code:
Entity class:
#Entity
public class Person implements Serializable {
private Long id;
private String firstName;
private String lastName;
private Integer age;
private String city;
private String state;
private String zipCode;
}
Controller class:
#Controller
public class PersonController {
private PersonService service;
private PersonController(PersonService service) {
this.service = service;
}
#GetMapping
public List<Person> getMatchingPersons(#RequestBody Person personFilter) {
return service.findMatchingPersons(personFilter);
}
}
Service class:
#Service
public class PersonService {
private PersonRepository repository;
private PersonService(PersonRepository repository) {
this.repository = repository;
}
public List<Person> getMatchingPersons(Person personFilter) {
return repository.findAll(Example.of(personFilter));
}
}
Repository class:
#Repository
public class PersonRepository implements JpaRepository<Person, Long> {
}
It's always best to return a DTO representation of an entity in the
REST response.
In your DTO, you can only map required fields from an entity and
ignore other optional parameters.
Check this out http://www.baeldung.com/entity-to-and-from-dto-for-a-java-spring-application
I'm having trouble to grasp, use this correctly.
I have a AbstractEntity/AbstractGraphEntity class like this:
public abstract class GraphEntity extends AbstractEntity {
#GraphId
#Getter #Setter
#JsonIgnore
protected Long nodeId;
#Getter #Setter
#Index(unique = true, primary = true)
protected String id;
}
And a BaseGraphRepository like this:
#NoRepositoryBean
public interface BaseGraphRepository<T extends GraphEntity> extends GraphRepository<T> {
T findById(String id);
Collection<T> findByIdIn(Collection<String> ids);
T deleteById(String id);
Collection<T> deleteByIdIn(Collection<String> ids);
}
The idea is to have multiple entities that are extenting GraphEntity, and use several repositories extending the BaseGraphRepository.
But each time I query on: findById, it keeps giving me null.
So I've tried using, GraphEntity as a #NodeEntity, but that's a terrible idea, since then if I do query for an ID of one kind of entity, let's say a Category, but on an ProductRepository, it will give me the GraphEntity object.
So how should I approach this ?
Thanks!
I've managed to solve the findById/findByIdIn using the Neo4jRepository as such:
public interface BaseGraphRepository<T extends GraphEntity> extends Neo4jRepository<T, String> {}
But even with the Neo4jRepository the Generated Cypher for a custom query with the GraphEntity would be wrongly generated.
MATCH (n:GraphEntity) WHERE n.`id` ...
There might be some workaround to this, or it's a bad practice, I'm not sure.
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