通用mapper使用注意点
本文由 通用mapper使用归纳而来
- 通用Mapper虽然方便,终于不用手写XML,但仍然要写POJO和Mapper接口。如果你们公司出于效率考虑,只允许增删改使用TkMapper,查询语句要自己手写XML(如果不需要手写sql则可以不用xml文件),那么你还要手动创建mapper.xml,工作量还是有一些的。
- 建议简单的增删改可以交给通用Mapper提供的接口,而查询最好自己手写SQL,做到接口和SQL分离,方便后期SQL优化及维护。
- 使用通用mapper需要注意的是 , 只能在一张表行进行操作!!!如果你要操作的是两张表+,则你需要手写sql语句
三个接口的作用
/*** 自定义BaseMapper可以让数据库表对应的mapper接口继承(我们一般数据库表对应的mapper接口直接继承Mapper,* 比如【public interface ActDetailMapper extends Mapper】但这样不够强大)* * Mapper接口:拥有基本的增、删、改、查方法* IdListMapper:支持根据IdList批量查询和删除* InsertListMapper:支持批量插入* 没有批量更新*
* 注意:一定要加@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 {@Autowiredprivate TkUserMapper userMapper;/*** INSERT INTO tk_user ( id,name,age,create_time,update_time,deleted )* VALUES( null,'bravo',16,null,null,null );* * 注意,如果不带xxxSelective,默认会插入null,null也算一种赋值,所以数据库设置的默认值不会生效*/@Testpublic void testInsert() {TkUserPojo tkUserPojo = new TkUserPojo();// 虽然我只设置了两个值,但实际执行会插入全量字段,默认nulltkUserPojo.setName("bravo");tkUserPojo.setAge(16);userMapper.insert(tkUserPojo);}/*** INSERT INTO tk_user ( id,name,age ) VALUES( null,'bravoSelective',16 );* * 只会插入id,name,age三个字段,此时数据库默认值会生效*/@Testpublic 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(),数据库默认值不会生效*/@Testpublic 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(严重错误)*/@Testpublic 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;* * 只更新设置的字段*/@Testpublic 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*/@Testpublic 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.模糊匹配要自己写%*/@Testpublic 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层重新写一遍*/@Testpublic void testSelectByExample() {Example example = new Example(TkUserPojo.class);// 指定查询列只查询id和name,指定条件列为ageexample.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*/@Testpublic void testSelectBatch() {List<Long> ids = Arrays.asList(1L, 2L, 3L, 4L, 5L);userMapper.selectByIdList(ids);}
}
注意点一
- 通用Mapper的方法会自动转换驼峰,但手写的SQL需要单独开启才能转换
application.yml
server:port: 5678spring:datasource:url: jdbc:mysql:///test?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=trueusername: rootpassword: rootdriver-class-name: com.mysql.jdbc.Driver# 打印SQL
logging:level:com.bravo.happy.dao: debugmybatis:# 即使引入通用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**/ *.properties</include><include>**/*.xmlfalse src/main/resources **/ *.yml</include><include>**/*.properties**/ *.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(),数据库默认值不会生效*/@Testpublic 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*/@Testpublic 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却为nullExample 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() {// 传入空的ListuserMapper.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,指定条件列为ageexample.selectProperties("id", "name").createCriteria().andEqualTo("age", 16);userMapper.selectByExample(example);
}
但其他诸如selectByPrimaryKey()方法是全列查询,因为mapper对象无法直接设置selectProperties()。
本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!
