Map multiple fields to one with MapStruct - mapstruct

I have these 3 classes in separate files
public class Book {
#Id
#GeneratedValue
private Long id;
#NonNull
private String title;
#NonNull
private Author author;
}
public class Author {
#Id
#GeneratedValue
private Long id;
#NonNull
private String firstName;
#NonNull
private String lastName;
}
public class BookDTO {
private Long id;
#NonNull
private String title;
#NonNull
private String author;
}
I have the following mapper
#Mapper
public interface BookMapper {
BookMapper INSTANCE = Mappers.getMapper(BookMapper.class);
#Mappings({
#Mapping(source = "author.lastName", target = "author")
})
BookDTO toDTO(Book book);
}
this currently only maps the lastName and works, and I want to map the author string in Book with
author.firstName + " " + author.lastName
how could I do that? I have not been able to find anything in the MapStruct Documentation.

MapSruct does not support mapping multiple source properties into a single target property.
You have 2 ways to achieve this:
Using Mapping#expression
#Mapping( target = "author", expression = "java(book.getAuthor().getFirstName() + \" \" + book.getAuthor().getLastName())")
Using #AfterMapping or #BeforeMapping
#Mapper
public interface BookMapper {
BookMapper INSTANCE = Mappers.getMapper(BookMapper.class);
#Mapping(target = "author", ignore = true)
BookDTO toDTO(Book book);
#AfterMapping
default void setBookAuthor(#MappingTarget BookDTO bookDTO, Book book) {
Author author = book.getAuthor();
bookDTO.setAuthor(author.getFirstName() + " " + author.getLastName());
}
}

Related

Spring Data JPA #OneToOne mapping is not projected

This question is already phrased as an issue here: https://github.com/spring-projects/spring-data-jpa/issues/2369 but for lack of a reaction there I am copying the contents of that issue here, hoping that somebody might find what's wrong with my code or confirm that this could be a bug:
I've set up an example project here that showcases what seems to be a bug in Spring Data projections: https://github.com/joheb-mohemian/gs-accessing-data-jpa/tree/primary-key-join-column-projection-bug/complete
I have a Customer entity that has a OneToOne mapping to an Address entity:
#Entity
public class Customer {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String firstName;
private String lastName;
#OneToOne(mappedBy = "customer", cascade = CascadeType.ALL)
#PrimaryKeyJoinColumn
private Address address;
//...
}
#Entity
public class Address {
#Id
#Column(name = "customer_id")
private Long id;
#OneToOne
#MapsId
#JoinColumn(name = "customer_id")
private Customer customer;
private String street;
//...
}
Then there are simple projection interfaces:
public interface CustomerProjection {
String getFirstName();
String getLastName();
AddressProjection getAddress();
}
public interface AddressProjection {
String getStreet();
}
But when I try to fetch a projected entity from a repository method like this one:
public interface CustomerRepository extends CrudRepository<Customer, Long> {
//...
<T> T findById(long id, Class<T> type);
}
, getAddress() on the projection will be null, whereas getAddress() when fetching the entity type is populated correctly. Of these two unit tests, only testEntityWithOneToOne()will be successful:
#BeforeEach
void setUpData() {
customer = new Customer("first", "last");
Address address = new Address(customer, "street");
customer.setAddress(address);
entityManager.persist(address);
entityManager.persist(customer);
}
#Test
void testEntityWithOneToOne() {
Customer customerEntity = customers.findById(customer.getId().longValue());
assertThat(customerEntity.getAddress()).isNotNull();
}
#Test
void testProjectionWithOneToOne() {
CustomerProjection customerProjection = customers.findById(customer.getId(), CustomerProjection.class);
assertThat(customerProjection.getAddress()).isNotNull();
}
What's the problem here?

JPA #ManyToOne, #OneToMany bidirectional relation

I'm studying bidirectional mapping.
I mapped Team and Member with #OneToOne and #ManyToMany annotations.
#Entity
public class Team {
#Id #GeneratedValue
private Long id;
private String name;
#OneToMany(mappedBy = "team")
private List<Member> members=new ArrayList<Member>();
//omit getter, setter ,toString
}
#Entity
public class Member {
#Id
#GeneratedValue
private Long id;
#Column(name="USERNAME")
private String name;
#ManyToOne
#JoinColumn(name="TEAM_ID")
private Team team;
#Enumerated(EnumType.STRING)
private Status status;
//omit getter, setter , toString
}
main method
public static void main(String args[]){
//...
Team team= new Team();
team.setName("RedTeam");
em.persist(team);
Member member= new Member();
member.setName("me");
member.setStatus(Status.ADMIN);
member.setTeam(team);
em.persist(member);
Member findmember= em.find(Member.class, member.getId());
Team findTeam= findmember.getTeam();
System.out.println("members: "+findTeam.getMembers());
//...
}
results:
members: []
I wonder why "members" were not added to the "members field" of "Team" in the code above.
Thank you in advance.
These are the minimal changes to make it to work
#Entity
public class Team {
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private #Id Long id;
private String name;
#OneToMany(mappedBy = "team", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Member> members = new ArrayList<>();
public String getName() {
return name;
}
public void setName(String pName) {
name = pName;
}
public void addMember(Member m) {
getMembers().add(m);
m.setTeam(this);
}
public void removeMember(Member m) {
getMembers().remove(m);
m.setTeam(null);
}
public List<Member> getMembers() {
return members;
}
}
Team team = new Team();
team.setName("RedTeam");
// em.persist(team);
Member member = new Member();
member.setName("" + new Random().nextInt(999999));
team.addMember(member);
em.persist(team);
Member findmember = em.find(Member.class, member.getId());
Team findTeam = findmember.getTeam();
System.out.println("Members: " + findTeam.getMembers());

Find an entity which use a class Id

To find an object from entity with primary key we use em.find(Person.class, <Id>).
I'm using JPA EclipseLink and I have a Person entity which has a composite primary key(#classId),
the Person entity:
#Entity
#IdClass(PersonId.class)
public class Person {
#Id
private int id;
#Id
private String name;
public String getName() {
return name;
}
// getters & setters
}
and the PersonID:
public class PersonId implements Serializable {
private static final long idVersionUID = 343L;
private int id;
private String name;
// must have a default construcot
public PersonId() {
}
public PersonId(int id, String name) {
this.id = id;
this.name = name;
}
//getters & setters
//hachCode & equals
}
How to use em.find to get a Person object?
I found the solution :
PersonId personeId = new PersonId(33, "Jhon");
Person persistedPerson = em.find(Person.class, personeId);
System.out.println(persistedPerson.getID() + " - " + persistedPerson.getName());

Spring Data JPA and QueryDSL - No property find found for type

I'm using Spring JPA 1.7 and QueryDSL 3.5.1. I am getting 'No property find found' error.
Here are my classes.
#Entity
#Table(name="Device")
public class Device implements Serializable {
private static final long serialVersionUID = 1L;
#NotEmpty
#Id
#Column(name="deviceId")
private String deviceId="";
#Column(name="accountId")
private String accountId="";
#Column(name="groupId")
private String groupId="";
#Column(name="equipmentType")
private String equipmentType="";
#Column(name="deviceCode")
private String deviceCode="";
#Column(name="deviceType")
private String deviceType="";
#NotEmpty
#Column(name="simId")
private String simId="";
#NotEmpty
#Column(name="imeiNumber")
private String imeiNumber="";
#Column(name="simPhoneNumber")
private String simPhoneNumber="";
#Column(name="driverId")
private String driverId="";
#Column(name="pushpinId")
private String pushpinId=""; //who registered device? JMA/MDM/JAMS
#Column(name="isActive", columnDefinition="INT(1)")
private boolean isActive = false;
#Column(name="displayName")
private String displayName="";
#Column(name="description")
private String description="";
#Column(name="notes")
private String notes="";
#Column
#JsonSerialize(using=DateSerializer.class)
private long creationTime;
#Column
#JsonSerialize(using=DateSerializer.class)
private long lastUpdateTime;
//Getters and setters
}
public interface DeviceRepository extends PagingAndSortingRepository<Device, String>, DeviceRepositoryCustom {
public Page<Device> findAll(com.mysema.query.types.Predicate predicate, Pageable pageable);
}
public interface DeviceRepositoryCustom {
public List<Device> selectByEquipmentTypeAndAnyColumnLike(String equipmentType, String creationTime, String searchField, String searchText, boolean hasPushId);
}
public class MdmPredicates {
public static com.mysema.query.types.Predicate anyFieldLike(String field, String text) {
QDevice device = QDevice.device;
//Do something later
return device.deviceId.contains(text);
}
}
Here is the error log:
aused by: org.springframework.data.mapping.PropertyReferenceException: No property find found for type entities.Device
at org.springframework.data.mapping.PropertyPath.<init>(PropertyPath.java:75)
at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:327)
at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:353)
at org.springframework.data.mapping.PropertyPath.create(PropertyPath.java:307)
at org.springframework.data.mapping.PropertyPath.from(PropertyPath.java:271)
at org.springframework.data.mapping.PropertyPath.from(PropertyPath.java:245)
at org.springframework.data.repository.query.parser.Part.<init>(Part.java:72)
at org.springframework.data.repository.query.parser.PartTree$OrPart.<init>(PartTree.java:188)
at org.springframework.data.repository.query.parser.PartTree$Predicate.buildTree(PartTree.java:277)
at org.springframework.data.repository.query.parser.PartTree$Predicate.<init>(PartTree.java:257)
at org.springframework.data.repository.query.parser.PartTree.<init>(PartTree.java:68)
at org.springframework.data.jpa.repository.query.PartTreeJpaQuery.<init>(PartTreeJpaQuery.java:57)
at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:90)
at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$CreateIfNotFoundQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:162)
at org.springframework.data.jpa.repository.query.JpaQueryLookupStrategy$AbstractQueryLookupStrategy.resolveQuery(JpaQueryLookupStrategy.java:68)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$QueryExecutorMethodInterceptor.<init>(RepositoryFactorySupport.java:290)
at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:158)
at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.getObject(RepositoryFactoryBeanSupport.java:162)
at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.getObject(RepositoryFactoryBeanSupport.java:44)
at org.springframework.beans.factory.support.FactoryBeanRegistrySupport.doGetObjectFromFactoryBean(FactoryBeanRegistrySupport.java:168)
I know that the error happens when I don't use real column name instead of property name. But the property names in the class are all same with column names.
Would you please tell me what I am I missing?
Your help would be appreciated.

Copy Entity ID at persist time

I want to copy the entity's UUID, generated at run time to another field.
The entity id is generated via the code described bellow:
package eclipselink.example;
public class UUIDSequence extends Sequence implements SessionCustomizer {
public UUIDSequence() {
super();
}
public UUIDSequence(String name) {
super(name);
}
#Override
public Object getGeneratedValue(Accessor accessor,
AbstractSession writeSession, String seqName) {
return UUID.randomUUID().toString().toUpperCase();
}
...
public void customize(Session session) throws Exception {
UUIDSequence sequence = new UUIDSequence("system-uuid");
session.getLogin().addSequence(sequence);
}
}
Persitence.xml:
property name="eclipselink.session.customizer" value="eclipselink.example.UUIDSequence"
The entity:
public abstract class MyEntity{
private String id;
private String idCopy;
#Id
#Basic(optional = false)
#GeneratedValue(generator="system-uuid")
#XmlElement(name = "ID")
public String getId() {
return id;
}
}
How can I instruct JPA (Eclipse-link) to copy the UUID generated at runtime to idCopy field as well?
I'm not 100% sure this will work (I don't know if EclipseLink calls the setter or assigns the field directly), but give this a try:
public abstract class MyEntity{
private String id;
private String idCopy;
#Id
#Basic(optional = false)
#GeneratedValue(generator="system-uuid")
#XmlElement(name = "ID")
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
this.idCopy = id;
// or
// this.setIdCopy(id);
}
}