【问题标题】:OpenCSV + JMS/MDB behavior + performance issueOpenCSV + JMS/MDB 行为 + 性能问题
【发布时间】:2025-12-07 22:00:02
【问题描述】:

我有一个在 Glassfish 4.1 下运行的 Web 应用程序,其中包含一些需要 JMS/MDB 的功能。 特别是我在使用 JMS/MDB 生成报告时遇到问题,即从表中获取数据并将它们转储到文件中。

这就是发生的事情,我有一个 JMS/MDB 消息,它在 Oracle 数据库中执行了几个任务,在表格中获得最终结果后,我想从该表(通常是 30M+ 记录)。

因此,在 JMS/MDB 中生成报告时会发生以下情况:

public boolean handleReportContent() {

    Connection conn = null;

    try {
        System.out.println("Handling report content... " + new Date());
        conn = DriverManager.getConnection(data.getUrl(), data.getUsername(), data.getPassword());
        int reportLine = 1;
        String sql = "SELECT FIELD_NAME, VALUE_A, VALUE_B, DIFFERENCE FROM " + data.getDbTableName() + " WHERE SET_PK IN ( SELECT DISTINCT SET_PK FROM " + data.getDbTableName() + " WHERE IS_VALID=? )";
        PreparedStatement ps = conn.prepareStatement(sql);
        ps.setBoolean(1, false);
        ResultSet rs = ps.executeQuery();

        List<ReportLine> lst = new ArrayList<>();
        int columns = data.getLstFormats().size();
        int size = 0;
        int linesDone = 0;

        while (rs.next()) {

            ReportLine rl = new ReportLine(reportLine, rs.getString("FIELD_NAME"), rs.getString("VALUE_A"), rs.getString("VALUE_B"), rs.getString("DIFFERENCE"));
            lst.add(rl);
            linesDone = columns * (reportLine - 1);
            size++;
            if ((size - linesDone) == columns) {
                reportLine++;

                if (lst.size() > 4000) {
                    appendReportContentNew(lst);
                    lst.clear();
                }
            }
        }

        if (lst.size() > 0) {
            appendReportContentNew(lst);
            lst.clear();
        }

        ps.close();
        conn.close();
        return true;
    } catch (Exception e) {
        System.out.println("exception handling report content new: " + e.toString());
        return false;
    }

这是有效的,我知道它很慢而且效率低下,很可能有更好的选择来执行相同的操作。 这个方法的作用是:

  • ResultSet中收集数据;
  • 将其转储到一个列表中;
  • 对于每个 4K 对象都会调用方法appendReportContentNew()
  • 将列表中的数据转储为 文件

    public void appendReportContentNew(List<ReportLine> lst) {
    
    File f = new File(data.getJobFilenamePath());
    
    try {
        if (!f.exists()) {
            f.createNewFile();
        }
    
        FileWriter fw = new FileWriter(data.getJobFilenamePath(), true);
        BufferedWriter bw = new BufferedWriter(fw);
    
        for (ReportLine rl : lst) {
            String rID = "R" + rl.getLine();
            String fieldName = rl.getFieldName();
            String rline = rID + "," + fieldName + "," + rl.getValue1() + "," + rl.getValue2() + "," + rl.getDifference();
            bw.append(rline);
            bw.append("\n");
        }
    
        bw.close();
    
    } catch (IOException e) {
        System.out.println("exception appending report content: " + e.toString());
    }
    

    }

使用这种方法,在 20 分钟内写入 800k 行(30Mb 文件)通常会达到 4Gb 或更多。如果可能的话,这就是我想要改进的地方。

所以我决定尝试OpenCSV,我得到了以下方法:

public boolean handleReportContentv2() {

    Connection conn = null;

    try {
        FileWriter fw = new FileWriter(data.getJobFilenamePath(), true);
        System.out.println("Handling report content v2... " + new Date());
        conn = DriverManager.getConnection(data.getUrl(), data.getUsername(), data.getPassword());
        String sql = "SELECT NLINE, FIELD_NAME, VALUE_A, VALUE_B, DIFFERENCE FROM " + data.getDbTableName() + " WHERE SET_PK IN ( SELECT DISTINCT SET_PK FROM " + data.getDbTableName() + " WHERE IS_VALID=? )";
        PreparedStatement ps = conn.prepareStatement(sql);
        ps.setBoolean(1, false);
        ps.setFetchSize(500);
        ResultSet rs = ps.executeQuery();

        BufferedWriter out = new BufferedWriter(fw);
        CSVWriter writer = new CSVWriter(out, ',', CSVWriter.NO_QUOTE_CHARACTER);
        writer.writeAll(rs, false);

        fw.close();
        writer.close();
        rs.close();
        ps.close();
        conn.close();
        return true;
    } catch (Exception e) {
        System.out.println("exception handling report content v2: " + e.toString());
        return false;
    }
}

所以我正在收集 ResultSet 中的所有数据,并转储到 CSVWriter 中。这个操作同样的20分钟,只写了7k行

但是同样的方法,如果我在JMS/MDB之外使用它,它有一个令人难以置信的不同,只是在前4分钟它写了3M行在文件。 在同样的 20 分钟内,它生成了一个 500Mb+ 的文件。

如果我想提高性能,显然使用 OpenCSV 是迄今为止最好的选择,我的问题是为什么它在 JMS/MDB 中的执行方式不同? 如果不可能,是否有任何可能的解决方案可以通过任何其他方式改进相同的任务?

感谢有关此问题的反馈和帮助,我正在尝试了解 JMS/MDB 内外行为/性能不同的原因。

**

编辑:

**

@MessageDriven(activationConfig = {
@ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"),
@ActivationConfigProperty(propertyName = "destinationLookup", propertyValue = "MessageQueue")})

public class JobProcessorBean implements MessageListener {

private static final int TYPE_A_ID = 0;
private static final int TYPE_B_ID = 1;

@Inject
JobDao jobsDao;

@Inject
private AsyncReport generator;

public JobProcessorBean() {
}

@Override
public void onMessage(Message message) {
    int jobId = -1;
    ObjectMessage msg = (ObjectMessage) message;
    try {
        boolean valid = true;
        JobWrapper jobw = (JobWrapper) msg.getObject();
        jobId = jobw.getJob().getJobId().intValue();

        switch (jobw.getJob().getJobTypeId().getJobTypeId().intValue()) {
            case TYPE_A_ID:
                jobsDao.updateJobStatus(jobId, 0);
                valid = processTask1(jobw);
                if(valid) {
                    jobsDao.updateJobFileName(jobId, generator.getData().getJobFilename());
                    System.out.println(":: :: JOBW FileName :: "+generator.getData().getJobFilename());
                    jobsDao.updateJobStatus(jobId, 0);
                }
                else {
                    System.out.println("error...");
                    jobsDao.updateJobStatus(jobId, 1);
                }
                **boolean validfile = handleReportContentv2();**
                if(!validfile) {
                    System.out.println("error file...");
                    jobsDao.updateJobStatus(jobId, 1);
                }
                break;
            case TYPE_B_ID:
                (...)
        }
        if(valid) {        
            jobsDao.updateJobStatus(jobw.getJob().getJobId().intValue(), 2); //updated to complete
        }
        System.out.println("***********---------Finished JOB " + jobId + "-----------****************");
        System.out.println();
        jobw = null;
    } catch (JMSException ex) {
        Logger.getLogger(JobProcessorBean.class.getName()).log(Level.SEVERE, null, ex);
        jobsDao.updateJobStatus(jobId, 1);
    } catch (Exception ex) {
        Logger.getLogger(JobProcessorBean.class.getName()).log(Level.SEVERE, null, ex);
        jobsDao.updateJobStatus(jobId, 1);
    } finally {
        msg = null;
    }
}

private boolean processTask1(JobWrapper jobw) throws Exception {

    boolean valid = true;
    jobsDao.updateJobStatus(jobw.getJob().getJobId().intValue(), 0);

    generator.setData(jobw.getData());
    valid = generator.deployGenerator();
    if(!valid) return false;
    jobsDao.updateJobParameters(jobw.getJob().getJobId().intValue(),new ReportContent());

    Logger.getLogger(JobProcessorBean.class.getName()).log(Level.INFO, null, "Job Finished");
    return true;
}

因此,如果相同的方法,handleReportContent()generator.deployGenerator() 内部执行,结果会很慢。如果我等待该方法中的所有内容并使该 bean JobProcessorBean 中的文件更快。我只是想弄清楚这种行为为什么/如何起作用。

【问题讨论】:

  • 消息驱动 bean 上的 @TransactionAttribute 注释是什么?如何在 MDB 之外调用 handleReportContentv2 方法?
  • @MaDa 我有一个类JobProcessorBean 具有以下注释:@MessageDriven(activationConfig = { @ActivationConfigProperty(propertyName = "destinationType", propertyValue = "javax.jms.Queue"), @ActivationConfigProperty(propertyName = "destinationLookup", propertyValue = "MessageQueue") }) 这个bean 注入另一个bean,我在其中调用deploy()方法。 handleReportContentv2() 在进行部署的 bean 内部使用,我得到了这些结果。如果我复制相同的方法并在不同的 bean 中运行,我会得到不同的结果。我将添加那部分代码。
  • 尝试在 bean 上添加@TransactionAttribute(NOT_SUPPORTED) 看看这是否对性能有影响。另外,请查看*.com/questions/19139426/…,了解将文件直接保存到 Web 应用程序文件夹中的其他问题。
  • @MaDa 会的,如果有什么不同我会告诉你的。
  • @MaDa 解决了这个问题,它在 MDB 中的执行方式相同。随意用你之前的评论回答这个问题,所以我可以接受。感谢您的时间和帮助。 :)

标签: java glassfish jms message-driven-bean opencsv


【解决方案1】:

在 bean 上添加 @TransactionAttribute(NOT_SUPPORTED) 注释可能会解决问题(正如您的评论所示,它确实解决了问题)。

为什么会这样?因为如果您不在消息驱动的 bean 上放置任何事务注释,默认值将变为 @TransactionAttribute(REQUIRED)(因此 bean 所做的一切都由事务管理器监督)。显然,这会减慢速度。

【讨论】: