zoukankan      html  css  js  c++  java
  • JavaFX 集成 Sqlite 和 Hibernate 开发爬虫应用

    前言:

    在开发 JavaFX 应用总是避免不了数据存储的,如果仅仅是一个简单的配置数据,那么不需要数据库即可实现,那么如果要面对几十万等大数据量的持久化存储,那免不了要和数据库和JDBC框架打交道了。

    数据库该怎么选呢? 首先考虑我比较熟的 MySql,可是要使用MySql,你就必须要去官网下载MySql的安装包,还要进行账号密码等配置,如果这软件是面向大众的,用户要使用总不能还要先装数据库,再看半天安装教程吧?

    这不行,那么我之前有接触过两个嵌入式数据库,一个是H2,一个就是开发Android 时接触的Sqlite。

    H2 我事先考察了一下,觉得资料并不是很多,远没有 Sqlite 使用广泛,而且 Sqlite 是 Android 官方内置的数据库,我还去看了 Sqlite 最大数据存储等测试文章,亿级的数据量下还能保持性能,这才放心使用。

    界面

    Maven 环境

    <dependencies>
    		<dependency>
    			<groupId>junit</groupId>
    			<artifactId>junit</artifactId>
    			<version>3.8.1</version>
    			<scope>test</scope>
    		</dependency>
    		<dependency>
    			<groupId>com.jfoenix</groupId>
    			<artifactId>jfoenix</artifactId>
    			<version>8.0.8</version>
    		</dependency>
    		<!--sqlite 版本3.7.2 -->
    		<dependency>
    			<groupId>org.xerial</groupId>
    			<artifactId>sqlite-jdbc</artifactId>
    			<version>3.7.2</version>
    		</dependency>
    
    		<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
    		<dependency>
    			<groupId>org.slf4j</groupId>
    			<artifactId>slf4j-api</artifactId>
    			<version>1.7.21</version>
    		</dependency>
    		<dependency>
    			<groupId>org.slf4j</groupId>
    			<artifactId>slf4j-log4j12</artifactId>
    			<version>1.7.21</version>
    		</dependency>
    		<dependency>
    			<groupId>org.apache.logging.log4j</groupId>
    			<artifactId>log4j-1.2-api</artifactId>
    			<version>2.8.2</version>
    		</dependency>
    		<dependency>
    			<groupId>junit</groupId>
    			<artifactId>junit</artifactId>
    			<version>4.12</version>
    			<scope>test</scope>
    		</dependency>
         <dependency>
                <groupId>org.jsoup</groupId>
                <artifactId>jsoup</artifactId>
                <version>1.11.3</version>
            </dependency>
    		<dependency>
    			<groupId>cn.hutool</groupId>
    			<artifactId>hutool-core</artifactId>
    			<version>4.1.21</version>
    		</dependency>
    		<!-- https://mvnrepository.com/artifact/io.datafx/flow -->
    		<dependency>
    			<groupId>io.datafx</groupId>
    			<artifactId>flow</artifactId>
    			<version>8.0.1</version>
    		</dependency>
    		<dependency>
    			<groupId>org.controlsfx</groupId>
    			<artifactId>controlsfx</artifactId>
    			<version>8.40.14</version>
    		</dependency>
    		<dependency>
    			<groupId>org.hibernate</groupId>
    			<artifactId>hibernate-core</artifactId>
    			<version>5.4.2.Final</version>
    		</dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <version>1.18.4</version>
            </dependency>
    		<!-- https://mvnrepository.com/artifact/com.github.inamik.text.tables/inamik-text-tables -->
    		<dependency>
    			<groupId>com.github.inamik.text.tables</groupId>
    			<artifactId>inamik-text-tables</artifactId>
    			<version>0.8</version>
    		</dependency>

    项目结构

    整合 Hibernate

    Hibernate 并不支持 Sqlite,但只是缺少一个数据库方言代码而已,这个在网上有很多,copy 一份在hibernate配置文件中引入就可以了。

    SQLiteDialect.java 数据库方言代码

    package util;
    
    import org.hibernate.dialect.Dialect;
    import org.hibernate.dialect.function.SQLFunctionTemplate;
    import org.hibernate.dialect.function.StandardSQLFunction;
    import org.hibernate.dialect.function.VarArgsSQLFunction;
    import org.hibernate.type.StandardBasicTypes;
    
    import java.sql.Types;
    
    public class SQLiteDialect extends Dialect {
        public SQLiteDialect() {
            super();
            registerColumnType(Types.BIT, "integer");
            registerColumnType(Types.TINYINT, "tinyint");
            registerColumnType(Types.SMALLINT, "smallint");
            registerColumnType(Types.INTEGER, "integer");
            registerColumnType(Types.BIGINT, "bigint");
            registerColumnType(Types.FLOAT, "float");
            registerColumnType(Types.REAL, "real");
            registerColumnType(Types.DOUBLE, "double");
            registerColumnType(Types.NUMERIC, "numeric");
            registerColumnType(Types.DECIMAL, "decimal");
            registerColumnType(Types.CHAR, "char");
            registerColumnType(Types.VARCHAR, "varchar");
            registerColumnType(Types.LONGVARCHAR, "longvarchar");
            registerColumnType(Types.DATE, "date");
            registerColumnType(Types.TIME, "time");
            registerColumnType(Types.TIMESTAMP, "timestamp");
            registerColumnType(Types.BINARY, "blob");
            registerColumnType(Types.VARBINARY, "blob");
            registerColumnType(Types.LONGVARBINARY, "blob");
            // registerColumnType(Types.NULL, "null");
            registerColumnType(Types.BLOB, "blob");
            registerColumnType(Types.CLOB, "clob");
            registerColumnType(Types.BOOLEAN, "integer");
    
            registerFunction("concat", new VarArgsSQLFunction(StandardBasicTypes.STRING, "", "||", ""));
            registerFunction("mod", new SQLFunctionTemplate(StandardBasicTypes.INTEGER, "?1 % ?2"));
            registerFunction("substr", new StandardSQLFunction("substr", StandardBasicTypes.STRING));
            registerFunction("substring", new StandardSQLFunction("substr", StandardBasicTypes.STRING));
        }
    
        public boolean supportsIdentityColumns() {
            return true;
        }
    
        public boolean hasDataTypeInIdentityColumn() {
            return false;
        }
    
        public String getIdentityColumnString() {
            return "integer";
        }
    
        public String getIdentitySelectString() {
            return "select last_insert_rowid()";
        }
    
        public boolean supportsLimit() {
            return true;
        }
    
        public String getLimitString(String query, boolean hasOffset) {
            return new StringBuffer(query.length() + 20).append(query).append(hasOffset ? " limit ? offset ?" : " limit ?")
                    .toString();
        }
    
        public boolean supportsTemporaryTables() {
            return true;
        }
    
        public String getCreateTemporaryTableString() {
            return "create temporary table if not exists";
        }
    
        public boolean dropTemporaryTableAfterUse() {
            return false;
        }
    
        public boolean supportsCurrentTimestampSelection() {
            return true;
        }
    
        public boolean isCurrentTimestampSelectStringCallable() {
            return false;
        }
    
        public String getCurrentTimestampSelectString() {
            return "select current_timestamp";
        }
    
        public boolean supportsUnionAll() {
            return true;
        }
    
        public boolean hasAlterTable() {
            return false;
        }
    
        public boolean dropConstraints() {
            return false;
        }
    
        public String getAddColumnString() {
            return "add column";
        }
    
        public String getForUpdateString() {
            return "";
        }
    
        public boolean supportsOuterJoinForUpdate() {
            return false;
        }
    
        public String getDropForeignKeyString() {
            throw new UnsupportedOperationException("No drop foreign key syntax supported by SQLiteDialect");
        }
    
        public String getAddForeignKeyConstraintString(String constraintName, String[] foreignKey, String referencedTable,
                                                       String[] primaryKey, boolean referencesPrimaryKey) {
            throw new UnsupportedOperationException("No add foreign key syntax supported by SQLiteDialect");
        }
    
        public String getAddPrimaryKeyConstraintString(String constraintName) {
            throw new UnsupportedOperationException("No add primary key syntax supported by SQLiteDialect");
        }
    
        public boolean supportsIfExistsBeforeTableName() {
            return true;
        }
    
        public boolean supportsCascadeDelete() {
            return false;
        }
    
        @Override
        public boolean bindLimitParametersInReverseOrder() {
            return true;
        }
    }

    hibernate.cfg.xml Hibernate配置文件

    <!DOCTYPE hibernate-configuration PUBLIC
            "-//Hibernate/Hibernate Configuration DTD 3.0//EN"
            "http://www.hibernate.org/dtd/hibernate-configuration-3.0.dtd">
    
    <hibernate-configuration>
        <session-factory>
            <property name="hibernate.dialect">util.SQLiteDialect</property> <!-- 数据库方言 -->
            <property name="hibernate.connection.driver_class">org.sqlite.JDBC</property><!-- 引用jdbc包 -->
            <property name="hibernate.connection.url">jdbc:sqlite:D:eclipse_workspaceLetvsrcmain
    esourcesdbletv.db</property> <!-- 数据库链接 -->
            <!--        <property name="hibernate.dialect">org.hibernate.dialect.MySQL5Dialect</property> <!– 数据库方言 –>-->
            <!--        <property name="hibernate.connection.driver_class">com.mysql.cj.jdbc.Driver</property><!– 引用jdbc包 –>-->
            <!--                <property name="hibernate.connection.url">jdbc:mysql://127.0.0.1:3306/bbs?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=CONVERT_TO_NULL&useSSL=false&serverTimezone=CTT&nullCatalogMeansCurrent=true</property> <!– 数据库链接 –>-->
            <!--        <property name="hibernate.connection.username">root</property>-->
            <!--        <property name="hibernate.connection.password">1234</property>-->
    
            <property name="hibernate.format_sql">true</property>
            <property name="hibernate.show_sql">true</property>
            <mapping resource="db/LetvConfigEntity.hbm.xml"/>
            <mapping resource="db/LetvCookieEntity.hbm.xml"/>
            <mapping resource="db/LetvLinkEntity.hbm.xml"/>
            <mapping resource="db/LetvUserEntity.hbm.xml"/>
    
    
        </session-factory>
    
    </hibernate-configuration>

    这里的 :<property name=”hibernate.connection.url”>jdbc:sqlite:D:eclipse_workspaceLetvsrcmain esourcesdbletv.db</property>

    是绝对路径,这个是用 idea 自动生成给我改了,可以使用相对路径在项目根目录下创建数据库文件,而 url 只需要 : String url = “jdbc:sqlite:src/main/resources/db/letv.db”;

    这样写就可以。

    项目初始化连接数据库自动建表:

    数据库那肯定不能没有表,而表又不可能让用户去建,所以只能让程序代劳,并不难,用 Navicat 打开数据库建好表,导出 sql 文件,将其中的建表语句提取出来,在项目初次加载时,找到 sqlite 的 db 后缀的数据库文件,如果没有,那么创建,连接数据库,执行建表语句。

    public class SqliteUtil {
    
        private static Connection connection;
    
        public synchronized static Connection getConnection() throws SQLException {
            //如果 当前练
            if (connection == null) {
                try {
                    String driverClass = "org.sqlite.JDBC";
                    Class.forName(driverClass);
                } catch (ClassNotFoundException e) {
                    e.printStackTrace();
                }
                String url = "jdbc:sqlite:src/main/resources/db/letv.db";
                return connection = DriverManager.getConnection(url);
            } else {
                return connection;
            }
        }
        public static void close() {
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            } else {
                throw new NullPointerException("连接未开启!");
            }
        }
    

    这段代码中,getConnection() 方法调用后会连接 Sqlite 数据库,如果没有,则会创建。

        public static final String DB_USER = "create table letv_user (" +
                "  letv_user_id integer(11) not null," +
                "  letv_user_uid text(11)," +
                "  letv_user_link text(40)," +
                "  primary key (letv_user_id)" +
                ");";
    
        public static void createDatabases() throws SQLException, IOException {
            Connection connection = getConnection();
            Statement statement = connection.createStatement();
            statement.execute(DB_CONFIG);
            statement.execute(DB_COOKIE);
            statement.execute(DB_LINK);
            statement.execute(DB_USER);
            close();
        }

    定义建表语句,调用 createDatabases() 则会执行建表语句创建表。

    程序初次运行创建数据库和表

     if (this.getClass().getResource("db/letv.db") == null) {
                FXMLLoader loader = new FXMLLoader(getClass().getResource("/fxml/Loading.fxml"));
                AnchorPane pane = loader.load();
                Scene scene = new Scene(pane, 500, 300);
                PleaseProvideController loadController = loader.getController();
                loadController.setInfo("正在创建数据库……");
                primaryStage.setScene(scene);
                primaryStage.initStyle(StageStyle.UNDECORATED);
                primaryStage.setAlwaysOnTop(true);
                primaryStage.getIcons().addAll(new Image("file:/resource/logo/logo.ico"));
                primaryStage.show();
                Platform.runLater(() -> {
                    try {
                        SqliteUtil.createDatabases();
                        logger.info("数据库创建成功!");
                        primaryStage.close();
                        start(new Stage());
                    } catch (SQLException | IOException e) {
                        e.printStackTrace();
                    }
                });
            }

    创建数据库需要一些时间,这个时候可以给一个 Loading 界面:

    经过测试发现创建数据库和表时间并不长,所以这一步可以省略,当然如果表多那就看情况了。

    JFoenix 界面开发

    JFoenix 的界面非常好看,Google Material 的设计风格,案例:

    这个 UI 库是开源的,非常美观,提供的控件也很丰富,只是文档感觉不是很好,但好在可以再官方提供的 Demo 案例查看控件的使用。

    Github 地址 : 
    https://github.com/jfoenixadmin/JFoenix

    官方文档 : 
    http://www.jfoenix.com/documentation.html

    JFoenix 表格 TreeTable

    官方案例:

    如果觉得 JFoenix 的表格实现代码要比原生的简单,那你就错了,代码量依旧比较大,而且如果需要对表的列绑定字段,字段不是只读,就是如果你需要表的字段可以被编辑操作,那么相应的绑定的字段类型必须是 JavaFX 提供的 Property 类型,JavaFX 为这种类型提供了绑定方法,但是如果是使用这种类型去结合 Hibernate 字段映射,报错没跑了。

    所以,我只能将用户映射表的实体类和绑定表的类分开,分为两个类,一个字段类型是原生的,另一个字段类型是 Property 类型。

    
    public class LetvCookieTable extends RecursiveTreeObject<LetvCookieTable> {
    
        public long cookieId;
    
        public StringProperty cookieKey;
    
        public StringProperty cookieValue;
    
        public LetvCookieTable(long cookieId,String cookieKey, String cookieValue) {
            this.cookieId = cookieId;
            this.cookieKey = new SimpleStringProperty(cookieKey);
            this.cookieValue = new SimpleStringProperty(cookieValue);
        }
    

    这个就是用来绑定表的实体类,再表格界面加载的时候,查询返回实体类结果集,接着将实体类转换成 Property 类型的类添加到 ObservableList 中。

    字段绑定

      //column
                JFXTreeTableColumn<LetvCookieTable, String> key = new JFXTreeTableColumn<>("Key");
                key.setCellValueFactory((TreeTableColumn.CellDataFeatures<LetvCookieTable, String> param) -> {
                    if (key.validateValue(param)) {
                        return param.getValue().getValue().cookieKey;
                    } else {
                        return key.getComputedValue(param);
                    }
                });
                JFXTreeTableColumn<LetvCookieTable, String> value = new JFXTreeTableColumn<>("Value");
                value.setCellValueFactory((TreeTableColumn.CellDataFeatures<LetvCookieTable, String> param) -> {
                    if (value.validateValue(param)) {
                        return param.getValue().getValue().cookieValue;
                    } else {
                        return value.getComputedValue(param);
                    }
                });

    TreeTable 绑定删除按钮

    现在需要一个删除的列,提供删除按钮,点击后删除这一行的数据。

    代码和 TableView 大体上是一样的,但在取值上有点小差异。

     JFXTreeTableColumn<LetvCookieTable, String> options = new JFXTreeTableColumn<>("options");
                options.setCellFactory(new Callback<TreeTableColumn<LetvCookieTable, String>, TreeTableCell<LetvCookieTable, String>>() {
                    @Override
                    public TreeTableCell<LetvCookieTable, String> call(TreeTableColumn<LetvCookieTable, String> param) {
                        JFXButton button = new JFXButton();
                        button.setText("删除");
                        return new TreeTableCell<LetvCookieTable, String>() {
                            JFXButton delBtn = button;
    
                            @Override
                            protected void updateItem(String item, boolean empty) {
                                super.updateItem(item, empty);
                                if (empty) {
                                    setGraphic(null);
                                    setText(null);
                                } else {
                                    delBtn.setOnMouseClicked(event -> {
                                        Transaction transaction = session.beginTransaction();
                                        logger.info("删除:" + getIndex());
                                        LetvCookieTable table = getTreeTableView().getTreeItem(getIndex()).getValue();
                                        session.doWork(connection -> {
                                            Statement st;
                                            logger.info("id:" + table.cookieId);
                                            String sql = "delete from letv_cookie where cookie_id = " + table.cookieId;
                                            st = connection.createStatement();
                                            st.executeUpdate(sql);
                                            st.close();
                                        });
                                        NotificationUtil.notification("信息", "删除成功", "info");
                                        transaction.commit();
                                        observableCookie.remove(getIndex());
                                    });
                                    setGraphic(button);
                                    setText(null);
                                }
                            }
                        };
                    }
                });

    JavaFX 获取当前行 :

    getTableView().getItems().get(getIndex())

    JFoenix :

    getTreeTableView().getTreeItem(getIndex()).getValue()

    TreeTable 可编辑

     key.setCellFactory((TreeTableColumn<LetvCookieTable, String> param) -> new GenericEditableTreeTableCell<>(
                        new TextFieldEditorBuilder()));
                key.setOnEditCommit((TreeTableColumn.CellEditEvent<LetvCookieTable, String> t) -> {
                  LetvCookieTable table =  t.getTreeTableView().getTreeItem(t.getTreeTablePosition()
                            .getRow())
                            .getValue();
                    table.cookieKey.set(t.getNewValue());
                    Transaction transaction = session.beginTransaction();
                    Query updateLink = session.createQuery("update db.LetvCookieEntity set cookieKey = :newVal where cookieId=" + table.cookieId);
                    updateLink.setParameter("newVal", t.getNewValue());
                    updateLink.executeUpdate();
                    transaction.commit();
                    session.clear();
                    NotificationUtil.notification("信息","更新成功!","info");
    
                });
    

    未完待续 ……

  • 相关阅读:
    select、poll和epoll
    Linux 常用命令之文件和目录
    SmartPlant Review 帮助文档机翻做培训手册
    SmartPlant Foundation 基础教程 3.4 菜单栏
    SmartPlant Foundation 基础教程 3.3 标题栏
    SmartPlant Foundation 基础教程 3.2 界面布局
    SmartPlant Foundation 基础教程 3.1 DTC登陆界面
    SmartPlant Foundation 基础教程 1.4 SPF架构
    SmartPlant Foundation 基础教程 1.3 SPF其他功能
    SmartPlant Foundation 基础教程 1.2 SPF集成设计功能
  • 原文地址:https://www.cnblogs.com/yangchaojie/p/11312477.html
Copyright © 2011-2022 走看看