通用mapper使用注意点

本文由 通用mapper使用归纳而来

  • 通用Mapper虽然方便,终于不用手写XML,但仍然要写POJO和Mapper接口。如果你们公司出于效率考虑,只允许增删改使用TkMapper,查询语句要自己手写XML(如果不需要手写sql则可以不用xml文件),那么你还要手动创建mapper.xml,工作量还是有一些的。
  • 建议简单的增删改可以交给通用Mapper提供的接口,而查询最好自己手写SQL,做到接口和SQL分离,方便后期SQL优化及维护。
  • 使用通用mapper需要注意的是 , 只能在一张表行进行操作!!!如果你要操作的是两张表+,则你需要手写sql语句

三个接口的作用

/**
 * 自定义BaseMapper可以让数据库表对应的mapper接口继承(我们一般数据库表对应的mapper接口直接继承Mapper<T>,
 * 比如【public interface ActDetailMapper extends Mapper<ActDetail>】但这样不够强大)
 * <p>
 * Mapper接口:拥有基本的增、删、改、查方法
 * IdListMapper:支持根据IdList批量查询和删除
 * InsertListMapper:支持批量插入
 * 没有批量更新
 * <p>
 * 注意:一定要加@RegisterMapper,注册接口
 *  * @author bravo
 * @date 2020-01-22 21:00
 */
@RegisterMapper
public interface BaseMapper<T> extends Mapper<T>, IdListMapper<T, Long>, InsertListMapper<T> {
}

常用sql

/**
 * @author qiyu
 * @date 2020-09-03 19:00:10
 */
@SpringBootTest
public class TkMapperTest {

    @Autowired
    private TkUserMapper userMapper;

    /**
     * INSERT INTO tk_user ( id,name,age,create_time,update_time,deleted )
     * VALUES( null,'bravo',16,null,null,null );
     * 
     * 注意,如果不带xxxSelective,默认会插入null,null也算一种赋值,所以数据库设置的默认值不会生效
     */
    @Test
    public void testInsert() {
        TkUserPojo tkUserPojo = new TkUserPojo();
        // 虽然我只设置了两个值,但实际执行会插入全量字段,默认null
        tkUserPojo.setName("bravo");
        tkUserPojo.setAge(16);
        userMapper.insert(tkUserPojo);
    }

    /**
     * INSERT INTO tk_user ( id,name,age ) VALUES( null,'bravoSelective',16 );
     * 
     * 只会插入id,name,age三个字段,此时数据库默认值会生效
     */
    @Test
    public void testInsertSelective() {
        TkUserPojo tkUserPojo = new TkUserPojo();
        tkUserPojo.setName("bravoSelective");
        tkUserPojo.setAge(16);
        userMapper.insertSelective(tkUserPojo);
    }

    /**
     * INSERT INTO tk_user ( id,name,age,create_time,update_time,deleted )
     * VALUES
     * ( null,'bravo1',18,null,null,null ) ,
     * ( null,'bravo2',19,null,null,null ) ,
     * ( null,'bravo3',20,null,null,null ) ,
     * ( null,'bravo4',21,null,null,null )
     * ...
     * 
     * insertList(list)方法底层是insert()而不是insertSelective(),数据库默认值不会生效
     */
    @Test
    public void testInsertBatch() {
        // 准备10个数据
        List<TkUserPojo> list = new ArrayList<>();
        TkUserPojo tkUserPojo = null;
        for (int i = 1; i <= 10; i++) {
            tkUserPojo = new TkUserPojo();
            tkUserPojo.setName("bravo" + i);
            tkUserPojo.setAge(17 + i);
            list.add(tkUserPojo);
        }
        // 批量插入
        userMapper.insertList(list);
    }

    /**
     * UPDATE tk_user SET name = 'bravoSelective',age = 100,create_time = null,update_time = null,deleted = null
     * WHERE id = 6;
     *
     * 没设置的值会被更新为null,可能产生两个问题:
     * 1.数据库默认值不会生效
     * 2.不想更新的字段被更新为null(严重错误)
     */
    @Test
    public void testUpdate() {
        TkUserPojo tkUserPojo = new TkUserPojo();
        tkUserPojo.setId(6L);
        tkUserPojo.setName("bravoSelective");
        tkUserPojo.setAge(100);
        userMapper.updateByPrimaryKey(tkUserPojo);
    }

    /**
     * UPDATE tk_user SET name = 'bravoSelective',age = 100
     * WHERE id = 6;
     * 
     * 只更新设置的字段
     */
    @Test
    public void testUpdateSelective() {
        TkUserPojo tkUserPojo = new TkUserPojo();
        tkUserPojo.setId(6L);
        tkUserPojo.setName("bravoSelective");
        tkUserPojo.setAge(100);
        userMapper.updateByPrimaryKeySelective(tkUserPojo);
    }

    /**
     * DELETE
     * FROM tk_user
     * WHERE id in (1,2,3,4,5);
     * 
     * deleteByIdList底层用的是IN,是物理删除DELETE,不是逻辑删除UPDATE
     */
    @Test
    public void testDeleteBatch() {
        List<Long> ids = Arrays.asList(1L, 2L, 3L, 4L, 5L);
        userMapper.deleteByIdList(ids);
    }

    /**
     * SELECT COUNT(*)
     * FROM tk_user
     * WHERE ( ( name like 'bravo' ) );
     * 
     * 有两点注意:
     * 1.这里的'name'只是是POJO的属性,而不是数据库字段
     * 2.模糊匹配要自己写%
     */
    @Test
    public void testCount() {
        Example example = new Example(TkUserPojo.class);
        example.createCriteria().andLike("name", "bravo");
        int i = userMapper.selectCountByExample(example);
        System.out.println("count记录数为:" + i);
    }
    
    /**
     * SELECT id , name FROM tk_user WHERE ( ( age = ? ) )
     *
     * 虽然通用Mapper也能只查询指定列,但是复用性不好,每次都要在service层重新写一遍
     */
    @Test
    public void testSelectByExample() {
        Example example = new Example(TkUserPojo.class);
        // 指定查询列只查询id和name,指定条件列为age
        example.selectProperties("id", "name").createCriteria().andEqualTo("age", 16);
        userMapper.selectByExample(example);
    }

    /**
     * SELECT id,name,age,create_time,update_time,deleted
     * FROM tk_user
     * WHERE id in (1,2,3,4,5);
     * 
     * selectByIdList底层也是用IN
     */
    @Test
    public void testSelectBatch() {
        List<Long> ids = Arrays.asList(1L, 2L, 3L, 4L, 5L);
        userMapper.selectByIdList(ids);
    }
}

注意点一

  • 通用Mapper的方法会自动转换驼峰,但手写的SQL需要单独开启才能转换

application.yml

server:
  port: 5678

spring:
  datasource:
    url: jdbc:mysql:///test?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
    username: root
    password: root
    driver-class-name: com.mysql.jdbc.Driver

# 打印SQL
logging:
  level:
    com.bravo.happy.dao: debug

mybatis:
  #  即使引入通用Mapper,还是可以使用MyBatis原生的方式手写SQL,指定XML扫描位置即可
  mapper-locations: classpath:mapper/**/*.xml
  # 通用Mapper的方法会自动转换驼峰,但手写的SQL需要开启才能转换
  configuration:
    map-underscore-to-camel-case: on

注意点二

需要在启动器类加上@MapperScan

/**
 * 注意,这里的@MapperScan是tk包下的,而不是org,扫码mapper接口所在路径
 * @author qiyu
 */
@MapperScan("com.bravo.happy.dao")
@SpringBootApplication
public class HappyDemoApplication {

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

}

默认情况下,如果没有指定resources,目前认为自动会将src/main/resources下的静态文件放到target里头的classes文件夹下的package下的文件夹里。
但是如果你在pom.xml中配置了resource,那么就不会将src/main/resources下的静态文件放到target里
所以一旦我们手动指定了resource的路径,则还需要把src/main/resources这个路径也指定进去

<!--作用是将:配置目录下的的要求后缀名文件拷贝到target内-->
        <resources>
            <!--如果不指定这个路径,则默认会把src/main/resources当成静态资源,一旦指定后,默认路径失效,所以如果src/main/resources下有静态资源,则需连同配置-->
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.yml</include>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.yml</include>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>

在这里插入图片描述

注意点三

  • insertList(list)方法底层是insert()而不是insertSelective(),数据库默认值不会生效。
  • deleteByIdList底层用的是IN,是物理删除DELETE,不是逻辑删除UPDATE
    /**
     * INSERT INTO tk_user ( id,name,age,create_time,update_time,deleted )
     * VALUES
     * ( null,'bravo1',18,null,null,null ) ,
     * ( null,'bravo2',19,null,null,null ) ,
     * ( null,'bravo3',20,null,null,null ) ,
     * ( null,'bravo4',21,null,null,null )
     * ...
     * 
     * insertList(list)方法底层是insert()而不是insertSelective(),数据库默认值不会生效
     */
    @Test
    public void testInsertBatch() {
        // 准备10个数据
        List<TkUserPojo> list = new ArrayList<>();
        TkUserPojo tkUserPojo = null;
        for (int i = 1; i <= 10; i++) {
            tkUserPojo = new TkUserPojo();
            tkUserPojo.setName("bravo" + i);
            tkUserPojo.setAge(17 + i);
            list.add(tkUserPojo);
        }
        // 批量插入
        userMapper.insertList(list);
    }
    /**
     * DELETE
     * FROM tk_user
     * WHERE id in (1,2,3,4,5);
     * 
     * deleteByIdList底层用的是IN,是物理删除DELETE,不是逻辑删除UPDATE
     */
    @Test
    public void testDeleteBatch() {
        List<Long> ids = Arrays.asList(1L, 2L, 3L, 4L, 5L);
        userMapper.deleteByIdList(ids);
    }

虽然,批量操作会比循环单个操作速度快,因为批量插入时是1w条SQL是一起发送给MySQL服务端执行的,而1w次循环插入则是发送了1w次SQL。1次网络连接和1w次网络连接,差距还是很大的。假设一次网络IO耗时1秒,那么一万次请求往返就会徒增1w秒耗时,这是不能接受的。

但批量操作没办法按条件,所以推荐:
通用Mapper提供的接口方法适合简单的单表增删改,对于查询和批量操作最好自己手写SQL。更何况实际编程批量操作的场景不会特别多,写几个SQL不会很耗费精力。

注意点四

没办法按条件批量操作(只能根据where id in(,)),所以需要自己手写sql,如果要求根据name批量删除记录,你会怎么写SQL?

<!--批量删除(逻辑删除)-->
<update id="deleteBatch">
    <foreach collection="list" item="user" separator=";">
        UPDATE tk_user set deleted = 1
        <where>
            <if test="user.name != null and user.name.trim() != ''">
                and name = #{user.name}
            </if>
            and deleted = 0
        </where>
    </foreach>
</update>

大部分人会这样写,你可能以为我是说上面的SQL语法有问题,其实上面的SQL是可以执行的,但逻辑不严谨,有可能引发致命BUG。
想像一下,如果user.name == null,上面的SQL最终会变成:
UPDATE tk_user set deleted = 1 WHERE deleted = 0
也就是删除所有数据!!!

不仅仅是我们手写的SQL,通用Mapper提供的方法一不小心也可能发生全表操作。比如:

@Test
public void testUpdate() {
    String name = null;
    // 设置了条件查询,但是name却为null
    Example example = new Example(TkUserPojo.class);
    example.createCriteria().andEqualTo("name", name);
    TkUserPojo tkUserPojo = new TkUserPojo();
    tkUserPojo.setAge(99);
    userMapper.updateByExampleSelective(tkUserPojo, example);
}

打印的SQL会进行全表更新:UPDATE tk_user SET age = 99;(where name=null没有了)

包括默认接口提供的一些批量操作,会在list.size()==0时发生全表修改:

@Test
public void testDelete() {
    // 传入空的List
    userMapper.deleteByIdList(new ArrayList<>());
}

DELETE FROM tk_user;
【where id in (null)】

所以,不论是手写SQL还是使用通用Mapper等框架,一定要经常注意如果条件失效时是否会造成全表范围的修改,最好是加一些必要的判断:
● 集合是否为空,比如deleteByIdList(new ArrayList())删除全表
● 条件字段是否全部为空

手写sql优点

有些公司之所以引入通用Mapper、MyBatis-Plus的同时却强制查询要手写SQL

  • 方便优化。将sql与代码分离,便于集中优化
  • 精准控制查询的列数,手写SQL便于精确控制查询的列(只查你需要的,不查多余的)

通用Mapper其实能控制查询的列,要通过Example对象:

/**
 * SELECT id , name FROM tk_user WHERE ( ( age = ? ) )
 *
 * 虽然通用Mapper也能只查询指定列,但是复用性不好,每次都要在service层重新写一遍
 */
@Test
public void testSelectByExample() {
    Example example = new Example(TkUserPojo.class);
    // 指定查询列只查询id和name,指定条件列为age
    example.selectProperties("id", "name").createCriteria().andEqualTo("age", 16);
    userMapper.selectByExample(example);
}

但其他诸如selectByPrimaryKey()方法是全列查询,因为mapper对象无法直接设置selectProperties()。

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值