dispatch_source_t

本文详细介绍了GCD中的DispatchSource,包括其基本概念、类型、创建及使用方法。通过实例展示了如何利用DispatchSource来监听定时器事件和自定义事件。


  • Dispatch Source是GCD中的一个基本类型,从字面意思可称为调度源,它的作用是当有一些特定的较底层的系统事件发生时,调度源会捕捉到这些事件,然后可以做其他的逻辑处理,调度源有多种类型,分别监听对应类型的系统事件。我们来看看它都有哪些类型:
  • Timer Dispatch Source:定时调度源。
  • Signal Dispatch Source:监听UNIX信号调度源,比如监听代表挂起指令的SIGSTOP信号。
  • Descriptor Dispatch Source:监听文件相关操作和Socket相关操作的调度源。
  • Process Dispatch Source:监听进程相关状态的调度源。
  • Mach port Dispatch Source:监听Mach相关事件的调度源。
  • Custom Dispatch Source:监听自定义事件的调度源。

这一节就来看看如何使用Dispatch Source。

用通俗一点的话说就是用GCD的函数指定一个希望监听的系统事件类型,再指定一个捕获到事件后进行逻辑处理的闭包或者函数作为回调函数,然后再指定一个该回调函数执行的Dispatch Queue即可,当监听到指定的系统事件发生时会调用回调函数,将该回调函数作为一个任务放入指定的队列中执行。也就是说当监听到系统事件后就会触发一个任务,并自动将其加入队列执行,这里与之前手动添加任务的模式不同,一旦将Diaptach Source与Dispatch Queue关联后,只要监听到系统事件,Dispatch Source就会自动将任务(回调函数)添加到关联的队列中。

有些时候回调函数执行的时间较长,在这段时间内Dispatch Source又监听到多个系统事件,理论上就会形成事件积压,但好在Dispatch Source有很好的机制解决这个问题,当有多个事件积压时会根据事件类型,将它们进行关联和结合,形成一个新的事件。

监听事件类型

Dispatch Source一共可以监听六类事件,分为11个类型,我们来看看都是什么:

  • DISPATCH_SOURCE_TYPE_DATA_ADD:属于自定义事件,可以通过dispatch_source_get_data函数获取事件变量数据,在我们自定义的方法中可以调用dispatch_source_merge_data函数向Dispatch Source设置数据,下文中会有详细的演示。
  • DISPATCH_SOURCE_TYPE_DATA_OR:属于自定义事件,用法同上面的类型一样。
  • DISPATCH_SOURCE_TYPE_MACH_SEND:Mach端口发送事件。
  • DISPATCH_SOURCE_TYPE_MACH_RECV:Mach端口接收事件。
  • DISPATCH_SOURCE_TYPE_PROC:与进程相关的事件。
  • DISPATCH_SOURCE_TYPE_READ:读文件事件。
  • DISPATCH_SOURCE_TYPE_WRITE:写文件事件。
  • DISPATCH_SOURCE_TYPE_VNODE:文件属性更改事件。
  • DISPATCH_SOURCE_TYPE_SIGNAL:接收信号事件。
  • DISPATCH_SOURCE_TYPE_TIMER:定时器事件。
  • DISPATCH_SOURCE_TYPE_MEMORYPRESSURE:内存压力事件。

创建Dispatch Source

我们可以使用dispatch_source_create函数创建Dispatch Source,该函数有四个参数:

  • type:第一个参数用于标识Dispatch Source要监听的事件类型,共有11个类型。
  • handle:第二个参数是取决于要监听的事件类型,比如如果是监听Mach端口相关的事件,那么该参数就是mach_port_t类型的Mach端口号,如果是监听事件变量数据类型的事件那么该参数就不需要,设置为0就可以了。
  • mask:第三个参数同样取决于要监听的事件类型,比如如果是监听文件属性更改的事件,那么该参数就标识文件的哪个属性,比如DISPATCH_VNODE_RENAME
  • queue:第四个参数设置回调函数所在的队列。
let dispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)        let dispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatchQueue)

上面的代码就是创建Dispatch Source的简单示例。

设置事件处理器

前文中提到过,当Dispatch Source监听到事件时会调用指定的回调函数或闭包,该回调函数或闭包就是Dispatch Source的事件处理器。我们可以使用dispatch_source_set_event_handlerdispatch_source_set_event_handler_f函数给创建好的Dispatch Source设置处理器,前者是设置闭包形式的处理器,后者是设置函数形式的处理器:

dispatch_source_set_event_handler(dispatchSource, {                print("Dispatch Source 事件处理器...")})// 根据闭包尾随的特性,还可以有下面的写法dispatch_source_set_event_handler(dispatchSource) {          print("Dispatch Source 事件处理器...")      }

从上面示例代码中可以看到,该函数有两个参数,第一个是设置目标Dispatch Source,第二个参数就是设置处理器了。

既然是事件处理器,那么肯定需要获取一些Dispatch Source的信息,GCD提供了三个在处理器中获取Dispatch Source相关信息的函数,比如handlemask。而且针对不同类型的Dispatch Source,这三个函数返回数据的值和类型都会不一样,下面来看看这三个函数:

  • dispatch_source_get_handle:这个函数用于获取在创建Dispatch Source时设置的第二个参数handle
    • 如果是读写文件的Dispatch Source,返回的就是描述符。
    • 如果是信号类型的Dispatch Source,返回的是int类型的信号数。
    • 如果是进程类型的Dispatch Source,返回的是pid_t类型的进程id。
    • 如果是Mach端口类型的Dispatch Source,返回的是mach_port_t类型的Mach端口。
  • dispatch_source_get_data:该函数用于获取Dispatch Source监听到事件的相关数据。 
    • 如果是读文件类型的Dispatch Source,返回的是读到文件内容的字节数。
    • 如果是写文件类型的Dispatch Source,返回的是文件是否可写的标识符,正数表示可写,负数表示不可写。
    • 如果是监听文件属性更改类型的Dispatch Source,返回的是监听到的有更改的文件属性,用常量表示,比如DISPATCH_VNODE_RENAME等。
    • 如果是进程类型的Dispatch Source,返回监听到的进程状态,用常量表示,比如DISPATCH_PROC_EXIT等。
    • 如果是Mach端口类型的Dispatch Source,返回Mach端口的状态,用常量表示,比如DISPATCH_MACH_SEND_DEAD等。
    • 如果是自定义事件类型的Dispatch Source,返回使用dispatch_source_merge_data函数设置的数据。
  • dispatch_source_get_mask:该函数用于获取在创建Dispatch Source时设置的第三个参数mask。在进程类型,文件属性更改类型,Mach端口类型的Dispatch Source下该函数返回的结果与dispatch_source_get_data一样。

注册Cancellation Handler

Cancellation Handler就是当Dispatch Source被释放时用来处理一些后续事情,比如关闭文件描述符或者释放Mach端口等。我们可以使用dispatch_source_set_cancel_handler函数或者dispatch_source_set_cancel_handler_f函数给Dispatch Source注册Cancellation Handler:

dispatch_source_set_cancel_handler(dispatchSource) {                print("进行善后处理...")      }

该函数有两个参数,第一个参数是目标Dispatch Source,第二个参数就是要进行善后处理的闭包或者函数。

更改Dispatch Source的目标队列

在上文中,我们说过可以使用dispatch_source_create函数创建Dispatch Source,并且在创建时会指定回调函数执行的队列,那么如果事后想更改队列,比如说想更改队列的优先级,这时我们可以使用dispatch_set_target_queue函数实现:

let dispatchQueueDefaultPriority = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)        let dispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatchQueueDefaultPriority)   let dispatchQueueLowPriority = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_LOW, 0)     dispatch_set_target_queue(dispatchSource, dispatchQueueLowPriority)

这里需要注意的是,如果在更改目标队列时,Dispatch Source已经监听到相关事件,并且回调函数已经在之前的队列中执行了,那么会一直在旧的队列中执行完成,不会转移到新的队列中去。

暂停恢复Dispatch Source

暂停和恢复Dispatch Source与Dispatch Queue一样,都适用dispatch_suspenddispatch_resume函数。这里需要注意的是刚创建好的Dispatch Source是处于暂停状态的,所以使用时需要用dispatch_resume函数将其启动。

废除Dispatch Source

如果我们不再需要使用某个Dispatch Source时,可以使用dispatch_source_cancel函数废除,该函数只有一个参数,那就是目标Dispatch Source。

Dispatch Source实践

说了这么多,这一节来看看Dispatch Source到底怎么用。

用Dispatch Source监听定时器

Dispatch Source能监听的事件中有一个类型就是定时器,我们来看看如何实现:

class TestDispatchSource {      func launch() {          let dispatchQueue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)        let timer = createTimerDispatchSource(dispatch_time(DISPATCH_TIME_NOW, 0), interval: NSEC_PER_SEC * 5, leeway: 0, queue: dispatchQueue) {            print("处理定时任务,该任务每5秒执行一次...")        }        dispatch_resume(timer)        sleep(30)    }    func createTimerDispatchSource(startTime: dispatch_time_t, interval: UInt64, leeway: UInt64, queue: dispatch_queue_t, handler: dispatch_block_t) -> dispatch_source_t {        let timerDispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, queue)        dispatch_source_set_timer(timerDispatchSource, startTime, interval, leeway)        dispatch_source_set_event_handler(timerDispatchSource, handler)        return timerDispatchSource    }}

上面的代码示例中一个新的函数dispatch_source_set_timer,该函数的作用就是给监听事件类型为DISPATCH_SOURCE_TYPE_TIMER的Dispatch Source设置相关属性,该函数有四个参数:

  • source:该参数为目标Dispatch Source,类型为dispatch_source_t.
  • start:该参数为定时器的起始时间,类型为dispatch_time_t
  • interval:该参数为定时器的间隔时间,类型为UInt64,间隔时间的单位是纳秒。
  • leeway:该参数为间隔时间的精度,类型为UInt64,时间单位也是纳秒。
用Dispatch Source监听自定义事件

Dispatch Source能监听的事件中有一个类型是自定义事件,下面我们来看看如何使用:

class TestDispatchSource {        func launch() {        var totalProcess = 0        let dispatchSource = dispatch_source_create(DISPATCH_SOURCE_TYPE_DATA_ADD, 0, 0, dispatch_get_main_queue())        dispatch_source_set_event_handler(dispatchSource) {            let process = dispatch_source_get_data(dispatchSource)            totalProcess += Int(process)            print("这里可以在主线程更新UI,显示进度条...进度为/(totalProcess)%")        }        dispatch_resume(dispatchSource)        generateCustomEvent(dispatchSource)    }    func generateCustomEvent(dispatchSource: dispatch_source_t) {        let queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)        for index in 0...100 {             dispatch_sync(queue) {                   print("模拟自定义事件...进度为/(index)%")                  dispatch_source_merge_data(dispatchSource, 1)                sleep(2)              }        }    }}

我们来看看generateCustomEvent(dispatchSource: dispatch_source_t)方法,该方法的作用的是模拟自定义事件,首先创建一个全局并发队列,然后循环让其执行任务,在执行的任务里调用dispatch_source_merge_data函数,就可以触发监听类型为DISPATCH_SOURCE_TYPE_DATA_ADD或者DISPATCH_SOURCE_TYPE_DATA_OR的Dispatch Source。该函数有两个参数,第一个参数是目标Dispatch Source,第二个参数的类型是无符号长整型,用于向目标Dispatch Source中的对应变量追加指定的数。

我们再来看看如何监听自定义时间,首先创建类型为DISPATCH_SOURCE_TYPE_DATA_ADD的Dispatch Source,然后设置回调闭包,在闭包中使用dispatch_source_get_data获取追加的变量值,该函数只有一个参数,就是目标Dispatch Source,这里需要注意的是通过dispatch_source_get_data函数获取的变量值并不是累加值,而是每次调用dispatch_source_merge_data函数时设置的值,所以在上面的示例中用totalProcess变量累加每次获取到的值。

上面的示例可以用来模拟后台进行下载,根据下载的数据量使用dispatch_source_merge_data函数给目标Dispatch Source设置相应的变量值,然后在主线程中监听到Dispatch Source的自定义事件,通过dispatch_source_get_data函数获取到变量,用于更新显示进度条的UI。



原文地址:http://www.th7.cn/Program/IOS/201605/849625.shtml

<think>我们遇到了一个MyBatis的异常,提示在`java.lang.String`类中没有名为`split('_')`的getter方法。 这是因为在MyBatis的OGNL表达式中,我们试图在一个字符串上调用`split`方法,但是MyBatis将其解释为要获取一个名为`split('_')`的属性,而不是调用方法。 在MyBatis的动态SQL中,我们可以在`<foreach>`标签的`collection`属性中指定要遍历的集合,并且可以使用`item`来指定每个元素的别名。 但是,在`<foreach>`标签内部,我们试图通过`#{key.split('_')}`来分割字符串,这是不允许的,因为MyBatis不允许在属性中直接调用方法。 解决方案: 1. 我们可以将分割操作提前,在Java代码中准备好每个key的三个部分,然后传递一个包含这些部分的对象列表。 2. 或者,我们可以修改SQL语句,避免在SQL中分割字符串。 考虑到这个SQL语句的目的是根据多个复合主键(由三个字段组成,用下划线连接)来查询记录,我们可以重新设计SQL。 这里提供两种解决方案: 方案一:在Java代码中预处理key 将传入的`keys`(一个字符串列表,每个字符串由下划线连接三个ID)在Java代码中分割,然后构造一个包含三个字段的对象列表,传递给MyBatis。 方案二:使用SQL函数分割(不推荐,因为不同数据库函数不同,且效率可能不高) 如果数据库支持字符串分割函数(如MySQL的SUBSTRING_INDEX),可以在SQL中分割,但这样会降低可移植性,而且当数据量大时可能影响性能。 我们选择方案一,因为更通用,性能也更好。 步骤: 1. 在Java方法中,将每个key分割成三个部分,并构造一个包含三个属性的对象(例如一个三元组)列表。 2. 修改MyBatis的mapper,接收这个对象列表,然后在SQL中直接引用对象的属性。 具体实现: 首先,定义一个简单的类来保存分割后的三个值: ```java public class CompositeKey { private String carOutDispatchId; private String alarmId; private String ambId; // 构造方法、getter和setter public CompositeKey(String carOutDispatchId, String alarmId, String ambId) { this.carOutDispatchId = carOutDispatchId; this.alarmId = alarmId; this.ambId = ambId; } // 省略getter和setter } ``` 然后,在调用MyBatis查询的方法中,将原来的`List<String> keys`转换为`List<CompositeKey> compositeKeys`: ```java List<CompositeKey> compositeKeys = keys.stream() .map(key -> { String[] parts = key.split("_"); if (parts.length != 3) { throw new IllegalArgumentException("Invalid key format: " + key); } return new CompositeKey(parts[0], parts[1], parts[2]); }) .collect(Collectors.toList()); ``` 最后,修改MyBatis的mapper文件,使用`CompositeKey`对象的属性: ```xml <select id="listByUniqueKeys" resultType="com.jsfj.ects.infrastructure.repository.entity.CarOutEntity"> SELECT t.* FROM jsfj_biz_car_out t WHERE EXISTS ( SELECT 1 FROM ( <foreach collection="list" item="key" separator=" UNION ALL "> SELECT #{key.carOutDispatchId} AS car_out_dispatch_id, #{key.alarmId} AS alarm_id, #{key.ambId} AS amb_id </foreach> ) temp_keys WHERE t.car_out_dispatch_id = temp_keys.car_out_dispatch_id AND t.alarm_id = temp_keys.alarm_id AND t.amb_id = temp_keys.amb_id ) </select> ``` 注意:这里我们将`collection`属性改为`list`(因为MyBatis默认将列表参数命名为`list`),或者如果使用@Param注解指定了参数名,则使用该名称。 如果原来的方法参数没有使用@Param注解,那么MyBatis会将它作为`param1`或者`list`(如果参数类型是List)来处理。为了明确,建议使用@Param注解: 在Mapper接口中: ```java List<CarOutEntity> listByUniqueKeys(@Param("keys") List<CompositeKey> keys); ``` 然后在XML中,将`collection`属性设置为`keys`: ```xml <foreach collection="keys" item="key" separator=" UNION ALL "> ``` 这样,在`<foreach>`循环中,每个`key`就是一个`CompositeKey`对象,可以直接使用`key.carOutDispatchId`等属性。 方案二:使用临时表(利用VALUES构造表)—— 如果数据库支持(如MySQL 8.0+,PostgreSQL等),可以更高效地使用行构造器。 例如,在MySQL 8.0+中,我们可以这样写: ```xml <select id="listByUniqueKeys" resultType="com.jsfj.ects.infrastructure.repository.entity.CarOutEntity"> SELECT t.* FROM jsfj_biz_car_out t JOIN ( <foreach collection="keys" item="key" separator=" UNION ALL "> SELECT #{key.carOutDispatchId} AS car_out_dispatch_id, #{key.alarmId} AS alarm_id, #{key.ambId} AS amb_id </foreach> ) temp_keys ON t.car_out_dispatch_id = temp_keys.car_out_dispatch_id AND t.alarm_id = temp_keys.alarm_id AND t.amb_id = temp_keys.amb_id </select> ``` 或者,使用VALUES语法(MySQL 8.0+): ```xml <select id="listByUniqueKeys" resultType="com.jsfj.ects.infrastructure.repository.entity.CarOutEntity"> SELECT t.* FROM jsfj_biz_car_out t JOIN ( VALUES <foreach collection="keys" item="key" separator=","> (#{key.carOutDispatchId}, #{key.alarmId}, #{key.ambId}) </foreach> ) AS temp_keys(car_out_dispatch_id, alarm_id, amb_id) ON t.car_out_dispatch_id = temp_keys.car_out_dispatch_id AND t.alarm_id = temp_keys.alarm_id AND t.amb_id = temp_keys.amb_id </select> ``` 但是请注意,VALUES语法在MySQL 8.0才支持,而且MyBatis绑定参数时,每个值都需要有对应的类型处理器。另外,不同的数据库有不同的语法。 考虑到兼容性,我们采用第一种方案(使用UNION ALL构造临时表)即可,它在大多数数据库中都可用。 总结:我们通过在Java代码中预处理字符串,将分割后的结果封装成对象列表,然后在MyBatis中直接引用对象的属性,从而避免了在MyBatis中调用方法。这样既解决了异常问题,又保证了代码的可读性和可维护性。 如果原方法不能改变传入参数的类型(比如仍然要接收`List<String> keys`),那么我们可以创建一个重载方法,或者将转换放在Mapper接口的默认方法中(如果使用Java8+)。 例如,在Mapper接口中: ```java public interface CarOutMapper { default List<CarOutEntity> listByUniqueKeys(List<String> keys) { List<CompositeKey> compositeKeys = keys.stream() .map(key -> { String[] parts = key.split("_"); if (parts.length != 3) { throw new IllegalArgumentException("Invalid key format: " + key); } return new CompositeKey(parts[0], parts[1], parts[2]); }) .collect(Collectors.toList()); return listByCompositeKeys(compositeKeys); } List<CarOutEntity> listByCompositeKeys(@Param("keys") List<CompositeKey> keys); } ``` 这样,外部调用仍然使用`listByUniqueKeys`方法传入字符串列表,内部转换为复合键列表再调用新的方法。 修改后的Mapper XML: ```xml <select id="listByCompositeKeys" resultType="com.jsfj.ects.infrastructure.repository.entity.CarOutEntity"> SELECT t.* FROM jsfj_biz_car_out t WHERE EXISTS ( SELECT 1 FROM ( <foreach collection="keys" item="key" separator=" UNION ALL "> SELECT #{key.carOutDispatchId} AS car_out_dispatch_id, #{key.alarmId} AS alarm_id, #{key.ambId} AS amb_id </foreach> ) temp_keys WHERE t.car_out_dispatch_id = temp_keys.car_out_dispatch_id AND t.alarm_id = temp_keys.alarm_id AND t.amb_id = temp_keys.amb_id ) </select> ``` 这样,我们既保持了对外接口不变,又解决了问题。 注意:如果采用这种默认方法的方式,需要确保MyBatis能够正确绑定参数(即Mapper接口使用动态代理,方法调用会传递参数)。 因此,最终的解决方案是: 1. 定义`CompositeKey`类。 2. 在Mapper接口中添加默认方法,将字符串列表转换为`CompositeKey`列表,并调用新的查询方法。 3. 添加新的查询方法`listByCompositeKeys`,并编写对应的SQL。 这样,原有的调用方不需要做任何修改。 如果项目中有多处类似情况,可以抽象一个工具方法来进行字符串分割和转换。 以上方案可以解决该异常,并且代码结构清晰。 </think>### MyBatis 动态 SQL 问题分析与解决方案 #### 问题分析 错误信息 `There is no getter for property named 'split('_')' in 'class java.lang.String'` 表明 MyBatis 在解析 OGNL 表达式时出现问题。关键问题在于: 1. **OGNL 表达式限制**:MyBatis 不支持在 XML 映射文件中直接调用方法(如 `split('_')`) 2. **字符串处理不当**:试图在 SQL 中使用 `split` 方法分割字符串 3. **嵌套表达式错误**:`#{key.split('_')[0]}` 这种语法在 MyBatis 中无效 --- ### 解决方案 #### 方案一:使用 Java 预处理数据(推荐) 在 Java 层预处理数据,将分割后的值直接传给 MyBatis: ```java // Java 服务层 public List<CarOutEntity> listByUniqueKeys(List<String> keys) { List<Triplet<String, String, String>> processedKeys = keys.stream() .map(key -> { String[] parts = key.split("_"); return Tuples.of(parts[0], parts[1], parts[2]); }) .collect(Collectors.toList()); return carOutMapper.listByProcessedKeys(processedKeys); } ``` ```java // Mapper 接口 List<CarOutEntity> listByProcessedKeys( @Param("keys") List<Triplet<String, String, String>> keys); ``` ```xml <!-- MyBatis XML 映射 --> <select id="listByProcessedKeys" resultType="com.jsfj.ects.infrastructure.repository.entity.CarOutEntity"> SELECT t.* FROM jsfj_biz_car_out t JOIN ( <foreach collection="keys" item="key" separator=" UNION ALL "> SELECT #{key.getValue0()} AS car_out_dispatch_id, #{key.getValue1()} AS alarm_id, #{key.getValue2()} AS amb_id </foreach> ) temp_keys ON t.car_out_dispatch_id = temp_keys.car_out_dispatch_id AND t.alarm_id = temp_keys.alarm_id AND t.amb_id = temp_keys.amb_id </select> ``` #### 方案二:使用 MyBatis 动态 SQL 函数 利用 MyBatis 的 `bind` 标签预处理值: ```xml <select id="listByUniqueKeys" resultType="com.jsfj.ects.infrastructure.repository.entity.CarOutEntity"> SELECT t.* FROM jsfj_biz_car_out t WHERE EXISTS ( SELECT 1 FROM ( <foreach collection="keys" item="key" separator=" UNION ALL "> <bind name="parts" value="key.split('_')"/> SELECT #{parts[0]} AS car_out_dispatch_id, #{parts[1]} AS alarm_id, #{parts[2]} AS amb_id </foreach> ) temp_keys WHERE t.car_out_dispatch_id = temp_keys.car_out_dispatch_id AND t.alarm_id = temp_keys.alarm_id AND t.amb_id = temp_keys.amb_id ) </select> ``` #### 方案三:数据库端分割(数据库相关) 使用数据库函数进行分割(以 MySQL 为例): ```xml <select id="listByUniqueKeys" resultType="com.jsfj.ects.infrastructure.repository.entity.CarOutEntity"> SELECT t.* FROM jsfj_biz_car_out t WHERE (car_out_dispatch_id, alarm_id, amb_id) IN ( <foreach collection="keys" item="key" separator=","> ( SUBSTRING_INDEX(#{key}, '_', 1), SUBSTRING_INDEX(SUBSTRING_INDEX(#{key}, '_', 2), '_', -1), SUBSTRING_INDEX(#{key}, '_', -1) ) </foreach> ) </select> ``` --- ### 最佳实践建议 1. **分离业务逻辑与 SQL**: ```java // 使用自定义 Key 对象 public class CompositeKey { private String dispatchId; private String alarmId; private String ambId; public CompositeKey(String combinedKey) { String[] parts = combinedKey.split("_"); this.dispatchId = parts[0]; this.alarmId = parts[1]; this.ambId = parts[2]; } // Getters } ``` 2. **使用 JOIN 优化查询**: ```xml <select id="listByKeys" resultType="CarOutEntity"> SELECT t.* FROM jsfj_biz_car_out t INNER JOIN ( VALUES <foreach collection="keys" item="key" separator=","> (#{key.dispatchId}, #{key.alarmId}, #{key.ambId}) </foreach> ) AS k(dispatch_id, alarm_id, amb_id) ON t.car_out_dispatch_id = k.dispatch_id AND t.alarm_id = k.alarm_id AND t.amb_id = k.amb_id </select> ``` 3. **批量处理优化**: ```java // 分批次处理防止 SQL 过长 public List<CarOutEntity> batchListByKeys(List<String> keys) { int batchSize = 500; List<CarOutEntity> result = new ArrayList<>(); for (int i = 0; i < keys.size(); i += batchSize) { List<String> batch = keys.subList(i, Math.min(i + batchSize, keys.size())); result.addAll(mapper.listByUniqueKeys(batch)); } return result; } ``` --- ### 性能对比 | 方案 | 优点 | 缺点 | 适用场景 | |------|------|------|----------| | Java 预处理 | 代码清晰,数据库无关 | 需要额外Java逻辑 | 通用场景 | | MyBatis bind | 无需修改Java代码 | OGNL依赖,性能稍差 | 简单分割 | | 数据库函数 | 单次查询完成 | 数据库依赖,可移植性差 | 特定数据库环境 |
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值