0

I've been trying to solve this thing for along time now, but I haven't gotten anywhere. I've been trying to save a entity that posses a reference to another entity.

User creates an place entity by filling out the form then presses save to save it. It should automatically make new rows into 'places' and 'place_urls' tables. Here is a link to SQL file that I'm loading into the application: https://pastebin.com/x8Gvk7ub

Parent entity:

@Entity
@Table(name="places")
public class Place {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name="id", nullable=false, updatable=false)
    private Long id;

    @Column(name="userId")
    private Long userId;

    @Column(name="name", nullable=false, unique=true)
    private String name;

    @Column(name="address", nullable=false)
    private String address;

    @Column(name="largeDescription", nullable=false)
    private String largeDescription;

    @Column(name="smallDescription", nullable=false)
    private String smallDescription;

    @OneToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
    @JoinColumn(name="id")
    private PlaceUrl placeUrl;

    @OneToMany(fetch=FetchType.LAZY, cascade=CascadeType.ALL)
    @JoinColumn(name="id")
    private List<Booking> bookings;

    getters and setters...
}

Child entity:

@Entity
@Table(name="placeUrls")
public class PlaceUrl {

    @Id
    @Column(name="id", nullable=false, updatable=false)
    private Long id;

    @Column(name="placeId", nullable=false, updatable=false)
    private Long placeId;

    @Column(name="url", nullable=false, updatable=true, unique=true)
    private String url;

    @OneToOne
    @JoinColumn(name="id", referencedColumnName="placeId")
    private Place place;

    getters and setters...
}

Controller:

@PostMapping("/place/add")
public String addPlace(@ModelAttribute Place place, @AuthenticationPrincipal UserDetails currentUser) {
     User user = userRepository.findUserByUsername(currentUser.getUsername());
     place.setUserId(user.getId());
     placeRepository.save(place);
     return "redirect:/places";
}

Hibernate naming is set to implicitic-strategy in application.properties

UPDATE:

Place entity:

@OneToOne(fetch=FetchType.LAZY, cascade=CascadeType.ALL, mappedBy="place")
private PlaceUrl placeUrl;

PlaceUrl entity:

Removed the placeId column, placeId variable and it's getters and setters.

@OneToOne(fetch=FetchType.LAZY)
@JoinColumn(name="placeId")
private Place place;

Controller changes:

@PostMapping("/place/add")
public String addPlace(@ModelAttribute Place place, @AuthenticationPrincipal UserDetails currentUser) {
User user = userRepository.findUserByUsername(currentUser.getUsername());
    place.setUserId(user.getId());
    place.getPlaceUrl().setUrl("something_nice");
    placeRepository.save(place);
    return "redirect:/places";
}

Now upon save I get: No message available java.lang.NullPointerException

UPDATE 2:

I got working by just messing around. I have no idea why it works, so someone else can explain.

@PostMapping("/place/add")
    public String addPlace(Model model, @ModelAttribute Place place, @AuthenticationPrincipal UserDetails currentUser) {
        PlaceUrl placeUrl = new PlaceUrl();
        User user = userRepository.findUserByUsername(currentUser.getUsername());
        placeUrl.setUrl(place.getName());
        place.setPlaceUrl(placeUrl);
        place.setUserId(user.getId());
        placeUrl.setPlace(place); <-- this line here made it all work
        placeRepository.save(place);
        return "redirect:/places";
    }

I followed the instructions of the guide book that I was suggested. Basically I added @OneToOne(mappedBy="place") to the parent then I added @OneToOne and @JoinColumn(name="placeId") to the child entity.

Andronicus
  • 25,419
  • 17
  • 47
  • 88
Skege
  • 111
  • 1
  • 12
  • That's not how you map a bidirectional OneToOne association. Please read the documentation to know how to do it properly. https://docs.jboss.org/hibernate/orm/current/userguide/html_single/Hibernate_User_Guide.html#associations-one-to-one-bidirectional – JB Nizet Oct 12 '19 at 15:04
  • @JBNizet Okay I updated the question. I read the part of bidirectional OneToOne annotation, but still getting error. This time the error is just NullPointerException. – Skege Oct 12 '19 at 15:33
  • Read https://stackoverflow.com/questions/218384/what-is-a-nullpointerexception-and-how-do-i-fix-it. You can't call a method on a null reference. That's what leads to a NullPointerException. You're calling setUrl() on place.getPlaceUrl(), and this method most probably returns null, because you never initalized it. – JB Nizet Oct 12 '19 at 15:37
  • @JBNizet Okay I read the thing and I solved it by instantiated new PlaceUrl() entity. Called **setUrl** on it then I called **setPlaceUrl** on Place entity to save it. Now I'm getting new error that tells me that place_id cannot be null. Caused by: java.sql.SQLIntegrityConstraintViolationException: Column 'place_id' cannot be null. How can I attach the placeId to PlaceUrl entity if Place is not even created in the first place? – Skege Oct 12 '19 at 16:05
  • @JBNizet Got it working! – Skege Oct 12 '19 at 19:52
  • make sure to add objects on both sides of the relation, so in your `Parent#setChild` method I would also do `if (child.getParent() != this) child.setParent(this);` and vice versa, basically that's what you did manually, the line you highlighted in your update 2, just if you add it to your setters then you don't have to worry anymore, if you want to know exactly why you need to do that, let me know – PaulD Oct 14 '19 at 06:49

1 Answers1

1

You're already mapping the relationship in parent entity but not using it in child. Instead, by defining another mapping, you cannot take advantage of cascading. The solution would be to add mappedBy in PlaceUrl:

@OneToOne(mappedBy = "placeUrl")
private Place place;

Or even better, use mappedBy on the child side, which is the clean approach.

Andronicus
  • 25,419
  • 17
  • 47
  • 88