mybatisplus比mybatis到底好在哪里?
Mybatis Plus：国人团队苞米豆在Mybatis的基础上开发的框架，在Mybatis基础上扩展了许多功能，荣获了2018最受欢迎国产开源软件第5名。Mybatis Plus Generator：同样为苞米豆开发，比Mybatis Generator更加强大，支持功能更多，自动生成Entity、Mapper、Service、Controller等。 MyBatis-plus 是一款 Mybatis 增强工具，用于简化开发，提高效率。下文使用缩写 mp来简化表示 MyBatis-plus，本文主要介绍 mp 搭配 Spring Boot 的使用。 注：本文使用的 mp 版本是当前最新的3.4.2，早期版本的差异请自行查阅文档 官方网站：http://baomidou.com/ 准备工作完成，数据库情况如下： 项目目录如下： 运行测试类 可以看到，针对单表的基本CRUD操作，只需要创建好实体类，并创建一个继承自BaseMapper的接口即可，可谓非常简洁。并且，我们注意到，User类中的managerId，createTime属性，自动和数据库表中的manager_id，create_time对应了起来，这是因为mp自动做了数据库下划线命名，到Java类的驼峰命名之间的转化。 mp一共提供了8个注解，这些注解是用在Java的实体类上面的。 注解在类上，指定类和数据库表的映射关系。实体类的类名（转成小写后）和数据库表名相同时，可以不指定该注解。 注解在实体类的某一字段上，表示这个字段对应数据库表的主键。当主键名为id时（表中列名为id，实体类中字段名为id），无需使用该注解显式指定主键，mp会自动关联。若类的字段名和表的列名不一致，可用value属性指定表的列名。另，这个注解有个重要的属性type，用于指定主键策略。 注解在某一字段上，指定Java实体类的字段和数据库表的列的映射关系。这个注解有如下几个应用场景。 排除非表字段 若Java实体类中某个字段，不对应表中的任何列，它只是用于保存一些额外的，或组装后的数据，则可以设置exist属性为false，这样在对实体对象进行插入时，会忽略这个字段。排除非表字段也可以通过其他方式完成，如使用static或transient关键字，但个人觉得不是很合理，不做赘述 字段验证策略 通过insertStrategy，updateStrategy，whereStrategy属性进行配置，可以控制在实体对象进行插入，更新，或作为WHERE条件时，对象中的字段要如何组装到SQL语句中。 字段填充策略 通过fill属性指定，字段为空时会进行自动填充 乐观锁注解 注解在枚举字段上 逻辑删除 序列主键策略（oracle） 插件过滤规则 mp封装了一些最基础的CRUD方法，只需要直接继承mp提供的接口，无需编写任何SQL，即可食用。mp提供了两套接口，分别是Mapper CRUD接口和Service CRUD接口。并且mp还提供了条件构造器Wrapper，可以方便地组装SQL语句中的WHERE条件。 只需定义好实体类，然后创建一个接口，继承mp提供的BaseMapper，即可食用。mp会在mybatis启动时，自动解析实体类和表的映射关系，并注入带有通用CRUD方法的mapper。BaseMapper里提供的方法，部分列举如下： 下面讲解几个比较特别的方法 BaseMapper接口还提供了一个selectMaps方法，这个方法会将查询结果封装为一个Map，Map的key为结果的列，value为值 该方法的使用场景如下： 只查部分列 当某个表的列特别多，而SELECT的时候只需要选取个别列，查询出的结果也没必要封装成Java实体类对象时（只查部分列时，封装成实体后，实体对象中的很多属性会是null），则可以用selectMaps，获取到指定的列后，再自行进行处理即可 比如 进行数据统计 比如 只会返回第一个字段（第一列）的值，其他字段会被舍弃 比如 得到的结果，只封装了第一列的id 查询满足条件的总数，注意，使用这个方法，不能调用QueryWrapper的select方法设置要查询的列了。这个方法会自动添加select count(1) 比如 另外一套CRUD是Service层的，只需要编写一个接口，继承IService，并创建一个接口实现类，即可食用。（这个接口提供的CRUD方法，和Mapper接口提供的功能大同小异，比较明显的区别在于IService支持了更多的批量化操作，如saveBatch，saveOrUpdateBatch等方法。 食用示例如下 另，IService也支持链式调用，代码写起来非常简洁，查询示例如下 更新示例如下 删除示例如下 mp让我觉得极其方便的一点在于其提供了强大的条件构造器Wrapper，可以非常方便的构造WHERE条件。条件构造器主要涉及到3个类，AbstractWrapper。QueryWrapper，UpdateWrapper，它们的类关系如下 在AbstractWrapper中提供了非常多的方法用于构建WHERE条件，而QueryWrapper针对SELECT语句，提供了select()方法，可自定义需要查询的列，而UpdateWrapper针对UPDATE语句，提供了set()方法，用于构造set语句。条件构造器也支持lambda表达式，写起来非常舒爽。 下面对AbstractWrapper中用于构建SQL语句中的WHERE条件的方法进行部分列举 使用示例 下面通过一些具体的案例来练习条件构造器的使用。（使用前文创建的user表） 条件构造器的诸多方法中，均可以指定一个boolean类型的参数condition，用来决定该条件是否加入最后生成的WHERE语句中，比如 调用构造函数创建一个Wrapper对象时，可以传入一个实体对象。后续使用这个Wrapper时，会以实体对象中的非空属性，构建WHERE条件（默认构建等值匹配的WHERE条件，这个行为可以通过实体类里各个字段上的@TableField注解中的condition属性进行改变） 示例如下 执行结果如下。可以看到，是根据实体对象中的非空属性，进行了等值匹配查询。 若希望针对某些属性，改变等值匹配的行为，则可以在实体类中用@TableField注解进行配置，示例如下 运行下面的测试代码 从下图得到的结果来看，对于实体对象中的name字段，采用了like进行拼接 @TableField中配置的condition属性实则是一个字符串，SqlCondition类中预定义了一些字符串以供选择 SqlCondition中提供的配置比较有限，当我们需要<或>等拼接方式，则需要自己定义。比如 测试如下 从下图得到的结果，可以看出，name属性是用like拼接的，而age属性是用>拼接的 allEq方法传入一个map，用来做等值匹配 当allEq方法传入的Map中有value为null的元素时，默认会设置为is null 若想忽略map中value为null的元素，可以在调用allEq时，设置参数boolean null2IsNull为false 若想要在执行allEq时，过滤掉Map中的某些元素，可以调用allEq的重载方法allEq(BiPredicate<R, V> filter, Map<R, V> params) lambda条件构造器，支持lambda表达式，可以不必像普通条件构造器一样，以字符串形式指定列名，它可以直接以实体类的方法引用来指定列。示例如下 像普通的条件构造器，列名是用字符串的形式指定，无法在编译期进行列名合法性的检查，这就不如lambda条件构造器来的优雅。 另外，还有个链式lambda条件构造器，使用示例如下 上面介绍的都是查询操作,现在来讲更新和删除操作。 BaseMapper中提供了2个更新方法 根据入参entity的id（主键）进行更新，对于entity中非空的属性，会出现在UPDATE语句的SET后面，即entity中非空的属性，会被更新到数据库，示例如下 根据实体entity和条件构造器wrapper进行更新，示例如下 额外演示一下，把实体对象传入Wrapper，即用实体对象构造WHERE条件的案例 注意到我们的User类中，对name属性和age属性进行了如下的设置 执行结果 再额外演示一下，链式lambda条件构造器的使用 由于BaseMapper提供的2个更新方法都是传入一个实体对象去执行更新，这在需要更新的列比较多时还好，若想要更新的只有那么一列，或者两列，则创建一个实体对象就显得有点麻烦。针对这种情况，UpdateWrapper提供有set方法，可以手动拼接SQL中的SET语句，此时可以不必传入实体对象，示例如下 BaseMapper一共提供了如下几个用于删除的方法 与前面查询和更新的操作大同小异，不做赘述 当mp提供的方法还不能满足需求时，则可以自定义SQL。 示例如下 使用xml时，若xml文件与mapper接口文件不在同一目录下，则需要在application.yml中配置mapper.xml的存放路径 若有多个地方存放mapper，则用数组形式进行配置 测试代码如下 结果 也可以使用mp提供的Wrapper条件构造器，来自定义SQL 示例如下 xml方式 BaseMapper中提供了2个方法进行分页查询，分别是selectPage和selectMapsPage，前者会将查询的结果封装成Java实体对象，后者会封装成Map<String,Object>。分页查询的食用示例如下 注意到，分页查询总共发出了2次SQL，一次查总记录数，一次查具体数据。若希望不查总记录数，仅查分页结果。可以通过Page的重载构造函数，指定isSearchCount为false即可 在实际开发中，可能遇到多表联查的场景，此时BaseMapper中提供的单表分页查询的方法无法满足需求，需要自定义SQL，示例如下（使用单表查询的SQL进行演示，实际进行多表联查时，修改SQL语句即可） ActiveRecord模式，通过操作实体对象，直接操作数据库表。与ORM有点类似。 让实体类User继承自Model 直接调用实体对象上的方法 结果 在定义实体类时，用@TableId指定主键，而其type属性，可以指定主键策略。 mp支持多种主键策略，默认的策略是基于雪花算法的自增id。全部主键策略定义在了枚举类IdType中，IdType有如下的取值 数据库ID自增，依赖于数据库。在插入操作生成SQL语句时，不会插入主键这一列 未设置主键类型。若在代码中没有手动设置主键，则会根据主键的全局策略自动生成（默认的主键全局策略是基于雪花算法的自增ID） 需要手动设置主键，若不设置。插入操作生成SQL语句时，主键这一列的值会是null。oracle的序列主键需要使用这种方式 当没有手动设置主键，即实体类中的主键属性为空时，才会自动填充，使用雪花算法 当实体类的主键属性为空时，才会自动填充，使用UUID ....（还有几种是已过时的，就不再列举） 可以针对每个实体类，使用@TableId注解指定该实体类的主键策略，这可以理解为局部策略。若希望对所有的实体类，都采用同一种主键策略，挨个在每个实体类上进行配置，则太麻烦了，此时可以用主键的全局策略。只需要在application.yml进行配置即可。比如，配置了全局采用自增主键策略 下面对不同主键策略的行为进行演示 在User上对id属性加上注解，然后将MYSQL的user表修改其主键为自增。 测试 结果 可以看到，代码中没有设置主键ID，发出的SQL语句中也没有设置主键ID，并且插入结束后，主键ID会被写回到实体对象。 在MYSQL的user表中，去掉主键自增。然后修改User类（若不配置@TableId注解，默认主键策略也是NONE） 插入时，若实体类的主键ID有值，则使用之；若主键ID为空，则使用主键全局策略，来生成一个ID。 其余的策略类似，不赘述 AUTO依赖于数据库的自增主键，插入时，实体对象无需设置主键，插入成功后，主键会被写回实体对象。 INPUT完全依赖于用户输入。实体对象中主键ID是什么，插入到数据库时就设置什么。若有值便设置值，若为null则设置null 其余的几个策略，都是在实体对象中主键ID为空时，才会自动生成。 NONE会跟随全局策略，ASSIGN_ID采用雪花算法，ASSIGN_UUID采用UUID 全局配置，在application.yml中进行即可；针对单个实体类的局部配置，使用@TableId即可。对于某个实体类，若它有局部主键策略，则采用之，否则，跟随全局策略。 mybatis plus有许多可配置项，可在application.yml中进行配置，如上面的全局主键策略。下面列举部分配置项 有如下几种可选配置 tablePrefix：添加表名前缀,比如: 然后将MYSQL中的表做一下修改。但Java实体类保持不变（仍然为User）。 测试 可以看到拼接出来的SQL，在表名前面添加了前缀 mp提供一个生成器，可快速生成Entity实体类，Mapper接口，Service，Controller等全套代码。 示例如下 运行后，可以看到生成了如下图所示的全套代码 高级功能的演示需要用到一张新的表user2 并创建对应的实体类User2 以及Mapper接口 首先，为什么要有逻辑删除呢？直接删掉不行吗？当然可以，但日后若想要恢复，或者需要查看这些数据，就做不到了。逻辑删除是为了方便数据恢复，和保护数据本身价值的一种方案。 日常中，我们在电脑中删除一个文件后，也仅仅是把该文件放入了回收站，日后若有需要还能进行查看或恢复。当我们确定不再需要某个文件，可以将其从回收站中彻底删除。这也是类似的道理。 mp提供的逻辑删除实现起来非常简单 只需要在application.yml中进行逻辑删除的相关配置即可 测试代码 结果 可以看到，发出的SQL不再是DELETE，而是UPDATE 此时我们再执行一次SELECT 可以看到，发出的SQL语句，会自动在WHERE后面拼接逻辑未删除的条件。查询出来的结果中，没有了id为6的王软蛋。 若想要SELECT的列，不包括逻辑删除的那一列，则可以在实体类中通过@TableField进行配置 可以看到下图的执行结果中，SELECT中已经不包含deleted这一列了 前面在application.yml中做的配置，是全局的。通常来说，对于多个表，我们也会统一逻辑删除字段的名称，统一逻辑已删除和未删除的值，所以全局配置即可。当然，若要对某些表进行单独配置，在实体类的对应字段上使用@TableLogic即可 开启mp的逻辑删除后，会对SQL产生如下的影响 注意，上述的影响，只针对mp自动注入的SQL生效。如果是自己手动添加的自定义SQL，则不会生效。比如 调用这个selectRaw，则mp的逻辑删除不会生效。 另，逻辑删除可在application.yml中进行全局配置，也可在实体类中用@TableLogic进行局部配置。 表中常常会有“新增时间”，“修改时间”，“操作人” 等字段。比较原始的方式，是每次插入或更新时，手动进行设置。mp可以通过配置，对某些字段进行自动填充，食用示例如下 测试 根据下图结果，可以看到对createTime进行了自动填充 注意，自动填充仅在该字段为空时会生效，若该字段不为空，则直接使用已有的值。如下 更新时的自动填充，测试如下 当出现并发操作时，需要确保各个用户对数据的操作不产生冲突，此时需要一种并发控制手段。悲观锁的方法是，在对数据库的一条记录进行修改时，先直接加锁（数据库的锁机制），锁定这条数据，然后再进行操作；而乐观锁，正如其名，它先假设不存在冲突情况，而在实际进行数据操作时，再检查是否冲突。乐观锁的一种通常实现是版本号，在MySQL中也有名为MVCC的基于版本号的并发事务控制。 在读多写少的场景下，乐观锁比较适用，能够减少加锁操作导致的性能开销，提高系统吞吐量。 在写多读少的场景下，悲观锁比较使用，否则会因为乐观锁不断失败重试，反而导致性能下降。 乐观锁的实现如下： 测试代码 执行之前先看一下数据库的情况 根据下图执行结果，可以看到SQL语句中添加了version相关的操作 当UPDATE返回了1，表示影响行数为1，则更新成功。反之，由于WHERE后面的version与数据库中的不一致，匹配不到任何记录，则影响行数为0，表示更新失败。更新成功后，新的version会被封装回实体对象中。 实体类中version字段，类型只支持int，long，Date，Timestamp，LocalDateTime 注意，乐观锁插件仅支持updateById(id)与update(entity, wrapper)方法 注意：如果使用wrapper，则wrapper不能复用！示例如下 可以看到在第二次复用wrapper时，拼接出的SQL中，后面WHERE语句中出现了2次version，是有问题的。 该插件会输出SQL语句的执行时间，以便做SQL语句的性能分析和调优。 注：3.2.0版本之后，mp自带的性能分析插件被官方移除了，而推荐食用第三方性能分析插件 1.引入maven依赖 2.修改application.yml 3.在src/main/resources资源目录下添加spy.properties 随便运行一个测试用例，可以看到该SQL的执行时长被记录了下来 多租户的概念：多个用户共用一套系统，但他们的数据有需要相对的独立，保持一定的隔离性。 多租户的数据隔离一般有如下的方式： 优点是：不同租户有不同的独立数据库，有助于扩展，以及对不同租户提供更好的个性化，出现故障时恢复数据较为简单。 缺点是：增加了数据库数量，购置成本，维护成本更高 优点是购置和维护成本低了一些，缺点是数据恢复较为困难，因为不同租户的数据都放在了一起 优点是，购置和维护成本最低，支持用户最多，缺点是隔离性最低，安全性最低 添加多租户拦截器配置。添加配置后，在执行CRUD的时候，会自动在SQL语句最后拼接租户id的条件 测试代码 当数据量特别大的时候，我们通常会采用分库分表。这时，可能就会有多张表，其表结构相同，但表名不同。例如order_1，order_2，order_3，查询时，我们可能需要动态设置要查的表名。mp提供了动态表名SQL解析器，食用示例如下 先在mysql中拷贝一下user2表 配置动态表名拦截器 测试 - 