Mybatis JPA-集成方案+代码解析

源码地址(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 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 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 {

	@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();

	}

}

 

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

	}

 

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

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

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

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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值