Mybatis jpa mini 代码解析
2018-04-18 update
当前文章已过时,请访问代码仓库查看当前版本wiki。
github https://github.com/cnsvili/mybatis-jpa
gitee https://gitee.com/svili/mybatis-jpa
--------------------------------------
源码地址(git):https://github.com/LittleNewbie/mybatis-jpa
一、Mybatis简介
mybatis中文官方文档:http://www.mybatis.org/mybatis-3/zh/index.html
简介是为后面用到的内容做铺垫,熟悉mybatis的朋友可以直接跳过,到第二章节。
关于mybatis-jpa的使用方式,请参见博文:http://www.cnblogs.com/svili/p/6828077.html
1.1 SqlSession
Mybatis中3个重要的概念:Configuration(容器),SqlSessionFactory(工厂),SqlSession;
相对于Spring中的applicationContext,BeanFactory,Bean。
不同之处在于SqlSession包含了所有的SQL方法,即这个SqlSession有且只有一个。SqlSession可以执行mybatis中注册的所有方法。官方示例说明
<!-- SqlSession 完全包含了面向数据库执行 SQL 命令所需的所有方法。
你可以通过 SqlSession 实例来直接执行已映射的 SQL 语句。例如:-->
SqlSession session = sqlSessionFactory.openSession();
try {
Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
} finally {
session.close();
} <!-- 映射器实例(Mapper Instances)-->
SqlSession session = sqlSessionFactory.openSession();
try {
BlogMapper mapper = session.getMapper(BlogMapper.class);
// do work
} finally {
session.close();
}
1.2 Namespace
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!-- xml中命名空间与java中Mapper接口一致 -->
<mapper namespace="org.apache.ibatis.submitted.rounding.Mapper"> </mapper>
1.3 ResultType 和 ResultMap
ResultMap是myabtis最重要最强大的元素,是SQL列名columnName与POJO属性名filedName的高级结果映射。对ResultMap不熟悉的朋友可以阅读官方文档了解。
ResultType可以理解为mybatis自动映射生成的简单的ResultMap,当POJO中的filedName与数据库ColumnName不一致时,无法完成映射。
1.4 javaType和typeHandler
ResultMap中使用
<resultMap id="userResultMap" type="User">
<!-- 主键 -->
<id property="id" column="user_id" />
<!-- 当column类型与field类型不一致时,需指定javaType或typeHandler,二者选其一即可。 -->
<!-- 简单的类型转换只需指定javaType即可,需要逻辑处理的使用typeHandler -->
<result property="remark" column="remark" javaType="string"/>
<result property="effective" column="is_effective" typeHandler="BooleanTypeHandler"/>
</resultMap>
1.5 jdbcType
官方说明:
jdbcType JDBC 类型是 仅需要对插入,更新和删除操作可能为空的列进行处理。这是 JDBC jdbcType 的需要,而不是 MyBatis 的。
NOTE: 如果 null 被当作值来传递,对于所有可能为空的列,JDBC Type 是需要的。你可以自己通过阅读预处理语句的 setNull() 方法的 JavaDocs 文档来研究这种情况。java.sql.PreparedStatement.setNull()
示例:
<insert id="insertAuthor">
insert into Author
(id, username)
values
<!-- 请注意,当sql中参数可能为null时,需要指定jdbcType,不然会出错 -->
('1', #{username,jdbcType=VARCHAR})
</insert>
1.6 MappedStatement
Mapper中的方法(方法签名和可执行的sql语句)会被封装为MappedStatement注册到Configuration中。
详见mybatis源码MapperBuilderAssistant.addMappedStatement(args);
二、Mybatis JPA
2.1 需求
1)首先,我们希望能够与spring集成,使用spring的依赖注入。
2)其次,我们希望能够兼容spring-mybatis集成的代码,拒绝污染。
3)解析注册ResultMap和MappedStatement。
2.2 主线
1)参考spring data jpa,使用@RepositoryDefinition注解,标记需要自动生成sql的dao。
我们使用@MapperDefinition和@StatementDefinition注解,标记需要自动生成sql的dao和method。
这个是关键,既保证了不污染原有代码,又可以使用spring-mybatis已经实现的依赖注入。
我们只需要在此基础上,对特定注解标注的mapper类和方法做处理即可。
2)参考spring-mybatis
MapperScannerConfigurer,扫描mapper并注册到mybatis Configuration中,继而生成代理类。
MapperAnnotationBuilder实现java注解生成ResultMap和MappedStatement。
2.3 入口-MapperEnhancerScaner
在spring容器初始化后,对@MapperDefinition标注的mapper类进行扫描。
2.4 解析POJO 生成ResultMap
重点:columnName与fieldName映射,特殊字段的jdbcType和typeHandler。
1)columnName与fieldName映射,使用JPA注解 @Cloumn即可,但是,我们希望能够自动转换驼峰与下划线风格,即对于符合规范命名的,不需要注解,直接映射。参见:PersistentUtil,ColumnNameUtil。
/**
* 将驼峰标识转换为下划线
*
* @param text
* @return camel
*/
public static String camelToUnderline(String text) {
if (text == null || "".equals(text.trim())) {
return "";
}
StringBuilder result = new StringBuilder(text.length() + 1);
result.append(text.substring(0, 1));
for (int i = 1; i < text.length(); i++) {
if (!Character.isLowerCase(text.charAt(i))) {
result.append('_');
}
result.append(text.substring(i, i + 1));
}
return result.toString().toLowerCase();
} /**
* 将下划线标识转换为驼峰
*
* @param text
* @return underline
*/
public static String underlineToCamel(String text) {
if (text == null || "".equals(text.trim())) {
return "";
}
int length = text.length();
StringBuilder result = new StringBuilder();
for (int i = 0; i < length; i++) {
char c = text.charAt(i);
if (c == '_') {
if (++i < length) {
result.append(Character.toUpperCase(text.charAt(i)));
}
} else {
result.append(c);
}
}
return result.toString();
}
ColunNameUtil
2)jdbcType和typeHandler
处理了以下3种类型:POJO中的Enum,Boolean,以及数据库中的CLOB,代码见MybatisColumnMeta。
需要强调说明的是,这里为所有的field都声明了jdbcType,是为了规避sql中参数为null时,产生异常。
/** meta resolver */
private static class ColumnMetaResolver { public static String resolveJdbcAlias(Field field) { Class<?> fieldType = field.getType();
if (field.getType().isEnum()) {
if (field.isAnnotationPresent(Enumerated.class)) {
// 获取注解对象
Enumerated enumerated = field.getAnnotation(Enumerated.class);
// 设置了value属性
if (enumerated.value() == EnumType.ORDINAL) {
return "INTEGER";
}
}
return "VARCHAR";
}
if (field.isAnnotationPresent(Lob.class)) {
if (String.class.equals(fieldType)) {
return "CLOB";
}
}
if (Integer.class.equals(fieldType)) {
return "INTEGER";
}
if (Double.class.equals(fieldType)) {
return "DOUBLE";
}
if (Float.class.equals(fieldType)) {
return "FLOAT";
}
if (String.class.equals(fieldType)) {
return "VARCHAR";
}
// date类型需声明
if (java.util.Date.class.isAssignableFrom(fieldType)) {
return "TIMESTAMP";
}
return null;
} public static JdbcType resolveJdbcType(String alias) {
if (alias == null) {
return null;
}
try {
return JdbcType.valueOf(alias);
} catch (IllegalArgumentException e) {
throw new BuilderException("Error resolving JdbcType. Cause: " + e, e);
}
} @SuppressWarnings("unchecked")
public static Class<? extends TypeHandler<?>> resolveTypeHandler(Field field) {
Class<? extends TypeHandler<?>> typeHandlerClass = null;
if (field.getType().isEnum()) {
typeHandlerClass = (Class<? extends TypeHandler<?>>) EnumTypeHandler.class;
if (field.isAnnotationPresent(Enumerated.class)) {
// 获取注解对象
Enumerated enumerated = field.getAnnotation(Enumerated.class);
// 设置了value属性
if (enumerated.value() == EnumType.ORDINAL) {
typeHandlerClass = (Class<? extends TypeHandler<?>>) EnumOrdinalTypeHandler.class;
}
}
} if (field.getType().equals(Boolean.class)) {
typeHandlerClass = (Class<? extends TypeHandler<?>>) BooleanTypeHandler.class;
}
return typeHandlerClass;
}
}
ColumnMetaResolver
3)ResultMap注册
见ResultMapAdapter.parseResultMap(args);
2.5 MappedStatement注册
分类处理,select需要用到ResultMap,默认为Pojo.getSimpleName() + "ResultMap";
insert和insertSelective的区别:在于null值的处理,假设column_1在数据库设置了默认值,而参数中的field_1为null值,则insert 在数据库写入null,而insertSelective写入数据库默认值.
需要特别说明的是,动态SQL需要使用"<script></script>"标签包围。
对于各种sql方法的语句生成方法,详见com.mybatis.jpa.statement.builder包下的类。
这里以InsertSelective和select为例
public class InsertSelectiveBuilder implements StatementBuildable {
@Override
public String buildSQL(PersistentMeta persistentMeta, Method method) {
// columns
StringBuilder columns = new StringBuilder();
columns.append("<trim prefix='(' suffix=')' suffixOverrides=',' > ");
// values
StringBuilder values = new StringBuilder();
values.append("<trim prefix='(' suffix=')' suffixOverrides=',' > ");
for (MybatisColumnMeta columnMeta : persistentMeta.getColumnMetaMap().values()) {
// columns
columns.append("<if test='" + columnMeta.getProperty() + "!= null'> ");
columns.append(columnMeta.getColumnName() + ", ");
columns.append("</if> ");
// values
values.append("<if test='" + columnMeta.getProperty() + "!= null'> ");
values.append(SqlAssistant.resolveSqlParameter(columnMeta) + ", ");
values.append("</if> ");
}
columns.append("</trim> ");
values.append("</trim> ");
return "<script>" + "INSERT INTO " + persistentMeta.getTableName() + columns.toString() + " VALUES "
+ values.toString() + "</script>";
}
@Override
public void parseStatement(MybatisStatementAdapter adapter, PersistentMeta persistentMeta, Method method) {
// 方法名
adapter.setMethodName(method.getName());
// 参数类型
adapter.setParameterTypeClass(persistentMeta.getType());
// sqlScript
adapter.setSqlScript(buildSQL(persistentMeta, method));
// 返回值类型
adapter.setResultType(int.class);
adapter.setResultMapId(null);
adapter.setSqlCommandType(SqlCommandType.INSERT);
// 主键策略
adapter.setKeyGenerator(new NoKeyGenerator());
adapter.setKeyProperty(null);
adapter.setKeyColumn(null);
adapter.parseStatement();
}
}
InsertSelectiveBuilder
public class SelectBuilder implements StatementBuildable {
@Override
public String buildSQL(PersistentMeta persistentMeta, Method method) {
return "SELECT " + persistentMeta.getColumnNames() + " FROM " + persistentMeta.getTableName()
+ SqlAssistant.buildSingleCondition(method, persistentMeta);
}
@Override
public void parseStatement(MybatisStatementAdapter adapter, PersistentMeta persistentMeta, Method method) {
// 方法名
adapter.setMethodName(method.getName());
// 参数类型
if (method.getParameterTypes().length > 0) {
// Mybatis mapper 方法最多支持一个参数,先设置成Object.class,mybatis会在sql中解析
adapter.setParameterTypeClass(Object.class);
} else {
adapter.setParameterTypeClass(void.class);
}
String orderBy = " ";
if (method.isAnnotationPresent(OrderBy.class)) {
orderBy = " order by " + method.getAnnotation(OrderBy.class).value();
}
// sqlScript
adapter.setSqlScript(buildSQL(persistentMeta, method) + orderBy);
// 返回值类型
adapter.setResultType(persistentMeta.getType());
adapter.setResultMapId(persistentMeta.getType().getSimpleName() + "ResultMap");
adapter.setSqlCommandType(SqlCommandType.SELECT);
// 主键策略
adapter.setKeyGenerator(new NoKeyGenerator());
adapter.setKeyProperty(null);
adapter.setKeyColumn(null);
adapter.parseStatement();
}
SelectBuilder
ok,以上就是mybatis-jpa的主要设计思路了,具体的细节,我已经尽可能的在代码中增加注释。
关于mybatis-jpa的代码构建使用方式,请参见博文:http://www.cnblogs.com/svili/p/6828077.html
由于个人能力有限,代码可能有些简陋,如有不妥之处,欢迎指正交流。
Mybatis jpa mini 代码解析的更多相关文章
- Mybatis JPA 代码构建
前段时间了解到Spring JPA,感觉挺好用,但其依赖于Hibernate,本人看到Hibernate就头大(不是说Hibernate不好哈,而是进阶太难),于是做了一个迷你版的Mybatis JP ...
- Mybatis JPA 插件简介
前段时间了解到Spring JPA,感觉挺好用,但其依赖于Hibernate,本人看到Hibernate就头大(不是说Hibernate不好哈,而是进阶太难),于是做了一个迷你版的Mybatis JP ...
- 【MyBatis】MyBatis自动生成代码查询之爬坑记
前言 项目使用SSM框架搭建Web后台服务,前台后使用restful api,后台使用MyBatisGenerator自动生成代码,在前台使用关键字进行查询时,遇到了一些很宝贵的坑,现记录如下.为展示 ...
- Mybatis JPA 插件简介(v2.1.0)
相比之前的版本(v1.1.0),此版本(v2.1.0)做了较大的改动. 项目地址: github https://github.com/cnsvili/mybatis-jpa gitee https: ...
- MyBatis 3源码解析(一)
一.SqlSessionFactory 对象初始化 //加载全局配置文件 String resource = "mybatis-config.xml"; InputStream i ...
- Springboot+MyBatis+JPA集成
1.前言 Springboot最近可谓是非常的火,本人也在项目中尝到了甜头.之前一直使用Springboot+JPA,用了一段时间发现JPA不是太灵活,也有可能是我不精通JPA,总之为了多学学Sp ...
- Mybatis最入门---代码自动生成(generatorConfig.xml配置)
[一步是咫尺,一步即天涯] 经过前文的叙述,各位看官是不是已经被Mybatis的强大功能给折服了呢?本文我们将介绍一个能够极大提升我们开发效率的插件:即代码自动生成.这里的代码自动生成包括,与数据库一 ...
- 集成Springboot+MyBatis+JPA
1.前言 Springboot最近可谓是非常的火,本人也在项目中尝到了甜头.之前一直使用Springboot+JPA,用了一段时间发现JPA不是太灵活,也有可能是我不精通JPA,总之为了多学学Spri ...
- springboot mybatis 自动生成代码(maven+IntelliJ IDEA)
1.在pom文件中加入需要的依赖(mybatis-generator-core) 和 插件(mybatis-generator-maven-plugin) <dependency> < ...
随机推荐
- Dede CMS如何在文章中增加“附件下载”操作说明
1.进入后台--在"附件管理"中选择"上传新文件" 2.在"说明标题"输入要上传文件的名字,并在下面浏览找到要上传的文件,保存. 3.在&q ...
- 脑洞大开--一条项目中常用的linux命令引发的经典算法题
小时候家里定了<读者>的月刊,里面记录一个故事:说有有个偏僻的乡村一日突然来了一个美女,她携着万贯家财子女在当地安家落户,成了当地的乡绅.她让她的子女世世代代的保守这个秘密,直到这个秘密不 ...
- .Net中的AOP系列之《AOP实现类型》
返回<.Net中的AOP>系列学习总目录 本篇目录 AOP是如何跑起来的 运行时编织 复习代理模式 动态代理 编译时编织 后期编译(PostCompiling) 来龙去脉 运行时编织 VS ...
- 请教 C# 异步 async await 问题
各位园友,之前对C#异步只是肤浅了解,请教一个具体问题. 需求: 前台会发送一个Array,这个数组都是 id的集合,比较大.分两步,首先保存这些id,然后去调用异步方法. 可以正常返回json,也可 ...
- [转] 传说中的WCF(2):服务协定的那些事儿
上一篇文章中,我们抛出了N个问题:WCF到底难不难学?复杂吗?如果复杂,可以化繁为简吗? 其实,这些问题的答案全取决于你的心态,都说“态度决定一切”,这句话,不知道各位信不信,反正我是信了.首先,敢于 ...
- 编写高质量代码:改善Java程序的151个建议(第二章:基本类型)
编写高质量代码:改善Java程序的151个建议(第二章:基本类型) 目录 建议21:用偶判断,不用奇判断 建议22:用整数类型处理货币 建议23:不要让类型默默转换 建议24:边界还是边界 建议25: ...
- ZooKeeper源码分析-Jute-第二部分
数据类型和流 本部分描述支持Hadoop的基础数据类型以及复合类型.我们的目的是支持一系列的类型,可以用于在不同的编程语言中简化和有效表达一定范围的记录类型. 基础类型 大部分情况下,Hadoop的大 ...
- WebService小记
这个问题找了好多地方都没有结果,自己暂且总结一下吧,也不算是解决问题的根本途径,但是也不失为一种办法.当时用了wsimport wsdl2java xfire 都没有解决,大牛能解决的话,欢迎留言. ...
- php下拉菜单选中值在选择之后保留并且显示
<select id="select_platform" name="source" style="height:35px;"> ...
- Chapter 1. Introduce
前言 本书全名是<H.264 and MPEG-4 Video Compression, Video Coding For Next-generation Multimedia>,作者为 ...