mybatisplus代码生成器使用(mybatisplus多表关联查询)

之前一直使用Mybatis-Plus,说实话,个人还是比较喜欢Mybatis-Plus。

ORM框架用的比较多的就两个,JPA和Mybatis。据说国内用Mybatis比较多,国外用JPA比较多。

而Mybatis-Plus是在Mybatis的基础上,增加了很多牛??的功能。

再粘一下官网介绍的特性,又啰嗦了:无侵入:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑损耗小:启动即会自动注入基本 CURD,性能基本无损耗,直接面向对象操作强大的 CRUD 操作:内置通用 Mapper、通用 Service,仅仅通过少量配置即可实现单表大部分 CRUD 操作,更有强大的条件构造器,满足各类使用需求支持 Lambda 形式调用:通过 Lambda 表达式,方便的编写各类查询条件,无需再担心字段写错支持主键自动生成:支持多达 4 种主键策略(内含分布式唯一 ID 生成器 – Sequence),可自由配置,完美解决主键问题支持 ActiveRecord 模式:支持 ActiveRecord 形式调用,实体类只需继承 Model 类即可进行强大的 CRUD 操作支持自定义全局通用操作:支持全局通用方法注入( Write once, use anywhere )内置代码生成器:采用代码或者 Maven 插件可快速生成 Mapper 、 Model 、 Service 、 Controller 层代码,支持模板引擎,更有超多自定义配置等您来使用内置分页插件:基于 MyBatis 物理分页,开发者无需关心具体操作,配置好插件之后,写分页等同于普通 List 查询分页插件支持多种数据库:支持 MySQL、MariaDB、Oracle、DB2、H2、HSQL、SQLite、Postgre、SQLServer 等多种数据库内置性能分析插件:可输出 Sql 语句以及其执行时间,建议开发测试时启用该功能,能快速揪出慢查询内置全局拦截插件:提供全表 delete 、 update 操作智能分析阻断,也可自定义拦截规则,预防误操作

详细的可以去官网看:https://mybatis.plus/

官网新域名也是牛??。反正用过的都说好。

至于JPA,虽然个人觉得有点太死板,不过也有值得学习的地方。

很早以前,用Mybatis-Plus的时候,有一个比较麻烦的问题,就是如果一组数据存在多张表中,这些表之间可能是一对一,一对多或者多对一,那我要想全部查出来就要调好几个Mapper的查询方法。代码行数一下就增加了很多。

之前也看过Mybatis-Plus的源码,想过如何使Mybatis-Plus支持多表联接查询。可是发现难度不小。因为Mybatis-Plus底层就只支持单表。

最近看到JPA的@OneToOne、@OneToMany、@ManyToMany这些注解,忽然一个想法就在我的脑海里闪现出来,如果像JPA那样使用注解的方式,是不是简单很多呢?

事先声明,全是自己想的,没有看JPA源码, 所以实现方式可能和JPA不一样。

说干就干添加注解处理注解打包发布

可能有人不知道,其实Mybatis也是支持拦截器的,既然如此,用拦截器处理注解就可以啦。

注解 One2One@Inherited@Documented@Target({ElementType.FIELD})@Retention(RetentionPolicy.RUNTIME)public@interfaceOne2One{/***本类主键列名*/Stringself()default”id”;/***本类主键在关联表中的列名*/Stringas();/***关联的mapper*/Class<?extendsBaseMapper>mapper();}

说一下,假如有两张表,A和B是一对一的关系,A表id在B表中是a_id,用这样的方式关联的。
在A的实体类中使用这个注解,self就是id,而as就是a_id,意思就是A的id作为a_id来查询,而mapper就是B的Mapper,下面是例子A就是UserAccount,B就是UserAddress。

@Data@EqualsAndHashCode(callSuper=false)@Accessors(chain=true)@ApiModel(value=”UserAccount对象”,description=”用户相关”)publicclassUserAccountextendsModel<UserAccount>{privatestaticfinallongserialVersionUID=1L;@ApiModelProperty(value=”id”)@TableId(value=”id”,type=IdType.AUTO)privateLongid;@ApiModelProperty(value=”昵称”)privateStringnickName;@TableField(exist=false)//把id的值作为userId在UserAddressMapper中查询@One2One(self=”id”,as=”user_id”,mapper=UserAddressMapper.class)privateUserAddressaddress;@OverrideprotectedSerializablepkVal(){returnthis.id;}}Mybatis拦截器 One2OneInterceptor

这里不再详细介绍拦截器了,之前也写了几篇关于Mybatis拦截器的,有兴趣的可以去看看。

Mybatis拦截器实现Geometry类型数据存储与查询

Mybatis拦截器打印完整SQL

@Intercepts({@Signature(type=ResultSetHandler.class,method=”handleResultSets”,args={Statement.class})})@Slf4jpublicclassOne2OneInterceptorimplementsInterceptor{@OverridepublicObjectintercept(Invocationinvocation)throwsThrowable{Objectresult=invocation.proceed();if(result==null){returnnull;}if(resultinstanceofArrayList){ArrayListlist=(ArrayList)result;for(Objecto:list){handleOne2OneAnnotation(o);}}else{handleOne2OneAnnotation(result);}returnresult;}@SneakyThrowsprivatevoidhandleOne2OneAnnotation(Objecto){Class<?>aClass=o.getClass();Field[]fields=aClass.getDeclaredFields();for(Fieldfield:fields){field.setAccessible(true);One2Oneone2One=field.getAnnotation(One2One.class);if(one2One!=null){Stringself=one2One.self();Objectvalue=MpExtraUtil.getValue(o,self);Stringas=one2One.as();Class<?extendsBaseMapper>mapper=one2One.mapper();BaseMapperbaseMapper=SpringBeanFactoryUtils.getApplicationContext().getBean(mapper);QueryWrapper<Object>eq=Condition.create().eq(as,value);Objectone=baseMapper.selectOne(eq);field.set(o,one);}}}@OverridepublicObjectplugin(Objecto){returnPlugin.wrap(o,this);}@OverridepublicvoidsetProperties(Propertiesproperties){}}

Mybatis拦截器可以针对不同的场景进行拦截,比如:

Executor:拦截执行器的方法。ParameterHandler:拦截参数的处理。ResultHandler:拦截结果集的处理。StatementHandler:拦截Sql语法构建的处理。

这里是通过拦截结果集的方式,在返回的对象上查找这个注解,找到注解后,再根据注解的配置,自动去数据库查询,查到结果后把数据封装到返回的结果集中。这样就避免了自己去多次调Mapper的查询方法。

难点:虽然注解上标明了是什么Mapper,可是在拦截器中取到的还是BaseMapper,而用BaseMapper实在不好查询,我试了很多方法,不过还好Mybatis-Plus支持使用Condition.create().eq(as, value);拼接条件SQL,然后可以使用baseMapper.selectOne(eq);去查询。

publicclassMpExtraUtil{@SneakyThrowspublicstaticObjectgetValue(Objecto,Stringname){Class<?>aClass=o.getClass();Field[]fields=aClass.getDeclaredFields();for(Fieldfield:fields){field.setAccessible(true);if(field.getName().equals(name)){returnfield.get(o);}}thrownewIllegalArgumentException(“未查询到名称为:” name “的字段”);}}

MpExtraUtil就是使用反射的方式,获取id的值。

再讲一个多对多的注解

@Inherited@Documented@Target({ElementType.FIELD})@Retention(RetentionPolicy.RUNTIME)public@interfaceMany2Many{/***本类主键列名*/Stringself()default”id”;/***本类主键在中间表的列名*/StringleftMid();/***另一个多方在中间表中的列名*/StringrightMid();/***另一个多方在本表中的列名*/Stringorigin();/***关联的mapper*/Class<?extendsBaseMapper>midMapper();/***关联的mapper*/Class<?extendsBaseMapper>mapper();}

假设有A、 A_B,B三张表,在A的实体类中使用这个注解, self就是A表主键id,leftMid就是A表的id在中间表中的名字,也就是a_id,而rightMid是B表主键在中间表的名字,就是b_id, origin就是B表自己主键原来的名字,即id,midMapper是中间表的Mapper,也就是A_B对应的Mapper,mapper是B表的Mapper。

这个确实有点绕。

还有一个@One2Many就不说了,和@One2One一样,至于Many2One,从另一个角度看就是@One2One。

@EnableMpExtra

配置拦截器

因为一般项目都会配置自己的MybatisConfiguration,我在这里配置后,打包,然后被引入,是无法生效的。

所以就想了一种折中的方法。

以前MybatisConfiguration是通过new出来的,现在通过MybatisExtraConfig.getMPConfig();来获取,这样获取到的MybatisConfiguration就已经添加好了拦截器。

完整Mybatis-Plus配置类例子,注意第43行:

MybatisConfigurationconfiguration=MybatisExtraConfig.getMPConfig();

@Slf4j@Configuration@MapperScan(basePackages=”com.ler.demo.mapper”,sqlSessionTemplateRef=”sqlSessionTemplate”)publicclassMybatisConfig{privatestaticfinalStringBASE_PACKAGE=”com.ler.demo.”;@Bean(“dataSource”)publicDataSourcedataSource(){try{DruidDataSourcedataSource=newDruidDataSource();dataSource.setDriverClassName(“com.mysql.cj.jdbc.Driver”);dataSource.setUrl(“jdbc:mysql://localhost/mp-extra?useUnicode=true&useSSL=false&characterEncoding=utf8&serverTimezone=Asia/Shanghai”);dataSource.setUsername(“root”);dataSource.setPassword(“adminadmin”);dataSource.setInitialSize(1);dataSource.setMaxActive(20);dataSource.setMinIdle(1);dataSource.setMaxWait(60_000);dataSource.setPoolPreparedStatements(true);dataSource.setMaxPoolPreparedStatementPerConnectionSize(20);dataSource.setTimeBetweenEvictionRunsMillis(60_000);dataSource.setMinEvictableIdleTimeMillis(300_000);dataSource.setValidationQuery(“SELECT1”);returndataSource;}catch(Throwablethrowable){log.error(“excaught”,throwable);thrownewRuntimeException();}}@Bean(name=”sqlSessionFactory”)publicSqlSessionFactorysqlSessionFactory(@Qualifier(“dataSource”)DataSourcedataSource)throwsException{MybatisSqlSessionFactoryBeanfactoryBean=newMybatisSqlSessionFactoryBean();factoryBean.setDataSource(dataSource);factoryBean.setVfs(SpringBootVFS.class);factoryBean.setTypeAliasesPackage(BASE_PACKAGE “entity”);Resource[]mapperResources=newPathMatchingResourcePatternResolver().getResources(“classpath*:mapper/*.xml”);factoryBean.setMapperLocations(mapperResources);// 43行 使用定义好的配置MybatisConfigurationconfiguration=MybatisExtraConfig.getMPConfig();configuration.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class);configuration.setJdbcTypeForNull(JdbcType.NULL);configuration.setMapUnderscoreToCamelCase(true);configuration.addInterceptor(newSqlExplainInterceptor());configuration.setUseGeneratedKeys(true);factoryBean.setConfiguration(configuration);returnfactoryBean.getObject();}@Bean(name=”sqlSessionTemplate”)publicSqlSessionTemplatesqlSessionTemplate(@Qualifier(“sqlSessionFactory”)SqlSessionFactorysqlSessionFactory){returnnewSqlSessionTemplate(sqlSessionFactory);}@Bean(name=”transactionManager”)publicPlatformTransactionManagerplatformTransactionManager(@Qualifier(“dataSource”)DataSourcedataSource){returnnewDataSourceTransactionManager(dataSource);}@Bean(name=”transactionTemplate”)publicTransactionTemplatetransactionTemplate(@Qualifier(“transactionManager”)PlatformTransactionManagertransactionManager){returnnewTransactionTemplate(transactionManager);}}在实体类上建立关系@Data@EqualsAndHashCode(callSuper=false)@Accessors(chain=true)@ApiModel(value=”UserAccount对象”,description=”用户相关”)publicclassUserAccountextendsModel<UserAccount>{privatestaticfinallongserialVersionUID=1L;@ApiModelProperty(value=”id”)@TableId(value=”id”,type=IdType.AUTO)privateLongid;@ApiModelProperty(value=”昵称”)privateStringnickName;@TableField(exist=false)//把id的值作为userId在UserAddressMapper中查询@One2One(self=”id”,as=”user_id”,mapper=UserAddressMapper.class)privateUserAddressaddress;@TableField(exist=false)@One2Many(self=”id”,as=”user_id”,mapper=UserHobbyMapper.class)privateList<UserHobby>hobbies;@TableField(exist=false)@Many2Many(self=”id”,leftMid=”user_id”,rightMid=”class_id”,origin=”id”,midMapper=UserMidClassMapper.class,mapper=UserClassMapper.class)privateList<UserClass>classes;@OverrideprotectedSerializablepkVal(){returnthis.id;}}

主要是那几个注解。对了还要加@TableField(exist = false),不然会报错。

测试接口@Slf4j@RestController@RequestMapping(“/user”)@Api(value=”/user”,description=”用户”)publicclassUserAccountController{@ResourceprivateUserAccountServiceuserAccountService;@ResourceprivateUserAccountMapperuserAccountMapper;@ApiOperation(“查询一个”)@ApiImplicitParams({@ApiImplicitParam(name=””,value=””,required=true),})@GetMapping(value=”/one”,name=”查询一个”)publicHttpResultone(){// serviceUserAccountaccount=userAccountService.getById(1L);// mapper//UserAccountaccount=userAccountMapper.selectById(1L);// AR模式//UserAccountaccount=newUserAccount();//account.setId(1L);//account=account.selectById();returnHttpResult.success(account);}}

接口非常简单,调用内置的getById,可是却查出了所有相关的数据,这都是因为配置的那些注解。

mybatisplus代码生成器使用(mybatisplus多表关联查询)

可以看到其实发送了好几条SQL。第一条是userAccountService.getById(1L),后面几条都是自动发送的。

源码 https://gitee.com/github-26359270/mp-extra

GitHub: https://github.com/LerDer/mp-extra

示例 https://gitee.com/github-26359270/mp-extra-demo

总结

发表评论

登录后才能评论