I am doing a home assignment where I have 2 classes, which are User and Car. User has a OneToMany relationship to Car entity, therefore I need to implement a controller method, which will be selecting all cars of a special user (by ID)
I guess it has something todo with #Query annotation in UserRepository which extends JpaRepository.
I am using a JpaRepository, which has generics.
Example: GET method - /users/{id}/cars
The data should be received in JSON format smth like that:
{
"id":"1",
"name":"Taavet Prulskih",
"cars":[{
"Id":"1",
"make":"BMW",
"model":"760",
"numberplate":"123FFF"
},
{
"Id":"2",
"make":"Opel",
"model":"Astra",
"numberplate":"789BFX"
}]
}
Question is: how does the query will be looking like?
You can use SpringData query methods, just create new method in CarRepository like that:
List<Car> findByUser_id(String id);
More examples in Query methods documentation.
You can create an UserDTO like this:
private static class UserDTO(){
private Long id;
private String name;
private List<Cars> cars;
//getters and setters
}
With JPA you can get the user that you want, in your repository:
User findByUserId(Long id)
And then fill the DTO like this:
UserDTO dto = new UserDTO();
User user = repository.findByUserId(id);
dto.setId(user.getUserId);
dto.setName(user.getName);
dto.setCars(user.getCars); //get the list of cars of the user and sets entire list to dto.cars
return dto;//finally return dto to controller
Related
I am new in Spring and although I can convert domain entities as List<Entity>, I cannot convert them properly for the the Optional<Entity>. I have the following methods in repository and service:
EmployeeRepository:
#Query(value = "SELECT ...")
Optional<Employee> findByUuid(#Param(value = "uuid") final UUID uuid);
EmployeeService:
#Override
#LogExecution
#Transactional(readOnly = true)
public Optional<EmployeeDTO> findByUuid(UUID uuid) {
Optional<Employee> employee = employeeRepository.findByUuid(uuid);
return employee
.stream()
.map(EmployeeDTO::new)
// .orElse(null);
//.findFirst(); /// ???
}
My questions:
1. How should I convert Optional<Employee> to Optional<EmployeeDTO> properly?
2. Does Spring JPA collect the fields in the SELECT clause and map them in the service method to the corresponding DTO by matching their names? If so, does it maintain the naming e.g. employee_name to employeeName in database table and domain model class?
The mapping that happens between the output of employeeRepository#findByUuid that is Optional<Employee> and the method output type Optional<EmployeeDTO> is 1:1, so no Stream (calling stream()) here is involved.
All you need is to map properly the fields of Employee into EmployeeDTO. Handling the case the Optional returned from the employeeRepository#findByUuid is actually empty could be left on the subsequent chains of the optional. There is no need for orElse or findFirst calls.
Assuming the following classes both with all-args constructor and getters:
class Employee {
private final long id;
private final String firstName;
private final String lastName;
}
class EmployeeDTO {
private final long id;
private final String name;
private final String surname;
}
... you can perform this. Nothing else than finding a way to create EmployeeDTO from Employee's fields is needed. If the Optional returned from the employeeRepository is returned, no mapping happens and an empty Optional is returned.
#Override
#LogExecution
#Transactional(readOnly = true)
public Optional<EmployeeDTO> findByUuid(UUID uuid) {
return employeeRepository
.findByUuid(uuid) // Optional<Employee>
.map(emp -> new EmployeeDTO( // Optional<EmployeeDTO>
emp.getId(), // .. id -> id
emp.getFirstName(), // .. firstName -> name
emp.getLastName())); // .. lastName -> surname
}
Note: For Employee -> EmployeeDTO mapping I recommend picking one of these:
Create a constructor accepting Employee in EmployeeDTO allowing to map with .map(EmployeeDTO::new) (drawback: creates a dependency).
Just map with getters/setters.
Use a mapping framework such as MapStruct or any other.
There are multiple options to map your entity to a DTO.
Using projections: Your repository can directly return a DTO by using projections. This might be the best option if you don't need the entity at all. You can find everything about projections here https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#projections
Using a library like mapstruct or modelmapper to generate your mapping code
Add a constructor or static factory method to your DTO. Something like
class EmployeeDTO {
// fields here ...
public static EmployeeDTO ofEntity(Employee entity) {
var dto = new EmployeeDTO();
// set fields
return dto;
}
}
And call employee.map(EmployeeDTO::ofEntity) in your service.
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!
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
i have dto and model layer.And i want convert dto to model layer like this code.How do i fix this error?I have all getter and setter i need
model/travel class
public Travel convert(TravelDTO dto) {
this.setTravelID(dto.getTravelID());
this.setTravelCost(dto.getTravelCost());
this.setTravelStart(dto.getTravelStart());
this.setTravelEnd(dto.getTravelEnd());
this.setLocation(dto.getLocation());
this.setTravelPurpose(dto.getTravelPurpose());
this.setUser(new User().convert(dto.getUser()));
return this;
}
dto/travelDTO class
public TravelDTO convert(Travel entity) {
this.setTravelID(entity.getTravelID());
this.setTravelCost(entity.getTravelCost());
this.setTravelStart(entity.getTravelStart());
this.setTravelEnd(entity.getTravelEnd());
this.setLocation(entity.getLocation());
this.setTravelPurpose(entity.getTravelPurpose());
this.setUser(new UserDTO().convert(entity.getUser()));
return this;
}
userDto / convert code
public UserDTO convert(User entity) {
this.setUserID(entity.getUserID());
this.setFirstName(entity.getFirstName());
this.setLastName(entity.getLastName());
this.setManagerId(entity.getManagerId());
this.setPassword(entity.getPassword());
this.setRegNumber(entity.getRegNumber());
this.setUserName(entity.getUserName());
this.setDepartment(new DepartmentDTO().convert(entity.getDepartment()));
this.setTravel(new TravelDTO().convert(entity.getTravel()));
return this;
}
The error message explains the issue :). It says the entity.getUser() is returning a list of users. But the method accepts one User object.
The problem is your entity.getUser() is returning List whereas your convert method of UserDTO is expecting single User model object.
As I can see from you screenshot, UserDTO.convert method accepts argument of type User, and you are trying to pass argument with type List. I guess, the possible solution is to make Travel.getUser() return User instead of List.
UPDATE
You may iterate through your list of Users, converting each one to UserDTO and then adding it to collection, then passing it as an argument to this.setUser. Something like this: `
List<UserDTO> userDTOs = new ArrayList<>();
List<User> users = entity.getUser();
for (User user : users) {
UserDTO userDTO = new UserDTO.convert(user);
userDTOs.add(userDTO);
}
this.setUser(userDTOs);
And please pay attention that your TravelDTO class has List<User> user field. I guess, it should be List<UserDTO> users.
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