How to annotate and use a java persistent object in a onetomany bidirectional relationship so that the entity can be converted to its XML representation which when taken up by a restful client can be be converted back to an entity object again
Here is a REAL code from one of my projects which probably (if I got it right) does exactly what you need.
#Entity
#Table(name = "KIOSK")
#XmlRootElement
public class RealKiosk implements Kiosk {
private List<Device> kioskDevices = new ArrayList<Device>();
#OneToMany(fetch = FetchType.LAZY, targetEntity = DeviceImpl.class, mappedBy = "kiosk", cascade = CascadeType.ALL)
#XmlElement(type = DeviceImpl.class)
public List<Device> getKioskDevices() {
return kioskDevices;
}
public void setKioskDevices(List<Device> kioskDevices) {
this.kioskDevices = kioskDevices;
}
}
In rare cases you would use
#XmlAnyElement(lax = true)
instead of
#XmlElement(type = DeviceImpl.class)
But if you are not using interfaces and just using classes only
#XmlRootElement
should be sufficient.
But all of that is relevant if you are not using Jackson with Spring for example. If so that would be a little bit different story.
Related
I started using MapStruct 1.4.0.CR1. I'm also using Gradle:
dependencies {
annotationProcessor("org.mapstruct:mapstruct-processor:${project.property("mapstruct.version")}")
implementation("org.mapstruct:mapstruct:${project.property("mapstruct.version")}")
}
I have some JPA entities I'm trying to map:
public class Exam implements Serializable {
// More class members here
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "exam", orphanRemoval = true)
private Set<Scan> scans;
public Exam() { } // ...no-argument constructor required by JPA
public Exam(final Builder builder) {
// ...set the rest also
scans = builder.scans;
}
// getters (no setters), hashCode, equals, and builder here
}
public class Scan implements Serializable {
// More class members here
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "scan", orphanRemoval = true)
private Set<Alarm> alarms;
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "scan", orphanRemoval = true)
private Set<Isotope> isotopes;
protected Scan() { } // ...no-argument constructor required by JPA
public Scan(final Builder builder) {
// ...set the rest also
alarms = builder.alarms;
isotopes = builder.isotopes;
}
// getters (no setters), hashCode, equals, and builder here
}
I have similar classes for mapping, but they don't have as many fields/members as the JPA entities, moreover, they are on a completely different sub-system (hence the mapping). The problem is that MapStruct is telling me there are no isotopes within Scans: java: No property named "scans.isotopes" exists in source parameter(s). Did you mean "scans.empty"?.
Basically, isotopes and alarms are not contained within a Set of scans in the (new) mapped Exam class. This is my ExamMapper:
#FunctionalInterface
#Mapper(componentModel = "spring", injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface ExamMapper {
// #Mapping(source = "scans.alarms", target = "alarms")
#Mapping(source = "scans.isotopes", target = "isotopes")
Exam valueFrom(tld.domain.Exam entity);
}
Is there a way to accomplish this? I think this may be trivial, but I'm fairly new to MapStruct ;)
The source and target attributes of the #Mapping can only reference bean properties.
This means that when using scans.isotopes, it will look for a property isotopes in Set<Scan> and thus the compile error.
In order to solve this you'll need to provide some custom mappings. From what I can understand you will need to do flat mapping here as well. The reason for that is that you have multiple scans, and each scan has multiple isotopes. You need to gather all of that and map it into a single collection.
One way to achieve this is in the following way:
#Mapper(componentModel = "spring", injectionStrategy = InjectionStrategy.CONSTRUCTOR)
public interface ExamMapper {
#Mapping(source = "scans", target = "isotopes")
Exam valueFrom(tld.domain.Exam entity);
Isotope valueFrom(tld.domain.Isotope isotope);
default Set<Isotope> flatMapIsotopes(Set<Scan> scans) {
return scans.stream()
.flatMap(scan -> scan.getIsotopes().stream())
.map(this::valueFrom)
.collect(Collectors.toSet());
}
}
I'm writing a Spring Application, which has two entities that are related by a one to many relationship, lets call them mother and kid.
When I create a mother entity via POST request, I want a kid entity be created automatically. Using the #OneToMany and #ManyToOne annotations, that works fine. At least, as long as I provide the kid information within the MotherService.
Here is my code
Mother.java
#Entity
#Table(name="mother")
public class Mother{
#Id
#Column(name="id", updatable = false, nullable = false)
private Long id;
#Column(name="name")
private String name;
#OneToMany(mappedBy = "mother", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Kid> kidList = new ArrayList<>();
//constructor, getter, setter
private void addKid(Kid kid) {
this.kidList.add(kid);
kid.setMother(this);
}
}
Kid.java
#Entity
#Table(name="kid")
public class Kid{
#Id
#Column(name="id", updatable = false, nullable = false)
private Long id;
#Column(name="name")
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "mother_id", nullable=false)
private Mother mother;
//constructor, getter, setter
}
MotherController.java
#RestController
#RequestMapping("mothers")
public class MotherController {
#Autowired
private MotherService motherService;
MotherController(MotherService motherService) {
this.motherService = motherService;
}
#PostMapping
Mother createMother(#RequestBody Mother mother) {
return this.motherService.createMother(mother);
}
}
MotherService.java
#Service
public class MotherService {
private MotherRepository motherRepository;
#Autowired
public MotherService (MotherRepository motherRepository) {
super();
this.motherRepository= motherRepository;
}
public Mother createMother(Mother mother) {
Kid kid = new Kid("Peter");
mother.addKid(kid);
return this.motherRepository.save(mother);
}
}
The repositories for mother and kid extend the JpaRepository without any custom methods so far.
My POST request is something like (using Postman)
{
"name":"motherName"
}
Now a mother is created with a name "motherName" and a kid with the name of "Peter".
My idea: Using a DTO
I now try to implement a DTO, that contains the mothers name and the kids name, map this information in the MotherService to the entities and save them via the corresponding repository, so I can define both names in the POST request.
motherDto.java
public class mother {
private String motherName;
private String kidName;
//getter, setter
}
So when I POST
{
"motherName":"Susanne",
"kidName":"Peter"
}
or even better
{
"mother": {
"name":"Susanne"
},
"kid": {
"name":"Peter"
}
}
a mother with name Susanne and a kid with name Peter are created.
My question is
How do I map a DTO to two entities?
Or do I not get something right? Is there an easier way to achieve my goal?
I know this is old and probably long solved, but let me offer a different take on the subject.
Another option would be to design a DTO solely for the purpose of creating the two entities you mentioned. You could call this MotherChildCreationDTO or something like that so the name already conveys its use and maybe create a REST-target consuming the DTO.
Asymmetric DTOs (receiving and sending) are an established pattern, and the DTOs are closely coupled to the REST controller any way.
First solution:
You can don't use DTO and send your JSON with same structure of Mother and kids and Jackson in Spring MVC deserialize it correctly for you.
{
id:2,
name:'sarah'
kidList:[{id:546,name:'bob'},{id:478,name:'tom'}]
}
Second solution:
If you want to different structure in JSON and Models and you can use Jackson annotation like #JsonProperty or #JsonDeserialize. Read this like for more information.
Third solution:
You can use DozzerMapper for complex mapping between your DTO and your Model. you define XML's file for mapping each model to your DTO and DozzerMapper map your DTO to your models.Read this link for more information.
You have 2 ways:
Map DTO to entities by yourself. In this case, you should create custom mapper and define how exactly DTO should be converted to entity. Then just inject and use your custom mapper in service.
Use one of existing mapper libraries. For example, good candidates are MapStruct and ModelMapper. You can find usage examples in corresponding getting started guides.
I'm using #OneToMany feature, with FetchType.EAGER set. But JPA (through EclipseLink) keep making one sub request for each sub object in my list.
I really dont get why.
I follow many thread I found, but cant make this select work in one single request.
Also, I retrive my object with CriteriaBuilder, CriteriaQuery and some Predicate. This may be an issue ?
Anyway, all this filter are only use for the top lvl classes. I still want the lower one to be fetched as well.
My entities look like :
#Entity
#Table(name = "mainTable")
public class MainTable extends SqlEntity {
#OneToMany(fetch = FetchType.EAGER)
private List<MainLocation> locations = new ArrayList<MainLocation>();
...
}
#Entity
#Table(name = "mainLocation")
public class MainLocation extends SqlEntity {
#OneToMany(fetch = FetchType.EAGER)
private List<MainDetail> details = new ArrayList<MainDetail>();
...
}
#Entity
#Table(name = "mainDetail")
public class MainDetail extends SqlEntity {
...
}
happy new year:)
I have a Spring MVC project using Hibernate and DataJPA.
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id", nullable = false)
private User user;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "restaurant_id", nullable = false)
#NotNull
private Restaurant restaurant;
As you can see, here is two fields with eager fetch. I want to make both a lazy. I need to user #NamedEntityGraph annotation asI made here:
#NamedQueries({
#NamedQuery(name = Restaurant.GET_BY_ID, query = "SELECT r FROM Restaurant r WHERE r.id=?1"),
})
#Entity
#NamedEntityGraph(name = Restaurant.GRAPH_WITH_MENU_HISTORY, attributeNodes = {#NamedAttributeNode("menuHistory")})
#Table(name = "restaurants")
public class Restaurant extends NamedEntity {
#OneToMany(cascade = CascadeType.REMOVE, fetch = FetchType.LAZY, mappedBy = "restaurant")
#OrderBy(value = "date DESC")
private List<Dish> menuHistory;
public static final String GRAPH_WITH_MENU_HISTORY = "Restaurant.withMenuHistory";
I want to know, if I'll write
#NamedEntityGraph(name = "G_NAME", attributeNodes = {#NamedAttributeNode("user", "restaurant")})
and if I'll request one of them, will the second load anyway or it will load only by request to him? May be, I need to user two graphs?
According to JPA 2.1 Spec 3.7.4:
The persistence provider is permitted to fetch additional entity state
beyond that specified by a fetch graph or load graph. It is required,
however, that the persistence provider fetch all state specified by
the fetch or load graph.
So actually the #NamedEntityGraph just guarantees what fields should be eagerly loaded, but not what fields should not be loaded.
So, if you make #NamedEntityGraph with user, your persistence provider (Hibernate for example) can load only user field or both user and restaurant fields eagerly. This is dependent on implementation and not guaranteed.
See this hibernate's issue.
But as far as I know, the Hibernate loads only simple fields in addition to specified in #NamedEntityGraph, and not loads lazy associations.
So if you use hibernate, it should work.
But of course you need two separate #NamedEntityGraphs for user and restaurant fields.
Or you can use ad-hoc spring-data-jpa's feature:
#Repository
public interface GroupRepository extends CrudRepository<GroupInfo, String> {
#EntityGraph(attributePaths = { "members" })
GroupInfo getByGroupName(String name);
}
With this code you don't need explicitly declare #NamedEntityGraph anymore. You can just specify fields inline.
I am struggling with my inheritance structure where I have a mapped superclass which contains a common field in the concrete classes. This superclass has a one-to-one mapping with a "wrapper" object.
The objects look like this;
#Entity
public class Wrapper {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "wrapper_id", nullable = false)
private Long wrapperId;
#OneToOne(fetch = FetchType.LAZY, mappedBy = "wrapper")
#Cascade(CascadeType.SAVE_UPDATE)
private Base base;
public Long getWrapperId() {
return wrapperId;
}
public void setWrapperId(Long wrapperId) {
this.wrapperId = wrapperId;
}
public Base getBase() {
return base;
}
public void setBase(Base base) {
this.base = base;
}
}
#MappedSuperclass
#Inheritance(strategy = InheritanceType.TABLE_PER_CLASS)
public class Base {
#OneToOne(fetch = FetchType.LAZY)
#Cascade(CascadeType.SAVE_UPDATE)
#JoinColumn(name = "wrapper_id")
protected Wrapper wrapper;
public Wrapper getWrapper() {
return wrapper;
}
public void setWrapper(Wrapper wrapper) {
this.wrapper = wrapper;
}
}
#Entity
public class SubA extends Base {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "sub_a_id", nullable = false)
private Long subAId;
public Long getSubAId() {
return subAId;
}
public void setSubAId(Long subAId) {
this.subAId = subAId;
}
}
For simplicity I have only included a single concrete class but I have several.
This mapping works great when I do not have a reference to "Base" in the wrapper object. As soon as I try to add the bi-directional relationship between the wrapper and base I start to get this error....which doesn't make sense as the field is there.
Caused by: org.hibernate.AnnotationException: Unknown mappedBy in: com.xxx.Wrapper.base, referenced property unknown: com.xxx.Base.wrapper
at org.hibernate.cfg.OneToOneSecondPass.doSecondPass(OneToOneSecondPass.java:153)
at org.hibernate.cfg.Configuration.originalSecondPassCompile(Configuration.java:1697)
at org.hibernate.cfg.Configuration.secondPassCompile(Configuration.java:1426)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1846)
at org.hibernate.cfg.Configuration.buildSessionFactory(Configuration.java:1930)
at org.springframework.orm.hibernate4.LocalSessionFactoryBuilder.buildSessionFactory(LocalSessionFactoryBuilder.java:372)
at org.springframework.orm.hibernate4.LocalSessionFactoryBean.buildSessionFactory(LocalSessionFactoryBean.java:453)
at org.springframework.orm.hibernate4.LocalSessionFactoryBean.afterPropertiesSet(LocalSessionFactoryBean.java:438)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1627)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1564)
... 50 more
What am I missing?
Thanks,
Quote from Java Platform, Enterprise Edition: The Java EE Tutorial:
37.2.2 Mapped Superclasses
Entities may inherit from superclasses that contain persistent state
and mapping information but are not entities. That is, the superclass
is not decorated with the #Entity annotation and is not mapped as an
entity by the Java Persistence provider. These superclasses are most
often used when you have state and mapping information common to
multiple entity classes. Mapped superclasses are specified by
decorating the class with the annotation
javax.persistence.MappedSuperclass:
...
Mapped superclasses cannot be queried and cannot be used in
EntityManager or Query operations. You must use entity subclasses of
the mapped superclass in EntityManager or Query operations. Mapped
superclasses can't be targets of entity relationships.
So it looks like you can`t use this Base class in entity relationships:
#OneToOne(fetch = FetchType.LAZY, mappedBy = "wrapper")
#Cascade(CascadeType.SAVE_UPDATE)
private Base base;
Looks like this hibernate bug: Hibernate complains about an unknown mappedBy property when mapping a bidirectional OneToOne relation with a derived identifier, which was fixed only in late hibernate 4.2.2, 4.3.0.Beta3 versions.
We ended up doing a lot of prototyping with the different options (mapped super class, hierarchical etc) and weighed the options.
In the end we decided to create object hierarchies coupled with the #Entity annotation, Inheritance strategy of SINGLE_TABLE and using Discriminator values to give us exactly what we needed without sacrificing too much.
Thank you all for your suggestions.