在Spring MVC应用程序中将DAO对象转换为DTO对象

3

背景:我在教育环境中工作,去年夏天我们的一位开发人员使用Spring MVC和Hibernate设计并构建了一个Java Web应用程序。它在九月份新学期开始时推出,用户非常高兴,因为它取代了一个过时的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所涉及的示例。服务层中的单个方法接受DAO对象,从中获取相关字段,将它们设置在新构造的DTO对象中,必要时进行转换(例如将Gregorian Calendar转换为日期格式化字符串),并返回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秒。

我知道他从这里学习了项目中的Spring MVC,所以他不是一个有经验的Spring开发人员,我也不是,鉴于我们看到如此长的请求时间,我们肯定做错了些什么。


1
这是一种将某个对象转换为DTO的方法。总体来说,代码看起来有点简单,但可能已经完成了任务,不应该引起那些问题。从您发布的代码中,它只能获取一个get()方法或ReportServiceImpl.ISO_DATE_TIME_FORMAT.format方法需要很长时间。您可以在可疑代码周围放置一些粗略的时间测量调试代码,以查看是否是它花费了时间。例如:long start = System.currentTimeMillis(); // code to measure; System.out.println("took: " + (System.currentTimeMillis() - start)); - pillingworth
4
如果 Hibernate 实体被配置为延迟加载实体,则查询数据库的实际工作可能仅在 convertToDto 方法中发生(最初可能只查询了PK列表)。将 Hibernate 日志设置为 DEBUG 并逐步执行代码。此外,根据 https://dev59.com/FGoy5IYBdhLWcg3wfeMR,从性能上考虑,拥有 VARCHAR(MAX) 可能不是一个好主意... - beny23
Pauli,我尝试了你说的方法,得到了“解析创建日期花费:0”的结果,但每次调用“convertToDto”方法整体耗时在150至300毫秒之间。我会研究一下你建议的内容,谢谢Beny。 - patrickm
2个回答

3

好的,正如beny23提到的那样,Hibernate使用了延迟加载(最初加载主键列表,然后在操作数据时加载剩余部分)。

我使用的解决方案是创建一个非Hibernate连接来使用普通JDBC连接读取数据,查询还将数据转换为我需要的格式(将日期作为字符串等),因此我不必转换为dto。这样我将一些工作卸载到了数据库,节省了我的应用程序的麻烦。


1

这可能不是你问题的原因(12秒很长),但还是值得一提。

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

日期格式不是同步的。建议为每个线程创建单独的格式实例。如果多个线程同时访问一个格式,则必须在外部进行同步。

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

另请参见this interesting blog post关于SimpleDateFormat的文章。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接