I have a document structure which has some generic class. For writing to mongodb everything is fine. But when reading documents from mongodb spring data converts document into object falsely. It converts a subdocument with another type. Both types (actual subcollection type and falsely converted type) are inherit from same abstract class.
Model Classes:(getter setters are generated by lombok )
#Data
public abstract class CandidateInfo {
private String _id;
}
#Data
public class CandidateInfoContainer<E extends CandidateInfo> {
private String _id;
private int commentCount = 0;
#Valid
private List<E> values = new ArrayList<>();
}
#Data
public class Responsibility extends CandidateInfo {
#NotNull
private String responsibilityId;
#ReadOnlyProperty
private String responsibilityText;
}
#Data
public class Experience extends CandidateInfo {
#Valid
private CandidateInfoContainer<Responsibility> responsibilities;
}
#Document
#JsonInclude(JsonInclude.Include.NON_NULL)
#Data
public class Candidate {
private String _id;
#Valid
private CandidateInfoContainer<Experience> experiences;
}
And if you create a mongoRepository like below:
#Repository
public interface CandidateRepository extends MongoRepository<Candidate,String>{
}
And use it like:
#Autowired
private CandidateRepository candidateRepository;
Candidate candidate = candidateRepository.findOne("documentId");
Then spring data mongo mapping converter creates candidates.experiences.responsibilities.values list as Experince list but it should be Responsibility list.
You can find a demo project in this link and more information about the issue. Can anyone point out what is wrong? Otherwise i have to write my own converter(demo has one)
If there is any unclear thing, you can ask.
Thanks.
I open an issue in spring-data-mongo here. Appareantly I caught a bug! Thanks everyone
Related
According to this post ResourceAssembler is changed to RepresentationModelAssembler
I have this code which is using Spring HATEOAS 1.0:
import org.springframework.hateoas.ResourceAssembler;
public class BaseAssembler<T extends BaseTransaction, D extends BaseResource>
implements ResourceAssembler<T, D> {
...
}
After migration to implementation 'org.springframework.boot:spring-boot-starter-hateoas:2.6.4'
I changed it to:
public class BaseAssembler<T extends BaseTransaction, D extends BaseResource>
implements RepresentationModelAssembler<T, D> {
.........
}
But I get error:
Type parameter 'D' is not within its bound; should extend 'org.springframework.hateoas.RepresentationModel<?>'
Do you know how I can fix this issue?
The compiler is reporting that the type parameter D is not within its bound in your definition:
public class BaseAssembler<T extends BaseTransaction, D extends BaseResource>
implements RepresentationModelAssembler<T, D> {
.........
}
In other words, it means that you cannot use D extends BaseResource to implement RepresentationModelAssembler<T, D> (note the type parameter D here) because that type should extend 'org.springframework.hateoas.RepresentationModel<?>'.
RepresentationModelAssembler gives you the ability to convert between domain types, your entities, to RepresentationModels, a based class conceived to enrich your DTOs to collect links.
It is defined as follows:
public interface RepresentationModelAssembler<T, D extends RepresentationModel<?>>
Note again the definition of the type parameter D.
In your code you need to use something like:
public class BaseAssembler<T extends BaseTransaction, D extends RepresentationModel<?>>
implements RepresentationModelAssembler<T, D> {
.........
}
Please, consider read for instance some this or this other article, they provide a great variety of examples and uses cases about showcasing how you can implement the desired behavior.
For example, given the following entity, extracted from one of the cited articles:
#Entity
public class Director {
#Id
#GeneratedValue
#Getter
private Long id;
#Getter
private String firstname;
#Getter
private String lastname;
#Getter
private int year;
#OneToMany(mappedBy = "director")
private Set<Movie> movies;
}
And the following DTO:
#Builder
#Getter
#EqualsAndHashCode(callSuper = false)
#Relation(itemRelation = "director", collectionRelation = "directors")
public class DirectorRepresentation extends RepresentationModel<DirectorRepresentation> {
private final String id;
private final String firstname;
private final String lastname;
private final int year;
}
Your RepresentationModelAssembler will look like:
#Component
public class DirectorRepresentationAssembler implements RepresentationModelAssembler<Director, DirectorRepresentation> {
#Override
public DirectorRepresentation toModel(Director entity) {
DirectorRepresentation directorRepresentation = DirectorRepresentation.builder()
.id(entity.getId())
.firstname(entity.getFirstname())
.lastname(entity.getLastname())
.year(entity.getYear())
.build();
directorRepresentation.add(linkTo(methodOn(DirectorController.class).getDirectorById(directorRepresentation.getId())).withSelfRel());
directorRepresentation.add(linkTo(methodOn(DirectorController.class).getDirectorMovies(directorRepresentation.getId())).withRel("directorMovies"));
return directorRepresentation;
}
#Override
public CollectionModel<DirectorRepresentation> toCollectionModel(Iterable<? extends Director> entities) {
CollectionModel<DirectorRepresentation> directorRepresentations = RepresentationModelAssembler.super.toCollectionModel(entities);
directorRepresentations.add(linkTo(methodOn(DirectorController.class).getAllDirectors()).withSelfRel());
return directorRepresentations;
}
}
In terms of your interfaces and object model:
#Entity
public class Director extends BaseTransaction{
#Id
#GeneratedValue
#Getter
private Long id;
#Getter
private String firstname;
#Getter
private String lastname;
#Getter
private int year;
#OneToMany(mappedBy = "director")
private Set<Movie> movies;
}
public class DirectorRepresentationAssembler
extends BaseAssembler<Director, DirectorRepresentation>
implements RepresentationModelAssembler<Director, DirectorRepresentation> {
//... the above code
}
DirectorRepresentation is the same as presented above.
The Spring HATEOAS reference guide itself provides some guidance as well about the changes performed in Spring HATEOAS 1.0 and about how to migrate from the previous version. It even includes a script that may be of help.
In any case, as indicated above, in your use case you only need to modify the BaseAssembler interface to be defined in terms of the type D extends RepresentationModel<?>; then try relating in some way BaseResource to RepresentationModel or get rid of BaseResources and use RepresentationModels instead.
For example, you couild try defining BaseResource as follows:
public class BaseResource extends RepresentationModel<BaseResource>{
// your implementation
}
Then, the bound will be right:
public class BaseAssembler<T extends BaseTransaction, D extends BaseResource>
implements RepresentationModelAssembler<T, D> {
// your implementation
}
With these changes, DirectorRepresentation will extend BaseResource:
public class DirectorRepresentation extends BaseResource {
}
And you can extend BaseAssembler like this:
public class DirectorRepresentationAssembler
extends BaseAssembler<Director, DirectorRepresentation>
implements RepresentationModelAssembler<Director, DirectorRepresentation> {
// your implementation
}
In my opinion, the code you published in your repository is mostly fine. I think the only problem is in this line of code, as I mentioned before, I think you need to provide the type parameter when defining your BaseResource class. For instance:
package com.hateos.test.entity.web.rest.resource;
import com.fasterxml.jackson.annotation.JsonProperty;
import io.swagger.annotations.ApiModelProperty;
import org.joda.time.DateTime;
import org.springframework.hateoas.RepresentationModel;
import java.util.UUID;
public class BaseResource extends RepresentationModel<BaseResource> {
#JsonProperty
#ApiModelProperty(position = 1, required = true)
public UUID id;
#JsonProperty
public DateTime creationTime;
#JsonProperty
public DateTime lastUpdatedTime;
}
Please, note the inclusion of the code fragment RepresentationModel<BaseResource> after the extends keyword.
I am not sure if it will work but at least with this change every compiles fine and it seems to work properly.
I'm a little bit confused about using projections in Spring Data JPA.
I wanted to optimize my queries by requesting only needed columns (preferably) in one query, and I thought that using projections is a good idea. But it seems that projection with nested projection becomes open and requests all columns and further nesting is impossible.
I've tried to find a solution with #Query (cannot find how to map nested lists), #EntityGraph (cannot find how to request only specified column) and #SqlResultSetMapping (cannot find how to make mapping nested lists), but it hasn't worked for me.
Is there any solution except receiving List<Object[]> and manually mapping?
I have the next entities classes (simplified for the question):
public class TestAttempt{
private Long id;
private User targetUser;
private Test test;
}
public class Test{
private Long id;
private String name;
private Set<Question> questions;
}
public class Question{
private Long id;
private String name;
private Test test;
}
And I wanted to write something like this (it can be just TestAttempt with null in unused fields):
public interface TestAttemptList {
Long getId();
Test getTest();
interface Test {
String getName();
List<Question> getQuestions();
interface Question {
String getName();
}
}
}
public interface TestAttemptRepository extends JpaRepository<TestAttempt, Long> {
List<TestAttemptList> getAllByTargetUserId(Long targetUserId);
}
And in result get something like this:
{
id: 1,
test: {
name: test1,
questions: [{
name: quest1
}, {
name: quest2
}]
}
}
Ive done something like this... You'll have your repository interfaces which will extend CrudRepository et. al. with the full objects (TestAttempt etc) You define your projections separately. The projection interfaces can contain other projection interfaces (TestAttemptSummary can contain a TestSummary) When the projection interface is used within the given repository the defined methods are applied to the object type the repository is configured for. Something like this.
public interface TestAttemptSummary {
Long getId();
TestSummary getTest();
}
public interface TestSummary {
String getName();
List<QuestionSummary> getQuestions();
}
public interface QuestionSummary {
String getName();
}
public interface TestAttemptRepository extends CrudRepository<TestAttempt, Long> {
TestAttemptSummary getTestAttemptSummary();
}
#Getter
#Builder
#AllArgsConstructor
#NoArgsConstructor
public class GenerateDaByContextDto {
private String cNumber;
private BusinessContext businessContext;
private String zCode;
private String yCode;
private String xCode;
private String event;
public GenerateContentDto toGenerateContentDto() {
return GenerateContentDto.builder()
.businessContext(businessContext)
.event(event)
.build();
}
}
I was making code review, when i wondered is it fine to change DTO's like that?
The need was that some methods have GenerateContentDto as param and it could be acquired from GenerateDaByContextDto DTO in the code.
Is there another option to make it better? Is it good regarding SRP rule?
I have simplified the DTOs fields.
Strongly speaking, it's opinion based and depends on project.
But let's remember single responsibility principle. DTO's responsible for data holding between layers, not for conversion. I prefer to have a simple converter with method like:
public class GenerateDaByContextDtoConverter {
public GenerateContentDto convert(GenerateDaByContextDto source) {...}
}
By the same reason, usually DTOs are immutable. You could use lombok's #Value annotation.
The one more solution may be composition, if it consistent with the business logic :
class GenerateDaByContextDto {
private GenerateContentDto generateContentDto;
...
}
You can replace #Getter ,#Builder,#AllArgsConstructor,#NoArgsConstructor with #Data
this is the better way to do it
#Data
public class GenerateDaByContextDto {
private String cNumber;
private BusinessContext businessContext;
private String zCode;
private String yCode;
private String xCode;
private String event;
/*
public GenerateContentDto toGenerateContentDto() {
return GenerateContentDto.builder()
.businessContext(businessContext)
.event(event)
.build();
}
*/
}
Given the following classes and a mapper that takes mulitple source arguments
(I use lombok to keep source as short as possible.)
#Getter
#Setter
public class MySourceOne {
private String src1;
}
#Getter
#Setter
public class MySourceTwo {
private String src2;
}
#Getter
#Setter
public class MyTargetObject {
private String prop1;
private String prop2;
}
#Mapper
public interface MyTargetObjectMapper {
#Mapping(target="prop1", source="a")
#Mapping(target="prop2", source="b")
public MyTargetObject mapMyObject(String a, String b);
}
#Getter
#Setter
public class MyComplexTargetObject {
private MyTargetObject myTargetObject;
}
I am trying to create a mapper for MyComplexTargetObject that will invoke implicitly the MyTargetObjectMapper .
But the "source" won't allow to map multiple parameter like this
#Mapper(uses= {MyTargetObjectMapper.class})
public interface MyComplexTargetObjectMapper {
#Mapping(target="myTargetObject", source="one.src1, two.src2")
public MyComplexTargetObject convert(MySourceOne one, MySourceTwo two);
}
So I am trying to use an expression="..." instead of source, but nothing works so far.
Any thoughts a clean way to do this without calling the MyTargetObjectMapper in a concrete method?
MapStruct does not support selection of methods with multiple sources.
However: you can do target nesting to do this.
#Mapper
public interface MyComplexTargetObjectMapper {
#Mapping(target="myTargetObject.prop1", source="one.src1" )
#Mapping(target="myTargetObject.prop2", source="two.src2")
public MyComplexTargetObject convert(MySourceOne one, MySourceTwo two);
}
And let MapStruct take care of generating the mapper. Note: you can still use a MyComplexTargetObjectMapper to do single source to target to achieve this.
I am trying to map the result of a couchbase query to a java reference type, so far I have found no way to do this.
How can I capture the following as a java reference type:
N1qlQueryResult result = couchbaseBucket.query(
N1qlQuery.simple("SELECT * FROM customers LIMIT 1"));
JsonObject cust = result.allRows().get(0).value();
How can I cast this 'cust' to a java object? What would be the best way of doing this, doesnt the couchbase SDK provide some solution to this?
There was a blog post published yesterday that shows you how to do this with couchbase spring-boot and spring data.
I'm not a Java expert at all, but it looks like you start by creating an entity class like this:
#Document
#Data
#AllArgsConstructor
#NoArgsConstructor
#EqualsAndHashCode
public class Building {
#NotNull
#Id
private String id;
#NotNull
#Field
private String name;
#NotNull
#Field
private String companyId;
// ... etc ...
}
Then, create a repository class.
#N1qlPrimaryIndexed
#ViewIndexed(designDoc = "building")
public interface BuildingRepository extends CouchbasePagingAndSortingRepository<Building, String> {
List<Building> findByCompanyId(String companyId);
// ... etc ...
}
Finally, you can use #Autowired in a service class or wherever to instantiate a BuildingRepository and start calling the methods on it. The full documentation for Spring Data Couchbase is available on docs.spring.io