简体   繁体   English

如何使用 JDBC 数据源在 JavaFX 中构建多级 TreeView?

[英]How do I build a multilevel TreeView in JavaFX using a JDBC datasource?

In my JavaFX program I want to dynamically generate a tree using the live data set from my database (I'm using MariaDB, but it could be any SQL database).在我的 JavaFX 程序中,我想使用数据库中的实时数据集动态生成一棵树(我使用的是 MariaDB,但它可以是任何 SQL 数据库)。

I had searched a bunch and could not find a direct answer to my question, so I spent some time learning how JDBC ResultSet 's work, how the next() method works, and how while loops work.我搜索了一堆,找不到我的问题的直接答案,所以我花了一些时间学习 JDBC ResultSet的工作原理、 next()方法的工作原理以及while循环的工作原理。 A few trial-and-error attempts finally led me to the result I wanted, so I thought I would share it in case anyone else finds themselves in my position.几次反复试验最终使我得到了我想要的结果,所以我想我会分享它,以防其他人发现自己在我的 position 中。

See my answer below.请看下面我的回答。

edit It seems I've designed the overall program poorly in not using threading to isolate the GUI from the JDBC query.编辑似乎我在不使用线程将 GUI 与 JDBC 查询隔离时设计的整体程序很差。 I think this can be fixed using JavaFX concurrency, but I've never used it so until I can update the code below, just ignore the stuff outside the while loop.我认为这可以使用 JavaFX 并发来解决,但我从来没有使用过它,所以在我可以更新下面的代码之前,只需忽略while循环之外的东西。

Firstly, I'm using following versions of MariaDB (10.4.12), Java (13.0.2.8), JavaFX (13.0.2), and MariaDB JDBC (2.6.0). Firstly, I'm using following versions of MariaDB (10.4.12), Java (13.0.2.8), JavaFX (13.0.2), and MariaDB JDBC (2.6.0). I don't think any of this will make a difference, but just in case.. I'm using FXML, which is why you won't see any UI formatting in there anywhere.我认为这些都不会产生影响,但以防万一……我使用的是 FXML,这就是为什么你在任何地方都看不到任何 UI 格式的原因。

This is the full method that generates the TreeView in my JavaFX program.这是在我的 JavaFX 程序中生成 TreeView 的完整方法。 It's then called from a separate class shortly after generating the Stage and Scene objects.然后在生成StageScene对象后不久从单独的 class 调用它。

They key part is the while loop, which was a struggle for me to get right.它们的关键部分是while循环,这对我来说是一场斗争。 I initially thought I needed a nested while(rs.next()) loop, but then I realised that this was causing rows to be skipped because each rs.next() call is related to the previous one, not to the while loop in which it is used.我最初认为我需要一个嵌套的while while(rs.next())循环,但后来我意识到这会导致行被跳过,因为每个rs.next()调用都与前一个调用相关,而不是与它被使用。

Note also that the SQL statement is important.另请注意,SQL 语句很重要。 If the statement gives results out of order the method doesn't work correctly.如果语句给出的结果乱序,则该方法无法正常工作。

    public void generateTree(DataSource dataSource) {

        source = null;
        this.source = dataSource;

        Connection con = null;
        Statement stmt = null;

        TreeItem<String> rootTreeItem = new TreeItem<String>("EMT");
        rootTreeItem.setExpanded(true);
        emtTree.setRoot(rootTreeItem);

        TreeItem<String> site = null;
        TreeItem<String> plant = null;

        try {
            con = source.getConnection();
            try {
                stmt = con.createStatement();
                ResultSet rs = stmt.executeQuery("SELECT sites.siteid, sites.longname, plants.plantid, plants.siteplantid, plants.shortname FROM sites INNER JOIN plants ON plants.siteid=sites.siteid ORDER BY sites.longname ASC, plants.siteplantid ASC");

                String site1 = "";  //It's not possible for the site name to be "" in the result set because the database design prevents it.

                //Steps through the result set from first row item to last row item.
                while(rs.next()) {

                    //This bit prevents repeating the same first level items multiple times.
                    //I only want each site to appear once, and under each site is a list
                    //of plants.
                    if (!site1.equals(rs.getString("sites.longname"))) {
                        site = new TreeItem<String>(rs.getString("sites.longname"));
                        rootTreeItem.getChildren().add(site);
                        site1 = rs.getString("sites.longname");
                    }

                        //This section is never skipped and will add all the plants to a given
                        //site until the next site is reached in the result set, then the if
                        //is triggered again and the process for the new site.
                        plant = new TreeItem<String>(rs.getInt("plants.siteplantid") + " " + rs.getString("plants.shortname"));
                        site.getChildren().add(plant);
                }
            } catch (SQLException e) {
                System.out.println("Statement Error");
                System.err.println("SQL State: " + ((SQLException)e).getSQLState());
                System.err.println("Error Code: " + ((SQLException)e).getErrorCode());
                System.err.println("Message: " + ((SQLException)e).getMessage());
                System.err.println("Cause: " + ((SQLException)e).getCause());
                return;
            }
        } catch (SQLException e) {
            e.printStackTrace();
            return;
        } finally {
            if (stmt != null) {
                try {
                    stmt.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (con != null) {
                try {
                    con.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }

I'll probably find ways to improve it in future, but for now I'm just happy to have it working.将来我可能会找到改进它的方法,但现在我很高兴让它工作。 Ignore the messy exception handling - its a work in progress!忽略凌乱的异常处理 - 它正在进行中!

Here is my try how to do it with not blocking the UI Thread:这是我尝试如何在不阻塞 UI 线程的情况下做到这一点:

public class YourClassName {

ArrayList<ResultBean> fetchedData = null;

public void createTree() {

    // run in other thread fetching the data
    Task task = new Task<Void>() {
        @Override
        protected Void call() throws Exception {
            // show loading pane -> you can use the one from import org.controlsfx.control.MaskerPane
            // https://github.com/controlsfx/controlsfx/wiki/ControlsFX-Features
            loadingPane.setVisible(true);

            // get data
            fetchedData = generateTreeData(yourDataSource);
            return null;
        }

        @Override
        protected void succeeded() {
            // the data has been fetched, now is safe to build your tree
            super.succeeded();
            buildTreeFromTheData(fetchedData);
        }
    };
    new Thread(task).start();
  }
}

Fetch the data from your database:从您的数据库中获取数据:

public ArrayList<ResultBean> getTreeData(DataSource dataSource) {

    ArrayList<ResultBean> resultBeans = new ArrayList<>();
    source = null;
    this.source = dataSource;

    Connection con = null;
    Statement stmt = null;

    try {
        con = source.getConnection();
        try {
            stmt = con.createStatement();
            ResultSet rs = stmt.executeQuery("SELECT " +
                    "sites.siteid, " +
                    "sites.longname, " +
                    "plants.plantid, " +
                    "plants.siteplantid, " +
                    "plants.shortname " +
                    "FROM sites " +
                    "INNER JOIN plants ON plants.siteid=sites.siteid " +
                    "ORDER BY sites.longname ASC, plants.siteplantid ASC");

            while (rs.next()) {

                ResultBean resBean = new ResultBean();
                resBean.setSiteId(rs.getString("sites.siteid"));
                //....
                //....
                // set all values and add it to the result array
                resultBens.add(resBean);
            }
        } catch (SQLException e) {
            e.printStackTrace();
        }
    } catch (SQLException e) {
        e.printStackTrace();
    } finally {
        if (stmt != null) {
            try {
                stmt.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
        if (con != null) {
            try {
                con.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    return resultBens;
}

ResultBean:结果豆:

public class ResultBean {
    private int siteid;
    private String longname;
    private int plantid;
    private int siteplantid;
    private String shortname;

    // setters
    ....
    // getters
    ....
}

And finally, build the tree with your logic:最后,用你的逻辑构建树:

    public void buildTreeFromTheData(ArrayList<ResultBean> treeData) {

    // do you logic here, loop over treeData ArrayList in while loop
    TreeItem<String> rootTreeItem = new TreeItem<String>("EMT");
    rootTreeItem.setExpanded(true);
    emtTree.setRoot(rootTreeItem);

    TreeItem<String> site = null;
    TreeItem<String> plant = null;

    //This bit prevents repeating the same first level items multiple times.
    //I only want each site to appear once, and under each site is a list
    //of plants.
    //if (!site1.equals(rs.getString("sites.longname"))) {
    //    site = new TreeItem<String>(rs.getString("sites.longname"));
    //    rootTreeItem.getChildren().add(site);
    //    site1 = rs.getString("sites.longname");
   // }

    //This section is never skipped and will add all the plants to a given
    //site until the next site is reached in the result set, then the if
    //is triggered again and the process for the new site.
    // plant = new TreeItem<String>(rs.getInt("plants.siteplantid") + " " + rs.getString("plants.shortname"));
    // site.getChildren().add(plant);

   // finally, hide the loading pane
   maskerPane.setVisible(false);
}

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM