【问题标题】:Populating JavaFX TableView using HQL JOIN returns weird data使用 HQL JOIN 填充 JavaFX TableView 会返回奇怪的数据
【发布时间】:2019-09-14 20:28:00
【问题描述】:

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

在连接列中重新调整的数据是这样的 Entity.PeopleEntity@864db1b7

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

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

【问题讨论】:

  • 如果对象不是Node,默认TableCell会显示对象的toString()。查看How can I Populate a ListView in JavaFX using Custom Objects?;虽然该问题与ListView 有关,但对于TableColumn,过程几乎相同。
  • 真的很困惑。这似乎与我的情况大不相同。我似乎无法理解我应该在哪里更改代码以及如何更改。我有列表,他有列表视图。我有一个 mysql dbd 并试图使用外键获取字符串的值,他正试图在方法中获取一些字符串。此外,除了外键之外,我对列没有问题。
  • 如果我错了,请纠正我,但您遇到的问题是您的 Name 列。该列中的那些单元格显示的内容(例如Entities.PeopleEntity@864db1b7)表示默认(即未覆盖)Object#toString() 方法。查看您的代码,该列是一个TableColumn&lt;PlacesEntity, PeopleEntity&gt;,这意味着该列中每个TableCell 包含的值是一个PeopleEntity 实例。默认的TableCell 实现(在未指定自定义单元工厂时使用)不知道如何显示PeopleEntity。您必须通过cellFactory 告诉它如何操作。

标签: hibernate join javafx tableview hql


【解决方案1】:

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

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

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

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

您看到此输出的原因是 TableColumn 的工作方式。一个TableColumn 有一个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,则默认使用这个。目前,如果item 是一个节点,它只是在graphic 属性中呈现TableCell 项属性,或者如果它不为null,它只是调用toString(),将结果字符串设置在text 属性中。

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

...

@FXML
private TableColumn<PlacesEntity, PeopleEntity> vstcolName;

...

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

...

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

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

});

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

【讨论】:

  • 你太棒了@Slaw!我现在开始明白这一点了。此外,代码就像一个魅力。我按照您的建议将它放在私有 void initialize() { ... } 方法中。有没有可以推荐我学习的视频教程或网站?我从您提供的链接中阅读了文档(也感谢您复制回复中内容的亮点)。此外,即使它是题外话,你能推荐我任何其他关于 javafx 和 hibernate 的教程吗?真的很感谢你的帮助人! :) 你让我今天一整天都感觉很好!此时我有点沮丧。
  • Hibernate 有extensive documentation and guides。对于 JavaFX,您可以查看 Oracle 的 tutorial(某些信息可能已过时)。还有books covering JavaFX。当然,阅读the Javadoc 也很有帮助。还有this。你可能会发现一些东西here
  • 有什么建议对 TextField 做同样的事情吗?
  • 有了TextField,你可以直接打电话给tf.setText(entity.getPplName()),这就是你要问的吗?如有必要,您还可以使用StringConverter 可能与TextFormatter 结合使用。如果您要创建可编辑的 TableView,请参阅 TextFieldTableCell。如果这没有帮助,我建议提出一个新问题。
  • 非常感谢。我会将我拥有的代码发送给您并返回给我“something@entityclass”。我会在下班后的下午尝试您的建议:)。谢谢!
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2013-06-01
  • 1970-01-01
  • 1970-01-01
  • 2016-02-16
  • 2014-10-08
  • 2018-10-14
  • 1970-01-01
相关资源
最近更新 更多