Reputation: 1337
I've two entities in a MySQL DB and I want to realize a bi-directional mapping, with an automatic save of the new child when save a new parent.
MACCHINA (parent) fields: id, marca
PERSONA (child) fields: id, nome, macchina_id (foreign key NOT NULL)
When I save a new MACCHINA, I want also to save a new PERSONA by this JSON:
{
"marca": "string",
"personas": [
{
"nome": "string"
}
]
}
MACCHINA entity:
@Entity
@Table(name = "macchina")
public class Macchina implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
@Column(name = "marca", nullable = false)
private String marca;
@OneToMany(mappedBy = "macchina", cascade = CascadeType.ALL)
private Set<Persona> personas = new HashSet<>();
// getters and setters ...
}
PERSONA entity:
@Entity
@Table(name = "persona")
public class Persona implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
@Column(name = "nome", nullable = false)
private String nome;
@ManyToOne(optional = false)
@JoinColumn(name="macchina_id", referencedColumnName = "id", nullable = false)
private Macchina macchina;
// getters and setters ...
}
In this scenario, when I call the JPA repository method .save() on the Macchina entity, I've the exception:
> Caused by:
> com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException:
> Column 'macchina_id' cannot be null
In this same scenario, I tried to remove in the database, the NotNull constraint to the field "macchina_id" in the Persona table; in this case, the transaction is executed, but the "macchina_id" field in the Persona table, is NULL.
I found a work-around by removing the NotNull constraint to the "macchina_id" in the database (and annotations in the entity) AND by modifying the mapping from parent towards child in this way:
@OneToMany(cascade = CascadeType.ALL)
@JoinColumn(name = "macchina_id")
private Set<Persona> personas = new HashSet<>();
I removed "mappedBy" and added the @JoinColumn. In this way it works: Hibernate execute an insert into Macchina, an insert into Persona, and finally an update into Persona (I guess to write/update the macchina_id field).
My goal is to mantain the NotNull properties for the field "macchina_id" in the database; mantain some properties values in the child entity on the mapping field private Macchina macchina;
like @NotNull / nullable = false / @ManyToOne(optional = false) and save the both entities at the same time with the "macchina_id" field validated automatically by Spring/Hibernate, without to write code by hand.
So, there is an automatic way (Spring/Hibernate) to save first the parent and then the children who has a NotNull foreign key towards the parent?
Any suggestion?
Regards, Andrea
Upvotes: 0
Views: 2886
Reputation: 13
I had the exact same problem, and found this thread after much searching. And I finally solved it. Basically, JPA (or Hibernate - not sure), doesn't seem to be able to set the parent-reference in the child by itself (even though you set the annotations). So in the ParentEntity you should do something along the lines of:
@Entity
public class ParentEntity {
@OneToMany(mappedBy = "parentReference", cascade = CascadeType.ALL) // parentReference, the property in ChildEntity class
private final List<ChildEntity> childEntities = new ArrayList<>();
public ParentEntity(ParentDTO parentDTO) {
...
this.childEntities.add(new ChildEntity(parentDTO.getChildDTO(), this)); // NOTE second param "this"
}
}
And in the ChildEntity, set the parent reference
@Entity
public class ChildEntity {
@ManyToOne
@JoinColumn(name = "child_foreign_key_column_name", nullable = false)
private final ParentEntity parentReference;
public ChildEntity(ChildDTO child, ParentEntity parentReference) {
...
this.parentReference = parentReference;
}
}
Upvotes: 1
Reputation: 1337
I would like to try to avoid to add Macchina
to every Persona
by hand. I'm trying to manage this by Spring/Hibernate (because in case I remove the not null constraint from the foreign key, it works).
To do this example, I'm using a project generated with the project generator JHipster (if you know), and it uses MapStruct to map DTO and domain:
/**
* Contract for a generic dto to entity mapper.
@param <D> - DTO type parameter.
@param <E> - Entity type parameter.
*/
public interface EntityMapper <D, E> {
public E toEntity(D dto);
public D toDto(E entity);
public List <E> toEntity(List<D> dtoList);
public List <D> toDto(List<E> entityList);
}
/**
* Mapper for the entity Macchina and its DTO MacchinaDTO.
*/
@Mapper(componentModel = "spring", uses = {PersonaMapper.class})
public interface MacchinaMapper extends EntityMapper <MacchinaDTO, Macchina> {
default Macchina fromId(Long id) {
if (id == null) {
return null;
}
Macchina macchina = new Macchina();
macchina.setId(id);
return macchina;
}
}
@Generated(
value = "org.mapstruct.ap.MappingProcessor",
date = "2017-07-21T11:57:12+0200",
comments = "version: 1.1.0.Final, compiler: Eclipse JDT (IDE) 3.12.3.v20170228-1205, environment: Java 1.8.0_141 (Oracle Corporation)"
)
@Component
public class MacchinaMapperImpl implements MacchinaMapper {
@Autowired
private PersonaMapper personaMapper;
@Override
public MacchinaDTO toDto(Macchina arg0) {
if ( arg0 == null ) {
return null;
}
MacchinaDTO macchinaDTO = new MacchinaDTO();
macchinaDTO.setId( arg0.getId() );
macchinaDTO.setMarca( arg0.getMarca() );
Set<PersonaDTO> set = personaSetToPersonaDTOSet( arg0.getPersonas() );
if ( set != null ) {
macchinaDTO.setPersonas( set );
}
return macchinaDTO;
}
@Override
public List<MacchinaDTO> toDto(List<Macchina> arg0) {
if ( arg0 == null ) {
return null;
}
List<MacchinaDTO> list = new ArrayList<MacchinaDTO>();
for ( Macchina macchina : arg0 ) {
list.add( toDto( macchina ) );
}
return list;
}
@Override
public Macchina toEntity(MacchinaDTO arg0) {
if ( arg0 == null ) {
return null;
}
Macchina macchina = new Macchina();
macchina.setId( arg0.getId() );
macchina.setMarca( arg0.getMarca() );
Set<Persona> set = personaDTOSetToPersonaSet( arg0.getPersonas() );
if ( set != null ) {
macchina.setPersonas( set );
}
return macchina;
}
@Override
public List<Macchina> toEntity(List<MacchinaDTO> arg0) {
if ( arg0 == null ) {
return null;
}
List<Macchina> list = new ArrayList<Macchina>();
for ( MacchinaDTO macchinaDTO : arg0 ) {
list.add( toEntity( macchinaDTO ) );
}
return list;
}
protected Set<PersonaDTO> personaSetToPersonaDTOSet(Set<Persona> set) {
if ( set == null ) {
return null;
}
Set<PersonaDTO> set_ = new HashSet<PersonaDTO>();
for ( Persona persona : set ) {
set_.add( personaMapper.toDto( persona ) );
}
return set_;
}
protected Set<Persona> personaDTOSetToPersonaSet(Set<PersonaDTO> set) {
if ( set == null ) {
return null;
}
Set<Persona> set_ = new HashSet<Persona>();
for ( PersonaDTO personaDTO : set ) {
set_.add( personaMapper.toEntity( personaDTO ) );
}
return set_;
}
}
Upvotes: 0
Reputation: 1337
The code used to do the save is very simple: I receive from REST the JSON into a DTO, then I map the DTO into a domain object, and finally I call the method .save() of JpaRepository.
@Override
@Transactional
public MacchinaDTO save(MacchinaDTO macchinaDTO) {
log.debug("Request to save Macchina : {}", macchinaDTO);
Macchina macchina = macchinaMapper.toEntity(macchinaDTO);
macchina = macchinaRepository.save(macchina);
return macchinaMapper.toDto(macchina);
}
Upvotes: 0