0

HQL Join gives me weird data for the joined column enter image description here

While my HQL query is working fine in the Hibernate Console, enter image description here i get weird results. I tried to change the row below in conjunction with @FXML annotations of the corresponding column, but either i get errors, either i get blank cells. vstcolName.setCellValueFactory(new PropertyValueFactory("peopleByPeopleId"));

Here are my Entity classes for the 1st table.

package Entities;

import javax.persistence.*;
import java.util.Objects;

@Entity
@Table(name = "people", schema = "learn", catalog = "")
public class PeopleEntity {
    private int pplId;
    private String pplName;

    @Id
    @Column(name = "pplID")
    public int getPplId() {
        return pplId;
    }

    public void setPplId(int pplId) {
        this.pplId = pplId;
    }

    @Basic
    @Column(name = "pplName")
    public String getPplName() {
        return pplName;
    }

    public void setPplName(String pplName) {
        this.pplName = pplName;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        PeopleEntity that = (PeopleEntity) o;
        return pplId == that.pplId &&
                Objects.equals(pplName, that.pplName);
    }

    @Override
    public int hashCode() {
        return Objects.hash(pplId, pplName);
    }
}

Here are my Entity classes for the 2nd table.

package Entities;

import javax.persistence.*;
import java.util.Objects;

@Entity
@Table(name = "places", schema = "learn", catalog = "")
public class PlacesEntity {
    private int plcId;
    private String place;
    private PeopleEntity peopleByPeopleId;

    @Id
    @Column(name = "plcID")
    public int getPlcId() {
        return plcId;
    }

    public void setPlcId(int plcId) {
        this.plcId = plcId;
    }

    @Basic
    @Column(name = "place")
    public String getPlace() {
        return place;
    }

    public void setPlace(String place) {
        this.place = place;
    }

    @Override
    public boolean equals(Object o) {
        if (this == o) return true;
        if (o == null || getClass() != o.getClass()) return false;
        PlacesEntity that = (PlacesEntity) o;
        return plcId == that.plcId &&
                Objects.equals(place, that.place);
    }

    @Override
    public int hashCode() {
        return Objects.hash(plcId, place);
    }

    @ManyToOne
    @JoinColumn(name = "people_ID", referencedColumnName = "pplID")
    public PeopleEntity getPeopleByPeopleId() {
        return peopleByPeopleId;
    }

    public void setPeopleByPeopleId(PeopleEntity peopleByPeopleId) {
        this.peopleByPeopleId = peopleByPeopleId;
    }
}

Here is my model that contains the HQL Query.

package Models;

import Entities.PlacesEntity;
import Interfaces.PlacesIF;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.Transaction;
import org.hibernate.cfg.Configuration;
import org.hibernate.query.Query;

import java.util.List;

public class PlacesIM implements PlacesIF {

    public List<PlacesEntity> readJoin() {
        session = sessionFactory.openSession();
        Query query;
        query = session.createQuery("SELECT PlcEnt.place as PLACES, pep.pplName as NAMES FROM PlacesEntity as PlcEnt JOIN PlcEnt.peopleByPeopleId as pep");
        List<PlacesEntity> tableList;
        tableList = query.list();
        return tableList;

    }
}

Here is the FXML Controller

package FX;

import Entities.PeopleEntity;
import Entities.PlacesEntity;
import Models.PlacesIM;
import javafx.collections.FXCollections;
import javafx.collections.ObservableList;
import javafx.event.ActionEvent;
import javafx.fxml.FXML;
import javafx.fxml.FXMLLoader;
import javafx.fxml.Initializable;
import javafx.scene.Node;
import javafx.scene.Parent;
import javafx.scene.Scene;
import javafx.scene.control.Button;
import javafx.scene.control.TableColumn;
import javafx.scene.control.TableView;
import javafx.scene.control.TextField;
import javafx.scene.control.cell.PropertyValueFactory;
import javafx.stage.Stage;

import java.io.IOException;
import java.net.URL;
import java.util.List;
import java.util.ResourceBundle;

public class placesController implements Initializable {
//Visit TableView

@FXML
TableView<PlacesEntity> vstTView;
//I think that this line may need modification
@FXML
private TableColumn<PlacesEntity, PeopleEntity> vstcolName;
@FXML
private TableColumn<PlacesEntity, String> vstcolPlace;

public void readJoin() {
    plcTView.getItems().clear();
    List<PlacesEntity> tableList = plcIM.read();
    for (PlacesEntity pEnt : tableList) {
        observableList.add(pEnt);
    }
//Here is the other tricky part (that's what i think at least)
    vstcolName.setCellValueFactory(new PropertyValueFactory<PlacesEntity, PeopleEntity>("peopleByPeopleId"));
    vstcolPlace.setCellValueFactory(new PropertyValueFactory<PlacesEntity, String>("place"));
    vstTView.setItems(observableList);
   }
}

The data that is retuned in the joined column is something like this Entities.PeopleEntity@864db1b7

...etc... I should get Names - i mean strings.. The other column is displayed normally.

By the way, i have exactly the same result, if i try to display the result of the HQL query in the console, instead of the tableview. Don't mind the change of the weird column. It's because i tried to place numbers (still strind data type) in the place of the names that i had before, to see if anything will change... But the problem still persists. enter image description here

  • 1
    The default `TableCell` will display the `toString()` of the object if the object is not a `Node`. Check out [How can I Populate a ListView in JavaFX using Custom Objects?](https://stackoverflow.com/questions/36657299/how-can-i-populate-a-listview-in-javafx-using-custom-objects); while that question relates to `ListView` the procedure is virtually the same for a `TableColumn`. – Slaw Sep 14 '19 at 21:46
  • Really confused. It seems much more different from my case. I can't seem to understand where should i change the code and how. I have list, he has listview. I have a mysql dbd and trying to get the values of strings using foreign keys, and he is trying to get some strings inside a method. Furthermore, i don't have problem with columns other than that with the foreign keys. –  Sep 14 '19 at 23:06
  • Correct me if I'm wrong, but the problem you're having is with your `Name` column. What those cells in that column are displaying (e.g. `Entities.PeopleEntity@864db1b7`) is indicative of the default (i.e. non-overridden) `Object#toString()` method. Looking at your code, the column is a `TableColumn` which means the value each `TableCell` in that column holds is a `PeopleEntity` instance. The default `TableCell` implementation (used when no custom cell factory is specified) does not know how to display a `PeopleEntity`. You have to tell it how, via a `cellFactory`. – Slaw Sep 14 '19 at 23:58

1 Answers1

1

If I understand correctly, your problem is related to the "Name" column. Specifically, the values being displayed in that column seem like some weird, esoteric language. Whenever you see output from Java that looks similar to "com.example.Foo@12ef485d" what you're most likely seeing is the result from the default Object#toString() method:

Returns a string representation of the object. In general, the toString method returns a string that "textually represents" this object. The result should be a concise but informative representation that is easy for a person to read. It is recommended that all subclasses override this method.

The toString method for class Object returns a string consisting of the name of the class of which the object is an instance, the at-sign character '@', and the unsigned hexadecimal representation of the hash code of the object. In other words, this method returns a string equal to the value of:

getClass().getName() + '@' + Integer.toHexString(hashCode())

The reason you're seeing this output is due to the way TableColumn works. A TableColumn has both a cellValueFactory:

The cell value factory needs to be set to specify how to populate all cells within a single TableColumn. A cell value factory is a Callback that provides a TableColumn.CellDataFeatures instance, and expects an ObservableValue to be returned. The returned ObservableValue instance will be observed internally to allow for immediate updates to the value to be reflected on screen. An example of how to set a cell value factory is:

lastNameCol.setCellValueFactory(new Callback<CellDataFeatures<Person, String>, ObservableValue<String>>() {
    public ObservableValue<String> call(CellDataFeatures<Person, String> p) {
        // p.getValue() returns the Person instance for a particular TableView row
        return p.getValue().lastNameProperty();
    }   
});  

A common approach is to want to populate cells in a TableColumn using a single value from a Java bean. To support this common scenario, there is the PropertyValueFactory class. Refer to this class for more information on how to use it, but briefly here is how the above use case could be simplified using the PropertyValueFactory class:

lastNameCol.setCellValueFactory(new PropertyValueFactory<Person,String>("lastName"));

And a cellFactory:

The cell factory for all cells in this column. The cell factory is responsible for rendering the data contained within each TableCell for a single table column.

By default TableColumn uses the default cell factory, but this can be replaced with a custom implementation, for example to show data in a different way or to support editing.There is a lot of documentation on creating custom cell factories elsewhere (see Cell and TableView for example).

Finally, there are a number of pre-built cell factories available in the javafx.scene.control.cell package.

In other words, the cellValueFactory determines what value will be displayed and the cellFactory can customize how said value will be displayed. If no custom cellFactory is set then it uses the DEFAULT_CELL_FACTORY:

If no cellFactory is specified on a TableColumn instance, then this one will be used by default. At present it simply renders the TableCell item property within the graphic property if the item is a Node, or it simply calls toString() if it is not null, setting the resulting string inside the text property.

In your code, you currently have:

...

@FXML
private TableColumn<PlacesEntity, PeopleEntity> vstcolName;

...

public void readJoin() 
    ...
    vstcolName.setCellValueFactory(new PropertyValueFactory<>("peopleByPeopleId"));
    ...
}

...

What this does is configure the vstcolName column to extract a PeopleEntity (from an PlacesEntity) as the value for each TableCell. The problem here is you don't set a cellFactory, which means each TableCell is going to display PeopleEntity#toString() (and since you didn't override that method you get the default Object#toString() output). Based on the name of the column (i.e. "Name") I'm assuming you actually want the cells to display PeopleEntity#getPplName(). In order to do this, you need to set a cellFactory and return a custom TableCell that overrides updateItem(T,boolean):

The updateItem method should not be called by developers, but it is the best method for developers to override to allow for them to customise the visuals of the cell. To clarify, developers should never call this method in their code (they should leave it up to the UI control, such as the ListView control) to call this method. However, the purpose of having the updateItem method is so that developers, when specifying custom cell factories (again, like the ListView cell factory), the updateItem method can be overridden to allow for complete customisation of the cell.

It is very important that subclasses of Cell override the updateItem method properly, as failure to do so will lead to issues such as blank cells or cells with unexpected content appearing within them. Here is an example of how to properly override the updateItem method:

protected void updateItem(T item, boolean empty) {
    super.updateItem(item, empty);

    if (empty || item == null) {
        setText(null);
        setGraphic(null);
    } else {
        setText(item.toString());
    }
}

Note in this code sample two important points:

  1. We call the super.updateItem(T, boolean) method. If this is not done, the item and empty properties are not correctly set, and you are likely to end up with graphical issues.
  2. We test for the empty condition, and if true, we set the text and graphic properties to null. If we do not do this, it is almost guaranteed that end users will see graphical artifacts in cells unexpectedly.

In your case, it might look something like:

vstcolName.setCellFactory(tv -> new TableCell<>() {

    @Override
    protected void updateItem(PeopleEntity item, boolean empty) {
        super.updateItem(item, empty);
        if (empty || item == null) {
            setText(null);
        } else {
            setText(item.getPplName());
        }
    }

});

Note: Configuring the cellValueFactory and cellFactory of a TableColumn should only happen once, during initialization. You currently set the cellValueFactory in a public method named readJoin() which seems like it might be called often. As you're using FXML, you should move the TableColumn configuration to the @FXML private void initialize() { ... } method of the controller.

Community
  • 1
  • 1
Slaw
  • 37,820
  • 8
  • 53
  • 80
  • You are awesome @Slaw! I'm getting to understand this now. Also, the code works like a charm. I placed it in the private void initialize() { ... } method as you recommended. Is there any video tutorial or site that you could recommend me to study? I read the docs from the links you brought up, (thanks for copying the highlight of the content inside the reply as well). Furthermore, even it is off topic, can you recommend me any other tutorial about javafx and hibernate? REALLY APPRECIATE your help man! :) You made my day! I was a bit frustrated at this point. –  Sep 15 '19 at 12:54
  • 1
    Hibernate has [extensive documentation and guides](https://hibernate.org/orm/documentation/5.4/). For JavaFX, you could check out Oracle's [tutorial](https://docs.oracle.com/javase/8/javafx/get-started-tutorial/index.html) (some information may be out-of-date). There are also [books covering JavaFX](https://www.google.com/search?q=javafx+books). Of course, reading [the Javadoc](https://openjfx.io/javadoc/12/) is also helpful. There's also [this](https://fxdocs.github.io/docs/index.html). And you might find something [here](https://www.google.com/search?q=javafx+and+hibernate+tutorial). – Slaw Sep 15 '19 at 20:15
  • Any suggestion to do the same for a TextField? –  Sep 17 '19 at 22:05
  • 1
    With a `TextField` you can just call `tf.setText(entity.getPplName())`, is that what you're asking about? If necessary, you can also use a [`StringConverter`](https://openjfx.io/javadoc/12/javafx.base/javafx/util/StringConverter.html) potentially combined with a [`TextFormatter`](https://openjfx.io/javadoc/12/javafx.controls/javafx/scene/control/TextFormatter.html). If you're creating an editable `TableView`, see [`TextFieldTableCell`](https://openjfx.io/javadoc/12/javafx.controls/javafx/scene/control/cell/TextFieldTableCell.html). If this doesn't help, I recommend asking a new question. – Slaw Sep 17 '19 at 23:11
  • Thank you very much. I will send you the code that i had and returned me "something@entityclass".and i will try your suggestion as well in the afternoon after work :) . Thank you! –  Sep 18 '19 at 07:52
  • Hi again, Thank you, you were helpful again. The code that worked for me is _____ tfName.setText((vstcolName.getCellData(i)).getPplName()); –  Sep 18 '19 at 19:58