【问题标题】:Converting DAO objects to DTO objects in a Spring MVC Application在 Spring MVC 应用程序中将 DAO 对象转换为 DTO 对象
【发布时间】:2013-01-04 17:51:07
【问题描述】:

背景:我在教育环境中工作,去年夏天,我们的一位开发人员使用 Spring MVC 和 Hibernate 设计并构建了一个 Java Web 应用程序。它在 9 月推出了新的术语,用户非常高兴,因为它取代了尘土飞扬的旧 Blackboard 插件。应用程序的主要功能用于为学生设置目标、给他们留言和为学生创建报告。

几个月过去了,原来的开发者已经离开,应用程序也遇到了一些成长的烦恼。

使用案例场景:教师登录后,他们会看到他们的主屏幕,其中包含他们所教授的课程列表,以及当前选定的目标、消息和报告的概述课程以及该课程的学生注册列表。如果课程包含少量目标等,则可以快速返回信息。但是随着信息量的增长,加载它所花费的时间似乎呈指数级增长。

经过调查,我想我已经找到了原因。我参加了一个示例课程并查看了报告以了解发生了什么。我发现数据库以毫秒为单位返回了相关数据,浏览器以毫秒为单位呈现了它,但是在浏览器等待从它返回的数据之间有 12 秒。在数据库查询完成和前端接收响应之间对对象所做的唯一事情就是转换为 DTO。

代码:这是报表对象在 DAO 层中的样子

@Entity
@Table(name = "REPORTS")
public class Report implements Serializable
{

    /**
     * 
     */
    private static final long   serialVersionUID    = -7659637777880535914L;

    @Id
    @GeneratedValue
    @Column(name = "REPORT_ID", insertable = true, updatable = false, nullable = false, unique=true)
    private Integer             reportID;

    @Column(name = "DATE_CREATED", insertable = true, updatable = false, nullable = false)
    private GregorianCalendar   dateCreated;

    @Column(name = "DATE_MODIFIED", insertable = true, updatable = true, nullable = true)
    private GregorianCalendar   dateModified;

    @Column(name = "TITLE", insertable = true, updatable = true, nullable = false, length=1000)
    private String              title;

    @Column(name = "CURRENT_PERFORMANCE_GRADE", insertable = true, updatable = true, nullable = false)
    private String              currentPerformanceGrade;

    @Column(name = "TARGET_GRADE", insertable = true, updatable = true, nullable = false)
    private String              targetGrade;

    //VARCHAR(MAX) as this is the main body of the tutor report comments. Here the tutor can write as much content as they like.
    @Column(name = "TUTOR_COMMENTS", insertable = true, updatable = true, nullable = false, columnDefinition="VARCHAR(MAX)")
    private String              tutorComments;
//getters and setters below
}

其中还有其他字段,例如报告链接到的用户、课程、编写报告的导师等,但为了简单起见,我在这里省略了它们。

public class ReportDTO implements Serializable
{

/**
 * 
 */
private static final long   serialVersionUID    = 2795129355073929139L;

private Integer             reportID;

private String              dateCreated;

private String              dateModified;

private String              title;

private String              currentPerformanceGrade;

private String              targetGrade;

private String              tutorComments;
//getters and setters below
}

因此,主要区别在于日期对象已成为日期格式的字符串,而不是 GregorianCalendar 对象,因此日期的前端显示是一种可读的格式。这是转换为 DTO 所涉及的示例。服务层中的 Single 方法,它获取 DAO 对象,从中获取相关字段,将它们设置在新构建的 DTO 对象中,根据需要进行转换(例如公历转换为日期格式的字符串)并返回 DTO:

public ReportDTO convertToDto(Report daoReport) throws Exception
{

    ReportDTO dtoReport = new ReportDTO();
    try
    {
                    if(daoReport.getReportID() != null)
        {
            dtoReport.setReportID(daoReport.getReportID());
        }
                    if(daoReport.getDateCreated() != null)
        {
            dtoReport.setDateCreated(ReportServiceImpl.ISO_DATE_TIME_FORMAT.format(daoReport.getDateCreated().getTime()));

        }

        if(daoReport.getDateModified() != null)
        {
             dtoReport.setDateModified(ReportServiceImpl.ISO_DATE_TIME_FORMAT.format(daoReport.getDateModified().getTime()));

        }

        if(daoReport.getTitle() != null)
        {
            dtoReport.setTitle(daoReport.getTitle());

        }
                     if(daoReport.getCurrentPerformanceGrade() != null)
        {
              dtoReport.setCurrentPerformanceGrade(daoReport.getCurrentPerformanceGrade());

        }

        if(daoReport.getTargetGrade() != null)
        {
            dtoReport.setTargetGrade(daoReport.getTargetGrade());

        }

        if(daoReport.getTutorComments() != null)
        {
            dtoReport.setTutorComments(daoReport.getTutorComments());

        }
                    return dtoReport;
    }
    catch(Exception e)
    {
        Exception myException = new Exception("Exception was thrown while converting a persistent Report object to it's data transport equivalent", e);
        throw myException;
    }

问题: 那么毕竟,我的问题是,这是从 DAO 转换为 DTO 的正确方法吗?自从他离开后,我一直在关注他的代码,任何新添加的内容都是以同样的方式完成的。将对象返回到前端而不转换它们,我会在 >300 毫秒而不是 12 秒内看到结果。

我知道他从 here 为该项目学习了 Spring MVC,因此他不是经验丰富的 Spring 开发人员,我也不是,而且从我们看到如此大的请求时间这一事实来看,我们一定是做错了什么。

【问题讨论】:

  • 这是一种从 DTO 转换的方法。一般来说,代码看起来有点简单,但可能完成了工作,不应该导致这些问题。从您发布的代码中,它只能获得一个 get() 方法或 ReportServiceImpl.ISO_DATE_TIME_FORMAT.format 方法需要很长时间。您可以在可疑代码周围放置一些粗略的时间测量调试代码,以查看是否需要花费时间。比如:long start = System.currentTimeMillis(); // code to measure; System.out.println("took: " + (System.currentTimeMillis() - start));
  • 思考一下,如果 Hibernate 实体被配置为延迟加载实体,那么查询数据库的实际工作可能只发生在 convertToDto 方法中(可能只查询了PK 最初)。将您的休眠日志记录设置为调试并逐步执行代码。同样根据stackoverflow.com/questions/8512164/… 的说法,拥有 VARCHAR(MAX) 在性能方面可能不是一个好主意...
  • Pauli 我试过你所说的,我得到 Parsing Date Created take: 0 但每次调用时,'convertToDto' 方法作为一个整体花费了 150 到 300 毫秒。我会看看你的建议 beny,谢谢

标签: java spring spring-mvc dao dto


【解决方案1】:

好的,正如 beny23 提到的,Hibernate 是延迟加载(最初加载 PK 列表,然后在对数据执行某些操作时加载其余部分)

我使用的解决方案是创建一个非休眠连接以使用普通 JDBC 连接读取数据,该查询还转换了数据,以便它以我需要的格式返回(日期为字符串等)所以和我不必转换为 dto。通过这种方式,我将一些工作卸载到数据库中,并为我的应用程序省去了这样做的麻烦。

【讨论】:

    【解决方案2】:

    这可能不是你问题的原因(12s 很大),但仍然值得一说。

    (Simple)DateFormat 类不是线程安全的:

    日期格式不同步。建议创建 每个线程的单独格式实例。如果多个线程访问 并发格式,必须对外同步。

    所以不要将它们存储在全局类属性中,否则您可能会遇到奇怪的问题。 一个简单的做法是在使用 (Simple)DateFormat 之前实例化它。

    另请参阅 this interesting blog post 关于 SimpleDateFormat。

    【讨论】:

      猜你喜欢
      • 2021-07-14
      • 2020-10-31
      • 2017-04-04
      • 1970-01-01
      • 2013-02-06
      • 2014-05-06
      • 1970-01-01
      • 2017-02-22
      • 1970-01-01
      相关资源
      最近更新 更多