Sending extra data in Java Exceptions to Flex Clients

本文介绍了一种通过自定义Java异常来传递错误信息到Flex客户端的方法,避免了使用单一结果对象来处理服务调用成功与失败的情况。这种方式使得错误处理更加清晰且易于维护。

When building Java services there is always need for handling exceptions thrown by all kind of sources. Wether those are ConstraintViolationException thrown by wrong database queries, NullPointerException thrown by wrong data manipulations or any other exceptions, all of them should end up handled in the Flex application calling the services.
One wrong usage of data transfer between client and backend services is when no matter what happens on the services, all exeptions are handled on the service and the client is basically listening only for the result method. Lets take a user management service for example. One method should be registerUser. The wrong usage would be that this method returns an generic object, if the register succeeded then it will return a User class (User class is mapped between Flex and Java using [RemoteClass] metadata in Actionscript) and if it fails then it will return an object with some messages and extra data in it. A better alternative to this is by using Java Exceptions, that are anyway handled in the services.

First, a custom exception need to be build, as in this example:

package com.myproject.exceptions;

public class UserServiceException extends Exception
{
    private static final long serialVersionUID = 1L;

    private String customMessage;

    public UserServiceException( String customMessage )
    {
        this.customMessage = customMessage;
    }

    public String getCustomMessage()
    {
        return customMessage;
    }
}


This exception has an extra “customMessage” property that will hold the message sent from Java to Flex when registration is invalid. Of course you can add as many properties as you want in this exception. Lets take a look at how the exception is thrown:

public User registerUser( User user ) throws UserServiceException
{
    //data processing such as transactions using Hibernate

    try
    {
        //updating such as session.save( user ) when using Hibernate
    }
    catch( ConstraintViolationException e )
    {
                //exception thrown when there is another user with the same userName registered

        String message = "User with username " + user.getUserName() +
            " already exists. Please choose another username";

        throw new UserServiceException( message );
    }

    return user;
}

The code is pretty straight forward, if there is something wrong when trying to register a new user, we throw a custom UserServiceException. Lets take a look at how the exception is handled on the Flex part:

private function registerUser():void
{
    var userService:RemoteObject =
        ServiceLocator.getInstance().getRemoteObject( "userService" );
   
    var user:User = new User();
    user.userName = "testUserName";
    user.firstName = "testFirstName";
    user.lastName = "testLastName";
    user.emailAddress = "testEmailAddress";
   
    var token:AsyncToken = userService.registerUser( user );
    var responder:AsyncResponder =
        new AsyncResponder( handleRegisterUserResult, handleRegisterUserFault, token );
    token.addResponder( responder );
}

private function handleRegisterUserResult( event:ResultEvent, token:AsyncToken ):void
{
    //process registration complete
    //event.result should be the new created User class
   
    var newRegisteredUser:User = event.result as User;
}

private function handleRegisterUserFault( event:FaultEvent, token:AsyncToken ):void
{
    var errorMessage:ErrorMessage = event.message as ErrorMessage;
    var customMessage:String = errorMessage.rootCause.customMessage;
   
    //process and show custom message
}

We have the basic call to the service using AsyncToken and AsyncResponder. Where we need to look is in the handleRegisterUserFault method where an mx.messaging.messages.ErrorMessage is present in event.message. The rootCause property is the actual Exception that we had thrown from Java service. And in it we have our customMessage property. If there is need for extra properties those can be added in UserServiceException and will end up in the rootCause property as well.

This is a good alternative to keep the result handler for a service call nice and clean and all fault events, no matter if that is a fault from network connection or a fault returned by the service, should be processed on the fault handler method.

摘自 : http://blogs.eyepartner.com/adrian/flex/sending-extra-data-in-java-exceptions-to-flex-clients/

<think>我们正在解决用户的问题:如何查看MySQL中'Sending data to client'阶段的详细耗时。 根据用户提供的引用和之前的知识,我们知道: - 引用[1]提到:Sending data是去磁盘中读取select的结果,然后将结果返回给客户端。这个过程会有大量的IO操作。 - 引用[3]提到:EXPLAIN命令可以帮助我们查看查询可能用到的索引以及影响的行数。 但是,用户现在明确要求查看的是'Sending data to client'阶段的耗时信息。在MySQL中,这个阶段通常被称为“Sending data”,但在某些上下文中可能被描述为“Sending to client”。 我们之前已经介绍过两种方法: 1. 使用SHOW PROFILE(需要MySQL版本支持,且该特性在MySQL 5.7.31之后被标记为过时,在MySQL 8.0中默认不启用,但可以通过设置profiling=1来启用) 2. 使用Performance Schema(MySQL 5.6及以上版本推荐) 由于用户现在特别关注'Sending data to client',我们需要注意在SHOW PROFILE的输出中,这个阶段通常被标记为“Sending data”。而实际上,根据MySQL官方文档,在SHOW PROFILE中,“Sending data”阶段包括从服务器读取数据并发送到客户端的过程,它并不仅仅指网络传输,还包括了在服务器内部读取和处理数据的时间。 因此,我们可以按照以下步骤操作: 方法一:使用SHOW PROFILE(适用于所有支持profiling的版本,包括5.7,但在8.0中可能被移除) 步骤: 1. 开启当前会话的profiling:`SET SESSION profiling = 1;` 2. 执行查询:`SELECT ...` (你的查询) 3. 查看查询ID:`SHOW PROFILES;` 4. 查看具体查询的详细阶段耗时:`SHOW PROFILE FOR QUERY <query_id>;` 在输出中,找到状态为“Sending data”的行,其Duration列即为该阶段的耗时。 方法二:使用Performance Schema(推荐,尤其MySQL 5.6及以上版本) 步骤: 1. 确保Performance Schema已启用(默认通常启用)。 2. 启用对应的事件收集器(默认可能未启用stage事件): ```sql UPDATE performance_schema.setup_instruments SET ENABLED = 'YES', TIMED = 'YES' WHERE NAME LIKE '%stage%'; UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME LIKE '%events%'; ``` 3. 执行你的查询。 4. 查询Performance Schema中的事件信息,以获取'Sending data'阶段的耗时。注意,在Performance Schema中,阶段事件的名称为`stage/sql/Sending data`。 ```sql SELECT EVENT_NAME, SOURCE, TIMER_WAIT/1000000000 AS 'Duration (s)' FROM performance_schema.events_stages_history WHERE EVENT_NAME = 'stage/sql/Sending data' ORDER BY THREAD_ID, EVENT_ID DESC LIMIT 1; ``` 注意:TIMER_WAIT的单位是皮秒(10的负12次方秒),除以1000000000转换为秒。 然而,用户可能执行了多个查询,我们需要定位到最近一次查询的Sending data阶段。我们可以通过连接`events_statements_history`和`events_stages_history`表来获取更精确的信息。 更详细的查询(获取指定语句的Sending data阶段耗时): ```sql SELECT stmt.SQL_TEXT, stage.EVENT_NAME, stage.TIMER_WAIT/1000000000 AS 'Duration (s)', stage.TIMER_END - stage.TIMER_START AS TIMER_DIFF FROM performance_schema.events_stages_history stage JOIN performance_schema.events_statements_history stmt ON stage.THREAD_ID = stmt.THREAD_ID AND stage.EVENT_ID BETWEEN stmt.EVENT_ID AND stmt.END_EVENT_ID WHERE stage.EVENT_NAME = 'stage/sql/Sending data' ORDER BY stmt.EVENT_ID DESC LIMIT 1; ``` 方法三:使用慢查询日志(间接方式) 配置慢查询日志,记录超过指定时间的查询,然后使用`mysqldumpslow`或`pt-query-digest`等工具分析,但这种方式不会给出各个阶段的耗时,所以不直接满足需求。 根据用户的需求,我们推荐前两种方法,尤其是Performance Schema方法,因为它更现代且提供更详细的信息。 注意:在MySQL 8.0中,SHOW PROFILE已经被废弃,官方建议使用Performance Schema。 因此,我们给出两种方法的详细步骤。 另外,引用[2]提到:STATUS列表中的TIME字段表示的是最后那个STAT状态持续的时间,而不是整个SQL执行的时间。所以使用`SHOW PROCESSLIST`虽然可以看到当前连接的状态(如State列为'Sending data'),但Time列表示的是该状态持续的时间,且这个时间会不断累加直到该状态结束。所以这种方法不能用于精确测量单个查询的Sending data阶段耗时,只能作为实时监控的参考。 因此,我们不推荐使用`SHOW PROCESSLIST`来获取精确的耗时。 总结步骤: 方法一(SHOW PROFILE): 1. 开启profiling:`SET SESSION profiling = 1;` 2. 执行查询。 3. 获取Query_ID:`SHOW PROFILES;` 4. 查看详细阶段:`SHOW PROFILE FOR QUERY <Query_ID>;`,然后查找“Sending data”行。 方法二(Performance Schema): 1. 确保Performance Schema启用,并启用stage事件收集器(如果未启用,则执行上述UPDATE语句)。 2. 执行查询。 3. 通过查询`events_stages_history`表来获取'Sending data'阶段的耗时(注意:该表默认只保留最近10条记录,可以通过调整`performance_schema_events_stages_history_size`来增加)。 注意:如果Performance Schema中未记录,可能是因为未启用或者历史记录被覆盖,可以尝试在查询前立即启用,并确保在查询后立即执行上述查询。 最后,如果用户使用的是MySQL 8.0,我们优先推荐Performance Schema方法。 由于用户没有提供具体的MySQL版本,我们给出通用方法。 示例: 假设我们使用SHOW PROFILE方法,执行以下查询: ```sql SET SESSION profiling = 1; SELECT * FROM large_table; SHOW PROFILES; -- 假设得到Query_ID为1 SHOW PROFILE FOR QUERY 1; ``` 输出可能如下: +----------------------+----------+ | Status | Duration | +----------------------+----------+ | starting | 0.000100 | | ... | ... | | Sending data | 1.234567 | <-- 这就是我们需要的耗时 | ... | ... | +----------------------+----------+ 在Performance Schema方法中,我们可以用以下查询获取最近一次查询的Sending data阶段耗时: ```sql SELECT EVENT_NAME, TIMER_WAIT/1000000000 AS 'Duration (s)' FROM performance_schema.events_stages_history WHERE EVENT_NAME = 'stage/sql/Sending data' ORDER BY THREAD_ID, EVENT_ID DESC LIMIT 1; ``` 如果用户需要更自动化的方式,可以编写存储过程来捕获这些信息。 注意:在Performance Schema中,阶段事件名称是固定的,我们可以通过查询`setup_instruments`表来查看所有可用的阶段事件: ```sql SELECT * FROM performance_schema.setup_instruments WHERE NAME LIKE '%stage%'; ``` 最后,我们提醒用户,要获取准确的性能数据,应在测试环境中进行,并避免同时有其他负载干扰。</think>在 MySQL 中查看 "Sending data to client" 阶段的详细耗时,可以通过以下两种主要方法实现。该阶段涉及从存储引擎读取数据并将结果集发送给客户端,是查询性能分析的关键指标[^1]。 ### 方法一:使用 SHOW PROFILE(推荐简单场景) 1. **启用会话级性能分析** ```sql SET SESSION profiling = 1; -- 开启性能分析 ``` 2. **执行目标查询** ```sql SELECT * FROM your_table WHERE conditions; -- 替换为实际查询 ``` 3. **查看概要信息** ```sql SHOW PROFILES; -- 获取Query_ID ``` | Query_ID | Duration | Query | |----------|----------|----------------------| | 1 | 0.456 | SELECT * FROM ... | 4. **查看详细耗时** ```sql SHOW PROFILE FOR QUERY 1; -- 替换为实际Query_ID ``` ```markdown | Status | Duration | |----------------------|----------| | starting | 0.00005 | | checking permissions | 0.00001 | | Opening tables | 0.00012 | | Sending data | 0.42000 | <-- 重点关注此项 | end | 0.00008 | | query end | 0.00003 | ``` 5. **计算耗时占比** ```math \text{Sending data 占比} = \frac{\text{Sending data 耗时}}{\text{总耗时}} \times 100 = \frac{0.420}{0.456} \times 100 \approx 92.1\% ``` ### 方法二:使用 Performance Schema(推荐生产环境) 1. **启用性能监控** ```sql -- 启用阶段事件监控 UPDATE performance_schema.setup_instruments SET ENABLED = 'YES' WHERE NAME LIKE 'stage/sql/%Sending%'; -- 启用事件消费者 UPDATE performance_schema.setup_consumers SET ENABLED = 'YES' WHERE NAME LIKE '%events%'; ``` 2. **执行目标查询** ```sql SELECT * FROM your_table WHERE conditions; ``` 3. **查询耗时详情** ```sql SELECT EVENT_NAME, FORMAT_PICO_TIME(TIMER_WAIT) AS Duration, ROUND(TIMER_WAIT / (SELECT SUM(TIMER_WAIT) FROM performance_schema.events_stages_history WHERE THREAD_ID = ps_thread_id) * 100, 2) AS Pct FROM performance_schema.events_stages_history WHERE THREAD_ID = (SELECT THREAD_ID FROM performance_schema.threads WHERE PROCESSLIST_ID = CONNECTION_ID()) AND EVENT_NAME LIKE '%Sending data%' ORDER BY EVENT_ID DESC LIMIT 1; ``` ```markdown | EVENT_NAME | Duration | Pct | |----------------------------|----------|-------| | stage/sql/Sending data | 420.00 ms| 92.11 | <-- 直接显示百分比 ``` ### 关键注意事项 1. **版本兼容性** - `SHOW PROFILE`:适用于 MySQL 5.0.37+,但在 8.0+ 中已弃用 - `Performance Schema`:推荐用于 MySQL 5.6+ 生产环境[^2] 2. **性能影响** - 性能分析会增加约 5-15% 的负载,建议在测试环境使用 - 生产环境使用 Performance Schema 时,按需启用特定指标[^4] 3. **结果解读** - "Sending data" 耗时高通常表示: - 查询返回大量数据(需检查 LIMIT 子句) - 缺少覆盖索引导致全表扫描[^1][^3] - 网络传输瓶颈(检查客户端连接速度) 4. **优化建议** ```sql -- 添加覆盖索引减少磁盘读取 ALTER TABLE your_table ADD INDEX idx_covering (col1, col2, ...); ``` - 使用 `EXPLAIN` 确认是否使用索引[^3] - 减少 `SELECT *` 的使用,仅获取必要字段 > **提示**:对于实时监控,可结合 `SHOW PROCESSLIST` 查看当前连接的 "State" 字段[^2],但无法获取精确耗时。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值