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

    未完待续 ……

  • 相关阅读:
    HTTP与HTTPS的区别
    Linux内核结构体--kfifo 环状缓冲区
    POSIX 线程详解
    linux的fork()函数-进程控制
    HDU 3435 A new Graph Game(最小费用最大流)&amp;HDU 3488
    Memcached安装使用和源代码调试
    结构-01. 有理数比較(10)
    Android:你不知道的 WebView 使用漏洞
    关于文件异步上传
    &lt;二代測序&gt; 批量下载 NCBI sra 文件
  • 原文地址:https://www.cnblogs.com/yangchaojie/p/11312477.html
Copyright © 2011-2022 走看看