Issue
HQL Join gives me weird data for the joined column
While my HQL query is working fine in the Hibernate Console,
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.
Solution
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 classObject
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 theitem
is a Node, or it simply callstoString()
if it is not null, setting the resulting string inside thetext
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:
- 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.
- 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.
Answered By - Slaw
Answer Checked By - Marilyn (JavaFixing Volunteer)