I have been working with MapStruct some days now and haven't yet achieved what i need.
As part of the exercises with Spring, I am writing a small app that will display information about the movies (title, description, director, etc.) and additionally the movie category.
Therefore, I created an additional Entity called Category, so that (e.g. an admin) could add or remove individual category names.
Movie Entity:
public class Movie {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String title;
private String content;
private String director;
private int year;
#ManyToMany
#Column(nullable = false)
private List<Category> category;
private LocalDate createdAt;
}
Category Entity
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String categoryName;
private LocalDate createdAt;
}
I packed it all into MapStruct and DTOs.
MovieDTORequest.java
public class MovieDTORequest {
private String title;
private String content;
private String director;
private List<Category> category;
private int year;
}
MovieDTOResponse.java
public class MovieDTOResponse {
private String title;
private String content;
private String director;
private String categoryName;
private int year;
private LocalDate createdAt;
}
And MovieMapper.java
#Mapper(componentModel = "spring")
public interface MovieMapper {
#Mapping(target = "categoryName", source = "category")
MovieDTOResponse movieToMovieDTO(Movie movie);
#Mapping(target = "id", source = "title")
#Mapping(target = "createdAt", constant = "")
Movie movieRequestToMovie(MovieDTORequest request);
#Mapping(target = "id", source = "title")
#Mapping(target = "createdAt", constant = "")
void updateMovie(MovieDTORequest request, #MappingTarget Movie target);
String map(List<Category> value);
}
However, I have a problem with Mapper. First, I got the error:
"Can't map property "List<Category> category" to "String categoryName". Consider to declare/implement a mapping method: "String map(List<Category> value)"
and when I wrote it in Mapper, I have one more error:
Can't generate mapping method from iterable type from java stdlib to non-iterable type.
I am asking for help, because I am already lost.
You should define default implementation for String map(List<Category> value) inside MovieMapper interface, what would Mapstruct use to map property List<Category> category to String categoryName. For example:
#Mapper(componentModel = "spring")
public interface MovieMapper {
#Mapping(target = "categoryName", source = "category")
MovieDTOResponse movieToMovieDTO(Movie movie);
default String map(List<Category> value){
//TODO: Implement your own logic that determines categoryName
return "Movie Categories";
}
}
Related
I have a
#Data
#Document(collection = "req_language")
public class Language implements ChangeDto {
#Id
private String id = UUID.randomUUID().toString();
private String name;
private String code;
private Boolean active = Boolean.TRUE;
private Boolean deleted = Boolean.FALSE;
private Date changeAt = Date.from(Instant.now());
private String changedBy;
private String correlationId = UUID.randomUUID().toString();
}
And
#Data
#Document(collection = "req_locale")
public class Locale implements ChangeDto {
#Id
private String id = UUID.randomUUID().toString();
private String description;
private Language language;
private String code;
private String fatherCode;
private Boolean active = Boolean.TRUE;
private Boolean deleted = Boolean.FALSE;
private Date changedAt = Date.from(Instant.now());
private String changedBy;
private String correlationId = UUID.randomUUID().toString();
}
With a simple repository
#Repository
#JaversSpringDataAuditable
public interface LocalesRepository extends MongoRepository<Locale, String>, LocalesCustom {
List<Locale> findByCode(String code);
}
If a try to use the findByCode, I receive this error:
No converter found capable of converting from type [java.lang.String] to type [XXX.Language]
When I try to use query in the LocalesCustom, for example (empty)
Query query = new Query();
List<Locale> localeList = mongoTemplate.find(query, Locale.class);
Same error
I have tried #DBRef in Language, and popped others errors (I couldn't query by the language code, query.addCriteria(Criteria.where("language.code")) in LocalesRepository.
There's a right way to do it?
I want implementation of method should to find all items,
that have a rating lower than passed as an argument. Using reviews associated with each item to calculate item rating.
Following are my classes
#Entity
public class Item {
#Id
#GeneratedValue
private Long id;
#Column(length = 100)
#NotEmpty
private String title;
#Column(length = 200)
private String description;
#OneToMany(mappedBy = "item", cascade = CascadeType.ALL, orphanRemoval = true)
Set<Review> reviews = new HashSet<>();
public Item() {
}
public Item(String title, String description) {
this.title = title;
this.description = description;
}
public Long getId() {
return id;
}
public String getTitle() {
return title;
}
public String getDescription() {
return description;
}
public Set<Review> getReviews() {
return reviews;
}
}
#Entity
public class Review {
#Id
#GeneratedValue
private Long id;
#Min(0)
#Max(10)
private Integer rating;
#Length(max=200)
private String comment;
#ManyToOne(optional = false)
private Item item;
#ManyToOne(optional = false)
private User author;
}
#Repository
#Transactional
public class ItemRepository {
#PersistenceContext
EntityManager entityManager;
public List<Item> findItemsWithAverageRatingLowerThan(Integer rating) {
return new ArrayList<>();
}
I want to find all the items who has average rating of supplied parameter.
Average of rating should be calculated from set of reviews present in Item entity
You can have a look on the following url:
JPA / Hibernate One to One Mapping Example with Spring Boot
I have the following Spring boot service for an object of type Report -
#Service
public class ReportService {
#Autowired
private ReportRepository reportRepository;
#Autowired
private UserRepository userRepository;
/*get all reports */
public List<Report> getAllReports(){
return reportRepository.findAll();
}
/*get a single report */
public Report getReport(Long id){
return reportRepository.getOne(id);
}
//other similar methods....
}
The problem arises while retrieving a single Report. If a report ID is send which doesn't exist, the following error is generated...
DefaultHandlerExceptionResolver : Failed to write HTTP message:
org.springframework.http.converter.HttpMessageNotWritableException: Could not
write JSON: Unable to find com.interact.restapis.model.Report with id 16;
nested exception is com.fasterxml.jackson.databind.JsonMappingException:
Unable to find com.interact.restapis.model.Report with id 16 (through
reference chain:
com.interact.restapis.model.Report_$$_jvst83c_1["fromUserId"])
Below is the code for my Report Controller
#RestController
public class ReportController {
#Autowired
private ReportService reportService;
//Get all reports
#GetMapping("/interactions")
public List<Report> getAllReports() {
return reportService.getAllReports();
}
//Get single report
#GetMapping("/interactions/{id}")
public ResponseEntity<Report> getReport(#PathVariable Long id) {
if(reportService.getReport(id) == null)
return new ResponseEntity<>(HttpStatus.NOT_FOUND);
return new ResponseEntity<>(reportService.getReport(id), HttpStatus.OK);
}
#PostMapping("/interactions")
public ResponseEntity<Report> addReport(#RequestBody Report report) {
Report report1 = reportService.addReport(report);
if(report1 == null)
return new ResponseEntity<>(report, HttpStatus.NOT_FOUND);
return new ResponseEntity<>(report1, HttpStatus.OK);
}
//Other request methods...
}
Below is the code for my Report Model class -
#Entity
#Table (name = "report")
#EntityListeners(AuditingEntityListener.class)
#JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
public class Report {
#Id
#Column (name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "from_user_id")
private Long fromUserId;
#Column(name = "to_user_id")
private Long toUserId;
#Column(name = "to_user_email")
private String toUserEmail;
#Column(name = "from_user_email")
private String fromUserEmail;
#Temporal(TemporalType.DATE)
#JsonFormat(pattern = "dd-MM-yyyy hh:mm:ss")
#CreatedDate
private Date createdAt;
#Column(nullable = false)
private String observation;
#Column(nullable = false)
private String context;
private String recommendation;
#Column(nullable = false)
private String eventName;
#Temporal(TemporalType.DATE)
#JsonFormat(pattern = "dd-MM-yyyy hh:mm:ss")
#Column(nullable = false)
private Date eventDate;
private boolean isAnonymous;
#Temporal(TemporalType.DATE)
#JsonFormat(pattern = "dd-MM-yyyy hh:mm:ss")
private Date acknowledgementDate;
#OneToMany(cascade = CascadeType.ALL, targetEntity = Action.class)
#JoinColumn(name = "report_id")
private List<Action> actionList;
#Value("${some.key:0}")
private int rating; //Range 0 to 4
private int type;
/*
Getter and setter methods...
*/
}
I want to know if reportRepository.getOne(Long id) returns null so that I can actually check if a particular report doesn't exist in the database. If not, how else can I implement the above?
The JpaRepository.getOne with throw EntityNotFoundException if it couldn't find a record with the given id.
You can use CrudRepository.findById (JpaRepository is a subclass of CrudRepository) which will return an Optional<Report> which can be empty if there are no record for the given id. You can use Optional.isPresent() to check whether it a Report is available or not and take actions accordingly.
Create a method in your ReportRepository.
It will return Report by matched id else return null.
public Optional<Report> findById(Long id);
Note: findById(Long id); should match with the property name in your Report entity.
I am assuming your Report entity is as follows:
public class Entity{
private Long id;
...
}
I have a Post entity
#Entity
public class Post {
#Id
private UUID id;
#NotNull
private String title;
#NotNull
private String content;
#NotNull
private String identifier;
#NotNull
private String category;
#NotNull
#Column(name = "created_at")
private Date createdAt;
#NotNull
#Column(name = "updated_at")
private Date updatedAt;
public Post (){
}
public Post (String title, String content, String category){
this.title = title;
this.content = content;
this.category = category;
}
// rest of the getters and setters
}
And this is my Comment entity:
#Entity
public class Comment {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private UUID id;
#NotNull
private String name;
#NotNull
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer identifier;
#NotNull
private String email;
#NotNull
private String content;
#NotNull
#ManyToOne
#JoinColumn(name = "post_id")
private Post postId;
#NotNull
#Column(name = "created_at")
private Date createdAt;
public Comment() {
}
public Comment(String name, String email, String content){
this.name = name;
this.email = email;
this.content = content;
}
}
And this is my post controller:
#RestController
#RequestMapping("/posts")
public class PostController {
private String getIdentifier(String str){
return String.join("-", str.split(" "));
}
#Autowired
private PostService postService;
#RequestMapping(value = "", method = {GET, HEAD})
public List<Post> getAllPosts(){
return postService.getAllPosts();
}
#RequestMapping(value = "", method = {POST, OPTIONS})
public Post addNewPost(#RequestBody Post post){
post.setId(UUID.randomUUID());
post.setIdentifier(this.getIdentifier(post.getTitle()));
post.setCreatedAt(new Date());
post.setUpdatedAt(new Date());
return postService.savePost(post);
}
#RequestMapping(value = "/{id}", method = {GET, HEAD})
public Post getOnePost(#PathVariable UUID id){
return postService.getOne(id);
}
#RequestMapping(value = "/{id}", method = DELETE)
public void deleteOnePost(#PathVariable UUID id){
postService.deleteOnePost(id);
}
}
My question is how do I fetch all the comments for each individual post, whenever I fetch all the posts?
Sorry, I come from a NoSQL background, so this is a bit daunting at first.
What you need to do is to create a bidirectional #OneToMany association from the Post to Comments:
add a field in Post class
#OneToMany(
mappedBy = "postId",
cascade = CascadeType.ALL
)
private List<Comments> comments = new ArrayList<>();
From now on, when you get Post from the database, Comments will be fetched at the same time.
i have an jpa entity like this.
#Entity
#Table(name = "location")
#Data
public class Location {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "LOCATION_ID", unique = true)
#NotEmpty(message = "Please Enter Location ID")
private String name;
#Column(name = "LOCATION_DESCRIPTION")
#NotEmpty(message = "Please Enter Location Description")
private String description;
#ManyToOne
#NotNull(message = "Please Choose a Building")
Building building;
#Version
Long version;
}
and the repository like this.
public interface LocationRepository extends PagingAndSortingRepository<Location, Long> {
Location findByName(#Param("name") String name);
}
i am using spring data rest i am able to create location with rest api by providing the following payload
{
"name":"adminxxxxx","description":"adminxxx" , "building": "http://localhost:8080/buildings/2"
}
now i am trying to write my custom controller which will persist the entity. this is my custom controller
#ExposesResourceFor(Location.class)
#RepositoryRestController
#BasePathAwareController
public class LocationController {
#Autowired
LocationRepository locationDao;
#Autowired
LocationResourceAssembler resourceAssembler;
#Value("${buildings.error.messages.uniqueconstraintviolation}")
String uniqueConstrainMessage;
static final String TAG = LocationController.class.getSimpleName();
#RequestMapping(value="locations",method = org.springframework.web.bind.annotation.RequestMethod.POST)
public ResponseEntity<?> save(#RequestBody #Valid Location location) {
try {
location = locationDao.save(location);
LocationResource b = resourceAssembler.toResource(location);
return ResponseEntity.ok().body(b);
} catch (DataIntegrityViolationException e) {
if (locationAlreadyExists(location.getName()))
throw new LocationAlreadyExistException(uniqueConstrainMessage, location);
else
throw new RuntimeException("Some Error Occured");
}
}
i am getting this error
exception is com.fasterxml.jackson.databind.JsonMappingException: Can not construct instance of com.alamdar.model.Building: no String-argument constructor/factory method to deserialize from String value ('http://localhost:8080/buildings/2')
at [Source: java.io.PushbackInputStream#5d468b16; line: 3, column: 60] (through reference chain: com.alamdar.model.Location["building"])</div></body></html>
can anyone please help?
I am not sure why you are writing a custom controller however the issue would appear to be that you do not have a default no args constructor so Jackson cannot instantiate an instance.
This is because you are using Lombok's #Data annotation:
https://projectlombok.org/features/Data.html
You should also annotate you class with #NoArgsConstructor to have a default no-args constructor generated:
#Entity
#Table(name = "location")
#Data
#NoArgsConstructor
public class Location {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "LOCATION_ID", unique = true)
#NotEmpty(message = "Please Enter Location ID")
private String name;
#Column(name = "LOCATION_DESCRIPTION")
#NotEmpty(message = "Please Enter Location Description")
private String description;
#ManyToOne
#NotNull(message = "Please Choose a Building")
Building building;
#Version
Long version;
}