2018-04-18 updatehtml
當前文章已過期,請訪問代碼倉庫查看當前版本wiki。java
github https://github.com/cnsvili/mybatis-jpagit
gitee https://gitee.com/svili/mybatis-jpagithub
--------------------------------------spring
源碼地址(git):https://github.com/LittleNewbie/mybatis-jpasql
mybatis中文官方文檔:http://www.mybatis.org/mybatis-3/zh/index.html數據庫
簡介是爲後面用到的內容作鋪墊,熟悉mybatis的朋友能夠直接跳過,到第二章節。apache
關於mybatis-jpa的使用方式,請參見博文:http://www.cnblogs.com/svili/p/6828077.htmlsession
Mybatis中3個重要的概念:Configuration(容器),SqlSessionFactory(工廠),SqlSession;mybatis
相對於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(); }
<!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>
ResultMap是myabtis最重要最強大的元素,是SQL列名columnName與POJO屬性名filedName的高級結果映射。對ResultMap不熟悉的朋友能夠閱讀官方文檔瞭解。
ResultType能夠理解爲mybatis自動映射生成的簡單的ResultMap,當POJO中的filedName與數據庫ColumnName不一致時,沒法完成映射。
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>
官方說明:
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>
Mapper中的方法(方法簽名和可執行的sql語句)會被封裝爲MappedStatement註冊到Configuration中。
詳見mybatis源碼MapperBuilderAssistant.addMappedStatement(args);
1)首先,咱們但願可以與spring集成,使用spring的依賴注入。
2)其次,咱們但願可以兼容spring-mybatis集成的代碼,拒絕污染。
3)解析註冊ResultMap和MappedStatement。
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。
在spring容器初始化後,對@MapperDefinition標註的mapper類進行掃描。
重點: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);
分類處理,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的代碼構建使用方式,請參見博文:http://www.cnblogs.com/svili/p/6828077.html
因爲我的能力有限,代碼可能有些簡陋,若有不妥之處,歡迎指正交流。