GVKun编程网logo

MyBatis通用Mapper实现原理及相关内容(mybatis mapper工作原理)

8

关于MyBatis通用Mapper实现原理及相关内容和mybatismapper工作原理的问题就给大家分享到这里,感谢你花时间阅读本站内容,更多关于mybatics通用mapper保存返回主键、myb

关于MyBatis通用Mapper实现原理及相关内容mybatis mapper工作原理的问题就给大家分享到这里,感谢你花时间阅读本站内容,更多关于mybatics通用mapper保存返回主键、mybatis - 通用 mapper、mybatis - 通用mapper、Mybatis 之通用 Mapper(基于 mybatis 的 Provider 机制无需第三方插件包)等相关知识的信息别忘了在本站进行查找喔。

本文目录一览:

MyBatis通用Mapper实现原理及相关内容(mybatis mapper工作原理)

MyBatis通用Mapper实现原理及相关内容(mybatis mapper工作原理)

今天小编就为大家分享一篇关于MyBatis通用Mapper实现原理及相关内容,小编觉得内容挺不错的,现在分享给大家,具有很好的参考价值,需要的朋友一起跟随小编来看看吧

MyBatis通用Mapper实现原理

本文会先介绍通用 Mapper 的简单原理,然后使用最简单的代码来实现这个过程。

基本原理

通用 Mapper 提供了一些通用的方法,这些通用方法是以接口的形式提供的,例如。

public interface SelectMapper { /** * 根据实体中的属性值进行查询,查询条件使用等号 */ @SelectProvider(type = BaseSelectProvider.class, method = "dynamicsql") List select(T record); }

接口和方法都使用了泛型,使用该通用方法的接口需要指定泛型的类型。通过 Java 反射可以很容易得到接口泛型的类型信息,代码如下。

Type[] types = mapperClass.getGenericInterfaces(); Class> entityClass = null; for (Type type : types) { if (type instanceof ParameterizedType) { ParameterizedType t = (ParameterizedType) type; //判断父接口是否为 SelectMapper.class if (t.getRawType() == SelectMapper.class) { //得到泛型类型 entityClass = (Class>) t.getActualTypeArguments()[0]; break; } } }

实体类中添加的 JPA 注解只是一种映射实体和数据库表关系的手段,通过一些默认规则或者自定义注解也很容易设置这种关系,获取实体和表的对应关系后,就可以根据通用接口方法定义的功能来生成和 XML 中一样的 sql 代码。动态生成 XML 样式代码的方式有很多,最简单的方式就是纯 Java 代码拼字符串,通用 Mapper 为了尽可能的少的依赖选择了这种方式。如果使用模板(如FreeMarker,VeLocity 和 beetl 等模板引擎)实现,自由度会更高,也能方便开发人员调整。

在 MyBatis 中,每一个方法(注解或 XML 方式)经过处理后,最终会构造成 MappedStatement 实例,这个对象包含了方法id(namespace+id)、结果映射、缓存配置、sqlSource 等信息,和 sql 关系最紧密的是其中的 sqlSource,MyBatis 最终执行的 sql 时就是通过这个接口的 getBoundsql 方法获取的。

在 MyBatis 中,使用@SelectProvider 这种方式定义的方法,最终会构造成 ProvidersqlSource,ProvidersqlSource 是一种处于中间的 sqlSource,它本身不能作为最终执行时使用的 sqlSource,但是他会根据指定方法返回的 sql 去构造一个可用于最后执行的 StaticsqlSource,StaticsqlSource的特点就是静态 sql,支持在 sql 中使用#{param} 方式的参数,但是不支持 , 等标签。

为了能根据实体类动态生成支持动态 sql 的方法,通用 Mapper 从这里入手,利用ProvidersqlSource 可以生成正常的 MappedStatement,可以直接利用 MyBatis 各种配置和命名空间的特点(这是通用 Mapper 选择这种方式的主要原因)。在生成 MappedStatement 后,“过河拆桥” 般的利用完就把 ProvidersqlSource 替换掉了,正常情况下,ProvidersqlSource 根本就没有执行的机会。在通用 Mapper 定义的实现方法中,提供了 MappedStatement 作为参数,有了这个参数,我们就可以根据 ms 的 id(规范情况下是 接口名.方法名)得到接口,通过接口的泛型可以获取实体类(entityClass),根据实体和表的关系我们可以拼出 XML 方式的动态 sql,一个简单的方法如下。

/** * 查询全部结果 * @param ms * @return */ public String selectAll(MappedStatement ms) { final Class> entityClass = getEntityClass(ms); //修改返回值类型为实体类型 setResultType(ms, entityClass); StringBuilder sql = new StringBuilder(); sql.append(sqlHelper.selectAllColumns(entityClass)); sql.append(sqlHelper.fromTable(entityClass, tableName(entityClass))); sql.append(sqlHelper.orderByDefault(entityClass)); return sql.toString(); }

拼出的 XML 形式的动态 sql,使用 MyBatis 的 XMLLanguageDriver 中的 createsqlSource 方法可以生成 sqlSource。然后使用反射用新的 sqlSource 替换ProvidersqlSource 即可,如下代码。

/** * 重新设置sqlSource * @param ms * @param sqlSource */ protected void setsqlSource(MappedStatement ms, sqlSource sqlSource) { MetaObject msObject = SystemMetaObject.forObject(ms); msObject.setValue("sqlSource", sqlSource); }

MetaObject 是MyBatis 中很有用的工具类,MyBatis 的结果映射就是靠这种方式实现的。反射信息使用的 DefaultReflectorFactory,这个类会缓存反射信息,因此 MyBatis 的结果映射的效率很高。

到这里核心的内容都已经说完了,虽然知道怎么去替换 sqlSource了,但是!什么时候去替换呢?

这一直都是一个难题,如果不大量重写 MyBatis 的代码很难万无一失的完成这个任务。通用 Mapper 并没有去大量重写,主要是考虑到以后的升级,也因此在某些特殊情况下,通用 Mapper 的方法会在没有被替换的情况下被调用,这个问题在将来的 MyBatis 3.5.x 版本中会以更友好的方式解决(目前的 ProvidersqlSource 已经比以前能实现更多的东西,后面会讲)。

针对不同的运行环境,需要用不同的方式去替换。当使用纯 MyBatis (没有Spring)方式运行时,替换很简单,因为会在系统中初始化 sqlSessionFactory,可以初始化的时候进行替换,这个时候也不会出现前面提到的问题。替换的方式也很简单,通过 sqlSessionFactory 可以得到 sqlSession,然后就能得到 Configuration,通过 configuration.getMappedStatements() 就能得到所有的 MappedStatement,循环判断其中的方法是否为通用接口提供的方法,如果是就按照前面的方式替换就可以了。

在使用 Spring 的情况下,以继承的方式重写了 MapperScannerConfigurer 和 Mapperfactorybean,在 Spring 调用 checkDaoConfig 的时候对 sqlSource 进行替换。在使用 Spring Boot 时,提供的 mapper-starter 中,直接注入 List sqlSessionFactoryList 进行替换。

下面我们按照这个思路,以最简练的代码,实现一个通用方法。

实现一个简单的通用Mapper

1. 定义通用接口方法

public interface BaseMapper { @SelectProvider(type = SelectMethodProvider.class, method = "select") List select(T entity); }

这里定义了一个简单的 select 方法,这个方法判断参数中的属性是否为空,不为空的字段会作为查询条件进行查询,下面是对应的 Provider。

public class SelectMethodProvider { public String select(Object params) { return "什么都不是!"; } }

这里的 Provider 不会最终执行,只是为了在初始化时可以生成对应的 MappedStatement。

2. 替换 sqlSource

下面代码为了简单,都指定的 BaseMapper 接口,并且没有特别的校验。

public class SimpleMapperHelper { public static final XMLLanguageDriver XML_LANGUAGE_DRIVER = new XMLLanguageDriver(); /** * 获取泛型类型 */ public static Class getEntityClass(Class> mapperClass){ Type[] types = mapperClass.getGenericInterfaces(); Class> entityClass = null; for (Type type : types) { if (type instanceof ParameterizedType) { ParameterizedType t = (ParameterizedType) type; //判断父接口是否为 BaseMapper.class if (t.getRawType() == BaseMapper.class) { //得到泛型类型 entityClass = (Class>) t.getActualTypeArguments()[0]; break; } } } return entityClass; } /** * 替换 sqlSource */ public static void changeMs(MappedStatement ms) throws Exception { String msId = ms.getId(); //标准msId为 包名.接口名.方法名 int lastIndex = msId.lastIndexOf("."); String methodName = msId.substring(lastIndex + 1); String interfaceName = msId.substring(0, lastIndex); Class> mapperClass = Class.forName(interfaceName); //判断是否继承了通用接口 if(BaseMapper.class.isAssignableFrom(mapperClass)){ //判断当前方法是否为通用 select 方法 if (methodName.equals("select")) { Class entityClass = getEntityClass(mapperClass); //必须使用"); //解析 sqlSource sqlSource sqlSource = XML_LANGUAGE_DRIVER.createsqlSource( ms.getConfiguration(), sqlBuilder.toString(), entityClass); //替换 MetaObject msObject = SystemMetaObject.forObject(ms); msObject.setValue("sqlSource", sqlSource); } } } }

changeMs 方法简单的从 msId 开始,获取接口和实体信息,通过反射回去字段信息,使用 标签动态判断属性值,这里的写法和 XML 中一样,使用 XMLLanguageDriver 处理时需要在外面包上

mybatics通用mapper保存返回主键

mybatics通用mapper保存返回主键

@Liuzh_533 你好,想跟你请教个问题:

 

mybatics  oracle  保存 返回主键 可以通过通用 save 方法么

mybatis - 通用 mapper

mybatis - 通用 mapper

作为持久层的 ORM 框架,目前在国内主流之一就是 MyBatis, 学会用它,用好它肯定是必备的功课

我会主要从下面几个方面入整理本篇博客

  1. 快速搭建快发环境
  2. 常见的注解
  3. 怎么玩?

<!--more-->

一。快速搭建开发环境

小插曲 , 添加测试模块的时候,引入 junit 模块和 spring-boot-text-starter 模块有先顺序,不然 ide 会报错...

坐标

  <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.4.RELEASE</version>
        <relativePath/><!-- lookup parent from repository -->
    </parent>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        <!--通用mapper启动器-->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
            <version>2.0.3</version>
        </dependency>
        <!--分页助手-->
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper-spring-boot-starter</artifactId>
            <version>1.2.5</version>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
        </dependency>
        <!--测试-->
        <!--先添加 junit  再添加下面的text  stater-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>text</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
  • 另外插一嘴 ---mysql 连接的版本适配

我现在用的云主机 docker 上的官方版 mysql, 版本比较新,因此我的调整版本到 8 以上,不然会报错说什么

com.mysql.jdbc.exceptions.jdbc4.MySQLNonTransientConnectionException: Could

配置文件:

  • 主要是配置数据库的连接,如果我们使用的是通用 mapper,Mybatis 可以做到零配置
server:
  port: 8089
spring:
  application:
    name: text-mybatis
  datasource:
    url: jdbc:mysql://211.159.XXX.XXX:8888/changwu?serverTimezone=UTC&useUnijava=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 2424zcw..
    driver-class-name: com.mysql.jdbc.Driver
#输出sql
mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

启动类

  • 在这里告诉通用 mapper 我们的 mapper 包路径
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication
@MapperScan("com.changwu.mapper")
public class MybatisApp {
    public static void main(String[] args) {
        SpringApplication.run(MybatisApp.class);
    }
}

编写 Mapper

  • 让我们自己的 Mapper 继承通用 mapper, 泛型是实体类名
  • 给它实体类名,就相当与告诉它了我们的表里的字段,不让他怎么会知道如何动态生成 sql ?
import tk.mybatis.mapper.common.Mapper;
public interface MyMapper extends Mapper<MyBrand> {}

ok, 到现在环境就搭建好了


二。常见的注解

  • 这里的注解主要是作用在实体类上的注解,当我们撸起袖子写代码的时候,被给它难住喽,尴尬

情景 1: 对于实体类

标记他对应那张表

import javax.persistence.Table;
@Table(name = "tb_brand")

情景 2: 对于主键

大多数情况主键一般都叫 id,bigint 类型,没什么超级特殊的情况我们都希望他可以自己增长,下面两个注解都可以做到这件事,但是前提表 id 属性必须设置成 autoincreament , 不然报错说,id 不能不写

  • 注解 1:
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

/*
*  使用的是jpa的策略生成器
*JPA提供的四种标准用法为TABLE,SEQUENCE,IDENTITY,AUTO. 
* TABLE:使用一个特定的数据库表格来保存主键。 
* SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列
* IDENTITY:主键由数据库自动生成(主要是自动增长型) 
* AUTO:主键由程序控制。
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) 
  • 注解 2:

直接使用 Mybatis 的原生注解

@Id
@KeySql(useGeneratedKeys = true)
private Long id;

情景 3:

我们都知道 springMVC 会把前端发送过来的 json 转化为我们的 javaBean, 因此我们的 javaBean 里面的属性名和前端发送的那个对象的属性名要意义对应的,这是规范!

设想,加入前端一顿收集数据,把商品的品牌信息和库存信息都发送过来了,只有关于品牌的 javabean, 数据接受不全怎么办?没关系,下面的注解可以搞定

  • @Transient (意味短暂的) 告诉 mapper 他不是 PO (持久层对象,Persistence Object) 的属性,而且,springMvc 还会把信息关于库存的信息,封装进去
@Transient

情景 4:

我们的 pojo 属性名,和数据库中的关键字重名怎么办?

@Column () 可以解决 数据库中的字段为数据库的关键字的问题

@Column(name="`numeric`")  //
private Boolean numeric;   //是否是数字类型

情景 5 :

数据表中 tingint (1) 对应的 javaBean 的属性咋写?

tingint(1) // 它是boolean的同义词

情景 5:

关于 VO , 如何选择性的返回给前端 javaBean 的属性?就比如说我们查询有没有这个用户,总不至于把密码,私人信息一块返回给前端吧?

import com.fasterxml.jackson.annotation.JsonIgnore;
@JsonIgnore

它是 jackson 的注解

  <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-annotations</artifactId>
        <version>RELEASE</version>
        <scope>compile</scope>
    </dependency>

三。怎么玩?

 下面就是介绍常用的 mapper 的 API

一. CRUD

  • 两种新增
//会有选择性的新增, 智能判断 brand里面有没有空的字段,有值,用这个值,没有值而使用数据库的默认值
insertSelective(brand) 

// 假如前端提交过来的 brand里面的数据是全的,用词方法
brandMapper.insert(brand);
  • 简单查询
/**
 * 根据实体中的属性值进行查询,查询条件使用等号
 * @param record
 * @return
 */
@SelectProvider(type = BaseSelectProvider.class, method = "dynamicSQL")
List<T> select(T record);


/**
 * 根据实体中的属性值进行查询,查询条件使用等号
 * @param record
 * @return
 */
@SelectProvider(type = BaseSelectProvider.class, method = "dynamicSQL")
List<T> select(T record);


/**
 * 查询全部结果
 * @return
 */
@SelectProvider(type = BaseSelectProvider.class, method = "dynamicSQL")
List<T> selectAll();


/**
 * 根据实体中的属性进行查询,只能有一个返回值,有多个结果是抛出异常,查询条件使用等号
 * @param record
 * @return
 */
@SelectProvider(type = BaseSelectProvider.class, method = "dynamicSQL")
T selectOne(T record);


/**
 * 根据实体中的属性查询总数,查询条件使用等号
 * @param record
 * @return
 */
@SelectProvider(type = BaseSelectProvider.class, method = "dynamicSQL")
int selectCount(T record);
  • 简单删除
/**
 * 根据主键字段进行删除,方法参数必须包含完整的主键属性
 * @param key
 * @return
 */
@DeleteProvider(type = BaseDeleteProvider.class, method = "dynamicSQL")
int deleteByPrimaryKey(Object key);


/**
 * 根据实体属性作为条件进行删除,查询条件使用等号
 * @param record
 * @return
 */
@DeleteProvider(type = BaseDeleteProvider.class, method = "dynamicSQL")
int delete(T record);

  • 如何玩修改?

修改主要用到下面这个 api

/**
 * 根据主键更新属性不为null的值
 * @param record
 * @return
 */
@UpdateProvider(type = BaseUpdateProvider.class, method = "dynamicSQL")
int updateByPrimaryKeySelective(T record);

看到这个方法有没有觉得很爽?它根据主键,修改不为 null 的属性,意思就是说,我们把不需要修改的地方设置为 null 就好了,这样看,前面需要我们一顿整,整啥呢?结合实际的业务逻辑,把前端给交过来的数据最新的数据放到我们的 bean 中直接修改,把不需要修改的属性设置为 null, 有属性可能需要直接删除,有的属性可能要去别的表中查询 (别忘了加上事务 @Transactional)

/**
 * 根据主键更新实体全部字段,null值会被更新
 * @param record
 * @return
 */
@UpdateProvider(type = BaseUpdateProvider.class, method = "dynamicSQL")
int updateByPrimaryKey(T record);

这个方法和上面的神似

二。分页,过滤 (模糊查询), 搜索

API

mapper.selectByExample(example)

参照下面原生的 sql. 拼接查询条件

 select * from 表名
        where name like ''% X %'' or  letter== ''x''
        order by  id desc

逻辑

public VO queryBrandByPage(Integer page, String key, Integer rows, String sortBy, Boolean desc) {
    //分页  -- 使用分页助手,在我们真正查询之前,调用这个下面的方法,开启分页查询,他很智能,会用mybatis的拦截器
    // 对接下来要执行查询的sql进行拦截,自动的在其后面拼接  limit语句
    PageHelper.startPage(page,rows); //当前页码,每页显示的数目

    //过滤 --key     (StringUtils用的是comment lang3 下面的)
    // 过滤条件,key是前端用户传递进来的,可能仅仅是一个 小米,  也可能是 小 ---  模糊查询
   /*   select * from tb_brand
    where name like ''% X %'' or  letter== ''x''
    order by  id desc
    */
    Example example = new Example(Brand.class);
    if(StringUtils.isNotBlank(key)){  //不为空,过滤
       example.createCriteria().orLike("name","%"+key+"%").andEqualTo("letter",key.toUpperCase());
      //  example.createCriteria().orLike("name","%"+key+"%").or
    }

    //排序
    if(StringUtils.isNotBlank(sortBy)) { //传递进来的排序不为空,设置我们的排序条件
        // 上面的 order by 可以帮我们生成, 但是后面的 id  desc 需要我们自己写
        // String orderByClause = "id desc" ; 写死了
        String orderByClause = sortBy + (desc ? " DESC " : " ASC ");  //坑坑坑   注意要加上 空格 不然拼接完了 就是 orderBy idASC 而不是orderBy id ASC
        example.setOrderByClause(orderByClause);
    }

    //查询 获取到list ,其实是  当前页的数据 page对象
    List<Brand> list = brandMapper.selectByExample(example);
    if (CollectionUtils.isEmpty(list)){
        throw new Exception ;
    }

   // 解析List
    PageInfo<Brand> pageInfo = new PageInfo<>(list);

    return new PageResult<Brand>(pageInfo.getTotal(),list);
}

三。自定义 sql

Mybatis 只能针对单表为我们生成 sql, 如果我们的需求是跨表操作,比如说涉及到两张表,我们就得去 mapper 里面自己写 sql

原生 sql

select * from tb_brand b
inner join tb_category_brand cb on b.id=cb.brand_id  -- 去笛卡尔积
where cb.category_id= ? ;

mapper

? 用 #{id} 取代

@Select("select * from tb_brand b inner join tb_category_brand cb on b.id=cb.brand_id where cb.category_id=#{cid}")
  List<Brand>  queryBrandByCid(@Param("cid") Long cid);

四。拓展包 -- 批量操作

  • 批量查询,批量删除

注意他的包啊!

import tk.mybatis.mapper.additional.idlist.IdListMapper;
public interface Mapper extends Mapper<Category> , IdListMapper<Category,Long> {}
/**
 * 根据主键字符串进行查询,类中只有存在一个带有@Id注解的字段
 * @param idList
 * @return
 */
@SelectProvider(type = IdListProvider.class, method = "dynamicSQL")
List<T> selectByIdList(@Param("idList") List<PK> idList);

/**
 * 根据主键字符串进行删除,类中只有存在一个带有@Id注解的字段
 * @param idList
 * @return
 */
@DeleteProvider(type = IdListProvider.class, method = "dynamicSQL")
int deleteByIdList(@Param("idList") List<PK> idList);

  • 批量插入:
import tk.mybatis.mapper.additional.insert.InsertListMapper;
public interface BaseMapper<T> extends InsertListMapper<T> {}


@RegisterMapper
public interface InsertListMapper<T> {
@InsertProvider(
    type = InsertListProvider.class,
    method = "dynamicSQL"
)
int insertList(List<? extends T> var1);
}

抽取出一个 baseMapper, 添加 @RegisterMapper 它才会被扫描到生效

import tk.mybatis.mapper.additional.idlist.IdListMapper;
import tk.mybatis.mapper.additional.insert.InsertListMapper;
import tk.mybatis.mapper.annotation.RegisterMapper;
import tk.mybatis.mapper.common.Mapper;

@RegisterMapper
public interface BaseMapper<T> extends IdListMapper<T,Long>,Mapper<T>, InsertListMapper<T> {
}

原文出处:https://www.cnblogs.com/ZhuChangwu/p/11632641.html

mybatis - 通用mapper

mybatis - 通用mapper

title: 玩转spring-boot-mybatis
date: 2019-03-11 19:36:57
type: "mybatis"
categories: mybatis #分类名
tags: mybatis

作为持久层的ORM框架,目前在国内主流之一就是MyBatis,学会用它,用好它肯定是必备的功课

我会主要从下面几个方面入整理本篇博客

  1. 快速搭建快发环境
  2. 常见的注解
  3. 怎么玩?

一. 快速搭建开发环境

小插曲,添加测试模块的时候,引入junit模块和spring-boot-text-starter模块有先顺序,不然ide会报错...

坐标

  <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.4.RELEASE</version>
        <relativePath/><!-- lookup parent from repository -->
    </parent>
    <properties>
        <java.version>1.8</java.version>
    </properties>
    <dependencies>
        <!--web-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <!--mybatis-->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
        </dependency>
        <!--通用mapper启动器-->
        <dependency>
            <groupId>tk.mybatis</groupId>
            <artifactId>mapper-spring-boot-starter</artifactId>
            <version>2.0.3</version>
        </dependency>
        <!--分页助手-->
        <dependency>
            <groupId>com.github.pageHelper</groupId>
            <artifactId>pageHelper-spring-boot-starter</artifactId>
            <version>1.2.5</version>
        </dependency>
        <!--MysqL-->
        <dependency>
            <groupId>MysqL</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.11</version>
        </dependency>
        <!--测试-->
        <!--先添加 junit  再添加下面的text  stater-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <scope>text</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
        </dependency>
    </dependencies>
  • 另外插一嘴---MysqL连接的版本适配

我现在用的云主机docker上的官方版MysqL,版本比较新,因此我的调整版本到 8以上,不然会报错说什么

com.MysqL.jdbc.exceptions.jdbc4.MysqLNonTransientConnectionException: Could

配置文件:

  • 主要是配置数据库的连接,如果我们使用的是通用mapper,Mybatis可以做到零配置
server:
  port: 8089
spring:
  application:
    name: text-mybatis
  datasource:
    url: jdbc:MysqL://211.159.XXX.XXX:8888/changwu?serverTimezone=UTC&useUnijava=true&characterEncoding=utf-8&useSSL=false
    username: root
    password: 2424zcw..
    driver-class-name: com.MysqL.jdbc.Driver
#输出SQL
mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

启动类

  • 在这里告诉通用mapper我们的mapper包路径
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import tk.mybatis.spring.annotation.MapperScan;
@SpringBootApplication
@MapperScan("com.changwu.mapper")
public class MybatisApp {
    public static void main(String[] args) {
        SpringApplication.run(MybatisApp.class);
    }
}

编写Mapper

  • 让我们自己的Mapper继承通用mapper,泛型是实体类名
  • 给它实体类名,就相当与告诉它了我们的表里的字段,不让他怎么会知道如何动态生成sql ?
import tk.mybatis.mapper.common.Mapper;
public interface MyMapper extends Mapper<MyBrand> {}

ok,到现在环境就搭建好了

二. 常见的注解

  • 这里的注解主要是作用在实体类上的注解,当我们撸起袖子写代码的时候,被给它难住喽,尴尬

情景1: 对于实体类

标记他对应那张表

import javax.persistence.Table;
@Table(name = "tb_brand")

情景2: 对于主键

大多数情况主键一般都叫id,bigint类型,没什么超级特殊的情况我们都希望他可以自己增长,下面两个注解都可以做到这件事,但是前提表id属性必须设置成 autoincreament,不然报错说,id不能不写

  • 注解1:
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

/*
*  使用的是jpa的策略生成器
*JPA提供的四种标准用法为TABLE,SEQUENCE,IDENTITY,AUTO. 
* TABLE:使用一个特定的数据库表格来保存主键。 
* SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列
* IDENTITY:主键由数据库自动生成(主要是自动增长型) 
* AUTO:主键由程序控制。
*/
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY) 
  • 注解2:

直接使用Mybatis的原生注解

@Id
@Keysql(useGeneratedKeys = true)
private Long id;

情景3:

我们都知道springMVC会把前端发送过来的json转化为我们的javaBean,因此我们的javaBean里面的属性名和前端发送的那个对象的属性名要意义对应的,这是规范!

设想,加入前端一顿收集数据,把商品的品牌信息和库存信息都发送过来了,只有关于品牌的javabean,数据接受不全怎么办? 没关系,下面的注解可以搞定

  • @Transient (意味短暂的)告诉mapper 他不是PO(持久层对象,Persistence Object)的属性,而且,springMvc还会把信息关于库存的信息,封装进去
@Transient

情景4:

我们的pojo属性名,和数据库中的关键字重名怎么办?

@Column() 可以解决 数据库中的字段为数据库的关键字的问题

@Column(name="`numeric`")  //
private Boolean numeric;   //是否是数字类型

情景5 :

数据表中tingint(1) 对应的javaBean的属性咋写?

tingint(1) // 它是boolean的同义词

情景5:

关于VO,如何选择性的返回给前端javaBean的属性?就比如说我们查询有没有这个用户,总不至于把密码,私人信息一块返回给前端吧?

import com.fasterxml.jackson.annotation.JsonIgnore;
@JsonIgnore

它是jackson的注解

  <dependency>
        <groupId>com.fasterxml.jackson.core</groupId>
        <artifactId>jackson-annotations</artifactId>
        <version>RELEASE</version>
        <scope>compile</scope>
    </dependency>

三. 怎么玩?

 下面就是介绍常用的mapper的API

一. CRUD

  • 两种新增
//会有选择性的新增,智能判断 brand里面有没有空的字段,有值,用这个值,没有值而使用数据库的默认值
insertSelective(brand) 

// 假如前端提交过来的 brand里面的数据是全的,用词方法
brandMapper.insert(brand);
  • 简单查询
/**
 * 根据实体中的属性值进行查询,查询条件使用等号
 * @param record
 * @return
 */
@SelectProvider(type = BaseSelectProvider.class,method = "dynamicsql")
List<T> select(T record);


/**
 * 根据实体中的属性值进行查询,查询条件使用等号
 * @param record
 * @return
 */
@SelectProvider(type = BaseSelectProvider.class,method = "dynamicsql")
List<T> select(T record);


/**
 * 查询全部结果
 * @return
 */
@SelectProvider(type = BaseSelectProvider.class,method = "dynamicsql")
List<T> selectAll();


/**
 * 根据实体中的属性进行查询,只能有一个返回值,有多个结果是抛出异常,查询条件使用等号
 * @param record
 * @return
 */
@SelectProvider(type = BaseSelectProvider.class,method = "dynamicsql")
T selectOne(T record);


/**
 * 根据实体中的属性查询总数,查询条件使用等号
 * @param record
 * @return
 */
@SelectProvider(type = BaseSelectProvider.class,method = "dynamicsql")
int selectCount(T record);
  • 简单删除
/**
 * 根据主键字段进行删除,方法参数必须包含完整的主键属性
 * @param key
 * @return
 */
@DeleteProvider(type = BaseDeleteProvider.class,method = "dynamicsql")
int deleteByPrimaryKey(Object key);


/**
 * 根据实体属性作为条件进行删除,查询条件使用等号
 * @param record
 * @return
 */
@DeleteProvider(type = BaseDeleteProvider.class,method = "dynamicsql")
int delete(T record);

  • 如何玩修改?

修改主要用到下面这个api

/**
 * 根据主键更新属性不为null的值
 * @param record
 * @return
 */
@UpdateProvider(type = BaseUpdateProvider.class,method = "dynamicsql")
int updateByPrimaryKeySelective(T record);

看到这个方法有没有觉得很爽? 它根据主键,修改不为null的属性,意思就是说,我们把不需要修改的地方设置为null就好了
,这样看,前面需要我们一顿整,整啥呢? 结合实际的业务逻辑,把前端给交过来的数据最新的数据放到我们的bean中直接修改,把不需要修改的属性设置为null,有属性可能需要直接删除,有的属性可能要去别的表中查询(别忘了加上事务 @Transactional )

/**
 * 根据主键更新实体全部字段,null值会被更新
 * @param record
 * @return
 */
@UpdateProvider(type = BaseUpdateProvider.class,method = "dynamicsql")
int updateByPrimaryKey(T record);

这个方法和上面的神似

二 .分页,过滤(模糊查询),搜索

API

mapper.selectByExample(example)

参照下面原生的sql.拼接查询条件

 select * from 表名
        where name like '% X %' or  letter== 'x'
        order by  id desc

逻辑

public VO queryBrandByPage(Integer page,String key,Integer rows,String sortBy,Boolean desc) {
    //分页  -- 使用分页助手,在我们真正查询之前,调用这个下面的方法,开启分页查询,他很智能,会用mybatis的拦截器
    // 对接下来要执行查询的sql进行拦截,自动的在其后面拼接  limit语句
    pageHelper.startPage(page,rows); //当前页码,每页显示的数目

    //过滤 --key     (StringUtils用的是comment lang3 下面的)
    // 过滤条件,key是前端用户传递进来的,可能仅仅是一个 小米,也可能是 小 ---  模糊查询
   /*   select * from tb_brand
    where name like '% X %' or  letter== 'x'
    order by  id desc
    */
    Example example = new Example(Brand.class);
    if(StringUtils.isNotBlank(key)){  //不为空,过滤
       example.createCriteria().orLike("name","%"+key+"%").andEqualTo("letter",key.toupperCase());
      //  example.createCriteria().orLike("name","%"+key+"%").or
    }

    //排序
    if(StringUtils.isNotBlank(sortBy)) { //传递进来的排序不为空,设置我们的排序条件
        // 上面的 order by 可以帮我们生成,但是后面的 id  desc 需要我们自己写
        // String orderByClause = "id desc" ; 写死了
        String orderByClause = sortBy + (desc ? " DESC " : " ASC ");  //坑坑坑   注意要加上 空格 不然拼接完了 就是 orderBy idASC 而不是orderBy id ASC
        example.setorderByClause(orderByClause);
    }

    //查询 获取到list,其实是  当前页的数据 page对象
    List<Brand> list = brandMapper.selectByExample(example);
    if (CollectionUtils.isEmpty(list)){
        throw new Exception ;
    }

   // 解析List
    PageInfo<Brand> pageInfo = new PageInfo<>(list);

    return new PageResult<Brand>(pageInfo.getTotal(),list);
}

三 .自定义sql

Mybatis只能针对单表为我们生成sql,如果我们的需求是跨表操作,比如说涉及到两张表,我们就得去mapper里面自己写sql

原生sql

select * from tb_brand b
inner join tb_category_brand cb on b.id=cb.brand_id  -- 去笛卡尔积
where cb.category_id= ? ;

mapper

?用#{id} 取代

@Select("select * from tb_brand b inner join tb_category_brand cb on b.id=cb.brand_id where cb.category_id=#{cid}")
  List<Brand>  queryBrandByCid(@Param("cid") Long cid);

四 .拓展包--批量操作

  • 批量查询,批量删除

注意他的包啊!

import tk.mybatis.mapper.additional.idlist.IdListMapper;
public interface Mapper extends Mapper<Category>,IdListMapper<Category,Long> {}
/**
 * 根据主键字符串进行查询,类中只有存在一个带有@Id注解的字段
 * @param idList
 * @return
 */
@SelectProvider(type = IdListProvider.class,method = "dynamicsql")
List<T> selectByIdList(@Param("idList") List<PK> idList);

/**
 * 根据主键字符串进行删除,类中只有存在一个带有@Id注解的字段
 * @param idList
 * @return
 */
@DeleteProvider(type = IdListProvider.class,method = "dynamicsql")
int deleteByIdList(@Param("idList") List<PK> idList);

  • 批量插入:
import tk.mybatis.mapper.additional.insert.InsertListMapper;
public interface BaseMapper<T> extends InsertListMapper<T> {}


@RegisterMapper
public interface InsertListMapper<T> {
@InsertProvider(
    type = InsertListProvider.class,method = "dynamicsql"
)
int insertList(List<? extends T> var1);
}

抽取出一个baseMapper,添加 @RegisterMapper 它才会被扫描到生效

import tk.mybatis.mapper.additional.idlist.IdListMapper;
import tk.mybatis.mapper.additional.insert.InsertListMapper;
import tk.mybatis.mapper.annotation.RegisterMapper;
import tk.mybatis.mapper.common.Mapper;

@RegisterMapper
public interface BaseMapper<T> extends IdListMapper<T,Long>,Mapper<T>,InsertListMapper<T> {
}

Mybatis 之通用 Mapper(基于 mybatis 的 Provider 机制无需第三方插件包)

Mybatis 之通用 Mapper(基于 mybatis 的 Provider 机制无需第三方插件包)

前言

几乎每个系统都需要单表的基础操作(即增删改查分页查询等),如果不使用通用的 Mapper 则需要每个 mapper 中都需要实现对应的重复方法,虽然 mybatis 逆向生成工具会生成对应的.xml 文件。里面已经含有一些通用的方法,但是每个实体对应一个.xml 文件太复杂。而 mybatis 也支持注解方式实现 sql,使用注解方式实现 sql 方式,个人感觉更简洁,也符合减少配置文件的趋势。如 springboot 都在简化配置文件。

通用 Mapper

通用 Mapper 就是为了解决单表增删改查,基于 Mybatis Provider 机制实现。开发人员不需要编写 SQL,不需要在 DAO 中增加方法,不需要引入其他多余的第三方框架。只要写好实体类,就能支持相应的增删改查方法。

mybatis 注解方式实现 sql 编写

/**
*注解方式实现sql
*/
public interface UserMapper{
	@Select("SELECT id, name FROM tb_user WHERE user_id=#{userId}")
	User selectById(Integer userId);
}

以上就是通过 mybatis 注解方式实现 sql 语句调用,是不是看上去就显得更简洁。

BaseMapper 代码如下:

/**
 * meilin.huang
 * @param <Entity>
 */
public interface BaseMapper<Entity> {

    /**
     * 插入新对象,并返回主键id值
     * @param entity 实体对象
     * @return
     */
    @InsertProvider(type = InsertSqlProvider.class, method = "sql")
    @Options(useGeneratedKeys = true)
    Integer insert(Entity entity);

//    @InsertProvider(type = BatchInsertSqlProvider.class, method = "sql")
//    @Options(useGeneratedKeys = true)
//    Integer batchInsert(List<Entity> entities);

    /**
     * 根据主键id更新实体,若实体field为null,则对应数据库的字段也更新为null
     * @param entity
     * @return
     */
    @UpdateProvider(type = UpdateSqlProvider.class, method = "sql")
    Integer updateByPrimaryKey(Entity entity);

    /**
     * 根据主键id更新实体,若实体field为null,则对应数据库的字段不更新
     * @param entity
     * @return
     */
    @UpdateProvider(type = UpdateSelectiveSqlProvider.class, method = "sql")
    Integer updateByPrimaryKeySelective(Entity entity);

    @DeleteProvider(type = DeleteSqlProvider.class, method = "sql")
    Integer deleteByPrimaryKey(Integer id);

    @DeleteProvider(type = DeleteByCriteriaSqlProvider.class, method = "sql")
    Integer deleteByCriteria(Entity criteria);

    @SelectProvider(type = SelectOneSqlProvider.class, method = "sql")
    Entity selectByPrimaryKey(Integer id);

    @SelectProvider(type = SelectAllSqlProvider.class, method = "sql")
    List<Entity> selectAll(String orderBy);

    @SelectProvider(type = SelectByCriteriaSqlProvider.class, method = "sql")
    List<Entity> selectByCriteria(Entity criteria);

    /**
     * 根据条件查询单个数据
     *
     * @param criteria
     * @return
     */
    @SelectProvider(type = SelectByCriteriaSqlProvider.class, method = "sql")
    Entity selectOneByCriteria(Entity criteria);

    @SelectProvider(type = CountSqlProvider.class, method = "sql")
    Long count();

    @SelectProvider(type = CountByCriteriaSqlProvider.class, method = "sql")
    Long countByCriteria(Entity criteria);


    class InsertSqlProvider extends SqlProviderSupport {
        public String sql(ProviderContext context) {
            TableInfo table = tableInfo(context);

            return new SQL()
                    .INSERT_INTO(table.getTableName())
                    .INTO_COLUMNS(table.getColumns())
                    .INTO_VALUES(Stream.of(table.getFields()).map(this::bindParameter).toArray(String[]::new))
                    .toString();

        }
    }

    class BatchInsertSqlProvider extends SqlProviderSupport {
        public String sql(Object entities, ProviderContext context) {
            TableInfo table = tableInfo(context);

            int size = ((List)((Map)entities).get("list")).size();
            String value = "(" + String.join(",", Stream.of(table.getFields()).map(this::bindParameter).toArray(String[]::new)) + ")";
            String[] values = new String[size];
            Arrays.fill(values, value);

            SQL sql = new SQL()
                    .INSERT_INTO(table.getTableName())
                    .INTO_COLUMNS(table.getColumns());
            StringBuilder sqlBuilder =  new StringBuilder(sql.toString());
            sqlBuilder.append(" VALUES ");
            sqlBuilder.append(String.join(",", values));
            return sqlBuilder.toString();
        }
    }

    class UpdateSqlProvider extends SqlProviderSupport {
        public String sql(ProviderContext context) {
            TableInfo table = tableInfo(context);

            return new SQL()
                    .UPDATE(table.getTableName())
                    .SET(Stream.of(table.getFields())
                            .filter(field -> !table.getPrimaryKeyColumn().equals(columnName(field)))
                            .map(field -> columnName(field) + " = " + bindParameter(field)).toArray(String[]::new))
                    .WHERE(table.getPrimaryKeyColumn() + " = #{id}")
                    .toString();
        }
    }

    class UpdateSelectiveSqlProvider extends SqlProviderSupport {
        public String sql(Object entity, ProviderContext context) {
            TableInfo table = tableInfo(context);

            return new SQL()
                    .UPDATE(table.getTableName())
                    .SET(Stream.of(table.getFields())
                            .filter(field -> value(entity, field) != null && !table.getPrimaryKeyColumn().equals(columnName(field)))
                            .map(field -> columnName(field) + " = " + bindParameter(field)).toArray(String[]::new))
                    .WHERE(table.getPrimaryKeyColumn() + " = #{id}")
                    .toString();
        }
    }

    class DeleteSqlProvider extends SqlProviderSupport {
        public String sql(ProviderContext context) {
            TableInfo table = tableInfo(context);

            return new SQL()
                    .DELETE_FROM(table.getTableName())
                    .WHERE(table.getPrimaryKeyColumn() + " = #{id}")
                    .toString();
        }
    }

    class DeleteByCriteriaSqlProvider extends SqlProviderSupport {
        public String sql(Object criteria, ProviderContext context) {
            TableInfo table = tableInfo(context);

            return new SQL()
                    .DELETE_FROM(table.getTableName())
                    .WHERE(Stream.of(table.getFields())
                            .filter(field -> value(criteria, field) != null)
                            .map(field -> columnName(field) + " = " + bindParameter(field))
                            .toArray(String[]::new))
                    .toString();
        }
    }

    class SelectOneSqlProvider extends SqlProviderSupport {
        public String sql(ProviderContext context) {
            TableInfo table = tableInfo(context);

            return new SQL()
                    .SELECT(table.getSelectColumns())
                    .FROM(table.getTableName())
                    .WHERE(table.getPrimaryKeyColumn() + " = #{id}")
                    .toString();
        }
    }

    class SelectAllSqlProvider extends SqlProviderSupport {
        public String sql(String orderBy, ProviderContext context) {
            TableInfo table = tableInfo(context);
            SQL sql = new SQL()
                    .SELECT(table.getSelectColumns())
                    .FROM(table.getTableName());
            if (StringUtils.isEmpty(orderBy)) {
                orderBy = table.getPrimaryKeyColumn() + " DESC";
            }
            return sql.ORDER_BY(orderBy).toString();
        }
    }

    class SelectByCriteriaSqlProvider extends SqlProviderSupport {
        public String sql(Object criteria, ProviderContext context) {
            TableInfo table = tableInfo(context);
            return new SQL()
                    .SELECT(table.getSelectColumns())
                    .FROM(table.getTableName())
                    .WHERE(Stream.of(table.getFields())
                            .filter(field -> value(criteria, field) != null)
                            .map(field -> columnName(field) + " = " + bindParameter(field))
                            .toArray(String[]::new)).ORDER_BY(table.getPrimaryKeyColumn() + " DESC").toString();
        }
    }

    class CountByCriteriaSqlProvider extends SqlProviderSupport {
        public String sql(Object criteria, ProviderContext context) {
            TableInfo table = tableInfo(context);
            return new SQL()
                    .SELECT("COUNT(*)")
                    .FROM(table.getTableName())
                    .WHERE(Stream.of(table.getFields())
                            .filter(field -> value(criteria, field) != null)
                            .map(field -> columnName(field) + " = " + bindParameter(field)).toArray(String[]::new))
                    .toString();
        }
    }

    class CountSqlProvider extends SqlProviderSupport {
        public String sql(Object criteria, ProviderContext context) {
            TableInfo table = tableInfo(context);
            return new SQL()
                    .SELECT("COUNT(*)")
                    .FROM(table.getTableName())
                    .toString();
        }
    }


    abstract class SqlProviderSupport {

        /**
         * 表前缀
         */
        private static final String TABLE_PREFIX = "tb_";

        /**
         * 主键名
         */
        private static final String DEFAULT_PRIMARY_KEY = "id";

        /**
         * key:interface class   value:tableInfo
         */
        private static Map<Class, TableInfo> tableCache = new ConcurrentHashMap<>(256);


        /**
         * 获取表信息结构
         * @param context
         * @return
         */
        protected TableInfo tableInfo(ProviderContext context) {
            TableInfo info = tableCache.get(context.getMapperType());
            if (info != null) {
                return info;
            }

            Class<?> entityClass = entityType(context);
            //获取不含有@NoColumn注解的fields
            Field[] fields = excludeNoColumnField(ReflectionUtils.getFields(entityClass));
            info = TableInfo.entityClass(entityClass)
                    .fields(fields)
                    .tableName(tableName(entityClass))
                    .primaryKeyColumn(primaryKeyColumn(fields))
                    .columns(columns(fields))
                    .selectColumns(selectColumns(fields))
                    .build();

            tableCache.put(context.getMapperType(), info);
            return info;
        }

        /**
         * 获取BaseMapper接口中的泛型类型
         * @param context
         * @return
         */
        protected Class<?> entityType(ProviderContext context) {
            return Stream.of(context.getMapperType().getGenericInterfaces())
                    .filter(ParameterizedType.class::isInstance)
                    .map(ParameterizedType.class::cast)
                    .filter(type -> type.getRawType() == BaseMapper.class)
                    .findFirst()
                    .map(type -> type.getActualTypeArguments()[0])
                    .filter(Class.class::isInstance).map(Class.class::cast)
                    .orElseThrow(() -> new IllegalStateException("未找到BaseMapper的泛型类 " + context.getMapperType().getName() + "."));
        }


        protected String tableName(Class<?> entityType) {
            return TABLE_PREFIX + StringUtils.camel2Underscore(entityType.getSimpleName());
        }

        /**
         * 过滤含有@NoColumn注解的field
         * @param totalField  entityClass所有的字段
         * @return   不包含@NoColumn注解的fields
         */
        protected Field[] excludeNoColumnField(Field[] totalField) {
            return Stream.of(totalField)
                    //过滤含有@NoColumn注解的field
                    .filter(field -> !field.isAnnotationPresent(NoColumn.class))
                    .toArray(Field[]::new);
        }
        /**
         * 获取查询对应的字段 (不包含pojo中含有@NoColumn主键的属性)
         *
         * @param fields p
         * @return
         */
        protected String[] selectColumns(Field[] fields) {
            return Stream.of(fields).map(this::selectColumnName).toArray(String[]::new);
        }

        /**
         * 获取所有pojo所有属性对应的数据库字段 (不包含pojo中含有@NoColumn主键的属性)
         *
         * @param fields entityClass所有fields
         * @return
         */
        protected String[] columns(Field[] fields) {
            return Stream.of(fields).map(this::columnName).toArray(String[]::new);
        }

        /**
         * 如果fields中含有@Primary的字段,则返回该字段名为主键,否则默认''id''为主键名
         * @param fields entityClass所有fields
         * @return  主键column(驼峰转为下划线)
         */
        protected String primaryKeyColumn(Field[] fields) {
            return Stream.of(fields).filter(field -> field.isAnnotationPresent(Primary.class))
                    .findFirst()    //返回第一个primaryKey的field
                    .map(this::columnName)
                    .orElse(DEFAULT_PRIMARY_KEY);
        }

        /**
         * 获取单个属性对应的数据库字段(带有下划线字段将其转换为"字段 AS pojo属性名"形式)
         *
         * @param field
         * @return
         */
        protected String selectColumnName(Field field) {
            String camel = StringUtils.camel2Underscore(field.getName());
            return camel.contains("_") ? camel + " AS " + field.getName() : camel;
        }

        /**
         * 获取单个属性对应的数据库字段
         *
         * @param field  entityClass中的field
         * @return
         */
        protected String columnName(Field field) {
            return StringUtils.camel2Underscore(field.getName());
        }

        protected String bindParameter(Field field) {
            return "#{" + field.getName() + "}";
        }

        protected Object value(Object bean, Field field) {
            try {
                field.setAccessible(true);
                return field.get(bean);
            } catch (IllegalAccessException e) {
                throw new IllegalStateException(e);
            } finally {
                field.setAccessible(false);
            }
        }
    }
}

以上就是 BaseMapper 的主要代码,就可以轻松实现一些通用的 mapper 方法。以上代码中还有些其他对象(如 TableInfo 以及 @NoColume 注解等),由于文章篇幅已经太多了。如果有需要的可以在个人的项目中查看,并 copy。 个人项目之 BaseMapper 目录链接:https://gitee.com/objs/mayfly/tree/master/mayfly-dao/src/main/java/mayfly/dao/base

使用方法

/**
*声明一个接口,继承BaseMapper接口,并将实体类传入BaseMapper的泛型参数中
*/
@Mapper
public interface MenuMapper extends BaseMapper<Menu> {

}

也可以使用 @NoColume 注解过滤非数据库表中的字段,加了该注解之后通用查询时候就不会查该属性,主要用户复合对象如:

public class User{
	@NoColume
	private Product product;
}

接下来就可以在 service 中注入对应 Mapper 就可以实现通用方法调用了。

总结

有了通用 Mapper 可以大幅减轻重复的工作量。个人项目中也有一些通用 Service 等通用功能,如感兴趣可前往查看,并使用,当然可能也存在 BUG, 欢迎大佬多多指导!BaseService 代码链接:https://gitee.com/objs/mayfly/tree/master/mayfly-sys/src/main/java/mayfly/sys/service/base

关于MyBatis通用Mapper实现原理及相关内容mybatis mapper工作原理的介绍已经告一段落,感谢您的耐心阅读,如果想了解更多关于mybatics通用mapper保存返回主键、mybatis - 通用 mapper、mybatis - 通用mapper、Mybatis 之通用 Mapper(基于 mybatis 的 Provider 机制无需第三方插件包)的相关信息,请在本站寻找。

本文标签: