2

OK, so today I tried a basic Hibernate tutorial and I'm struggling to make it behave how I want.

This is a simple snippet, assume all other fields/methods are defined (e.g. id etc.)

@Entity
@Table(name = "CITIES")
public static class City {

    @ManyToOne
    @OnDelete(action = OnDeleteAction.CASCADE)
    private Country country;


}


@Entity
@Table(name = "COUNTRIES")
public static class Country {

    @OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
    private Set<City> cities;
}

The above solution utilizes an (automatically created) association table - COUNTRY_CITIES, but this does work only in one way - i.e. I see country's cities (when I select one), but I don't see a city's country (when I select a city).

So what should I do to make cities see their country?

I can add a @JoinColumn(name = "COUNTRY_ID") to the Country.cities field and it works in two ways.

But I don't like to add columns to city's table from country's code.

So I added the @JoinColumn to the City.country field and it didn't work.

So - how to make the association work in 2 ways w/o an association table??


OK, to be precise: I used the above code add feed the DB with this objects:

    Country germany = new Country("Germany", new HashSet<>(asList(
            new City("Berlin"),
            new City("Hamburg")
    )));

.. and viewed the DB. It looked like this:

table COUNTRIES_CITIES (note, I didn't declare it; Hibernate does automatically)

COUNTRIES_ID   CITIES_ID  
1              1
1              2

table COUNTRIES

ID      NAME  
1       Germany

table CITIES (note I didn't declare the COUNTRY_ID column! Hibernate again..)

ID   NAME      COUNTRY_ID  
1    Berlin    null
2    Hamburg   null

And that's it. Fetching the country gives me the cities, fetching the city gives me null country.


If I add a @JoinColumn to the Country class, then the a column is appended to the CITIES table and fetching works two ways.

Parobay
  • 2,549
  • 3
  • 24
  • 36
  • Hey, the class definitions you have provided don't make sense. Are Country and City Inner classes to an outer class? Your class declarations have the static identifier which is illegal in outer class declarations. – SpartanElite Dec 13 '13 at 14:24
  • @SpartanElite yeah, they are both nested inside an `public class Example1 { .. }` just to keep it all in one place – Parobay Dec 13 '13 at 14:25
  • I'm not even going to look at the content of your question, the answers or the comments. Why would a one to many relationship without a 3rd table *ever* be a problem? Many to many, sure; one to many -- then you, or somebody else (framework authors?) are doing it wrong – Dexygen Dec 13 '13 at 15:25

3 Answers3

3

After reading your edit problem is clear.

The way you are adding cities and associating with Country...

Country germany = new Country("Germany", new HashSet<>(asList(
        new City("Berlin"),
        new City("Hamburg")
)));

If you do this then City does not have a country. No where you setting City's country property. ie. city.country = germany;
In a bidirectional relationship you cannot rely on JPA or your provider to entirely manage object persistence for you.

So This is what you need to do..

Country germany = new Country("Germany");
City berlin = new City("Berlin");
City Hamburg = new City("Hamburg");
berlin.country = germany;
hamburg.country = germany;
germany.cities = new HashSet<>(asList(berlin, hamburg));
germany.saveOrUpdate(); // or whichever definition you use

Now in the db you Cities Table will have correct country ids. Also it is recommended, since this is a bidirectional relationship, that you use the mappedBy attribute in your @OneToMany relationship. Using it you are telling JPA that cities is the inverse side of the Country 1-n City relationship.

SpartanElite
  • 624
  • 4
  • 13
  • Yes, this was the case. Thank you very much, now I understand the issue. The behavior is not entirely intuitive though :) – Parobay Dec 16 '13 at 06:22
2

Have you tried using the mappedBy property?

@OneToMany(mappedBy="country", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private Set<City> cities;

For an explanation, look here: Can someone please explain mappedBy in hibernate?

Community
  • 1
  • 1
Ben
  • 1,157
  • 6
  • 11
  • simply adding this only made things worse, now I see neither cities in country nor country in city – Parobay Dec 13 '13 at 13:36
  • Strange, searching in stackoverflow also suggests this approach: http://stackoverflow.com/questions/6649032/avoid-relation-table-in-hibernates-mapping-one-to-manyor-one-to-many-associat – Ben Dec 13 '13 at 13:40
  • nope, doesn't help... It seems my example works "the opposite" way than expected! – Parobay Dec 13 '13 at 13:54
  • 1
    @Parobay Ben's answer is correct: you need a mappedBy property. If you do not do that, it is very likely you will get other problems later. – V G Dec 13 '13 at 13:55
  • Do I understand correctly that there is a third join table in between? Because then you need to apply the @JoinTable annotation as demonstrated here: http://stackoverflow.com/questions/5478328/jpa-jointable-annotation – Gimby Dec 13 '13 at 14:08
  • He actually does not want a third table. But your link also suggests to use `mappedBy` for his case. @Parobay seems like something else is wrong, its impossible to help you here any further. – Ben Dec 13 '13 at 14:12
  • Oh. Oops, I spot it in the title now. Messy problem description threw me off :/ Indeed, mappedBy SHOULD be the solution. So if there is an error, lets see it. – Gimby Dec 13 '13 at 14:35
0

step 1: Add mappedBy at OneToMany side.

 @OnetoMany(mappedBy="users")
   private List<Address> address;

step 2: Add JoinColumn at ManyToOne Side.

 @ManytoOne
 @JoinColumn("userId")
 private User user;