Clone object without ids using mapstruct - java

I have an object which contains several list. is there a method to clone this object without id using mapstruct even for the nested object list in an automatic way to be able to persist it.
#Data
public class ParentDto {
#Id
private Long id;
private String name;
private List<Child1Dto> children1;
private List<Child1Dto> children2;
}
#Data
public class Child1Dto {
#Id
private Long id;
private String name;
}
#Data
public class Child2Dto {
#Id
private Long id;
private String name;
}
Actual mapper
#Mapper(mappingControl = DeepClone.class)
public interface CloneParentMapper {
#Mapping(target = "id", ignore = true)
ParentDto cloneWithoutId(ParentDto parentDto );
#Mapping(target = "id", ignore = true)
Child1Dto cloneWithoutId(Child1Dto child1Dto );
#Mapping(target = "id", ignore = true)
Child2Dto cloneWithoutId(Child2Dto child2Dto );
}
is there a way to ignore all id without doing #Mapping(target = "id", ignore = true) on every list?

I really feel this is the best and easiest way to ignore fields.
But still if u wanna ignore fields and not mark them as ignored specifically, then you can use constructors based mappings and have a separate constructor without id field. You will have to mark this constructor as #Default.
https://mapstruct.org/documentation/stable/reference/html/#mapping-with-constructors

Related

Object field mapping problem when mapping MapStruct List to List

I have a problem when mapping a List from a List.
All default fields of Entity are well mapped.
ex) (Entity)String date -> (DTO) String date
However, the Join object field exists in Entity.
We need to pull the data out of this object field and map it anew.
In the case of a single Entity to single DTO rather than a List to List, this was easily possible.
#Mapping(target = ".", source = "user")
This way we were able to map all fields of the user object field that the Entity has to the remaining unmapped fields.
However, trying the same on a List has no effect.
Which method should I use?
#Entity(name = "refund")
public class RefundEntity extends BaseEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int refundId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private UserEntity user;
private int refundAmount;
}
and
public class AdminRefundUserDto {
#AllArgsConstructor
#Getter
public static class Response {
private int refundAmount;
private String marketName;
private String bizNumber;
private String bizUserName;
}
}
and
#Entity(name = "user")
public class UserEntity extends BaseEntity {
#Id
#GeneratedValue(generator = "uuid4")
#GenericGenerator(name = "uuid", strategy = "uuid4")
#Column(columnDefinition = "BINARY(16)")
private UUID userId;
private String userName;
private String password;
private String phoneNumber;
private String marketName;
private String bizUserName;
private String bizNumber;
}
and I used
#Mapping(target = ".", source = "refundList.user")
List<AdminRefundUserDto.Response> toDtoList(List<RefundEntity> refundList);
First of all, create a method for the mapper with a SourceObject parameter that return a TargetObject
#Named("toResponseDto")
//add your mapping
AdminRefundUserDto.Response toResponseDto(RefundEntity refundEntity);
if you have complex logic of mapping, you can also create a custom method to map a certain target parameter: see Custom Mapper with MapStruct
Then add an IterableMapping
#IterableMapping(qualifiedByName = "toResponseDto")
List<AdminRefundUserDto.Response> toDtoList(List<RefundEntity> refundList);

Map JoinColum value instead of association in JSON

For a given #Entity with a #ManyToOne relation I want to suppress the mapping of the associated type. The JSON representation should contain the forein key (#JoinColumn) instead.
#Entity
public class OrderPosition implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne
#JoinColumn(name = "order_id", referencedColumnName = "id")
//#Json???
private Order order;
// ...
}
orderPosition = new OrderPosition()
// ...
ObjectMapper objectMapper = new ObjectMapper();
String json = objectMapper.writeValueAsString(orderPosition);
assertThat(json).contains("\"order_id\":");
How could I archieve it?
You can use the following solution:
#JsonProperty("order")
public Integer getOrderId() {
return this.order.getId();
}
The JsonProperty annotation causes to use this getter for serialization the "order" attribute. So you can only return the id for your use case. I would recommand to use a nice name of getter (not getOrder() ...).
Probably you could just add a #JsonIgnore to the order field and another field for the orderId.

JsonView - define Default View

I am working on a Spring boot (MVC, JPA) application and it is required to return different attributes on different requests. I found the #JsonView annotation and it seems to work. But do I need to annotate every attribute with a basic view?
Example:
Entity1
#Entity
public class Entity1 implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JsonView(JsonViews.ExtendedView.class)
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "entity1", fetch = FetchType.EAGER)
List<Entity2> entities2;
#JsonView(JsonView.ExtendedView.class)
#OneToMany(cascade = CascadeType.ALL, mappedBy = "entity1", fetch = FetchType.LAZY)
List<Entity3> entities3;
}
Entity2
#Entity
public class Entity2 implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
Entity3
#Entity
public class Entity3 implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}
Views
public class JsonViews {
public static class BasicView { }
public static class ExtendedView extends BasicView { }
}
Controller
#RequestMapping(method = RequestMethod.GET)
#JsonView(JsonViews.BasicView.class)
public #ResponseBody List<Entity1> index() {
return repositoryEntity1.findAll();
}
This is a trimmed example but I think it applies to the problem. I expect that the controller returns the Ids and the list of Entity2 objects. But it returns an empty object with "No Properties". If I annotate every attribute of every class involved in this request, it seems to work, but is this really needed or the best solution? Is there a way to define a "DefaultView"?
thanks
Edit: If I annotate the JpaRepository it returns the entire object including the list with Entity3 objects.
No, you do not need to define views on all properties. Insert
spring.jackson.mapper.default-view-inclusion=true
in your application.properties. This will cause properties without the #JsonView annotation to be included in the response and only the annotated properties will be filtered.
In your Controller, properties without a view or with the BasicView annotated will be returned.

Persisting a #ManyToOne-referenced object only if it does not exist

I'm fairly new to Spring/JPA so this is somewhat a trivial question.
I have two entities with a many-to-one relationship: Item and ItemType. Basically, ItemType simply represents a unique name for a set of Items. I use a CrudRepository<Item, Long> to store them. The relevant code is as follows (getters/setters/equals()/hashCode() omitted):
#Entity
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#ManyToOne(cascade = {CascadeType.PERSIST, CascadeType.MERGE})
#JoinColumn(name = "type_id")
private ItemType itemType;
public Item() {}
public Item(ItemType itemType) {
this.itemType = itemType;
}
}
#Entity
public class ItemType {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(unique = true, nullable = false)
private String name;
public ItemType() {}
public ItemType(String name) {
this.name = name;
}
}
#Controller
public class ItemsController {
#Autowired private ItemsRepo itemsRepo;
#RequestMapping(value = "/item", method = RequestMethod.POST)
#ResponseBody
public Item addQuestionSet(#RequestBody Item item) {
return itemsRepo.save(item);
}
}
When I insert a new Item into the database, I want it to get a type_id from either an ItemType with the given name if it already exists, or from a newly persisted ItemType otherwise.
As of now, I naturally get an exception when trying to insert the second item with the same type:
org.hsqldb.HsqlException: integrity constraint violation: unique constraint or index violation
I could probably make a boilerplate check in my controller before saving a new item into repository. But this task is rather generic, I'm pretty sure there must be a convenient solution in JPA.
Thanks.
It seems you are persist() method on the Item object rather than merge() method. I hope it will resolve your query.
I can see that the problem is when you "persist", try with "lazy" type. You could get the data only when you need it and EAGER always.
I can give you an example how i do it
this is my class "CentroEstudio"
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "idCentroEstudio",nullable=false)
private Long idCentroEstudio;
#ManyToOne(fetch = FetchType.EAGER,cascade=CascadeType.ALL)
#JoinColumn(name = "idTipoCentroEstudio", nullable = false)
private TipoCentroEstudio tipoCentroEstudio;
#Column(name="nombre",nullable=false)
private String nombre;
#Column(name="activo",nullable=false)
private boolean activo;
this is my class "TipoCentroEstudio"
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name="idTipoCentroEstudio",nullable=false)
private Long idTipoCentroEstudio;
#Column(name="descripcion",nullable=false)
private String descripcion;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "tipoCentroEstudio")
private Set<CentroEstudio> centroEstudio = new HashSet<CentroEstudio>(0);
I'm sorry for the Spanish in the example, but I'm peruvian and I speak Spanish.
I hope this helps you ...

Hibernate: Overriding Field Optionality in Subclass

Is there a way, using Hibernate or JPA annotations, to override the optionality of a field in a subclass? Take the following example:
Parent Class
This is a base class that defines a number of common fields. For the example below, I am just showing a single field that I want to override in a few sub classes. In the #MappedSuperclass, this field is required (doesn't allow null).
#MappedSuperclass
public abstract class GenericLog {
protected String sessionId;
#Basic(optional = false)
#Column(name = FIELD__SESSION_ID__COLUMN, length = 50)
public String getSessionId() {
return sessionId;
}
public void setSessionId(String sessionId) {
this.sessionId = sessionId;
}
}
Child Class
This is a subclass. It has the same sessionId field defined in the parent class, the only difference is that the field should allow nulls in this class.
#Entity
#Table(name = LogError.TABLE_NAME)
#Cache(usage = CacheConcurrencyStrategy.NONE)
public class LogError extends GenericLog {
#Basic(optional = true)
#Column(name = FIELD__SESSION_ID__COLUMN, length = 50)
#Override
public String getSessionId() {
return super.getSessionId();
}
#Override
public void setSessionId(String sessionId) {
super.setSessionId(sessionId);
}
}
I tried using the #AttributeOverride annotation, but that didn't work, even specifying the nullable property.
P.S. I'm using Hibernate 4.1.9 (JPA 2.0 annotations).
I think there's some non-documented interaction between #Basic and #Column that prevent the effect of #AttributeOverride.
Remove the #Basic and move the annotation to the field level instead of method level did the trick for me:
Here is my setup:
#MappedSuperclass
public abstract class GenericLog {
#Column(name = "sessionId", length = 50, nullable = false)
protected String sessionId;
// getter setter here
}
#Entity
#Table(name = "child")
#AttributeOverride(column = #Column(name = "sessionId", length = 50, nullable = true), name = "sessionId")
public class ChildEntity extends SuperClass {
#Id
#Column
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// getter setter here
}
#Entity
#Table(name = "child2")
public class ChildEntity2 extends SuperClass {
#Id
#Column
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
// Getter setter here
}
Here's the result:

Categories