之前始终应用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 操作智能剖析阻断,也可自定义拦挡规定,预防误操作
具体的能够去官网看: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 @interface One2One {
/**
* 本类主键列名
*/
String self() default "id";
/**
* 本类主键在关联表中的列名
*/
String as();
/**
* 关联的 mapper
*/
Class<? extends BaseMapper> 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 = "用户相干")
public class UserAccount extends Model<UserAccount> {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "id")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@ApiModelProperty(value = "昵称")
private String nickName;
@TableField(exist = false)
// 把 id 的值 作为 userId 在 UserAddressMapper 中 查问
@One2One(self = "id", as = "user_id", mapper = UserAddressMapper.class)
private UserAddress address;
@Override
protected Serializable pkVal() {return this.id;}
}
Mybatis 拦截器 One2OneInterceptor
这里不再具体介绍拦截器了,之前也写了几篇对于 Mybatis 拦截器的,有趣味的能够去看看。
Mybatis 拦截器实现 Geometry 类型数据存储与查问
Mybatis 拦截器打印残缺 SQL
@Intercepts({@Signature(type = ResultSetHandler.class, method = "handleResultSets", args = {Statement.class})
})
@Slf4j
public class One2OneInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {Object result = invocation.proceed();
if (result == null) {return null;}
if (result instanceof ArrayList) {ArrayList list = (ArrayList) result;
for (Object o : list) {handleOne2OneAnnotation(o);
}
} else {handleOne2OneAnnotation(result);
}
return result;
}
@SneakyThrows
private void handleOne2OneAnnotation(Object o) {Class<?> aClass = o.getClass();
Field[] fields = aClass.getDeclaredFields();
for (Field field : fields) {field.setAccessible(true);
One2One one2One = field.getAnnotation(One2One.class);
if (one2One != null) {String self = one2One.self();
Object value = MpExtraUtil.getValue(o, self);
String as = one2One.as();
Class<? extends BaseMapper> mapper = one2One.mapper();
BaseMapper baseMapper = SpringBeanFactoryUtils.getApplicationContext().getBean(mapper);
QueryWrapper<Object> eq = Condition.create().eq(as, value);
Object one = baseMapper.selectOne(eq);
field.set(o, one);
}
}
}
@Override
public Object plugin(Object o) {return Plugin.wrap(o, this);
}
@Override
public void setProperties(Properties properties) {}}
Mybatis
拦截器能够针对不同的场景进行拦挡,比方:
Executor
:拦挡执行器的办法。ParameterHandler
:拦挡参数的解决。ResultHandler
:拦挡后果集的解决。StatementHandler
:拦挡 Sql 语法构建的解决。
这里是通过拦挡后果集的形式,在返回的对象上查找这个注解,找到注解后,再依据注解的配置,主动去数据库查问,查到后果后把数据封装到返回的后果集中。这样就防止了本人去屡次调 Mapper
的查询方法。
难点:尽管注解上表明了是什么 Mapper
,可是在拦截器中取到的还是BaseMapper
,而用BaseMapper
切实不好查问,我试了很多办法,不过还好 Mybatis-Plus
反对应用 Condition.create().eq(as, value);
拼接条件 SQL
,而后能够应用baseMapper.selectOne(eq);
去查问。
public class MpExtraUtil {
@SneakyThrows
public static Object getValue(Object o, String name) {Class<?> aClass = o.getClass();
Field[] fields = aClass.getDeclaredFields();
for (Field field : fields) {field.setAccessible(true);
if (field.getName().equals(name)) {return field.get(o);
}
}
throw new IllegalArgumentException("未查问到名称为:" + name + "的字段");
}
}
MpExtraUtil
就是应用反射的形式,获取 id
的值。
再讲一个多对多的注解
@Inherited
@Documented
@Target({ElementType.FIELD})
@Retention(RetentionPolicy.RUNTIME)
public @interface Many2Many {
/**
* 本类主键列名
*/
String self() default "id";
/**
* 本类主键在两头表的列名
*/
String leftMid();
/**
* 另一个多方在两头表中的列名
*/
String rightMid();
/**
* 另一个多方在本表中的列名
*/
String origin();
/**
* 关联的 mapper
*/
Class<? extends BaseMapper> midMapper();
/**
* 关联的 mapper
*/
Class<? extends BaseMapper> 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
。
应用办法
先把表创立好,而后代码生成器一键生成代码。
CREATE TABLE `user_account` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT 'id',
`nick_name` varchar(40) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci DEFAULT NULL COMMENT '昵称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='用户相干';
CREATE TABLE `user_address` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '地址 id',
`user_id` bigint(20) DEFAULT NULL COMMENT '用户 id',
`address` varchar(200) DEFAULT NULL COMMENT '具体地址',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `user_class` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '课程 id',
`class_name` varchar(20) DEFAULT NULL COMMENT '课程名称',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=9 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `user_hobby` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '喜好 id',
`user_id` bigint(20) DEFAULT NULL COMMENT '用户 id',
`hobby` varchar(40) DEFAULT NULL COMMENT '喜好名字',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `user_mid_class` (`id` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '两头表 id',
`user_id` bigint(20) DEFAULT NULL COMMENT '用户 id',
`class_id` bigint(20) DEFAULT NULL COMMENT '课程 id',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=5 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
增加依赖,曾经公布到地方仓库了,能够间接应用:
<dependency>
<groupId>top.lww0511</groupId>
<artifactId>mp-extra</artifactId>
<version>1.0.1</version>
</dependency>
在启动类上增加注解
@EnableMpExtra
配置拦截器
因为个别我的项目都会配置本人的MybatisConfiguration
,我在这里配置后,打包,而后被引入,是无奈失效的。
所以就想了一种折中的办法。
以前 MybatisConfiguration
是通过 new
进去的,当初通过 MybatisExtraConfig.getMPConfig();
来获取,这样获取到的 MybatisConfiguration
就曾经增加好了拦截器。
残缺 Mybatis-Plus
配置类例子,留神第 43 行:
@Slf4j
@Configuration
@MapperScan(basePackages = "com.ler.demo.mapper", sqlSessionTemplateRef = "sqlSessionTemplate")
public class MybatisConfig {
private static final String BASE_PACKAGE = "com.ler.demo.";
@Bean("dataSource")
public DataSource dataSource() {
try {DruidDataSource dataSource = new DruidDataSource();
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("SELECT 1");
return dataSource;
} catch (Throwable throwable) {log.error("ex caught", throwable);
throw new RuntimeException();}
}
@Bean(name = "sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dataSource) throws Exception {MybatisSqlSessionFactoryBean factoryBean = new MybatisSqlSessionFactoryBean();
factoryBean.setDataSource(dataSource);
factoryBean.setVfs(SpringBootVFS.class);
factoryBean.setTypeAliasesPackage(BASE_PACKAGE + "entity");
Resource[] mapperResources = new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml");
factoryBean.setMapperLocations(mapperResources);
// 43 行 获取配置
MybatisConfiguration configuration = MybatisExtraConfig.getMPConfig();
configuration.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class);
configuration.setJdbcTypeForNull(JdbcType.NULL);
configuration.setMapUnderscoreToCamelCase(true);
configuration.addInterceptor(new SqlExplainInterceptor());
configuration.setUseGeneratedKeys(true);
factoryBean.setConfiguration(configuration);
return factoryBean.getObject();}
@Bean(name = "sqlSessionTemplate")
public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {return new SqlSessionTemplate(sqlSessionFactory);
}
@Bean(name = "transactionManager")
public PlatformTransactionManager platformTransactionManager(@Qualifier("dataSource") DataSource dataSource) {return new DataSourceTransactionManager(dataSource);
}
@Bean(name = "transactionTemplate")
public TransactionTemplate transactionTemplate(@Qualifier("transactionManager") PlatformTransactionManager transactionManager) {return new TransactionTemplate(transactionManager);
}
}
在实体类上建设关系
@Data
@EqualsAndHashCode(callSuper = false)
@Accessors(chain = true)
@ApiModel(value = "UserAccount 对象", description = "用户相干")
public class UserAccount extends Model<UserAccount> {
private static final long serialVersionUID = 1L;
@ApiModelProperty(value = "id")
@TableId(value = "id", type = IdType.AUTO)
private Long id;
@ApiModelProperty(value = "昵称")
private String nickName;
@TableField(exist = false)
// 把 id 的值 作为 userId 在 UserAddressMapper 中 查问
@One2One(self = "id", as = "user_id", mapper = UserAddressMapper.class)
private UserAddress address;
@TableField(exist = false)
@One2Many(self = "id", as = "user_id", mapper = UserHobbyMapper.class)
private List<UserHobby> hobbies;
@TableField(exist = false)
@Many2Many(self = "id", leftMid = "user_id", rightMid = "class_id", origin = "id"
, midMapper = UserMidClassMapper.class, mapper = UserClassMapper.class)
private List<UserClass> classes;
@Override
protected Serializable pkVal() {return this.id;}
}
次要是那几个注解。对了还要加@TableField(exist = false)
,不然会报错。
源码:https://www.douban.com/note/7…
测试接口
@Slf4j
@RestController
@RequestMapping("/user")
@Api(value = "/user", description = "用户")
public class UserAccountController {
@Resource
private UserAccountService userAccountService;
@Resource
private UserAccountMapper userAccountMapper;
@ApiOperation("查问一个")
@ApiImplicitParams({@ApiImplicitParam(name = "", value ="", required = true),
})
@GetMapping(value = "/one", name = "查问一个")
public HttpResult one() {
//service
UserAccount account = userAccountService.getById(1L);
//mapper
// UserAccount account = userAccountMapper.selectById(1L);
//AR 模式
// UserAccount account = new UserAccount();
// account.setId(1L);
// account = account.selectById();
return HttpResult.success(account);
}
}
接口非常简单,调用内置的getById
,可是却查出了所有相干的数据,这都是因为配置的那些注解。
能够看到其实发送了好几条SQL
。第一条是userAccountService.getById(1L)
,前面几条都是主动发送的。
总结
切实不想贴太多代码,其实还是挺简略的,源码地址还有示例地址都贴出来啦,有趣味的能够去看一下。感觉好用能够点个Star
。欢送大家一起来奉献。