0

I have 2 tables: Trip and Place (Many-To-One) and my problem is that when I add 2 trips with different data but the same place, it adds me 2 records to Trip table and 2 records into Place table, while it should add just one record into Place.

For example I had 2 trips with different dates but they were in the same place - Italy, Rome. So there should be only one record in Place with these data: Italy, Rome.

How can I avoid such behaviour in my application?

Trip class:

public class Trip implements java.io.Serializable { 
    private int idTrip;
    private int idHotel;
    private Date date;
    private int cost;
    private int profit;
    private String organisator;
    private int period;
    private String food;
    private String transport;
    private int persons;
    private int kidsAmount;

    private String ownerName;
    private String ownerLastName;

    private Place place;

+ constructors, get() and set() methods,

Place class:

public class Place implements java.io.Serializable {

    private int idPlace;
    private String country;
    private String city;
    private String island;
    private String information;
    private Set<Trip> trips;

+ constructors, get() and set() methods,

Trip mapping file:

<hibernate-mapping>
<class name="pl.th.java.biuro.hibernate.Trip" table="Trip">
    <id column="idTrip" name="idTrip" type="int">
        <generator class="native"/>       
    </id>
    <property column="date" name="date" type="date"/>
    <property column="cost" name="cost" type="int"/>
    <property column="profit" name="profit" type="int"/>
    <property column="organisator" name="organisator" type="string"/>
    <property column="period" name="period" type="int"/>
    <property column="food" name="food" type="string"/>
    <property column="transport" name="transport" type="string"/>
    <property column="persons" name="persons" type="int"/>
    <property column="kidsAmount" name="kidsAmount" type="int"/>
    <property column="idHotel" name="idHotel" type="int"/>
    <many-to-one fetch="select" name="place" class="pl.th.java.biuro.hibernate.Place">
        <column name="idPlace" not-null="true"></column>
    </many-to-one>
</class>
</hibernate-mapping>

Place mapping file:

<hibernate-mapping>
<class name="pl.th.java.biuro.hibernate.Place" table="Place">
    <id column="idPlace" name="idPlace" type="int">
        <generator class="native"/>       
    </id>
    <property column="country" name="country" type="string"/>
    <property column="city" name="city" type="string"/>
    <property column="island" name="island" type="string"/>
    <property column="information" name="information" type="string"/>
    <set name="trips" table="Trip" inverse="true" lazy="true" fetch="select">
        <key>
            <column name="idPlace" not-null="true" />
        </key>
        <one-to-many class="pl.th.java.biuro.hibernate.Trip" />
    </set>
</class>
</hibernate-mapping>

I also add some screenshot with my MySQL database, maybe there is some issue which not allow me to do this properly: MySQL database

EDIT: Added one-to-many relation in Place mapping file and Set in Place class, but still got the same problem.

EDIT2: Adding code with persisting entites into database:

Session session = DatabaseConnection.getFactory().openSession();
        Transaction tx = null;
        try {
            tx = session.beginTransaction();

                trip.setPlace(place);
                session.save(place);                    
                session.save(trip);

            tx.commit();
        } catch (HibernateException e) {
            if (tx != null) {
                tx.rollback();
            }
            System.out.println("Exception found while adding new trip: " + e.getMessage());
            e.printStackTrace();
        } finally {
            session.close();
        }

But I am still having the same problem... I add one trip with place A, then in next step I add another trip with the same same, and here is what I get: Duplicate places

EDIT3: Creating Trip and Place objects:

Trip trip = null;
    Place place = null;

    SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
    String dateInString = tripDateField.getText();
    java.util.Date date = null;
    try {
        date = sdf.parse(dateInString);
        place = new Place(tripCountryField.getText(), tripCityField.getText(), tripIslandField.getText(), tripPlaceInfoTextArea.getText());
        int period = 0, persons = 0, kidsAmount = 0;

        //W razie braku niewymaganych liczbowych danych ustawiane są wartości -1
        if (tripPeriodField.getText().equals("")) {
            period = -1;
        }
        if (tripPersonsField.getText().equals("")) {
            persons = -1;
        }
        if (tripKidsAmountField.getText().equals("")) {
            kidsAmount = -1;
        }

    trip = new Trip(new Date(date.getTime()), Integer.parseInt(tripCostField.getText()), Integer.parseInt(tripProfitField.getText()),
                tripOrganisatorField.getText(), period, tripFoodField.getText(), tripTransportField.getText(),
                persons, kidsAmount, tripClientNameField.getText(), tripClientLastNameField.getText());

} catch (ParseException ex) {
        Logger.getLogger(MainFrame.class.getName()).log(Level.SEVERE, null, ex);
        try {
            date = sdf.parse("0000-00-00");
        } catch (ParseException ex1) {
            Logger.getLogger(MainFrame.class.getName()).log(Level.SEVERE, null, ex1);
        }
    } catch (NumberFormatException e) {
        place = new Place("111", "111", "111", "111");
        trip = new Trip(null, WIDTH, WIDTH, dateInString, WIDTH, dateInString, dateInString, WIDTH, ABORT, dateInString, dateInString);

        System.out.println("Exception while getting trip / place data: " + e.toString());
    }

    dataEdition.addTrip(trip, place, dataEdition.validateTripData(trip, place), addRemoveSearchTripDialog);

I get these objects data from textFields and parse them to int if needed, so that should be ok I guess. After this I pass these 2 objects into another method where I persist them into database.

Tomek
  • 101
  • 1
  • 9
  • 1
    Hm, maybe in your Place mapping file the one-to-many tag is missing. So how should your program know the other direction of many-to-one? Like `` – aw-think May 25 '15 at 20:13
  • Good point, that could be the issue, but when I add this after last I get this exception error: ERROR: HHH000196: Error parsing XML (2) : The content of element type "class" must match. What am I doing wrong? – Tomek May 25 '15 at 20:24
  • What about your hibernate.cfg.xml file? Is there a line like this: "classpath://org/hibernate/hibernate-mapping-3.0.dtd" or is there a line like this "classpath://org/hibernate/hibernate-**configuration**-3.0.dtd">. And you need to put the one-to-many tag in a set tag. Look here on No. 5: http://viralpatel.net/blogs/hibernate-one-to-many-xml-mapping-tutorial/ – aw-think May 25 '15 at 20:42
  • I've got this in hibernate.cfg.xml file: – Tomek May 25 '15 at 20:47
  • I've updated my comment. Some people say, that they need to set it to mapping-3.0.dtd to work. Which version of hibernate you are using? 3.x, 4.x or 5.x? – aw-think May 25 '15 at 20:51
  • I've added Set into Place class, added in place.hbm and still getting duplicates... don't get it why. I am using 4.3.9 Hibernate. I have edited code here with one-to-many, but still no luck. – Tomek May 25 '15 at 22:10
  • Could you show the code how you persisted the 2 trips and the place? I mean, when 2 places are commited, then there must also be 2 place identiefiers be generated. – sfrutig May 26 '15 at 05:23
  • Code added, example problem also, please look at this. I am adding 2 trips not in the same moment. – Tomek May 26 '15 at 09:08
  • Like @NwDx already commented, I think that you persist the place twice. So how do you create/get the place ? – sfrutig May 26 '15 at 11:07
  • Code added, please check. – Tomek May 26 '15 at 11:46

3 Answers3

2

It looks like you create a NEW place on every submission.

Place place = new Place(tripCountryField.getText(), tripCityField.getText(), tripIslandField.getText(), tripPlaceInfoTextArea.getText());

and then you expect Hibernate to somehow magically determine that you may actually want to work with an existing entry in the database.

This will not work.

If the submitted place already exists then you need somehow to load the existing persistent entity and work with that.

You can either fix this on the front-end by allowing user to select an existing place and sending through the ID for that or you query for a matching place on form submission.

e.g.

Place place = myDatabaseService.findPlace(country, name ...) //does not account for misspellings

if(place == null){
     place = new Place(tripCountryField.getText(), tripCityField.getText(), tripIslandField.getText(), tripPlaceInfoTextArea.getText());
}
Alan Hay
  • 22,665
  • 4
  • 56
  • 110
  • Hmm I thought that it can specify if such Place already exists, maybe not "magically" but in some decent way :) So before every trip adding I have to check if a place already exists, if it does, I should load this place into Place object, make trip.setPlace(place) and then session.save(trip)? Will it work? – Tomek May 26 '15 at 14:34
  • Done as I said in previous comment and it works. Thank you for help, I know now that hibernate isn't as "magical" as I thought :) – Tomek May 27 '15 at 16:15
1

I've tried this one, and it is not making any duplicates:

enter image description here

And creation of the two classes and tables in DB, I created two mapping files in the same package as the classes.

The places mapping:

<hibernate-mapping package="pl.th.java.biuro.hibernate"> 
    <class name="Place" table="PLACE"> 
        <id name="idPlace" type="java.lang.Long" column="ID_PLACE">
            <generator class="native">
        </generator></id>        

        <set name="trips" table="Trip" inverse="true" lazy="true" fetch="select">
            <key>
                <column name="idPlace" not-null="true">
            </column></key>
            <one-to-many class="pl.th.java.biuro.hibernate.Trip">
        </one-to-many></set>
    </class>
</hibernate-mapping>

And the trip mapping:

<hibernate-mapping package="pl.th.java.biuro.hibernate"> 
    <class name="Trip" table="TRIP">
        <id name="idTrip" type="java.lang.Long" column="ID_TRIP">
            <generator class="native">
        </generator></id>

        <many-to-one name="place" class="pl.th.java.biuro.hibernate.Place" fetch="select">
            <column name="idPlace" not-null="true">
        </column></many-to-one> 
    </class>
</hibernate-mapping>

I have seen, that your trip id is not generated on your table? Is that correct? And your naming convention is a bit error-prone. Try to name db tables uppercase and the set variable in class Place to maybe trips. In your mappings you mapped the id to an int. Most Databases map it to long, so maybe there is an error too.

aw-think
  • 4,723
  • 2
  • 21
  • 42
  • I have edited as you said except id's to long, have seen many examples with int which worked so I have left it for now. But problem still occurs... could you check edited code up here? – Tomek May 26 '15 at 09:09
  • 1
    The only thing now I think is, that you persist the place twice. Please add the code where you create the place and the trip. BTW: In your MySQL Screens, the table place is missing a cross at "UQ" for unique. But normaly this should be clear, that a pk is unique :-) And maybe you can try to replace `session.save(place)` by `session.saveOrUpdate(place)` Look here: http://stackoverflow.com/questions/161224/what-are-the-differences-between-the-different-saving-methods-in-hibernate – aw-think May 26 '15 at 09:41
  • Code added here, changed this primary key from Place into unique. I also have changed the method from "save" to "saveOrUpdate" but I still get the same... – Tomek May 26 '15 at 11:45
  • 1
    What Alan mentioned is true, you do not try to load an existing entry first, you always create a new place object and persist it. If you extend your primary key on this table to include country, city and zip code, this will throw you an exception on your try to save this entry to table, but a artifical key like id is not aware of this behaviour. – aw-think May 26 '15 at 13:00
  • Thank you for all your help, I did some checking now before adding new place into db and it works perfectly as I wanted. – Tomek May 27 '15 at 16:16
0

I think your entities need to override hashcode() and equals() methods to be able to recognize their uniqueness.

over9k
  • 115
  • 1
  • 10