因为mybatis-plus不是官方开发的,所以没有提供对应的starter。但是民间有大神,有着对应的提供,那么先去官网上找一下:
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.4.3.4</version> </dependency>
然后分析一下pom依赖:
可以看到这里引入了mybatis-spring的整合包,注解等等。以及对应的jdbc操作等等。书库连接池使用的是Hikari等
就代表着我们不需要配置任何的东西就已经可以来操作数据库了。
那么去看一下对应的maven依赖中的autoconfig,找到spring.factories目录下的自动配置
# Auto Configure org.springframework.boot.env.EnvironmentPostProcessor=\ com.baomidou.mybatisplus.autoconfigure.SafetyEncryptProcessor org.springframework.boot.autoconfigure.EnableAutoConfiguration=\ com.baomidou.mybatisplus.autoconfigure.IdentifierGeneratorAutoConfiguration,\ com.baomidou.mybatisplus.autoconfigure.MybatisPlusLanguageDriverAutoConfiguration,\ com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration
那么直接看最后一个:
com.baomidou.mybatisplus.autoconfigure.MybatisPlusAutoConfiguration
看下源码:
@Configuration(proxyBeanMethods = false) // 导入mybatis就有这两个类 @ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class}) // 如果只有一个数据库连接池的类 @ConditionalOnSingleCandidate(DataSource.class) // 配置文件中和类属性进行绑定 @EnableConfigurationProperties(MybatisPlusProperties.class) @AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisPlusLanguageDriverAutoConfiguration.class}) public class MybatisPlusAutoConfiguration implements InitializingBean {
继续看看类中配置了什么:
@Bean @ConditionalOnMissingBean public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
这段代码就是配置SqlSessionFactory,然后将容器中的DataSource导入进来,然后下面在进行疯狂的set操作。
SqlSessionTemplate的配置,也就是SqlSession的配置
@Bean @ConditionalOnMissingBean public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { ExecutorType executorType = this.properties.getExecutorType(); if (executorType != null) { return new SqlSessionTemplate(sqlSessionFactory, executorType); } else { return new SqlSessionTemplate(sqlSessionFactory); } }
AutoConfiguredMapperScannerRegistrar的注册,也就是配置的mapper包的路径
@Configuration(proxyBeanMethods = false) @Import(AutoConfiguredMapperScannerRegistrar.class) @ConditionalOnMissingBean({MapperFactoryBean.class, MapperScannerConfigurer.class}) public static class MapperScannerRegistrarNotFoundConfiguration implements InitializingBean { @Override public void afterPropertiesSet() { logger.debug( "Not found configuration for registering mapper bean using @MapperScan, MapperFactoryBean and MapperScannerConfigurer."); } }
看一下配置文件:
/** * Configuration properties for MyBatis. * * @author Eddú Meléndez * @author Kazuki Shimizu */ @Data @Accessors(chain = true) @ConfigurationProperties(prefix = Constants.MYBATIS_PLUS) public class MybatisPlusProperties { private static final ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(); /** * Location of MyBatis xml config file. 全局配置文件 */ private String configLocation; /** * Locations of MyBatis mapper files. * mapper映射接口的文件的位置,这里默认的是/mapper路径下的所有的xml文件 * @since 3.1.2 add default value */ private String[] mapperLocations = new String[]{"classpath*:/mapper/**/*.xml"}; /** * Packages to search type aliases. (Package delimiters are ",; \t\n") 别名操作 */ private String typeAliasesPackage; /** * The super class for filtering type alias. * If this not specifies, the MyBatis deal as type alias all classes that searched from typeAliasesPackage. */ private Class<?> typeAliasesSuperType; /** * Packages to search for type handlers. (Package delimiters are ",; \t\n") */ private String typeHandlersPackage; /** * Indicates whether perform presence check of the MyBatis xml config file. 是否需要来进行检查全局配置文件 */ private boolean checkConfigLocation = false; /** * Execution mode for {@link org.mybatis.spring.SqlSessionTemplate}. */ private ExecutorType executorType; /** * The default scripting language driver class. (Available when use together with mybatis-spring 2.0.2+) * <p> * 如果设置了这个,你会至少失去几乎所有 mp 提供的功能 */ private Class<? extends LanguageDriver> defaultScriptingLanguageDriver; /** * Externalized properties for MyBatis configuration. */ private Properties configurationProperties; /** * A Configuration object for customize default settings. If {@link #configLocation} * is specified, this property is not used. * TODO 使用 MybatisConfiguration mybatis-plus的全局配置 */ @NestedConfigurationProperty private MybatisConfiguration configuration; /** * TODO 枚举包扫描 */ private String typeEnumsPackage;
配置文件
spring: datasource: driver-class-name: com.mysql.cj.jdbc.Driver url: jdbc:mysql://localhost:3306/mp?serverTimezone=Asia/Shanghai&useSSL=false username: root password: 123456 # mybatisplus几乎零配置 mybatis-plus: configuration: # 添加控制台日志 log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
首先需要在yaml中来进行指定:
mybatis-plus: global-config: db-config: # 要被删除的实体类中的字段 logic-delete-field: deleted # 定义数据库中逻辑删除的数据的值 logic-delete-value: 1 # 定义数据库中未删除的值 logic-not-delete-value: 0
看一个测试:
@Test public void testFindAll(){ List<User2> users = user2Mapper.selectList(null); users.forEach(System.out::println); }
查看对应的sql:
SELECT id,name,age,email,manager_id,create_time,update_time,version,deleted FROM user2 WHERE deleted=0
可以发现,这里已经自动的给我们添加上了对应的逻辑删除的值,而且对应着配置文件中的,只是会去查询未被删除的值,而不会再次去进行查询没有删除的值。
查看上面的SQL语句,我们可以看到,在进行select的字段里面有一个字段deleted,但是这个字段通常来说,是不需要出现在select后面的字段中的,因为不需要。
所以如果有需要,那么在实体类上加上注解:
@TableField(select = false) private Integer deleted;
那么再次执行之后,就看不到这个值了。
看一下对应的SQL语句:
SELECT id,name,age,email,manager_id,create_time,update_time,version FROM user2 WHERE deleted=0
可以看到这里已经没有了对应的deleted字段了。
在全局配置文件中,配置上了逻辑删除的字段的值。对于多个表来说,我们会将重复的字段设置成一样的。
但是像这种需要来进行单独配置的来说,那么就需要加上@TableField注解即可。
再来看一个测试:
@Test public void testDeleteById(){ int i = user2Mapper.deleteById(1); System.out.println(i); }
然后查看对应的SQL语句:
UPDATE user2 SET deleted=1 WHERE id=1 AND deleted=0
可以看到这里做的是update语句,而不是delete语句。因为这里将deleted修改成了我们需要去进行修改的值了。
配置了逻辑删除之后带来的影响:
逻辑删除 | select | insert | update | delete |
---|---|---|---|---|
无影响 | 追加where条件,过滤掉已经删除的数据 | 追加where条件,过滤掉已经删除的字段 | 转换成update语句 | |
注意,上述的影响,只针对mp自动注入的SQL生效。 如果是自己手动添加的自定义SQL,则不会生效。比如:
public interface User2Mapper extends BaseMapper<User2> { @Select("select * from user2") List<User2> selectRaw(); }
调用这个selectRaw
,则mp的逻辑删除不会生效。
逻辑删除可在application.yml
中进行全局配置,也可在实体类中用@TableLogic
进行局部配置。
表中常常会有“新增时间”,“修改时间”,“操作人” 等字段。比较原始的方式,是每次插入或更新时,手动进行设置。mp可以通过配置,对某些字段进行自动填充,食用示例如下
@TableField
设置自动填充;@Data public class User2 { private Long id; private String name; private Integer age; private String email; private Long managerId; @TableField(fill = FieldFill.INSERT) private LocalDateTime createTime; @TableField(fill = FieldFill.UPDATE) private LocalDateTime updateTime; private Integer version; @TableField(select = false) private Integer deleted; }
2、然后再添一个组件到容器中去
@Component public class MyMetaObjectHandler implements MetaObjectHandler { // 注意第二个参数要填写实体类中的字段名称,而不是表的列名称 @Override public void insertFill(MetaObject metaObject) { strictFillStrategy(metaObject,"createTime", LocalDateTime::now); } // 注意第二个参数要填写实体类中的字段名称,而不是表的列名称 @Override public void updateFill(MetaObject metaObject) { strictFillStrategy(metaObject,"updateTime", LocalDateTime::now); } }
然后来进行测试:
@Test public void testInsert() { User2 user = new User2(); user.setId(8L); user.setName("王一蛋"); user.setAge(29); user.setEmail("yd@baomidou.com"); user.setManagerId(2L); user2Mapper.insert(user); }
可以看到数据库中插入时间进行了更新
然后测试一下修改的:
@Test public void testUpdate() { User2 user = new User2(); user.setId(8L); user.setName("王一蛋"); user.setAge(30); user.setEmail("yd@baomidou.com"); user.setManagerId(2L); user2Mapper.updateById(user); }
查看一下数据库,发现对应的事件也是修改成功的
注意,自动填充仅在该字段为空时会生效,若该字段不为空,则直接使用已有的值
当出现并发操作时,需要确保各个用户对数据的操作不产生冲突,此时需要一种并发控制手段。悲观锁的方法是,在对数据库的一条记录进行修改时,先直接加锁(数据库的锁机制),锁定这条数据,然后再进行操作;而乐观锁,正如其名,它先假设不存在冲突情况,而在实际进行数据操作时,再检查是否冲突。乐观锁的一种通常实现是版本号 ,在MySQL中也有名为MVCC的基于版本号的并发事务控制。
在读多写少的场景下,乐观锁比较适用,能够减少加锁操作导致的性能开销,提高系统吞吐量。
在写多读少的场景下,悲观锁比较使用,否则会因为乐观锁不断失败重试,反而导致性能下降。
乐观锁的实现如下:
这种思想和CAS(Compare And Swap)非常相似。
乐观锁的实现步骤如下:
配置乐观锁插件
@Configuration public class MybatisPlusConfig { /** * 3.4.0以后的mp版本,推荐用如下的配置方式 * @return */ /* @Bean public MybatisPlusInterceptor mybatisPlusInterceptor() { MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); return interceptor; }*/ /** * 旧版mp可以采用如下方式。注意新旧版本中,新版的类,名称带有Inner,旧版的不带,不要配错了 * @return */ @Bean public OptimisticLockerInterceptor opLocker() { return new OptimisticLockerInterceptor(); } }
写一个单元测试:
@Test public void testOpLocker() { int version = 1; // 假设这个version是先前查询时获得的 User2 user = new User2(); user.setId(8L); user.setEmail("version@baomidou.com"); user.setVersion(version); int i = user2Mapper.updateById(user); }
看一下对应的SQL语句:
UPDATE user2 SET email='version@baomidou.com', update_time='2021-11-05T00:34:08.190', version=2 WHERE id=8 AND version=1 AND deleted=0
可以看到,在插入的时候首先来进行查询对应的version的值,如果是符合的,那么就开始进行操作。
当UPDATE返回了1,表示影响行数为1,则更新成功。反之,由于WHERE后面的version与数据库中的不一致,匹配不到任何记录,则影响行数为0,表示更新失败。更新成功后,新的version会被封装回实体对象中。
注意,乐观锁插件仅支持updateById(id)
与update(entity, wrapper)
方法
注意:如果使用wrapper
,则wrapper
不能复用! 示例如下
@Test public void testOpLockerTwo() { User2 user = new User2(); user.setId(8L); user.setVersion(1); user.setAge(2); // 第一次使用 LambdaQueryWrapper<User2> wrapper = new LambdaQueryWrapper<>(); wrapper.eq(User2::getName, "王一蛋"); user2Mapper.update(user, wrapper); // 第二次复用 user.setAge(3); user2Mapper.update(user, wrapper); }
查看下对应的SQL如下所示:
Consume Time:20 ms 2021-11-05 00:38:48 Execute SQL:UPDATE user2 SET age=2, update_time='2021-11-05T00:38:47.079', version=2 WHERE deleted=0 AND (name = '王一蛋' AND version = 1) Consume Time:19 ms 2021-11-05 00:38:48 Execute SQL:UPDATE user2 SET age=3, update_time='2021-11-05T00:38:47.079', version=3 WHERE deleted=0 AND (name = '王一蛋' AND version = 1 AND version = 2)
可以看到在第二次复用wrapper
时,拼接出的SQL中,后面WHERE语句中出现了2次version,是有问题的。
该插件会输出SQL语句的执行时间,以便做SQL语句的性能分析和调优。
注:3.2.0版本之后,mp自带的性能分析插件被官方移除了,而推荐使用第三方性能分析插件
1、引入maven依赖
<dependency> <groupId>p6spy</groupId> <artifactId>p6spy</artifactId> <version>3.9.1</version> </dependency>
2、修改application.yaml
spring: datasource: driver-class-name: com.p6spy.engine.spy.P6SpyDriver #换成p6spy的驱动 url: jdbc:p6spy:mysql://localhost:3306/mp?serverTimezone=Asia/Shanghai #url修改 username: root password: root
3、在src/main/resources
资源目录下添加spy.properties
#spy.properties #3.2.1以上使用 modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory # 真实JDBC driver , 多个以逗号分割,默认为空。由于上面设置了modulelist, 这里可以不用设置driverlist #driverlist=com.mysql.cj.jdbc.Driver # 自定义日志打印 logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger #日志输出到控制台 appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger #若要日志输出到文件, 把上面的appnder注释掉, 或者采用下面的appender, 再添加logfile配置 #不配置appender时, 默认是往文件进行输出的 #appender=com.p6spy.engine.spy.appender.FileLogger #logfile=log.log # 设置 p6spy driver 代理 deregisterdrivers=true # 取消JDBC URL前缀 useprefix=true # 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset. excludecategories=info,debug,result,commit,resultset # 日期格式 dateformat=yyyy-MM-dd HH:mm:ss # 是否开启慢SQL记录 outagedetection=true # 慢SQL记录标准 2 秒 outagedetectioninterval=2 # 执行时间设置, 只有超过这个执行时间的才进行记录, 默认值0, 单位毫秒 executionThreshold=10
随便执行一条SQL语句,可以看到控制台会打印出来对应的日志:
Consume Time:19 ms 2021-11-05 00:38:48 Execute SQL:UPDATE user2 SET age=3, update_time='2021-11-05T00:38:47.079', version=3 WHERE deleted=0 AND (name = '王一蛋' AND version = 1 AND version = 2)
详细的去官网或者博客找一篇看看就行了,这里不再来进行赘述。
mybatis-plus提供了两个接口,BaseMapper,也就是mapper接口;另外一个IService,则是service接口
二者最大的区别在于service中支持了大量的批量操作。
mp最强大的一点在于提供了wapper,可以非常的构造出来where条件。从这里可以看到这里是拼接where条件的。
那么只要知道了这个宗旨,那么后面的比较好理解了。
AbstractWrapper接口本身已经提供了很多的方法用于构建where条件。
而QueryWrapper接口主要用作查询;
UpdateWrapper接口用来进行set方法,用于进行set值的;
这块官方文档说明的还算详细。
在条件构造的过程中,有存在着一个boolean类型的condition参数,用来决定最终的条件是否需要添加到where条件中去。
lambda条件构造器,支持lambda表达式。
接下来就没有什么好说的了,具体的就可以来参考文档操作了。
参考文档:https://mp.weixin.qq.com/s/SBkYZrBbGEgBe09erNr7tg