首页 > 解决方案 > 使用 HQL JOIN 填充 JavaFX TableView 会返回奇怪的数据

问题描述

HQL Join 给了我连接列的奇怪数据 在此处输入图像描述

虽然我的 HQL 查询在 Hibernate 控制台中运行良好,但 在此处输入图像描述 我得到了奇怪的结果。我尝试结合相​​应列的@FXML 注释更改下面的行,但要么我得到错误,要么我得到空白单元格。vstcolName.setCellValueFactory(new PropertyValueFactory("peopleByPeopleId"));

这是我的第一个表的实体类。

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);
    }
}

这是我的第二张表的实体类。

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;
    }
}

这是我的包含 HQL 查询的模型。

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;

    }
}

这是 FXML 控制器

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);
   }
}

在连接列中重新调整的数据类似于 Entities.PeopleEntity@864db1b7

...等...我应该得到名称 - 我的意思是字符串.. 另一列正常显示。

顺便说一句,如果我尝试在控制台而不是 tableview 中显示 HQL 查询的结果,我会得到完全相同的结果。不要介意怪异列的变化。这是因为我试图将数字(仍然是 strind 数据类型)放在我之前拥有的名称的位置,看看是否会发生任何变化......但问题仍然存在。 在此处输入图像描述

标签: hibernatejoinjavafxtableviewhql

解决方案


如果我理解正确,您的问题与“名称”列有关。具体来说,该列中显示的值看起来像是某种奇怪的、深奥的语言。每当您看到与"com.example.Foo@12ef485d"您最有可能看到的 Java 输出相似的输出时,就是默认Object#toString()方法的结果:

返回对象的字符串表示形式。通常,该toString方法返回一个“以文本方式表示”该对象的字符串。结果应该是一个简洁但信息丰富的表示,易于人们阅读。建议所有子类重写此方法。

class的toString方法Object返回一个字符串,该字符串由对象作为实例的类的名称、at 符号字符“ @”和对象的哈希码的无符号十六进制表示形式组成。换句话说,此方法返回一个等于以下值的字符串:

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

您看到此输出的原因是由于工作方式TableColumn。ATableColumn有两个cellValueFactory

需要设置单元格值工厂以指定如何填充单个 TableColumn 中的所有单元格。单元格值工厂是一个回调,它提供一个 TableColumn.CellDataFeatures 实例,并期望返回一个 ObservableValue。返回的 ObservableValue 实例将在内部进行观察,以允许立即更新要反映在屏幕上的值。如何设置单元格值工厂的示例是:

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();
    }   
});  

一种常见的方法是希望使用 Java bean 中的单个值填充 TableColumn 中的单元格。为了支持这种常见场景,有 PropertyValueFactory 类。有关如何使用它的更多信息,请参阅此类,但这里简要介绍如何使用 PropertyValueFactory 类简化上述用例:

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

还有一个cellFactory

此列中所有单元的单元工厂。单元工厂负责为单个表格列呈现每个 TableCell 中包含的数据。

默认情况下,TableColumn 使用默认的单元格工厂,但这可以用自定义实现替换,例如以不同的方式显示数据或支持编辑。有很多关于在其他地方创建自定义单元格工厂的文档(参见 Cell 和 TableView例如)。

最后,javafx.scene.control.cell 包中提供了许多预先构建的单元工厂。

换句话说,它cellValueFactory决定了将显示什么cellFactory值,并且可以自定义如何显示所述值。如果未cellFactory设置自定义,则使用DEFAULT_CELL_FACTORY

如果没有在 TableColumn 实例上指定 cellFactory,则默认使用这个。目前,如果它是一个节点,它只是在graphic属性中呈现 TableCell 项属性,或者如果它不为空,它只是调用,在属性内设置结果字符串。itemtoString()text

在您的代码中,您目前拥有:

...

@FXML
private TableColumn<PlacesEntity, PeopleEntity> vstcolName;

...

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

...

这样做是将vstcolName列配置为从 a 中提取 aPeopleEntity作为PlacesEntityeach 的值TableCell。这里的问题是您没有设置 a cellFactory,这意味着每个TableCell都将显示PeopleEntity#toString()(并且由于您没有覆盖该方法,因此您将获得默认Object#toString()输出)。根据列的名称(即“名称”),我假设您实际上希望单元格显示PeopleEntity#getPplName()。为此,您需要设置 acellFactory并返回一个TableCell覆盖的自定义updateItem(T,boolean)

updateItem 方法不应由开发人员调用,但它是开发人员重写以允许他们自定义单元格的视觉效果的最佳方法。澄清一下,开发人员不应该在他们的代码中调用这个方法(他们应该把它留给 UI 控件,例如 ListView 控件)来调用这个方法。但是,使用 updateItem 方法的目的是让开发人员在指定自定义单元工厂(再次,如 ListView 单元工厂)时,可以重写 updateItem 方法以允许对单元进行完全自定义。

Cell 的子类正确覆盖 updateItem方法非常重要,否则会导致出现空白单元格或单元格中出现意外内容等问题。以下是如何正确覆盖 updateItem 方法的示例:

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

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

请注意此代码示例中的两个重要点:

  1. 我们调用 super.updateItem(T, boolean) 方法。如果不这样做,则 item 和 empty 属性设置不正确,您最终可能会遇到图形问题。
  2. 我们测试空条件,如果为真,我们将文本和图形属性设置为空。如果我们不这样做,几乎可以保证最终用户会意外地看到单元格中的图形伪影。

在您的情况下,它可能看起来像:

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());
        }
    }

});

注意:配置a的cellValueFactoryand应该只发生一次,在初始化期间。您当前在一个名为的公共方法中设置了它,该方法似乎经常被调用。当您使用 FXML 时,您应该将配置移动到控制器的方法中。cellFactoryTableColumncellValueFactoryreadJoin()TableColumn@FXML private void initialize() { ... }


推荐阅读