Load objects related to @Onetoone

Asked

Viewed 812 times

5

concerning the doubt that I have is basic, but it is taking my sleep.

I have an entity in which I call Destination. In the Destination entity I have several relationships of the type @OneToOne with other entities unidirectionally as below:

Destination.java (Owner of the relationship)

@Entity
@Table(name="destination")
public class Destination implements Serializable {
    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="id_destination")
    private Long idDestination;

    @Column(name="appear_website")
    private Boolean dtAppearWebsite;

    @Lob
    @Column(name="description")
    @NotEmpty(message="O campo \"Descrição do Destino\" não pode estar em branco.")
    private String dtDescription;

    @Column(name="highlight_website")
    private Boolean dtHighlightWebsite;

    @Column(name="name")
    @NotEmpty(message="O campo \"Nome do Destino\" não pode estar em branco.")
    private String dtName;

    @OneToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE})
    @JoinColumn(name="fk_streetview")
    private StreetView streetView;

    @OneToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE})
    @JoinColumn(name="fk_video")
    private Video video;

    @ManyToOne(cascade=CascadeType.REFRESH)
    @JoinColumn(name="fk_category")
    private Category categories;

    //Profiles of System. These profiles are all enum type.
    @OneToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE})
    @JoinColumn(name="fk_economic")
    private EconomicProfile economicProfiles;

    @OneToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE})
    @JoinColumn(name="fk_general")
    private GeneralProfile generalProfiles;

    @OneToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE})
    @JoinColumn(name="fk_social")
    private SocialProfile socialProfiles;

    @OneToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE})
    @JoinColumn(name="fk_trip")
    private TripProfile tripProfiles;

    @OneToOne(cascade={CascadeType.PERSIST, CascadeType.MERGE})
    @JoinColumn(name="fk_weather")
    private WeatherProfile weatherprofile;
    //End of Profiles of System.

    @OneToMany(cascade={CascadeType.PERSIST, CascadeType.MERGE}, fetch=FetchType.EAGER)
    @JoinColumn(name="fk_destination")
    @Valid
    private Set<Image> images;

        //Getters an setters...

As can be seen above I have a "handful" of relationships as much as @OneToOne how much @OneToMany. All of which are as Scade for both persistence and merge.

To demonstrate the problem I will use the relationship between Destiantion and Weatherprofile.

@Entity
@Table(name="weather_profile")
public class WeatherProfile implements Serializable{

    private static final long serialVersionUID = 1L;

    @Id
    @GeneratedValue(strategy=GenerationType.IDENTITY)
    @Column(name="id_weather")
    private Long id;

    private Boolean heat;
    private Boolean cold;
    private Boolean winter;

    public WeatherProfile(){

    }
    //Getter and Setter...
}

Okay, let’s get to the problem!

In the edit I recover my object as follows:

destination.setIdDestination(id);

The same returns to me the object populated with all relationships. The print below is of the object Destination:

inserir a descrição da imagem aqui

Only the doubt appears now. When I merge into the object the entities that are related are persisted again in the database. I think this is because of the id of the objects that is null as shown in the image above.

'Cause when I carry my main object

destination.setIdDestination(id);

references to other objects are not coming with the id attached?

Thank you all!


EDITION

Currently use the Spring MVC in the project. Below the update flow:

//Entra na tela de edição de Destinos
@RequestMapping(value="/editDestination", method = RequestMethod.GET)
public String editDestination(Long id, Model model){

        //Inicializa o componente de paises
        List<Country> countriesList = dashboardFacade.getCountriesList();       
        model.addAttribute("countriesList", countriesList);

        List<Category> allCategory = getActiveCategories();
        model.addAttribute("categoryDropDown", allCategory);

        model.addAttribute("destinationModify", dashboardFacade.getDestinationId(id));
        return "destination/editDestination";   
}

Above I retrieve my object and place it in an attribute to popular my view: model.addAttribute("destinationModify",dashboardFacade.getDestinationId(id));

The object is loaded normally! The problem is when I will recover this attribute in the method:

    @RequestMapping(value="/mergeDestination", method = RequestMethod.POST)
    public ModelAndView mergeDestination(@ModelAttribute("destinationModify") @Valid Destination destination, BindingResult result, RedirectAttributes redirectAttributes, Model model, @RequestParam(value="id", required=true) Long id){

        //destination.setIdDestination(id);
        Destination destinationId = dashboardFacade.getDestinationId(id);
        //Realiza os tratamentos para merge

}

The Destination object comes with all information except the id’s of the related entities!


2nd Edition

Currently my recording method is more or less like below, ie I am setting attribute by attribute in my tables related to Destination.java

@RequestMapping(value="/mergeDestination", method = RequestMethod.POST)
    public ModelAndView mergeDestination(@ModelAttribute("destinationModify") @Valid Destination altDestination, BindingResult result, RedirectAttributes redirectAttributes, Model model, @RequestParam(value="id", required=true) Long id){

        //destination.setIdDestination(id);
        Destination destinationId = dashboardFacade.getDestinationId(id);

                try {
                    //Atualiza a Categoria
                    Category category = dashboardFacade.getCategoryId(altDestination.getCategories().getIdCategory());
                    destinationId.setCategories(category);

                    //Atualiza o Pais
                    Country country = dashboardFacade.getCountryId(altDestination.getCountry().getIdCountry());
                    destinationId.setCountry(country);

                    //Atualiza o Street View                
                    destinationId.getStreetView().setCode(altDestination.getStreetView().getCode());    

                    //Atualiza os Video do Youtube
                    destinationId.getVideo().setCode(altDestination.getVideo().getCode());

                    //Profile
                    //Economic Profile
                    destinationId.getEconomicProfiles().setEconomic(altDestination.getEconomicProfiles().getEconomic());
                    destinationId.getEconomicProfiles().setIntermediate(altDestination.getEconomicProfiles().getIntermediate());
                    destinationId.getEconomicProfiles().setLuxury(altDestination.getEconomicProfiles().getLuxury());

                    //General Profile
                    destinationId.getGeneralProfiles().setBeach(altDestination.getGeneralProfiles().getBeach());
                    destinationId.getGeneralProfiles().setCity(altDestination.getGeneralProfiles().getCity());
                    destinationId.getGeneralProfiles().setCottage(altDestination.getGeneralProfiles().getCottage());
                    destinationId.getGeneralProfiles().setMountain(altDestination.getGeneralProfiles().getMountain());

                    //Social Profiles
                    destinationId.getSocialProfiles().setAccompanying(altDestination.getSocialProfiles().getAccompanying());
                    destinationId.getSocialProfiles().setAlone(altDestination.getSocialProfiles().getAlone());
                    destinationId.getSocialProfiles().setChildren(altDestination.getSocialProfiles().getChildren());
                    destinationId.getSocialProfiles().setElderly(altDestination.getSocialProfiles().getElderly());
                    destinationId.getSocialProfiles().setFamilyChildren(altDestination.getSocialProfiles().getFamilyChildren());
                    destinationId.getSocialProfiles().setFriends(altDestination.getSocialProfiles().getFriends());
                    destinationId.getSocialProfiles().setTeenager(altDestination.getSocialProfiles().getTeenager());

1 answer

2


The problem is that the view does not contain part of the data of the objects referenced by destination (ids, non-enterable values, etc). This is a common problem. When a system user submits the form at a high level what happens is the following:

  1. The browser generates a request (POST) with form data only
  2. The server receives this request, which is intercepted and handled by Spring MVC
  3. Spring MVC generates an incomplete representation of the object tree with root in destination and passes on this object to your method mergeDestination.
  4. When you make a merge using the incomplete tree (without the ids of the referenced objects) your JPA provider understands that new objects must be persisted.

In these cases we have three alternatives:

Inputs Hidden

We can store the ids and non-enterable values in inputs of the kind Hidden in view. These values will be returned in the POST request and the objects will be rebuilt correctly by Spring MVC. I’m not a fan of this strategy for several reasons (unnecessary bandwidth consumption, user ability to edit values that should not, etc).

Update value to value

The second strategy is to only feed the view with the values that can be changed and assume that the returned object tree will be incomplete. To do the update we must fetch the original object from the persistence context and update value to value:

Destination dest = entityManager.find(Destination.class, id);
if (dest.getValue1() == null || !dest.getValue1().equals(viewDest.getValue1())) {
    dest.setValue1(viewDest.getValue1()));            
}

For situations where the values of one reference will not be changed, but one referenced entity can be replaced by another (e.g., replace the destination country with another stored in the bank), we can optimize the process by replacing the method find by the method getReference (that only returns one proxy for the entity).

if (dest.getCountry() == null || !dest.getCountry().getId().equals(
        viewDest.getCountry().getId())) {
    dest.setCountry(entityManager.getReference(Country.class, viewDest.getCountry().getId()));            
}

Additionally, if you don’t mind updating only different values from the original ones, you can skip the conditions and set all values directly on proxy:

Destination dest = entityManager.getReference(Destination.class, id);
dest.setValue1(viewDest.getValue1());
dest.setCountry(entityManager.getReference(Country.class, viewDest.getCountry().getId()));    

Finally, for trees of complex objects this kind of work can become tedious. We can ease our work by using our own libraries for mapping / recursive copy of objects like the Dozer.

// Configurado com map-null="false" e o DozerProxyResolver correto
Destination dest = entityManager.getReference(Destination.class, id);
Mapper mapper = DozerBeanMapperSingletonWrapper.getInstance();
mapper.map(viewDest, dest);

JPQL / Criteria for update

You can also merge each referenced object (not using CASCADE) and / or use JPQL or Criterias to make the updates. If you go this way take care of synchronization issues between the persistence context and the bank (updates direct can leave the persistence context out of sync with the bank).

Browser other questions tagged

You are not signed in. Login or sign up in order to post.