关于通用mapper的使用

最近有看到通用mapper的一些新用法,记录一下通用mapper的相关使用

官方文档:

https://gitee.com/free/Mapper/wikis/Home

1 通用Mapper简介

通用 Mapper是一个可以实现任意 MyBatis 通用方法的框架,项目提供了常规的增删改查操作以及Example 相关的单表操作。通用 Mapper 是为了解决 MyBatis 使用中 90% 的基本操作,使用它可以很方便的进行开发,可以节省开发人员大量的时间。

通用Mapper可以通过Mybatis的拦截器原理,动态的帮我们实现单表的增删改查功能.

2 通用Mapper基本原理

先从通用Mapper中提供的通用方法,来查看其原理

/**
 * 通用Mapper接口,查询
 *
 * @param <T> 不能为空
 * @author liuzh
 */
@RegisterMapper
public interface SelectMapper<T> {
    
    

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

}

SelectMapper接口和方法都使用的是泛型, 使用时需要指定泛型类型. 通过反射很容易得到泛型接口的类型信息

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 代码

看到注解SelectProvider上type为BaseSelectProvider.class类, 查看其类代码:

/**
 * BaseSelectProvider实现类,基础方法实现类
 *
 * @author liuzh
 */
public class BaseSelectProvider extends MapperTemplate {
    
    

    public BaseSelectProvider(Class<?> mapperClass, MapperHelper mapperHelper) {
    
    
        super(mapperClass, mapperHelper);
    }

    /**
     * 查询
     *
     * @param ms
     * @return
     */
    public String selectOne(MappedStatement ms) {
    
    
        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.whereAllIfColumns(entityClass, isNotEmpty()));
        return sql.toString();
    }

    /**
     * 查询
     *
     * @param ms
     * @return
     */
    public String select(MappedStatement ms) {
    
    
        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.whereAllIfColumns(entityClass, isNotEmpty()));
        sql.append(SqlHelper.orderByDefault(entityClass));
        return sql.toString();
    }

    /**
     * 查询
     *
     * @param ms
     * @return
     */
    public String selectByRowBounds(MappedStatement ms) {
    
    
        return select(ms);
    }

    /**
     * 根据主键进行查询
     *
     * @param ms
     */
    public String selectByPrimaryKey(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.wherePKColumns(entityClass));
        return sql.toString();
    }

    /**
     * 查询总数
     *
     * @param ms
     * @return
     */
    public String selectCount(MappedStatement ms) {
    
    
        Class<?> entityClass = getEntityClass(ms);
        StringBuilder sql = new StringBuilder();
        sql.append(SqlHelper.selectCount(entityClass));
        sql.append(SqlHelper.fromTable(entityClass, tableName(entityClass)));
        sql.append(SqlHelper.whereAllIfColumns(entityClass, isNotEmpty()));
        return sql.toString();
    }

    /**
     * 根据主键查询总数
     *
     * @param ms
     * @return
     */
    public String existsWithPrimaryKey(MappedStatement ms) {
    
    
        Class<?> entityClass = getEntityClass(ms);
        StringBuilder sql = new StringBuilder();
        sql.append(SqlHelper.selectCountExists(entityClass));
        sql.append(SqlHelper.fromTable(entityClass, tableName(entityClass)));
        sql.append(SqlHelper.wherePKColumns(entityClass));
        return sql.toString();
    }

    /**
     * 查询全部结果
     *
     * @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("<where>");
        sql.append(SqlHelper.whereLogicDelete(entityClass, false));
        sql.append("</where>");

        sql.append(SqlHelper.orderByDefault(entityClass));
        return sql.toString();
    }
}

在 mybatis 中,每一个方法(注解或 XML )经过处理后,最终会构造成 MappedStatement 对象.

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

通用 Mapper 从这里入手,利用ProviderSqlSource 可以生成正常的 MappedStatement,在生成 MappedStatement 后,就把 ProviderSqlSource 替换掉了.

接口方法中根据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("<where>");
        sql.append(SqlHelper.whereLogicDelete(entityClass, false));
        sql.append("</where>");

        sql.append(SqlHelper.orderByDefault(entityClass));
        return sql.toString();
    }

拼出的 XML 形式的动态 SQL,使用 mybatis 的 XMLLanguageDriver 中的 createSqlSource 方法可以生成 SqlSource。然后使用反射用新的 SqlSource 替换ProviderSqlSource .

tk.mybatis.mapper.mapperhelper.MapperTemplate通用Mapper模板类中

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

3 通用Mapper使用

通用mapper提供了一些基础的增删改查的方法:

        // 1 新增
        // Mapper接口只提供基础的增删改查  批量新增的需要使用 MySqlMapper 接口,该接口继承自 InsertListMapper 接口
        userMapper.insert(new User());
        userMapper.insertSelective(new User());
        userMapper.insertList(new ArrayList<User>());

        // 2 修改
        userMapper.updateByPrimaryKey(new User());
        userMapper.updateByPrimaryKeySelective(new User());
        userMapper.updateByExample(new User(), new Example(User.class));
        userMapper.updateByExampleSelective(new User(), new Example(User.class));

        // 3 删除
        userMapper.delete(new User());
        userMapper.deleteByPrimaryKey("id");
        userMapper.deleteByExample(new Example(User.class));

        // 4 查询
        userMapper.selectAll();
        userMapper.select(new User());
        userMapper.selectOne(new User());
        userMapper.selectByExample(new Example(User.class));

0 数据库准备

-- 建表语句
CREATE TABLE `user` (
  `id` int NOT NULL COMMENT '主键',
  `username` varchar(64) DEFAULT NULL COMMENT '名称',
  `phone` varchar(64) DEFAULT NULL COMMENT '电话',
  `icon` varchar(255) DEFAULT NULL COMMENT '二进制',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户表';

-- 添加数据
INSERT INTO `test`.`user`(`id`, `username`, `phone`, `icon`) VALUES (1, '李子柒', '77777', '李子柒的头像');
INSERT INTO `test`.`user`(`id`, `username`, `phone`, `icon`) VALUES (2, '2', '2', 'world');
INSERT INTO `test`.`user`(`id`, `username`, `phone`, `icon`) VALUES (3, '3', '3', '3333');
INSERT INTO `test`.`user`(`id`, `username`, `phone`, `icon`) VALUES (5, '5', '5', '123adb');
INSERT INTO `test`.`user`(`id`, `username`, `phone`, `icon`) VALUES (7, '5', '5', '汉字你好');

1 新建一个SpringBoot环境

2 添加maven依赖

    <!-- https://mvnrepository.com/artifact/tk.mybatis/mapper -->
    <dependency>
      <groupId>tk.mybatis</groupId>
      <artifactId>mapper</artifactId>
      <version>4.1.5</version>
    </dependency>

    <!--mybatis和spring整合-->
    <dependency>
      <groupId>org.mybatis.spring.boot</groupId>
      <artifactId>mybatis-spring-boot-starter</artifactId>
      <version>1.3.1</version>
    </dependency>

    <!--MySQL数据库驱动-->
    <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
    </dependency>

    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-web</artifactId>
    </dependency>

    <dependency>
      <groupId>org.projectlombok</groupId>
      <artifactId>lombok</artifactId>
      <optional>true</optional>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
    </dependency>

3 添加配置文件

spring.datasource.url=jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=root
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

4 添加实体类

@Data
@Table(name = "user")
public class User {
    
    

    @Id
    private String id;
    @Column
    private String username;
    @Column
    private String phone;
    @Column
    private String icon;

    @Transient
    private Date queryTime = new Date();

}

5 添加mapper接口

// MySqlMapper接口主要是使用批量新增功能
public interface UserMapper extends Mapper<User>, MySqlMapper<User> {
    
    

}

6 添加controller控制器


@Controller
@RequestMapping("/helloworld")
public class HelloWorld {
    
    

    @Autowired
    private UserMapper userMapper;
    
    @GetMapping("/queryByCondition")
    @ResponseBody
    public String queryByCondition(@RequestParam("keyword") String keyword) {
    
    

        // 使用example来实现复杂关系的查询
        // 1 example可以查询自己需要的字段,并设置排序规则  (可选,默认是查询所有,无排序条件)
        Example example = new Example(User.class);
        example.selectProperties("id", "phone", "icon")
                .orderBy("id")
                .desc();

        // 2 模糊查询需要自己拼百分号% (mybatis-plus不需要)
        example.createCriteria()
                .andLike("username", "%" + keyword + "%")
                .orEqualTo("phone", keyword);

        List<User> users = userMapper.selectByExample(example);
        System.out.println(users);

        return "<h1>Hello Wrold</h1>";
    }

}

7 添加启动类

@MapperScan("com.cf.demo.mapper")
@SpringBootApplication
public class DemoApplication {
    
    

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

8 测试

在本地浏览器访问

http://localhost:8080/helloworld/queryByCondition?keyword=子

浏览器页面展示:

Hello Wrold

控制台展示:

[User(id=1, username=null, phone=77777, icon=李子柒的头像, queryTime=Mon Oct 25 19:25:44 CST 2021)]

猜你喜欢

转载自blog.csdn.net/ABestRookie/article/details/120960654
今日推荐