SpringBoot集成Redis,以及MybatisPlus分页、Mapper的使用(一)
来源:互联网 发布:mac 下安装ipython 编辑:程序博客网 时间:2024/06/03 15:52
概述
这段时间接触很多知识,也逐渐深入到项目整个流程开发,对Linux也有逐步地认识。虽然有去探索,但感觉能力还不足以写出来跟大家分享。撰写本文是了解到有些读者反馈,对于MP(MybatisPlus缩写)不太了解以及如何去使用,但更多还是笔者用完觉得非常强大,有必要share。文章还会主讲Redis配置使用,篇幅有点长,因此分为两节讲解。
构建
- 开发环境:Spring Tool Suite(Version: 3.8.2.RELEASE当然,譬如Eclipse、MyEclipse等其他IDE工具也行)
- 数据库:MySQL
项目结构
新建Maven项目,导入相关依赖包,pom.xml(根据个人需求可以去掉不必要的包)
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> <modelVersion>4.0.0</modelVersion> <groupId>com.jekin.example</groupId> <artifactId>redis-example</artifactId> <version>0.0.1-SNAPSHOT</version> <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <spring.boot.version>1.5.3.RELEASE</spring.boot.version> <java.version>1.8</java.version> <redis.version>1.4.7.RELEASE</redis.version> <druid.version>1.0.0</druid.version> <mybatisplus-spring-boot-starter.version>1.0.1</mybatisplus-spring-boot-starter.version> <fastjson.version>1.2.31</fastjson.version> <mysql.version>5.1.42</mysql.version> <commons.lang3.version>3.5</commons.lang3.version> </properties> <dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-dependencies</artifactId> <version>${spring.boot.version}</version> <type>pom</type> <scope>import</scope> </dependency> </dependencies> </dependencyManagement> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-redis</artifactId> <version>${redis.version}</version> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <dependency> <groupId>com.alibaba</groupId> <artifactId>fastjson</artifactId> <version>${fastjson.version}</version> </dependency> <!-- 数据库连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>${druid.version}</version> </dependency> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatisplus-spring-boot-starter</artifactId> <version>${mybatisplus-spring-boot-starter.version}</version> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-lang3</artifactId> <version>${commons.lang3.version}</version> </dependency> </dependencies> <build> <plugins> <!-- Maven编译版本 --> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-compiler-plugin</artifactId> <version>3.1</version> <configuration> <executable>true</executable> <source>1.8</source> <target>1.8</target> </configuration> </plugin> <!-- 为Spring Boot提供Maven操作支持 --> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> <configuration> <finalName>redis</finalName> </configuration> <executions> <execution> <!-- 重新打包成可执行包文件,达到一键启动运行 --> <goals> <goal>repackage</goal> </goals> </execution> </executions> </plugin> <plugin> <groupId>org.apache.maven.plugins</groupId> <artifactId>maven-war-plugin</artifactId> <version>2.6</version> <configuration> <warSourceExcludes>src/main/resources/**</warSourceExcludes> <failOnMissingWebXml>false</failOnMissingWebXml> </configuration> </plugin> </plugins> <resources> <resource> <directory>src/main/resources</directory> </resource> <resource> <directory>src/main/java</directory> <includes> <include>**/*.xml</include> </includes> </resource> </resources> </build></project>
SpringBoot配置文件,application.yaml
server: port: 8080spring: main: banner-mode: "off" application: name: redis profiles: active: local thymeleaf: cache: false #前后端分离,关闭前端模板引擎,官方建议 http: converters: preferred-json-mapper: fastjson redis: #redis配置,不使用可删除 host: 192.168.1.70 password: rs0720$ port: 6379 pool: max-idle: 10 min-idle: 1 max-active: 1000 max-wait: -1 datasource: url: jdbc:mysql://192.168.1.70:3306/sharing?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false username: *** #本地数据库用户名 password: *** #本地数据库密码 filters: mergeStatlogging: config: classpath:logback-${spring.profiles.active}.xml################### mybatis-plus配置 ###################mybatis-plus: mapper-locations: classpath*:com/jekin/example/**/mapping/*.xml typeAliasesPackage: com.jekin.example.entity global-config: id-type: 3 #1:数据库ID自增 2:用户输入id 3:全局唯一id(IdWorker) 4:全局唯一ID(uuid) db-column-underline: false refresh-mapper: true is-capital-mode: false configuration: map-underscore-to-camel-case: true cache-enabled: true #配置的缓存的全局开关 lazyLoadingEnabled: true #延时加载的开关 jdbcTypeForNull: NULL multipleResultSetsEnabled: true #开启的话,延时加载一个属性时会加载该对象全部属性,否则按需加载属性 #log-impl: org.apache.ibatis.logging.stdout.StdOutImpl #打印sql语句,调试用
数据源配置文件,DruidProperties.java
package com.jekin.example.properties;import java.sql.SQLException;import org.springframework.boot.context.properties.ConfigurationProperties;import org.springframework.stereotype.Component;import com.alibaba.druid.pool.DruidDataSource;/** * <p>数据库数据源配置</p> * <p>说明:这个类中包含了许多默认配置,若这些配置符合您的情况,您可以不用管,若不符合,建议不要修改本类,建议直接在"application.yml"中配置即可</p> * * @date 2017-05-21 11:18 */@Component@ConfigurationProperties(prefix = "spring.datasource")public class DruidProperties { private String url = "jdbc:mysql://127.0.0.1:3306/operation?autoReconnect=true&useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull"; private String username = "root"; private String password = "admin"; private String driverClassName = "com.mysql.jdbc.Driver"; private Integer initialSize = 2; private Integer minIdle = 1; private Integer maxActive = 20; private Integer maxWait = 60000; private Integer timeBetweenEvictionRunsMillis = 60000; private Integer minEvictableIdleTimeMillis = 300000; private String validationQuery = "SELECT 'x' from dual"; private Boolean testWhileIdle = true; private Boolean testOnBorrow = false; private Boolean testOnReturn = false; private Boolean poolPreparedStatements = true; private Integer maxPoolPreparedStatementPerConnectionSize = 20; private String filters = "stat"; public void coinfig(DruidDataSource dataSource) { dataSource.setUrl(url); dataSource.setUsername(username); dataSource.setPassword(password); dataSource.setDriverClassName(driverClassName); dataSource.setInitialSize(initialSize); //定义初始连接数 dataSource.setMinIdle(minIdle); //最小空闲 dataSource.setMaxActive(maxActive); //定义最大连接数 dataSource.setMaxWait(maxWait); //最长等待时间 // 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis); // 配置一个连接在池中最小生存的时间,单位是毫秒 dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis); dataSource.setValidationQuery(validationQuery); dataSource.setTestWhileIdle(testWhileIdle); dataSource.setTestOnBorrow(testOnBorrow); dataSource.setTestOnReturn(testOnReturn); // 打开PSCache,并且指定每个连接上PSCache的大小 dataSource.setPoolPreparedStatements(poolPreparedStatements); dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize); try { dataSource.setFilters(filters); } catch (SQLException e) { e.printStackTrace(); } } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } public String getDriverClassName() { return driverClassName; } public void setDriverClassName(String driverClassName) { this.driverClassName = driverClassName; } public Integer getInitialSize() { return initialSize; } public void setInitialSize(Integer initialSize) { this.initialSize = initialSize; } public Integer getMinIdle() { return minIdle; } public void setMinIdle(Integer minIdle) { this.minIdle = minIdle; } public Integer getMaxActive() { return maxActive; } public void setMaxActive(Integer maxActive) { this.maxActive = maxActive; } public Integer getMaxWait() { return maxWait; } public void setMaxWait(Integer maxWait) { this.maxWait = maxWait; } public Integer getTimeBetweenEvictionRunsMillis() { return timeBetweenEvictionRunsMillis; } public void setTimeBetweenEvictionRunsMillis(Integer timeBetweenEvictionRunsMillis) { this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis; } public Integer getMinEvictableIdleTimeMillis() { return minEvictableIdleTimeMillis; } public void setMinEvictableIdleTimeMillis(Integer minEvictableIdleTimeMillis) { this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis; } public String getValidationQuery() { return validationQuery; } public void setValidationQuery(String validationQuery) { this.validationQuery = validationQuery; } public Boolean getTestWhileIdle() { return testWhileIdle; } public void setTestWhileIdle(Boolean testWhileIdle) { this.testWhileIdle = testWhileIdle; } public Boolean getTestOnBorrow() { return testOnBorrow; } public void setTestOnBorrow(Boolean testOnBorrow) { this.testOnBorrow = testOnBorrow; } public Boolean getTestOnReturn() { return testOnReturn; } public void setTestOnReturn(Boolean testOnReturn) { this.testOnReturn = testOnReturn; } public Boolean getPoolPreparedStatements() { return poolPreparedStatements; } public void setPoolPreparedStatements(Boolean poolPreparedStatements) { this.poolPreparedStatements = poolPreparedStatements; } public Integer getMaxPoolPreparedStatementPerConnectionSize() { return maxPoolPreparedStatementPerConnectionSize; } public void setMaxPoolPreparedStatementPerConnectionSize(Integer maxPoolPreparedStatementPerConnectionSize) { this.maxPoolPreparedStatementPerConnectionSize = maxPoolPreparedStatementPerConnectionSize; } public String getFilters() { return filters; } public void setFilters(String filters) { this.filters = filters; }}
MP注解配置,MybatisPlusConfig.java
package com.jekin.example.config;import org.mybatis.spring.annotation.MapperScan;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import com.alibaba.druid.pool.DruidDataSource;import com.baomidou.mybatisplus.enums.DBType;import com.baomidou.mybatisplus.plugins.PaginationInterceptor;import com.jekin.example.properties.DruidProperties;/** * MybatisPlus配置 * * @author Jekin * @date 2017年10月31日上午11:36:30 */@Configuration@MapperScan(basePackages = {"com.jekin.example.mapper"})public class MybatisPlusConfig { @Autowired DruidProperties druidProperties; /** * mybatis-plus分页插件 */ @Bean public PaginationInterceptor paginationInterceptor() { PaginationInterceptor paginationInterceptor = new PaginationInterceptor(); paginationInterceptor.setDialectType(DBType.MYSQL.getDb()); return paginationInterceptor; } /** * druid数据库连接池 */ @Bean(initMethod = "init") public DruidDataSource dataSource() { DruidDataSource dataSource = new DruidDataSource(); druidProperties.coinfig(dataSource); return dataSource; }}
启动类,RedisExampleApplication.java
package com.jekin.example;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.boot.SpringApplication;import org.springframework.boot.autoconfigure.SpringBootApplication;import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;/** * 启动类 * * @author fengjk * @date 2017年6月27日 * @since 1.0 */@SpringBootApplicationpublic class RedisExampleApplication extends WebMvcConfigurerAdapter{ protected final static Logger logger = LoggerFactory.getLogger(RedisExampleApplication.class); public static void main(String[] args) { SpringApplication.run(RedisExampleApplication.class, args); logger.info("RedisExampleApplication is sussess!"); }}
至此,SpringBoot集成MP框架就算搭建完成。下面,看下MP封装的常见mapper方法的使用。
创建实体类(略),映射文件(略),UserMapper.java
package com.jekin.example.mapper;import java.util.List;import org.apache.ibatis.annotations.Param;import com.baomidou.mybatisplus.mapper.BaseMapper;import com.baomidou.mybatisplus.plugins.Page;import com.jekin.example.entity.User;/** * 用户表 数据层 * */public interface UserMapper extends BaseMapper<User> { List<User> getUserPage(@Param("page") Page<User> page, @Param("orderByField") String orderByField, @Param("isAsc") boolean isAsc);}
getUserPage是分页方法,下节会提到。这里主要看下BaseMapper(MP对外mapper封装类)
/** * Copyright (c) 2011-2020, hubin (jobob@qq.com). * <p> * Licensed under the Apache License, Version 2.0 (the "License"); you may not * use this file except in compliance with the License. You may obtain a copy of * the License at * <p> * http://www.apache.org/licenses/LICENSE-2.0 * <p> * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the * License for the specific language governing permissions and limitations under * the License. */package com.baomidou.mybatisplus.mapper;import java.io.Serializable;import java.util.List;import java.util.Map;import org.apache.ibatis.annotations.Param;import org.apache.ibatis.session.RowBounds;/** * <p> * Mapper 继承该接口后,无需编写 mapper.xml 文件,即可获得CRUD功能 * </p> * <p> * 这个 Mapper 支持 id 泛型 * </p> * * @author hubin * @Date 2016-01-23 */public interface BaseMapper<T> { /** * <p> * 插入一条记录 * </p> * * @param entity * 实体对象 * @return int */ Integer insert(T entity); /** * <p> * 插入一条记录 * </p> * * @param entity * 实体对象 * @return int */ Integer insertAllColumn(T entity); /** * <p> * 根据 ID 删除 * </p> * * @param id * 主键ID * @return int */ Integer deleteById(Serializable id); /** * <p> * 根据 columnMap 条件,删除记录 * </p> * * @param columnMap * 表字段 map 对象 * @return int */ Integer deleteByMap(@Param("cm") Map<String, Object> columnMap); /** * <p> * 根据 entity 条件,删除记录 * </p> * * @param wrapper * 实体对象封装操作类(可以为 null) * @return int */ Integer delete(@Param("ew") Wrapper<T> wrapper); /** * <p> * 删除(根据ID 批量删除) * </p> * * @param idList * 主键ID列表 * @return int */ Integer deleteBatchIds(List<? extends Serializable> idList); /** * <p> * 根据 ID 修改 * </p> * * @param entity * 实体对象 * @return int */ Integer updateById(T entity); /** * <p> * 根据 ID 修改 * </p> * * @param entity * 实体对象 * @return int */ Integer updateAllColumnById(T entity); /** * <p> * 根据 whereEntity 条件,更新记录 * </p> * * @param entity * 实体对象 * @param wrapper * 实体对象封装操作类(可以为 null) * @return */ Integer update(@Param("et") T entity, @Param("ew") Wrapper<T> wrapper); /** * <p> * 根据 ID 查询 * </p> * * @param id * 主键ID * @return T */ T selectById(Serializable id); /** * <p> * 查询(根据ID 批量查询) * </p> * * @param idList * 主键ID列表 * @return List<T> */ List<T> selectBatchIds(List<? extends Serializable> idList); /** * <p> * 查询(根据 columnMap 条件) * </p> * * @param columnMap * 表字段 map 对象 * @return List<T> */ List<T> selectByMap(@Param("cm") Map<String, Object> columnMap); /** * <p> * 根据 entity 条件,查询一条记录 * </p> * * @param entity * 实体对象 * @return T */ T selectOne(@Param("ew") T entity); /** * <p> * 根据 Wrapper 条件,查询总记录数 * </p> * * @param wrapper * 实体对象 * @return int */ Integer selectCount(@Param("ew") Wrapper<T> wrapper); /** * <p> * 根据 entity 条件,查询全部记录 * </p> * * @param wrapper * 实体对象封装操作类(可以为 null) * @return List<T> */ List<T> selectList(@Param("ew") Wrapper<T> wrapper); /** * <p> * 根据 Wrapper 条件,查询全部记录 * </p> * * @param wrapper * 实体对象封装操作类(可以为 null) * @return List<T> */ List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> wrapper); /** * <p> * 根据 Wrapper 条件,查询全部记录 * </p> * * @param wrapper * 实体对象封装操作类(可以为 null) * @return List<Object> */ List<Object> selectObjs(@Param("ew") Wrapper<T> wrapper); /** * <p> * 根据 entity 条件,查询全部记录(并翻页) * </p> * * @param rowBounds * 分页查询条件(可以为 RowBounds.DEFAULT) * @param wrapper * 实体对象封装操作类(可以为 null) * @return List<T> */ List<T> selectPage(RowBounds rowBounds, @Param("ew") Wrapper<T> wrapper); /** * <p> * 根据 Wrapper 条件,查询全部记录(并翻页) * </p> * * @param rowBounds * 分页查询条件(可以为 RowBounds.DEFAULT) * @param wrapper * 实体对象封装操作类 * @return List<Map<String, Object>> */ List<Map<String, Object>> selectMapsPage(RowBounds rowBounds, @Param("ew") Wrapper<T> wrapper);}
就如作者说的,只要继承该接口,就可以使用里面的CURD,用起来十分方便快捷。
单元测试(使用例子),RedisTest.java
package com.jekin.example.test;import java.util.List;import org.junit.Test;import org.junit.runner.RunWith;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.boot.test.context.SpringBootTest;import org.springframework.boot.test.context.SpringBootTest.WebEnvironment;import org.springframework.test.context.ActiveProfiles;import org.springframework.test.context.junit4.SpringRunner;import com.alibaba.fastjson.JSON;import com.baomidou.mybatisplus.mapper.EntityWrapper;import com.jekin.example.RedisExampleApplication;import com.jekin.example.entity.User;import com.jekin.example.mapper.UserMapper;import com.jekin.example.service.RedisService;@RunWith(SpringRunner.class)@SpringBootTest(classes = RedisExampleApplication.class,webEnvironment=WebEnvironment.RANDOM_PORT)@ActiveProfiles("local")public class RedisTest { private static final Logger LOGGER = LoggerFactory.getLogger(RedisTest.class); @Autowired RedisService redisService; @Autowired UserMapper userMapper; /*@Test public void testRedis(){ redisService.set("haha", "1"); String str = redisService.get("haha"); LOGGER.info(str); }*/ @Test public void testMapper(){ // 新增 User use = new User(); userMapper.insert(use); // 根据主键删除 userMapper.deleteById(898474800007831552L); // 条件删除 userMapper.delete(new EntityWrapper<User>().eq("name", "Jekin")); // 根据ID修改 userMapper.updateById(use); // 条件修改 userMapper.update(use, new EntityWrapper<User>().eq("name", "Jekin")); // 主键查询 User user = userMapper.selectById(898474800007831552L); LOGGER.info("get user object:{}", JSON.toJSONString(user)); // 条件查询 List<User> users = userMapper.selectList(new EntityWrapper<User>().eq("name", "Jekin")); LOGGER.info("get users list:{}", JSON.toJSONString(users)); // 多条件查询 List<User> users1 = userMapper.selectList(new EntityWrapper<User>().notIn("type", 1).eq("name", "Jekin")); LOGGER.info("get users1 list:{}", JSON.toJSONString(users1)); }}
当存在多个环境时,才需要使用@ActiveProfiles来指定加载文件,此处可省略。以上基本覆盖了常用的方法,还有个别笔者没有一一列举,但都是大同小异,相信大家都可以举一反三。
日志文件,logback-local.xml
<?xml version="1.0" encoding="UTF-8"?><configuration debug="true"> <contextName>logback</contextName> <property name="log.path" value="logs/redis.log" /> <appender name="console" class="ch.qos.logback.core.ConsoleAppender"> <filter class="ch.qos.logback.classic.filter.ThresholdFilter"> <level>debug</level> </filter> <encoder> <pattern>%d{HH:mm:ss.SSS} [%thread] %-5level %logger{36} - %msg%n </pattern> </encoder> </appender> <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender"> <file>${log.path}</file> <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy"> <fileNamePattern>${log.path}.%d{yyyy-MM-dd}.zip</fileNamePattern> </rollingPolicy> <encoder> <pattern>%date %level [%thread] %logger{36} [%file : %line] %msg%n </pattern> </encoder> </appender> <root level="debug"> <appender-ref ref="console" /> </root> <logger name="org.springframework" level="error" /> <logger name="org.apache.http" level="error" /> <logger name="org.mybatis" level="debug" /> <logger name="java.sql" level="error" /></configuration>
总结
本章主要讲的是MP的集成,框架搭建,至此,如果只想学习SpringBoot项目搭建的话,可以到此止步,不必看下节内容,去掉Redis依赖包就完事了。下节讲解MP的分页以及Redis的配置使用。
如有问题或笔误,可以评论留言,亦可加Q群讨论学习:583138104。笔者每篇文章源码都会上传上去。
源码下载
- SpringBoot集成Redis,以及MybatisPlus分页、Mapper的使用(一)
- SpringBoot集成Redis,以及MybatisPlus分页、Mapper的使用(二)
- SpringBoot集成MyBatisPlus
- SpringBoot集成通用Mapper 分页插件 Generator
- springboot 集成redis (单点)
- MyBatis基于Spring-boot集成通用Mapper以及pagehelper分页插件(含源码下载)
- Spring集成MyBatis 通用Mapper以及 pagehelper分页插件
- springboot整合mybatisplus配置
- SpringBoot集成MyBatis时要配置的Mapper中的xml
- SpringBoot集成MyBatis的分页插件PageHelper
- springBoot集成shiro+redis遇到的坑
- springboot集成shiro(一)
- 一步一步学springboot (九)集成redis
- SpringBoot -- 集成Redis/CacheManager
- SpringBoot -- 集成Redis/CacheManager
- springboot集成redis
- springboot集成Redis
- springboot 集成redis 哨兵
- 按钮布局
- MySQL基础(一)
- (转)惊魂24小时!真实还原光大“乌龙指”事件,比小说还离奇
- APP中集成移动端车牌识别系统都能达到什么效果
- 数据结构实验一
- SpringBoot集成Redis,以及MybatisPlus分页、Mapper的使用(一)
- Unity简单利用屏幕坐标转换实现鼠标控制物体移动
- poj3669 之bfs解法
- spring笔记
- nvm和nodejs安装使用 (转)
- 多表查询内连接与外连接的区别;多表之间内连接;(常用)(只连接两个表都有效数据); 多表之间的外连接:(查询所有的内容); 多表内连接:(显示和隐式结果是一样的,得到的是交集的部分)
- android+postman
- maven管理jar包和传统方法管理jar的比较
- JUnit(二)高级之运行器_MD