MapStruct, map inheritance - java

I have 2 entities Vehicle, which is an interface, and VehicleDto, a class.
Vehicle has 2 subtypes: Car and Truck. All 3 of them have a field name.
VehicleDto has 2 fields Car and Truck, and only one of those may have a value.
How do I map the subtypes Car and Truck to the class VehicleDto so that the appropriate field of the VehicleDto is filled in with the name value of the subtype?
Currently I have this:
#Mapping(source = "name", target = "Car")
#Mapping(source = "name", target = "Truck")
VehicleDto mapToVehicle(Vehicle vehicle);
But that obviously does not work because now both Car and Truck fields of VehicleDto are filled in.
I also tried something like this
#Mapping(source = "name", target = "Car")
VehicleDto mapToVehicle(Car car);
#Mapping(source = "name", target = "Truck")
VehicleDto mapToVehicle(Truck truck);
But that also doesn't work properly.
What's a good way to implement this behavior? The reference guide of MapStruct was not very clear to me.

Related

Deserialize subclasses with Jackson by parent class field value

I have a parent class:
open class NetworkMessage(var type: NetworkMessageType)
and a bunch of it's subclasses, like
class ConnectionAcceptedResponseMessage(
val accepted: Boolean,
val uid: String
) : NetworkMessage(NetworkMessageType.CONNECTION_ACCEPTED)
so every message type is clearly determined by parent class field value. Is there any way to deserialize concrete subclass using only annotations and without using treeNode?
In Java, you can do something like this on the parent class:
#JsonTypeInfo(
use = JsonTypeInfo.Id.NAME,
include = JsonTypeInfo.As.EXISTING_PROPERTY,
property = "type"
)
#JsonSubTypes({
#JsonSubTypes.Type(value = ConnectionAcceptedResponseMessage.class, name = "CONNECTION_ACCEPTED"),
#JsonSubTypes.Type(value = OtherResponseMessage.class, name = "OTHER")
})
I guess in Kotlin this will be similar.

Mapstruct source is List and target is direct attribute

I want to map UserDTO and UserGroupDTO where User has address as a list in which it has all address fields and Usergroup has individual address fields. Please let me know how could I map these fields.
There is currently no official support for that, but there is a workaround using expressions, as described in the ticket : https://github.com/mapstruct/mapstruct/issues/1321#issuecomment-339807380
This would work your case:
#Mapper
public abstract class UserDTOMapper {
#Mapping( expression = "java(userDTO.getAddress().get(0))", target = "street")
#Mapping( expression = "java(userDTO.getAddress().get(1))", target = "zipCode")
#Mapping( expression = "java(userDTO.getAddress().get(2))", target = "country")
abstract public UserGroupDTO mapTo(UserDTO userDTO);
}
But you have to make sure that the address property implemented as List will always contain the same number of fields and in correct order, else the mapping based on list index will not work as expected.

JaxB - unmarshal an unwrapped objects list as a collection

Have got an epic REST API with the XML response type that has an unwrapped collection of objects at the root element.
It looks like this:
<root>
<carName>Car1</carName>
<carNumber>1</carNumber>
<carType>SAV1</carType>
<carName>Car2</carName>
<carNumber>2</carNumber>
<carType>SAV2</carType>
<carName>Car3</carName>
<carNumber>3</carNumber>
<carType>SAV3</carType>
<count>3</count>
</root>
I'm trying to unmarshal this as a List of child Car objects:
#XmlRootElement
public Root {
#XmlElement
private List<Car> cars;
#XmlElement(name = "count")
private Integer count;
...
}
... where the Car is the simple jaxb bean with #XmlElement(name = "...") annotation per the each it's primitive typed field. However, all the fields except of count are null.
Have tried to use the construction like this instead of nameless #XmlElement:
#XmlElements({
#XmlElement(name = "carName", type = String.class),
#XmlElement(name = "carNumber", type = Integer.class),
#XmlElement(name = "carType", type = String.class)
})
... now I've got a 9 items array instead of 3, where's the each field of child Car class is stored as an independent array item.
So, the question is how to unmarshall XML structure like this to the well presented POJO List?

Designing JPA Entity object for onToMany mapping on Same Entity

If we have a table say EMP_CONTACTTYPE which is as below
EMP EMPNAME CONTACTTYPE CONTACT
1 W 1 EMAIL
2 X 1 EMAIL
3 Y 2 PHONE
4 Z 2 PHONE
If we want to display the details as
EMAIL PHONE
W Y
X z
How should we design the entity object's as its mapping to the same table " EMP_CONTACTTYPE " .
I created two Entity Object one for Contact and one for Emp as below and have got a onetomany mapping on Contact Entity
Below is the Contact Entity
#Entity
#Table(name = "EMP_CONTACTTYPE")
public class CONTACT
{
private String CONTACT_TYPE;
private String CONTACT;
#OneToMany(fetch = FetchType.EAGER)
#JoinTable(name = "EMP_CONTACTTYPE", joinColumns = { xxxxxx })
private List<EMP> EMP;
}
and below is the EMP Entity
#Entity
#Table(name = "EMP_CONTACTTYPE")
public class EMP
{
private String EMPLOYEE_NAME;
private String EMPLOYEE_KEY;
}
The result expected is like for Contact Object Type (1 and Email) We need two objects of Employee (W and X). Am not sure whether join is the solution for this or am not clear how to add a join for this. Any suggestion in designing this scenario.
You can combine your two Entity classes into a single Entity class or use two tables.
Preferably, however, you would split this into two tables and create an appropriate relationship between the two tables. The current schema design is not optimal.
Anyway, given you situation and based on your comment, since you have a single table that contains all of this data, you can extend the EMP class from the CONTACT class and create a discriminator column. Going with this approach you will find the following class annotations useful:
e.g.
#Inheritance(strategy = InheritanceType.JOINED)
#DiscriminatorColumn(name = "columnname", discriminatorType = DiscriminatorType.INTEGER)
#DiscriminatorValue(value = "value")
You should research these annotations and choose the appropriate strategy, etc. This should get you on the right path.
If you use Discrimators with Hibernate, you also have the option to specify a formula
e.g.
<discriminator formula="case when CLASS_TYPE in ('a', 'b', 'c') then 0 else 1 end" type="integer"/>

How to use Hibernate #Any-related annotations?

Could someone explain to me how Any-related annotations (#Any, #AnyMetaDef, #AnyMetaDefs and #ManyToAny) work in practice. I have a hard time finding any useful documentation (JavaDoc alone isn't very helpful) about these.
I have thus far gathered that they somehow enable referencing to abstract and extended classes. If this is the case, why is there not an #OneToAny annotation? And is this 'any' referring to a single 'any', or multiple 'any'?
A short, practical and illustrating example would be very much appreciated (doesn't have to compile).
Edit: as much as I would like to accept replies as answers and give credit where due, I found both Smink's and Sakana's answers informative. Because I can't accept several replies as the answer, I will unfortunately mark neither as the answer.
Hope this article brings some light to the subject:
Sometimes we need to map an
association property to different
types of entities that don't have a
common ancestor entity - so a plain
polymorphic association doesn't do the
work.
For example let's assume three different applications which manage a media library - the first application manages books borrowing, the second one DVDs, and the third VHSs. The applications have nothing in common. Now we want to develop a new application that manages all three media types and reuses the exiting Book, DVD, and VHS entities. Since Book, DVD, and VHS classes came from different applications they don't have any ancestor entity - the common ancestor is java.lang.Object. Still we would like to have one Borrow entity which can refer to any of the possible media type.
To solve this type of references we can use the any mapping. this mapping always includes more than one column: one column includes the type of the entity the current mapped property refers to and the other includes the identity of the entity, for example if we refer to a book it the first column will include a marker for the Book entity type and the second one will include the id of the specific book.
#Entity
#Table(name = "BORROW")
public class Borrow{
#Id
#GeneratedValue
private Long id;
#Any(metaColumn = #Column(name = "ITEM_TYPE"))
#AnyMetaDef(idType = "long", metaType = "string",
metaValues = {
#MetaValue(targetEntity = Book.class, value = "B"),
#MetaValue(targetEntity = VHS.class, value = "V"),
#MetaValue(targetEntity = DVD.class, value = "D")
})
#JoinColumn(name="ITEM_ID")
private Object item;
.......
public Object getItem() {
return item;
}
public void setItem(Object item) {
this.item = item;
}
}
The #Any annotation defines a polymorphic association to classes from multiple tables. This type of mapping
always requires more than one column. The first column holds the type of the associated entity. The remaining
columns hold the identifier. It is impossible to specify a foreign key constraint for this kind of association, so
this is most certainly not meant as the usual way of mapping (polymorphic) associations. You should use this
only in very special cases (eg. audit logs, user session data, etc).
The #Any annotation describes the column holding the metadata information. To link the value of the
metadata information and an actual entity type, The #AnyDef and #AnyDefs annotations are used.
#Any( metaColumn = #Column( name = "property_type" ), fetch=FetchType.EAGER )
#AnyMetaDef(
idType = "integer",
metaType = "string",
metaValues = {
#MetaValue( value = "S", targetEntity = StringProperty.class ),
#MetaValue( value = "I", targetEntity = IntegerProperty.class )
} )
#JoinColumn( name = "property_id" )
public Property getMainProperty() {
return mainProperty;
}
idType represents the target entities identifier property type and metaType the metadata type (usually String).
Note that #AnyDef can be mutualized and reused. It is recommended to place it as a package metadata in this
case.
//on a package
#AnyMetaDef( name="property"
idType = "integer",
metaType = "string",
metaValues = {
#MetaValue( value = "S", targetEntity = StringProperty.class ),
#MetaValue( value = "I", targetEntity = IntegerProperty.class )
} )
package org.hibernate.test.annotations.any;
//in a class
#Any( metaDef="property", metaColumn = #Column( name = "property_type" ), fetch=FetchType.EAGER )
#JoinColumn( name = "property_id" )
public Property getMainProperty() {
return mainProperty;
}
#ManyToAny allows polymorphic associations to classes from multiple tables. This type of mapping always requires
more than one column. The first column holds the type of the associated entity. The remaining columns
hold the identifier. It is impossible to specify a foreign key constraint for this kind of association, so this is most
certainly not meant as the usual way of mapping (polymorphic) associations. You should use this only in very
special cases (eg. audit logs, user session data, etc).
#ManyToAny(
metaColumn = #Column( name = "property_type" ) )
#AnyMetaDef(
idType = "integer",
metaType = "string",
metaValues = {
#MetaValue( value = "S", targetEntity = StringProperty.class ),
#MetaValue( value = "I", targetEntity = IntegerProperty.class ) } )
#Cascade( { org.hibernate.annotations.CascadeType.ALL } )
#JoinTable( name = "obj_properties", joinColumns = #JoinColumn( name = "obj_id" ),
inverseJoinColumns = #JoinColumn( name = "property_id" ) )
public List<Property> getGeneralProperties() {
Src: Hibernate Annotations Reference Guide 3.4.0GA
Hope it Helps!
The #Any annotation defines a polymorphic association to classes from multiple tables, right, but polymorphic associations such as these are an SQL anti-pattern! The main reason is that you canĀ“t define a FK constraint if a column can refer to more than one table.
One of the solutions, pointed out by Bill Karwin in his book, is to create intersection tables to each type of "Any", instead of using one column with "type", and using the unique modifier to avoid duplicates. This solution may be a pain to work with JPA.
Another solution, also proposed by Karwin, is to create a super-type for the connected elements. Taking the example of borrowing Book, DVD or VHS, you could create a super type Item, and make Book, DVD and VHS inherit from Item, with strategy of Joined table. Borrow then points to Item. This way you completely avoid the FK problem. I translated the book example to JPA bellow:
#Entity
#Table(name = "BORROW")
public class Borrow{
//... id, ...
#ManyToOne Item item;
//...
}
#Entity
#Table(name = "ITEMS")
#Inheritance(strategy=JOINED)
public class Item{
// id, ....
// you can add a reverse OneToMany here to borrow.
}
#Entity
#Table(name = "BOOKS")
public class Book extends Item {
// book attributes
}
#Entity
#Table(name = "VHS")
public class VHS extends Item {
// VHSattributes
}
#Entity
#Table(name = "DVD")
public class DVD extends Item {
// DVD attributes
}
Have you read the Hibernate Annotations documentation for #Any? Haven't used that one myself yet, but it looks like some extended way of defining references. The link includes an example, though I don't know if it's enough to fully understand the concept...

Categories

Resources