MyBatis-Plus BaseMapper 是怎样实现的?

背景

提到 ORM 框架,我们现在使用最多的是 MyBatis,MyBatis 解决了 Hibernate 不够灵活的问题,但是由于 MyBatis 需要手动指定数据库表和实体类之间的映射关系,对于单表而言,简单的增删改查我们也不得不写大量的 xml 配置。

MyBatis 官方为此又推出了一个 MyBatis Generator 的项目,可以为我们生成 Mapper 接口和配置文件,这大大缓解了开发者的手工编写 Mapper 配置文件的工作。

如果只是针对每个表每次都生成 Mapper 文件还好,但是 MyBatis Generator 有一个大大的弊端,它生成的动态 SQL 不够灵活,条件基本上是判断参数不为空然后指定列的值。

后来开源社区又推出了一个 MyBatis-Plus 的项目,它集多种特性于一身,包括内置通用 Mapper、分页插件、代码生成等,这些功能使开发者对它爱不释手。

MyBatis-Plus BaseMapper 快速上手

MyBatis-Plus 最核心的功能要数通用 Mapper 了,只要我们的 Mapper 接口实现了 BaseMapper,就可以完成单表大部分的 CRUD 操作了,并且它还附带了一个功能强大的条件构造器。

假定我们的项目已经引入 SpringBoot,现在正在做一个登陆功能,用户表如下。

create table user
(
    id          bigint unsigned auto_increment comment '主键'
        primary key,
    username    varchar(100) null comment '用户名',
    password    varchar(100) null comment '密码',
    create_time datetime     null comment '创建时间'
)

我们期望在项目中引入 MyBatis-Plus,首先我们需要在项目中引入 MyBatis-Plus 依赖和数据库驱动。

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3.3</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.20</version>
        </dependency>

然后需要在 application.proeprties 中配置数据源。

spring.datasource.url=jdbc:mysql://localhost:3306/test
spring.datasource.username=root
spring.datasource.password=12345678
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

使用 MyBatis-Plus 官网提供的 Idea 插件 MyBatis-X 生成的代码结构如下。

.
├── pom.xml
└── src
    └── main
        ├── java
        │   └── com
        │       └── zzuhkp
        │           └── blog
        │               ├── App.java
        │               ├── domain
        │               │   └── User.java
        │               └─── mapper
        │                   └── UserMapper.java
        └── resources
            ├── application.properties
            └── mapper
                └── UserMapper.xml

看下生成的 UserMapper 接口。

public interface UserMapper extends BaseMapper<User> {

}

空空如也,UserMapper 接口仅仅继承了接口 BaseMapper,这样就可以进行单表增删改查。看下我们的测试代码。

@MapperScan("com.zzuhkp.blog.mapper")
@SpringBootApplication
public class App implements CommandLineRunner {

    public static void main(String[] args) {
        SpringApplication.run(App.class);
    }

    @Autowired
    private UserMapper userMapper;

    @Override
    public void run(String... args) throws Exception {
        String username = "hkp";
        String password = "123";

        LambdaQueryWrapper<User> wrapper = Wrappers.lambdaQuery();
        wrapper.eq(User::getUsername, username).eq(User::getPassword, password);

        User user = this.userMapper.selectOne(wrapper);
    }
}

测试代码使用 LambdaQueryWrapper 构造复杂的查询条件,然后使用 UserMapper 继承的 BaseMapper 中的方法查询用户。

不得不说,使用 MyBatis-Plus 进行单表操作实在太方便了,只是再美味的菜肴吃多了也会索然无味,时间长了,我们不免会产生疑问,BaseMapper 是怎样帮我们注入 SQL 的?带着这个疑问我们继续下面的分析。

MyBatis-Plus BaseMapper 实现分析

我们先来思考下 BaseMapper 的实现思路。正常情况下,我们定义了 Mapper 接口,然后会在对应的 xml 文件中提供动态 SQL 及映射关系,或者直接在 Mapper 接口方法上添加注解,MyBatis 将 xml 中的配置或者注解作为元数据进行解析,然后将解析后的 SQL 语句存至 Configuration。参考 MyBatis 对 xml 或注解的实现,只要我们能够将元数据解析成动态 SQL 存至 Configuration 即可。这就是基本的实现思路了,那么 MyBatis-Plus 具体是怎么做的呢?

MyBatis-Plus 的整体思路是使用自己的组件替换 MyBatis 中的组件,以实现自定义的逻辑。包括但不限于以下的组件。

MyBatis 组件替换成的 MyBatis-Plus 组件主要作用
MybatisLanguageDriverAutoConfigurationMybatisPlusLanguageDriverAutoConfiguration仅复制 MyBatis 组件代码,无特殊逻辑
MybatisAutoConfigurationMybatisPlusAutoConfiguration创建自定义的 MybatisSqlSessionFactoryBean,设置必须的 bean 到全局配置
SqlSessionFactoryBeanMybatisSqlSessionFactoryBean使用自定义的 MybatisConfiguration、MybatisXMLConfigBuilder
XMLConfigBuilderMybatisXmlConfigBuilder使用自定义的 MybatisConfiguration
ConfigurationMybatisConfiguration使用自定义的 MybatisMapperRegistry、MybatisXMLLanguageDriver
MapperRegistryMybatisMapperRegistry使用自定义的 MybatisMapperProxyFactory、MybatisMapperAnnotationBuilder
MapperProxyFactoryMybatisMapperProxyFactory使用自定义的 MybatisMapperMethod
MapperMethodMybatisMapperMethod分页支持
ParameterHandlerMybatisParameterHandler填充主键、自定义属性值
XMLLanguageDriverMybatisXMLLanguageDriver使用自定义的 MybatisParameterHandler
MapperAnnotationBuilderMybatisMapperAnnotationBuilder动态 SQL 注入

按照官网的说法,MyBatis-Plus 在 MyBatis 的基础上只做增强不做改变。我们使用 mybatis-plus-boot-starter 替换了 mybatis 提供的 mybatis-boot-starter,引入这个 starter 之后,MyBatis-Plus 就会进行一些自动化配置。mybatis-plus-boot-starter 类路径下META-INF/spring.factories文件内容如下。

# Auto Configure
org.springframework.boot.env.EnvironmentPostProcessor=\
  com.baomidou.mybatisplus.autoconfigure.SafetyEncryptProcessor
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
  com.baomidou.mybatisplus.autoconfigure.IdentifierGeneratorAutoConfiguration,\
  com.baomidou.mybatisplus.autoconfigure.MybatisPlusLanguageDriverAutoConfiguration,\
  com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration

让我们将重点放在 MybatisPlusAutoConfiguration 类,这个类会替代 MyBatis 官方的 MybatisAutoConfiguration 进行自动化配置,核心源码如下。

@Configuration
public class MybatisPlusAutoConfiguration implements InitializingBean {

    private final MybatisPlusProperties properties;

    @Bean
    @ConditionalOnMissingBean
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
        MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean();
        ...省略部分代码
        GlobalConfig globalConfig = this.properties.getGlobalConfig();
        ...省略部分代码
        this.getBeanThen(ISqlInjector.class, globalConfig::setSqlInjector);
        ...省略部分代码
        factory.setGlobalConfig(globalConfig);
        return factory.getObject();
		}
}

MybatisPlusAutoConfiguration 使用 MybatisSqlSessionFactoryBean 替代了 MyBatis 官方的的 SqlSessionFactoryBean,这个类主要使用 MybatisConfiguration 替代了 MyBatis 官方的 Configuration。

让我们将重点放在和 BaseMapper 实现有关的部分。不管是使用 MyBatis 还是 MyBatis-Plus,我们都会使用 mapperLocations 配置 mapper xml 文件的位置。MybatisSqlSessionFactoryBean 创建 SqlSessionFactory 时会解析 mapper xml 文件。核心代码如下。

public class MybatisSqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
 
    private Resource[] mapperLocations;

    protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
        ...省略部分代码
                for (Resource mapperLocation : this.mapperLocations) {
                    if (mapperLocation == null) {
                        continue;
                    }
                    try {
                        XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
                            targetConfiguration, mapperLocation.toString(), targetConfiguration.getSqlFragments());
                        xmlMapperBuilder.parse();
                    } catch (Exception e) {
                        throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
                    } finally {
                        ErrorContext.instance().reset();
                    }
                }
       ...省略部分代码
    }
}

这里解析 mapper xml 文件使用的是 MyBatis 官网提供的 XMLMapperBuilder,但是传入的配置 targetConfiguration 类型是 MybatisConfiguration。XMLMapperBuilder 解析 mapper xml 文件时还会解析 namespace 中的接口,然后添加到配置中。核心源码如下。

public class XMLMapperBuilder extends BaseBuilder {
  private void bindMapperForNamespace() {
    String namespace = builderAssistant.getCurrentNamespace();
    if (namespace != null) {
      Class<?> boundType = null;
      try {
        boundType = Resources.classForName(namespace);
      } catch (ClassNotFoundException e) {
        // ignore, bound type is not required
      }
      if (boundType != null && !configuration.hasMapper(boundType)) {
        configuration.addLoadedResource("namespace:" + namespace);
        configuration.addMapper(boundType);
      }
    }
  }
}

MyBatis-Plus 提供的 MybatisConfiguration 重写了 addMapper 方法。

public class MybatisConfiguration extends Configuration {
    protected final MybatisMapperRegistry mybatisMapperRegistry = new MybatisMapperRegistry(this);

    @Override
    public <T> void addMapper(Class<T> type) {
        mybatisMapperRegistry.addMapper(type);
    }
}

MybatisConfiguration 重点在于使用了自定义的 MybatisMapperRegistry 添加 Mapper。

public class MybatisMapperRegistry extends MapperRegistry {
    @Override
    public <T> void addMapper(Class<T> type) {
        if (type.isInterface()) {
            boolean loadCompleted = false;
            ...省略部分代码
            try {
                knownMappers.put(type, new MybatisMapperProxyFactory<>(type));
                MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type);
                parser.parse();
                loadCompleted = true;
            } finally {
                if (!loadCompleted) {
                    knownMappers.remove(type);
                }
            }
        }
    }
}

自定义的 MybatisMapperRegistry 添加 Mapper 时又用到了 自定义的 MybatisMapperAnnotationBuilder,在这里 Mybatis-Plus 除了解析 Mapper 接口方法上的注解就会添加自己的逻辑了。

public class MybatisMapperAnnotationBuilder extends MapperAnnotationBuilder {

    @Override
    public void parse() {
        String resource = type.toString();
        if (!configuration.isResourceLoaded(resource)) {
            ...省略解析 Mapper 方法注解代码
            // TODO 注入 CURD 动态 SQL , 放在在最后, because 可能会有人会用注解重写sql
            try {
                if (GlobalConfigUtils.isSupperMapperChildren(configuration, type)) {
                    parserInjector();
                }
            } catch (IncompleteElementException e) {
                configuration.addIncompleteMethod(new InjectorResolver(this));
            }
        }
    }
    
    void parserInjector() {
        GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type);
    }    
}

到了这里解析 Mapper 接口的时候,MyBatis-Plus 会判断接口是否继承 Mapper 接口,如果是的话就会注入动态 CURD 动态 SQL。这里我们可以看一下注入 SQL 的接口。

public interface ISqlInjector {
    void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass);
}

这个接口的默认实现是 DefaultSqlInjector。

public class DefaultSqlInjector extends AbstractSqlInjector {

    @Override
    public List<AbstractMethod> getMethodList(Class<?> mapperClass) {
        return Stream.of(
            new Insert(),
            new Delete(),
            new DeleteByMap(),
            new DeleteById(),
            new DeleteBatchByIds(),
            new Update(),
            new UpdateById(),
            new SelectById(),
            new SelectBatchByIds(),
            new SelectByMap(),
            new SelectOne(),
            new SelectCount(),
            new SelectMaps(),
            new SelectMapsPage(),
            new SelectObjs(),
            new SelectList(),
            new SelectPage()
        ).collect(toList());
    }
}

BaseMapper#selectList 方法为例,MyBatis-Plus 使用的 AbstractMethod 为 SelectList。

public class SelectList extends AbstractMethod {
    @Override
    public MappedStatement injectMappedStatement(Class<?> mapperClass, Class<?> modelClass, TableInfo tableInfo) {
        SqlMethod sqlMethod = SqlMethod.SELECT_LIST;
        String sql = String.format(sqlMethod.getSql(), sqlFirst(), sqlSelectColumns(tableInfo, true), tableInfo.getTableName(),
            sqlWhereEntityWrapper(true, tableInfo), sqlOrderBy(tableInfo), sqlComment());
        SqlSource sqlSource = languageDriver.createSqlSource(configuration, sql, modelClass);
        return this.addSelectMappedStatementForTable(mapperClass, getMethod(sqlMethod), sqlSource, tableInfo);
    }
}

MyBatis-Plus 有能力从 BaseMapper 中根据泛型解析出实体类,然后从实体类中根据注解解析出数据库表字段的信息,最终,生成的动态 SQL 大概如下。

<script>
    <choose>
        <when test="ew != null and ew.sqlFirst != null">
              ${ew.sqlFirst}
          </when>
        <otherwise></otherwise>
    </choose>
      SELECT
    <choose>
        <when test="ew != null and ew.sqlSelect != null">
              ${ew.sqlSelect}
          </when>
        <otherwise>${keyColumn as keyProperty,column1 as property1,column2 as property2}</otherwise>
    </choose>
      FROM {tableName}
    <if test="ew != null">
        <where>
            <if test="ew.entity != null">
                <if test="ew.entity.keyProperty != null">keyColumn=#{{ew.entity.keyProperty}}</if>
                <if test="ew.entity.propertyName != null and ew.entity.propertyName != ''">
                      AND columnName=#{ew.entity.propertyName,jdbcType={jdbcTypeName},javaType={javaTypeName},typeHandler={typeHandlerName},numericScale={numericScaleName}}
                  </if>
            </if>
            <if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.nonEmptyOfWhere">
                <if test="ew.nonEmptyOfEntity and ew.nonEmptyOfNormal"> AND</if> ${ew.sqlSegment}
            </if>
        </where>
        <if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.emptyOfWhere">
              ${ew.sqlSegment}
          </if>
    </if>
    <if test="ew == null or ew.expression == null or ew.expression.orderBy == null or ew.expression.orderBy.size() == 0">
          ORDER BY {column1} asc|desc,{column2} asc|desc
      </if>
    <choose>
        <when test="ew != null and ew.sqlComment != null">
              ${ew.sqlComment}
          </when>
        <otherwise></otherwise>
    </choose>
</script>

注入自定义动态 SQL

MyBatis-Plus 注入动态 SQL 的接口是 ISqlInjector,如果你仔细观察了前面代码中的 MybatisPlusAutoConfiguration 类,你就会发现,MyBatis-Plus 会使用 Spring 类型为 ISqlInjector 的 bean 替代默认 DefaultSqlInjector,因此,如果你想注入自己的动态 SQL,不妨自己提供一个 ISqlInjector 接口的实现作为 bean。MyBatis-Plus 原生并未提供使用 join 多表联查的能力,感兴趣话可自行尝试使用 ISqlInjector 实现。

  • 45
    点赞
  • 64
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
### 回答1: Mybatis-Plus BaseMapper 是一个基础的 Mapper 接口,提供了一些常用的 CRUD 操作方法,可以让我们快速地进行数据库操作。使用 BaseMapper 需要继承它,然后就可以直接使用其中的方法了。同时,Mybatis-Plus 还提供了很多其他的功能,比如自动生成代码、分页查询、条件构造器等等,可以大大提高开发效率。 ### 回答2: Mybatis-plusMybatis的增强工具包,其中包含了基于MybatisBasemapperBasemapper 是一个范型类,提供了一组基础的CRUD操作,可以让我们在开发过程中避免大量的重复代码,尤其是针对单表的操作。下面详细介绍Basemapper的使用方法: 1. 引入依赖:在pom.xml文件中添加如下依赖 ```xml <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.1.0</version> </dependency> ``` 2. 继承BaseMapper:创建DAO层接口,继承BaseMapper<T>,T表示实体类 ```java public interface UserMapper extends BaseMapper<User> { } ``` 3. 接口调用:在Service中注入mapper,就可以使用了。以下是常用方法: - 插入数据 ```java int result = userMapper.insert(user); ``` - 根据ID删除 ```java int result = userMapper.deleteById(1); ``` - 更新数据 ```java User user = new User(); user.setId(1); user.setNickname("test"); int result = userMapper.updateById(user); ``` - 根据ID查询 ```java User user = userMapper.selectById(1); ``` - 分页查询 ```java Page<User> page = new Page<>(1, 5); QueryWrapper<User> wrapper = new QueryWrapper<>(); wrapper.lambda().gt(User::getId, 0); IPage<User> iPage = userMapper.selectPage(page, wrapper); ``` 以上就是Mybatis-plus Basemapper 的基本使用方法,大大简化了对单表的CRUD操作。而且Mybatis-plus还提供了更多的查询、更新、删除、分页等高级功能,可以根据具体需求自行选择使用。 ### 回答3: Mybatis-Plus是基于Mybatis的增强框架,它提供了很多良好的功能拓展,其中mybatis-plus basemapper是其中的一个模块。它实现Mybatis-Plus框架的通用Mapper接口,优化了数据访问,提高了代码效率。 Mybatis-Plusbasemapper模块提供了许多注解和方法,最基本的CRUD操作都可以通过这个模块完成。在使用mybatis-plus basemapper时需要在Mapper接口中使用特殊的泛型,并且需要使用具有该泛型的Mapper接口进行扩展。例如: ``` public interface UserMapper extends BaseMapper<User> { } ``` 通过继承BaseMapper<User>,UserMapper可以继承到BaseMapper中定义的基本的CRUD方法,也可以使用注解和查询构建器等方式执行高级查询。以下是基本的使用方法: 1.添加依赖 在项目的pom.xml文件中添加mybatis-plus-boot-starter的依赖: ``` <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3.1</version> </dependency> ``` 2.配置文件 在应用程序的配置文件中添加以下配置信息: ``` spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/demo?characterEncoding=UTF-8&serverTimezone=GMT%2B8 username: root password: root mybatis-plus: configuration: map-underscore-to-camel-case: true ``` 3.实体类定义 定义需要进行CRUD操作的实体类,例如User: ``` @Data public class User { private Long id; private String name; private Integer age; private String email; } ``` 4.Mapper接口定义 定义UserMapper,并继承BaseMapper<User>: ``` public interface UserMapper extends BaseMapper<User> { } ``` 5.Service层定义 定义UserService,使用@Autowired注解装配UserMapper接口: ``` @Service public class UserService { @Autowired UserMapper userMapper; public void addUser(User user){ userMapper.insert(user); } public void updateUser(User user){ userMapper.updateById(user); } public User getUserById(Long id){ return userMapper.selectById(id); } public void deleteUserById(Long id){ userMapper.deleteById(id); } public List<User> getAllUser(){ return userMapper.selectList(null); } } ``` 上述方法都是一些基本的CRUD操作,这里只是展示了一小部分。 总之,Mybatis-PlusbasemapperMybatis-Plus框架中的一个重要部分,它提供了很多方便和高效的数据访问方法,极大地简化了数据库操作代码。开发人员可以通过托管一些基本的数据操作来实现高效的数据访问。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

大鹏cool

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值