为什么80%的码农都做不了架构师?>>>   

源码地址(git):https://github.com/LittleNewbie/mybatis-jpa

一、Mybatis简介

mybatis中文官方文档:http://www.mybatis.org/mybatis-3/zh/index.html

简介是为后面用到的内容做铺垫,熟悉mybatis的朋友可以直接跳过,到第二章节。

关于mybatis-jpa的代码构建方式,请参见博文:https://my.oschina.net/LittleNewbie/blog/895198

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 mapperPUBLIC "-//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 jdbcType和typeHandler

ResultMap中使用

<resultMap id="userResultMap" type="User"><!--  主键   --><id property="id" column="user_id" /><!--  当column类型与field类型不一致时,需指定jdbcType或typeHandler,二者选其一即可。   --><!--  简单的类型转换只需指定jdbcType即可,需要逻辑处理的使用typeHandler  --><result property="remark" column="remark" jdbcType="CLOB"/><result property="effective" column="is_effective" typeHandler="BooleanTypeHandler"/>
</resultMap>

sql中使用

<insert id="insertAuthor">insert into Author(id, username)values<!--  请注意,当sql中参数可能为null时,需要指定jdbcType,不然会出错  -->('1', #{username,jdbcType=VARCHAR})
</insert>

1.5 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();}

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;}}

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 {@Overridepublic String buildSQL(PersistentMeta persistentMeta, Method method) {// columnsStringBuilder columns = new StringBuilder();columns.append("<trim prefix='(' suffix=')' suffixOverrides=',' > ");// valuesStringBuilder values = new StringBuilder();values.append("<trim prefix='(' suffix=')' suffixOverrides=',' > ");for (MybatisColumnMeta columnMeta : persistentMeta.getColumnMetaMap().values()) {// columnscolumns.append("<if test='" + columnMeta.getProperty() + "!= null'> ");columns.append(columnMeta.getColumnName() + ", ");columns.append("</if> ");// valuesvalues.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>";}@Overridepublic void parseStatement(MybatisStatementAdapter adapter, PersistentMeta persistentMeta, Method method) {// 方法名adapter.setMethodName(method.getName());// 参数类型adapter.setParameterTypeClass(persistentMeta.getType());// sqlScriptadapter.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();}}
public class SelectBuilder implements StatementBuildable {@Overridepublic String buildSQL(PersistentMeta persistentMeta, Method method) {return "SELECT " + persistentMeta.getColumnNames() + " FROM " + persistentMeta.getTableName()+ SqlAssistant.buildSingleCondition(method, persistentMeta);}@Overridepublic 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();}// sqlScriptadapter.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();}

ok,以上就是mybatis-jpa的主要设计思路了,具体的细节,我已经尽可能的在代码中增加注释。

关于mybatis-jpa的代码构建方式,请参见博文:https://my.oschina.net/LittleNewbie/blog/895198

由于个人能力有限,代码可能有些简陋,如有不妥之处,欢迎指正交流。

转载于:https://my.oschina.net/svili/blog/1486630

Mybatis JPA-集成方案+代码解析相关推荐

  1. BitGo的TSS门限签名方案代码解析

    1. 引言 前序博客有: 门限签名 threshold signature verifiable secret sharing可验证的秘密共享 开源代码见: https://github.com/Bi ...

  2. Musig方案代码解析

    1. 引言 Musig方案由Blockstream团队2018年论文<Simple Schnorr Multi-Signatures with Applications to Bitcoin&g ...

  3. Compact Multi-Signatures for Smaller Blockchains代码解析

    1. 引言 Boneh等人2018年论文<Compact Multi-Signatures for Smaller Blockchains>,论文解读参见博客 Compact Multi- ...

  4. mybatis连接mysql数据库连接池_对于数据库连接池的一些思考和MyBatis的集成与使用...

    Java应用要连接数据库需要先通过jdbc与数据库之间产生connection,然后通过sql语句产生statment,再执行这个statment查询的到ResultSet返回给应用,应用解析Resu ...

  5. 对于数据库连接池的一些思考和MyBatis的集成与使用

    Java应用要连接数据库需要先通过jdbc与数据库之间产生connection,然后通过sql语句产生statment,再执行这个statment查询的到ResultSet返回给应用,应用解析Resu ...

  6. SpringBoot | 第三十五章:Mybatis的集成和使用

    2019独角兽企业重金招聘Python工程师标准>>> 前言 最近收到公众号留言说,单纯的Mybatis的集成和使用.前面在第九章:Mybatis-plus的集成和使用介绍了基于my ...

  7. 海康视频监控集成方案

    注意问题 高版本监控与低版本监控 高版本监控可以在高版本浏览器上使用websocket进行解析播放,低版本监控只能在低版本浏览器上使用插件播放. 针对不同的设备选择不同的集成方案即可.可以在我的资源中 ...

  8. D2Admin - 优雅的管理系统PC端集成方案

    D2Admin 是一个 注重外观表现 的管理系统 PC端 集成方案,现在正在紧张开发维护. 前言 距离 D2Admin 正式和大家见面(开源),已经过去一个多月了. 现在想想这一个月真是很忙碌,几乎所 ...

  9. element vue 动态单选_软件更新丨vue-element-admin 4.0.0 beta 发布,后台集成方案

    vue-element-admin 4.0.0 beta 发布了. vue-element-admin 是一个后台集成解决方案,它基于 vue 和 element.它使用了最新的前端技术栈,内置了 i ...

最新文章

  1. 什么是LAN(局域网)
  2. python里的tplt什么意思 Python的format格式化输出
  3. Object arrays cannot be loaded when allow_pickle=False
  4. 区别于2.4g 4.33的780m无线模块
  5. 07/11/20 资料整理
  6. 【数据结构与算法】之深入解析“寻找旋转排序数组中的最小值II”的求解思路与算法示例
  7. 150. 逆波兰表达式求值---JAVA---LeetCode
  8. mysql 性能问题_mysql 性能问题
  9. 深入探究VC —— 资源编译器rc.exe(3)
  10. 蓝桥杯2017年第八届C/C++省赛C组第八题-九宫幻方
  11. ThymeLeaf的eclipse插件安装
  12. mysql 修改密码
  13. sap代加工流程图_委外加工_SAP的两种典型委外处理方法
  14. ORB_SLAM2系列之三:ORB_SLAM2跑RGBD SLAM数据集
  15. Online Adaptation of Convolutional Neural Networks for Video Object Segmentation论文阅读
  16. 万达商管冲刺香港上市:上半年净赚20亿元,已提前“套现”约63亿
  17. JS - 解决鼠标单击、双击事件冲突问题(同时实现两种事件响应)
  18. Java去掉字符串首尾的““
  19. 【高德地图进阶】--- 带图片的点(1)
  20. 计算机名词解释(计算机科学导论)

热门文章

  1. CentOS6安装Cisco模拟器Dynamips
  2. hadoop 动态添加节点datanode及tasktracker
  3. IE 6和IE 7对于按钮背景图案的处理 【转载】
  4. [详解]Linux的目录里都装些啥??
  5. 2017年网络犯罪现状分析报告
  6. PHP Object对象转换为Array数组
  7. 解决Android单个dex文件不能超过65536个方法问题
  8. 不用ajax调用搞后台小技巧
  9. Android学习系列(16)--App列表之圆角ListView
  10. 基于SpringBoot的后台管理系统(异常、注解、node、page)(二)