How to get a whole entity in Jhipster using mappers and DTOs? - jhipster

I'm new to mapstruct and I have a couple questions that I could not find an answer for.
I have an Entity1 (object) that has a Many2Many relationship with the Entity2. In this case, my Entity1DTO can bring all the data (Swagger) from Entity1 using a Set:
private Set<Entity2DTO> appusers = new HashSet<>();
That Set has all the information about all the Entity2DTO objects.
Then, I have another Entity3 with a relation (One2Many) with Entity1. When I want to get all the same information with Entity3 (One2Many) it only brings the ID of Entity3 and the rest of the attributes are null.
private Entity3DTO entity3;
Mapper:
#Mapping(target = "entity3", source = "entity3", qualifiedByName = "id")
Entity3DTO toDto(Entity3 s);
If I change the mapper and include, not only the id, but the other attributes (it will work), but I want to ask if there is an easier or more direct way to change the DTO or the mapper to bring the rest of the data, is there any?
And... Why it brings all data in the case of the Set, but not in the other case?
MapStruct documentation is not clear enough, so if you have any other examples with Jhipster that would be great.
Thanks for your help.

I'm not sure I have understood correctly, but I'm guessing your problem is that you want Entity1Mapper to map all the attributes of the nested Entity3 instead of just its id.
In your Entity1Mapper.java, change this:
#Mapping(target = "entity3", source = "entity3", qualifiedByName = "id")
Entity1DTO toDto(Entity1 s);
Into this:
#Mapping(target = "entity3", source = "entity3")
Entity1DTO toDto(Entity1 s);
This way you are telling MapStruct to use the full toDto(...) mapping method instead of toDtoId(...) that only fills the object ID.
Example project
I've generated a small JHipster 7.0.1 project to reproduce and fix your issue.
This is the JDL:
entity Entity1
entity Entity2
entity Entity3 { name String }
relationship ManyToMany { Entity1 to Entity2 }
relationship OneToMany { Entity3 to Entity1 }
dto all with mapstruct
With the freshly generated project you can start it up and go to the Entity1 main page and edit the first one to add an Entity3. If you inspect the GET call in the detail page for the Entity1 you just edited you will see that Entity3 only has an ID (notice how the name is NULL) as was expected.
{
"id" : 1,
"entity2s" : [ {
"id" : 1
} ],
"entity3" : {
"id" : 1,
"name" : null
}
}
Then go to Entity1Mapper.java and remove the , qualifiedByName = "id" part in the mapping of entity3. Like so:
Before:
#Mapping(target = "entity2s", source = "entity2s", qualifiedByName = "idSet")
#Mapping(target = "entity3", source = "entity3", qualifiedByName = "id")
Entity1DTO toDto(Entity1 s);
After:
#Mapping(target = "entity2s", source = "entity2s", qualifiedByName = "idSet")
#Mapping(target = "entity3", source = "entity3")
Entity1DTO toDto(Entity1 s);
Wait for the server side to recompile and reload the page, and there you have it:
{
"id" : 1,
"entity2s" : [ {
"id" : 1
} ],
"entity3" : {
"id" : 1,
"name" : "Incredible Account"
}
}

Related

Mapstruct: HashMap<String, Object> as source to Object

How could I use a HashMap<String, MyObjectSource> as source to an object?
Here is my target object:
public class QuantityDTO{
private Integer shoes;
private Integer pants;
}
Here is my object source:
public class Product{
private String name;
private Integer quantity;
}
Here is my map<String, Product> :
<("shoes", Product), ("pants", Product)>
I've tried to use this approach, but it's failing for me.
My Mapper:
#Mapping(target = "shoes", source = "shoes.quantity")
#Mapping(target = "pants", source = "pants.quantity")
QuantityDTO mapToQuantityDto(Map<String, Product> map);
The problem here is that Map to Bean mappings and nested source mappings do not combine that well. The problem with this is that MapStruct can not know whether the . is meant as a nested mapping identifier or if it is part of the key for the map itself. Both options are valid.
The behaviour to allow '.' as part of the key is listed here as a bug.
My suggestion would be to write out the mapping yourself with something like:
#Mapping(target = "shoes", source = "shoes")
#Mapping(target = "pants", source = "pants")
QuantityDTO mapToQuantityDto(Map<String, Product> map);
default Integer ProductToQuantity(Product product){
return product == null ? null : product.getQuantity();
}
This way the source field only contains the key, and the rest of the mapping is done at the default method.
For more complex mappings you could add another mapping annotated method to handle the complexity.
Also it would be useful to know what the message is that you get as a reason for it failing, or if you do not get a message what the mapstruct generated code looks like.

How to map object references in mapstruct using JHipster?

Let's say that you create a JHipster app for a Blog with Posts using a JDL script like this and you want to have a BlogDTO that shows the Posts within it (and a BlogDTO that shows the Comments each Post has):
entity Blog {
creationDate Instant required
title String minlength(2) maxlength(100) required
}
entity Post {
creationDate Instant required
headline String minlength(2) maxlength(100) required
bodytext String minlength(2) maxlength(1000) required
image ImageBlob
}
entity Comment {
creationDate Instant required
commentText String minlength(2) maxlength(1000) required
}
// RELATIONSHIPS:
relationship OneToMany {
Blog to Post{blog required}
Post{comment} to Comment{post(headline) required}
}
// Set pagination options
paginate all with pagination
// DTOs for all
dto * with mapstruct
// Set service options to all except few
service all with serviceClass
// Filtering
filter *
Jhipster will create your Blog, Post and Comment entities with their DTOs and makes the assumption that you do not want to populate the Blog with the Posts or the Posts with the comments, so your BlogMapper will look like this:
#Mapper(componentModel = "spring", uses = {})
public interface BlogMapper extends EntityMapper<BlogDTO, Blog> {
#Mapping(target = "posts", ignore = true)
Blog toEntity(BlogDTO blogDTO);
default Blog fromId(Long id) {
if (id == null) {
return null;
}
Blog blog = new Blog();
blog.setId(id);
return blog;
}
}
with a BlogDTO like this:
public class BlogDTO implements Serializable {
private Long id;
#NotNull
private Instant creationDate;
#NotNull
#Size(min = 2, max = 100)
private String title;
//GETTERS, SETTERS, HASHCODE, EQUALS & TOSTRING
Can anybody help to modify the code so the BlogDTO will show the Posts (and the PostDTO will show the Comments). Thanks
PD: Because I changed the Annotation to include the PostMapper class
#Mapper(componentModel = "spring", uses = {PostMapper.class})
And the #Mapping(target = "posts", ignore = false) to FALSE but it does not work. The API example (Swagger) look fine, but then the PostDTO is null (even when the data is there).
Add a Set<PostDTO> posts; to your BlogDTO and a Set<CommentDTO> comments; to your PostDTO. Also add getters and setters for those fields in the DTO files. Then in your mappers, make sure that the BlogMapper uses PostMapper and that the PostMapper uses CommentMapper.
You may also need to configure the caching annotations on the posts field in Blog.java and the comments field in Post.java to fit your use-case. With NONSTRICT_READ_WRITE, there can be a delay in updating the cache, resulting in stale data returned by the API.

OpenXava populating Collection from database

I have a problem populating a detail view from the database.
application.xml snippet:
<module name="ModuleB">
<model name="B"/>
<view name="ViewB"/>
...
<mode-controller name="DetailOnly"/>
</module>
I have three entity classes:
#Entity
#Table(name="A")
class A {
#OneToMany(mappedBy = "a")
#ListProperties("col1")
#CollectionView("cs")
private Collection<C> cs;//+getter/setters
}
#Entity
#Table(name="A")
#View(name = "ViewB", ...)
class B {
#OneToMany(mappedBy = "a")
#ListProperties(...)
#CollectionView("ViewC")
private Collection<C> cs;//+getter/setters
}
#Entity
#Table(name="C")
#View(name = "ViewC", ...)
class C {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "a_id")
private A a;
}
I want to read an existing B instance from the database by clicking on a link, then editing/modify it.
When I set the model object of the view with getView().setModel() or even using getView().findObject(), on the screen everything looks good, the collection shows its proper content.
On the other hand when i try to save it back, in the save action the getView().getEntity().getCs() collection is null.
What do I have to do to make the view being correspond to the entity behind?
I am using OpenXava 5.0.1, java 1.7
Important note: I am not allowed to change OpenXava version, since it is a legacy project.
My editing (20170126)
I've made a new class to avoid a reference problem:
#Entity
#Table(name="C")
#View(name = "ViewC", ...)
class D {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "a_id")
private B a;
}
and modified the class B accordingly:
#Entity
#Table(name="A")
#View(name = "ViewB", ...)
class B {
#OneToMany(mappedBy = "a")
#ListProperties(...)
#CollectionView("ViewC")
private Collection<D> cs;//+getter/setters
}
But the result is the same: The records of the subview (Collection ViewC) are loaded from the DB,
and shown on the screen correctly, but I get an error message if I want to edit the Collection ViewC
( eg: add a new entry ):
"Impossible to execute Save action: Impossible to create: an object with that key already exists"
+ As before: in the save action the getView().getEntity().getCs() collection is null
OpenXava uses JPQL to obtain collection data instead of just calling the collection getter, this is for allowing filtering and ordering. The problem here is that the query sentence generated by OpenXava gets the data but the getter of the JPA entity not. Surely JPA/Hibernate is not very happy with a mappedyBy to a reference to another type. That is you write:
class B {
#OneToMany(mappedBy = "a")
#ListProperties(...)
#CollectionView("ViewC")
private Collection<C> cs;//+getter/setters
}
Where a is a reference to A, not to B.
Given that A and B map the same table, why not to use just one entity?

Building a NSManagedObjectModel from several models

There are several reasons why somebody wants to merge multiple NSManagedObjectModel's. If you search the web, all responses are that it is not possible or that it is only possible for two unrelated entities that share one or more relationships. See this and this link for example.
However with a bit or more work it is (I think) possible to merge NSManagedObjectModels, even if the entities are related (as in parent-child) or if the attributes are spread out across multiple models.
Though it will not show as readily in the Xcode model editor and out-of-box transitions (probably) won't work.
In the answer below my observations about core data and my code on merging several models. If you find any bugs or have suggestions for improvements, please respond here.
Some things I noticed:
Copying a NSPropertyDescription (attribute, relationship) copies all its values, but not the entity to which it belongs. Same for the destinationEntity and inverseRelationship.
Thus a copied NSPropertyDescription should be added to an entity. As a result, all the children entities of that entity automatically get the property as well.
Copying a NSEntityDescription does not include the parent entity. So the tree (of NSManagedObjectEntity) has to rebuild manually.
If you set the parent of an entity, that (child) entity will immediately and automatically inherit all its parent properties. In other words when you ask an entity for its attributes, this entity already knows about all its attributes. It will not first query its parent. (reasonable assumption)
Adding entities to a model fills in the destination entities and inverse relationship descriptions of the relationsDescriptions of the added entities.
if you do not set the name of any entity or property before using it, core data will complain. That is the copy by name instead of value aspect.
Adding a property to an entity which already has a property with the same name (either from itself or inherited from its ancestor) will make core data complain.
This translates into the following code:
extension NSPropertyDescription
{
var isPlaceholder : Bool { return self.userInfo?["isPlaceholder"] != nil }
}
extension NSEntityDescription
{
var isPlaceholder : Bool { return self.userInfo?["isPlaceholder"] != nil }
}
func mergeModels(models: [NSManagedObjectModel]) -> NSManagedObjectModel?
{
var entities : [String : NSEntityDescription] = [:]
//support functions
let makeEntity : String -> NSEntityDescription = { entityName in
let newEntity = NSEntityDescription()
entities[entityName] = newEntity
newEntity.name = entityName
return newEntity
}
let setParent : (String, NSEntityDescription) -> () = { parentName, child in
if let parent = entities[parentName]
{
parent.subentities.append(child)
}
else //parent has not yet been encountered, so generate it
{
let newParentEntity = makeEntity(parentName)
newParentEntity.subentities.append(child)
}
}
//rebuild model: generate new description for each entity and add non-placeholder properties
for model in models
{
for entity in model.entities
{
guard let entityName = entity.name else { fatalError() }
let mergedEntity = entities[entityName] ?? makeEntity(entityName)
//set entity properties
if !entity.isPlaceholder
{
mergedEntity.abstract = entity.abstract
mergedEntity.managedObjectClassName = entity.managedObjectClassName
}
//set parent, if any
if mergedEntity.superentity == nil, //no parent set
let parentName = entity.superentity?.name //but parent is required
{
setParent(parentName, mergedEntity)
}
//set properties
for property in entity.properties
{
if property.isPlaceholder ||
mergedEntity.properties.contains({$0.name == property.name})
{ continue }
let newProperty = property.copy() as! NSPropertyDescription
mergedEntity.properties.append(newProperty)
}
}
}
//generate final model
let mergedModel = NSManagedObjectModel()
mergedModel.entities = Array(entities.values) //sets the destination entity and inverse relationship descriptions
return mergedModel
}
In the managedObjectModel (xcode editor) set the "placeholder" flag in the user info dictionary of the entity and/or the property.
The model generation can be refined by setting additional keys in the user info dictionary to specify which model has the prime entity/attribute/relationship (settings) and appropriately adjusting this code fragment.
However, if you can avoid using multiple models then avoid it. Your life will be much simpler by sticking to the standard single Model approach.
[Disclaimer: as far as I can tell, this code should work. No guarantees though.]
The NSManagedObjectModel class has the following factory methods / constructors
class func mergedModel(from: [Bundle]?)
class func mergedModel(from: [Bundle]?, forStoreMetadata: [String : Any])
init?(byMerging: [NSManagedObjectModel]?)
init?(byMerging: [NSManagedObjectModel], forStoreMetadata: [String : Any])
The optional forStoreMetadata attribute allows to specify the models' version.
see https://developer.apple.com/documentation/coredata/nsmanagedobjectmodel
(I suspect these methods not being available at the time the op asked & answered the question.)

Fluent Automapper issue with tag creation

POST EDITED - see edit below
I have a query about the FLuent Automapping which is used as part of the SHarp Architecture. Running one of the tests cases will generate a schema which I can use to create tables in my DB.
I'm developing a site with Posts, and Tags associated with these posts. I want a tag to be able to be associated with more than one post, and for each post to have 0 or more tags.
I wanting to achieve a DB schema of:
Post {Id, Title, SubmitTime, Content}
Tag {Id, Name}
PostTag {PostId, TagId}
Instead, I'm getting:
Post {Id, Title, SubmitTime, Content}
Tag {Id, Name, PostID (FK)}
I'm using sharp architecture, and may classes look as follows (more or less):
public class Post : Entity
{
[DomainSignature]
private DateTime _submittime;
[DomainSignature]
private String _posttitle;
private IList<Tag> _taglist;
private String _content;
public Post() { }
public Post(String postTitle)
{
_submittime = DateTime.Now;
_posttitle = postTitle;
this._taglist = new List<Tag>();
}
public virtual DateTime SubmitTime { get { return _submittime; } private set { _submittime = value; } }
public virtual string PostTitle { get { return _posttitle; } private set { _posttitle = value; } }
public virtual string Content { get { return _content; } set { _content = value; } }
public virtual IList<Tag> TagList { get { return _taglist; } set { _taglist = value; } }
public class Tag : Entity
{
[DomainSignature]
private String _name;
public Tag() { }
public Tag(String name)
{
this._name = name;
}
public virtual String Name
{
get { return _name; }
private set { _name = value; }
}
public virtual void EditTagName(String name)
{
this.Name = name;
}
}
I can see why it's gone for the DB schema set up that it has, as there will be times when an object can only exist as part of another. But a Tag can exist separately.
How would I go about achieving this? I'm quite new to MVC, Nhibernate, and SHarp architecture, etc, so any help would be much appreciated!
EDIT*
OK, I have now adjusted my classes slightly. My issue was that I was expecting the intermediate table to be inferred. Instead, I realise that I have to create it.
So I now have (I've simplified the classes a bit for readability's sake.:
class Post : Entity
{
[DomainSignature]
String Title
[DomainSignature]
DateTime SubmitTime
IList<PostTag> tagList
}
class Tag : Entity
{
[DomainSignature]
string name
}
class PostTag : Entity
{
[DomainSignature]
Post post
[DomainSignature]
Tag tag
}
This gives me the schema for the intermediate entity along with the usual Post and Tag tables:
PostTag{id, name, PostId(FK)}
The problem with the above is that it still does not include The foreign key for Tag. Also, should it really have an ID column, as it is a relational table? I would think that it should really be a composite key consisting of the PK from both Post and Tag tables.
I'm sure that by adding to the Tag class
IList<PostTag> postList
I will get another FK added to the PostTag schema, but I don't want to add the above, as the postList could be huge. I don't need it every time I bring a post into the system. I would have a separate query to calculate that sort of info.
Can anyone help me solve this last part? Thanks for your time.
Ok, I'd been led to believe that modelling the composite class in the domain was the way forward, but I finally come across a bit of automapper override code which creates the composite table without me needing to create the class for it, which was what I was expecting in the first place:
public class PostMappingOverride
: IAutoMappingOverride
{
public void Override(AutoMapping map)
{
map.HasManyToMany(e => e.TagList)
.Inverse()
.Cascade.SaveUpdate();
}
}
This will now give me my schema (following schema non simplified):
create table Posts (
Id INT not null,
PublishTime DATETIME null,
SubmitTime DATETIME null,
PostTitle NVARCHAR(255) null,
Content NVARCHAR(255) null,
primary key (Id)
)
create table Posts_Tags (
PostFk INT not null,
TagFk INT not null
)
create table Tags (
Id INT not null,
Name NVARCHAR(255) null,
primary key (Id)
)
alter table Posts_Tags
add constraint FK864F92C27E2C4FCD
foreign key (TagFk)
references Tags
alter table Posts_Tags
add constraint FK864F92C2EC575AE6
foreign key (PostFk)
references Posts
I think the thrower is that I've been looking for a one-to-many relationship, which it is, but it is called HasManytoMAny here...

Resources