Load objects related to @OneToOne

5

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

I have an entity that I call Destination. In the Destination entity I have several relationships of type @OneToOne with other entities in a unidirectional way 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 both as @OneToOne and @OneToMany . Being that all are as cascade for both persistence and merge.

To demonstrate the problem I 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 into the problem!

In editing I retrieve my object as follows:

destination.setIdDestination(id);

The same thing returns me the object populated with all relationships. The print below is from the destination object:

Onlythedoubtappearsnow.Whendoingthemergeontheobjecttheentitiesthatarerelatedarepersistedagaininthedatabase.Ithinkthisisbecauseoftheidofobjectsthatisnullasshownintheimageabove.

BecausewhenIloadmymainobject

destination.setIdDestination(id);

Areotherobjectreferencesnotcomingwiththetrailerid?

Thankyouall!

EDITION

IcurrentlyuseSpringMVCintheproject.Belowtheupdateflow:

//EntranateladeediçãodeDestinos@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";   
}

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

The object loads normally! The problem is when I retrieve 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 related entities!

2nd Edition

Currently my write method is more or less as below, ie I'm 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());
    
asked by anonymous 26.09.2014 / 18:41

1 answer

2

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

  • The browser generates a request (POST) with only form data
  • The server receives this request, which is intercepted and handled by Spring MVC
  • Spring MVC generates an incomplete representation of the root tree in destination and passes this object to its mergeDestination method.
  • 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 non-editable ids and values in inputs of type 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 much of a fan of this strategy for several reasons (unnecessary bandwidth consumption, user's ability to edit values that should not, etc.).

    Value-by-value upgrade

    The second strategy is to just feed the view with values that can be changed and assume that the returned object tree is incomplete. To do the update we must look for the original object of the persistence context and make the update value by 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 a reference will not be changed but a referenced entity can be replaced by another one (eg, replace the destination country with another one stored in the database), we can optimize the process by replacing the find by the getReference method (which only returns a 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 do not mind just updating values other than the originals, you can skip conditionals and set all values directly in proxy :

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

    Finally, for complex object trees this kind of work can become tedious. We can alleviate our work by using proprietary libraries for recursive mapping / copying of objects such as 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 do the updates . If you go this route be careful about synchronization issues between the persistence context and the direct database may leave the persistence context out of sync with the database.)

        
    27.09.2014 / 17:38