【问题标题】:Cannot parse file using JDBC无法使用 JDBC 解析文件
【发布时间】:2020-07-01 22:33:10
【问题描述】:

我正在尝试解析管道分隔文件并将字段插入表中。当我启动应用程序时,我的数据库中没有任何反应。我的数据库有 4 列(account_name、command_name 和 system_name、CreateDt)。我正在解析的文件在第一行有日期,然后是额外数据。 i 之后的行只需要每个字段中的前 3 个字段,其余的是额外数据。最后一行是行数。我跳过了插入日期,因为现在但想在至少能够插入前 3 个字段之后回到它。我在解析文件和在数据库中存储数据方面几乎没有经验,并且已经查看了 jdbc 示例以达到这一点,但我正在努力,并且确信有更好的方法。

文件示例

20200310|extra|extra|extra||
Mn1223|01192|windows|extra|extra|extra||
Sd1223|02390|linux|extra|extra|extra||
2

表格格式

account_name      command_name   system_name    createDt
Mn1223            01192          windows        20200310
Sd1223            02390          linux          20200310

解析并插入数据库的代码

public List insertZygateData (List<ZygateEntity> parseData) throws Exception {
        String filePath = "C:\\DEV\\Test_file.xlsx";


        List<String> lines = Files.readAllLines(Paths.get(filePath));

        // remove date and amount
        lines.remove(0);
        lines.remove(lines.size() - 1);

        for (ZygateEntity zygateInfo : parseData){
                    new MapSqlParameterSource("account_name", zygateInfo.getAccountName())
                            .addValue("command_name", zygateInfo.getCommandName())
                            .addValue("system_name", zygateInfo.getSystemName())
                            .getValues();

        }
        return lines.stream()
                .map(s -> s.split("[|]")).map(val -> new ZygateEntity(val[0],val[1],val[2])).collect(Collectors.toList());


    }


    public boolean cleantheTable() throws SQLException {

        String sql = "INSERT INTO Landing.midrange_xygate_load (account_name,command_name,system_name)"+
                "VALUES (:account_name,:command_name,:system_name)";

        boolean truncated = false;
        Statement stmt = null;
        try {
            String sqlTruncate = "truncate table Landing.midrange_xygate_load";
            jdbcTemplate.execute(sqlTruncate);
            truncated = true;

        } catch (Exception e) {
            e.printStackTrace();
            truncated = false;
            return truncated;
        } finally {
            if (stmt != null) {
                jdbcTemplate.execute(sql);
                stmt.close();
            }

        }
        log.info("Clean the table return value :" + truncated);
        return truncated;
    }
}

实体/模型

public ZygateEntity(String accountName, String commandName, String systemName){
        this.accountName=accountName;
        this.commandName=commandName;
        this.systemName=systemName;
    }

   //getters and setters
    @Override
    public String toString() {
        return "ZygateEntity [accountName=" + accountName + ", commandName=" + commandName + ", systemName=" + systemName + ", createDt=" + createDt +"]";
    }
}

【问题讨论】:

    标签: java spring-boot jdbc spring-jdbc csv


    【解决方案1】:

    看看您提供的内容,您似乎有一堆杂乱无章的代码,虽然其中大部分都在那里,但并不是全部都在那里,而且顺序也不完全正确。

    为了清楚起见,请尝试将您正在做的事情分解为单独的步骤,并制定一个专注于每个步骤的方法。特别是,你写

    我正在尝试解析管道分隔文件并将字段插入表中

    这自然分为两部分:

    • 解析管道分隔文件,并
    • 将字段插入表中。

    对于第一部分,您的insertZygateData 方法中似乎已经有了大部分部分。特别是,这一行将文件的所有行读入一个列表:

        List<String> lines = Files.readAllLines(Paths.get(filePath));
    

    这些行然后从读取的行列表中删除第一行和最后一行:

        // remove date and amount
        lines.remove(0);
        lines.remove(lines.size() - 1);
    

    然后你有一些看起来有点不合适的代码:这似乎与插入数据库有关,但我们还没有创建我们的 ZygateEntity 对象列表,因为我们还没有读完文件。让我们暂时将这个for 循环放在一边。

    最后,我们获取读取的行列表,使用管道将它们拆分,从部分创建ZygateEntity 对象并创建这些对象的List,然后我们将其返回。

        return lines.stream()
                .map(s -> s.split("[|]")).map(val -> new ZygateEntity(val[0],val[1],val[2])).collect(Collectors.toList());
    

    把这些放在一起,我们有一个有用的方法来解析文件,完成任务的第一部分:

        private List<ZygateEntity> parseZygateData() throws IOException {
            String filePath = "C:\\DEV\\Test_file.xlsx";
    
            List<String> lines = Files.readAllLines(Paths.get(filePath));
    
            // remove date and amount
            lines.remove(0);
            lines.remove(lines.size() - 1);
    
            return lines.stream()
                    .map(s -> s.split("[|]")).map(val -> new ZygateEntity(val[0],val[1],val[2])).collect(Collectors.toList());
        }
    

    (当然,我们可以为要读取的文件路径添加一个参数,但是为了让某些东西正常工作,坚持使用当前的硬编码文件路径是可以的。)


    所以,我们得到了ZygateEntity 对象的列表。我们如何编写将它们插入数据库的方法?

    我们可以在您的代码示例中找到一些我们需要的成分。首先,我们需要 SQL 语句来插入数据。这是在您的 cleanThetable 方法中:

        String sql = "INSERT INTO Landing.midrange_xygate_load (account_name,command_name,system_name)"+
                "VALUES (:account_name,:command_name,:system_name)";
    

    然后我们有这个循环:

        for (ZygateEntity zygateInfo : parseData){
                    new MapSqlParameterSource("account_name", zygateInfo.getAccountName())
                            .addValue("command_name", zygateInfo.getCommandName())
                            .addValue("system_name", zygateInfo.getSystemName())
                            .getValues();
        }
    

    此循环从每个ZygateEntity 对象中创建一个MapSqlParameterSource,然后通过调用getValues() 方法将其转换为Map&lt;String, Object&gt;。但是它对这个值没有任何作用。实际上,您正在创建这些对象并再次摆脱它们​​而无需对它们做任何事情。这并不理想。

    MapSqlParameterSource 与弹簧 NamedParameterJdbcTemplate 一起使用。您的代码提到了jdbcTemplate,它似乎是类中的一个字段,用于解析数据并插入数据库,但您没有显示此类的完整代码。我将不得不假设它是NamedParameterJdbcTemplate,而不是“普通”JdbcTemplate

    NamedParameterJdbcTemplate 包含一个方法 update,它接受一个 SQL 字符串和一个 SqlParameterSource。我们有一个 SQL 字符串,并且我们正在创建 MapSqlParameterSource 对象,因此我们可以使用它们来执行插入操作。创建这些MapSqlParameterSource 对象之一只是为了将其转换为地图并没有多大意义,所以让我们删除对getValues() 的调用。

    所以,我们现在有了一种将数据插入数据库的方法:

    public void insertZygateData(List<ZygateEntity> parseData) {
        String sql = "INSERT INTO Landing.midrange_xygate_load (account_name,command_name,system_name)"+
                "VALUES (:account_name,:command_name,:system_name)";
    
        for (ZygateEntity zygateInfo : parseData){
            SqlParameterSource source = new MapSqlParameterSource("account_name", zygateInfo.getAccountName())
                    .addValue("command_name", zygateInfo.getCommandName())
                    .addValue("system_name", zygateInfo.getSystemName());
            jdbcTemplate.update(sql, source);
        }
    }
    

    最后,让我们看看你的cleanThetable 方法。与其他人一样,让我们​​将注意力集中在一项任务上:目前您正试图从表中删除数据,然后以相同的方法将其插入,但让我们只专注于删除数据因为我们现在有了插入数据的方法。

    我们不能立即删除 String sql = ... 行,因为您的代码中的 finally 块使用它。如果stmt 不为空,则尝试运行INSERT 语句,然后关闭stmt

    但是,stmt 从未被赋予除null 以外的任何值,因此它仍然是null。因此stmt != null 始终为false,因此INSERT 语句永远不会运行。你的 finally 块从不做任何事情,所以你最好完全删除它。随着您的 finally 块消失,您还可以摆脱您的局部变量 stmtsql 字符串,为我们留下一个重点是截断表格的方法:

    public boolean cleantheTable() throws SQLException {
        boolean truncated = false;
        try {
            String sqlTruncate = "truncate table Landing.midrange_xygate_load";
            jdbcTemplate.execute(sqlTruncate);
            truncated = true;
    
        } catch (Exception e) {
            e.printStackTrace();
            truncated = false;
            return truncated;
        }
        log.info("Clean the table return value :" + truncated);
        return truncated;
    }
    

    我将由您来编写调用这些方法的代码。为此我编写了一些代码,它运行成功并插入到数据库中。


    因此,总而言之,没有数据被写入您的数据库,因为您从未调用数据库来插入任何数据。在您的 insertZygateData 方法中,您正在创建参数源对象,但没有对它们做任何有用的事情,在您的 cleanThetable 方法中,看起来您正在尝试插入数据,但您的行 jdbcTemplate.execute(sql) 试图这样做这从未运行过。即使stmt 不为空,此行也不起作用,因为您没有在任何地方传递参数值:您会从数据库中得到一个异常,因为它期望参数值但您从未给它任何。

    希望我的解释能够让您的代码正常工作,并帮助您理解为什么不正常。

    【讨论】:

    • 我在使用 jdbctemplate 的 insertZygatedata 方法中遇到此错误,错误为 nested exception is java.sql.SQLException: Unable to convert between MapSqlParameterSource and JAVA_OBJECT
    • @NormX 在这种情况下,看起来您使用的是“普通”JdbcTemplate 而不是NamedParameterJdbcTemplate。如果你想使用MapSqlParameterSource 并且有命名参数,那么你需要使用NamedParameterJdbcTemplate 而不是JdbcTemplate。我用NamedParameterJdbcTemplate 运行了更新后的代码,它对我有用。
    猜你喜欢
    • 1970-01-01
    • 2012-10-14
    • 2010-09-25
    • 2020-10-03
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2022-11-28
    • 1970-01-01
    相关资源
    最近更新 更多