本文共 9898 字,大约阅读时间需要 32 分钟。
Mybatis中3个重要的概念:Configuration(容器),SqlSessionFactory(工厂),SqlSession;
相对于Spring中的applicationContext,BeanFactory,Bean。
不同之处在于SqlSession包含了所有的SQL方法,即这个SqlSession有且只有一个。SqlSession可以执行mybatis中注册的所有方法。官方示例说明
SqlSession session = sqlSessionFactory.openSession();try { Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);} finally { session.close();} SqlSession session = sqlSessionFactory.openSession();try { BlogMapper mapper = session.getMapper(BlogMapper.class); // do work} finally { session.close();}
ResultMap是myabtis最重要最强大的元素,是SQL列名columnName与POJO属性名filedName的高级结果映射。对ResultMap不熟悉的朋友可以阅读官方文档了解。
ResultType可以理解为mybatis自动映射生成的简单的ResultMap,当POJO中的filedName与数据库ColumnName不一致时,无法完成映射。
ResultMap中使用
sql中使用
insert into Author (id, username) values ('1', #{username,jdbcType=VARCHAR})
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时,产生异常。
1 /** meta resolver */ 2 private static class ColumnMetaResolver { 3 4 public static String resolveJdbcAlias(Field field) { 5 6 Class fieldType = field.getType(); 7 if (field.getType().isEnum()) { 8 if (field.isAnnotationPresent(Enumerated.class)) { 9 // 获取注解对象10 Enumerated enumerated = field.getAnnotation(Enumerated.class);11 // 设置了value属性12 if (enumerated.value() == EnumType.ORDINAL) {13 return "INTEGER";14 }15 }16 return "VARCHAR";17 }18 if (field.isAnnotationPresent(Lob.class)) {19 if (String.class.equals(fieldType)) {20 return "CLOB";21 }22 }23 if (Integer.class.equals(fieldType)) {24 return "INTEGER";25 }26 if (Double.class.equals(fieldType)) {27 return "DOUBLE";28 }29 if (Float.class.equals(fieldType)) {30 return "FLOAT";31 }32 if (String.class.equals(fieldType)) {33 return "VARCHAR";34 }35 // date类型需声明36 if (java.util.Date.class.isAssignableFrom(fieldType)) {37 return "TIMESTAMP";38 }39 return null;40 }41 42 public static JdbcType resolveJdbcType(String alias) {43 if (alias == null) {44 return null;45 }46 try {47 return JdbcType.valueOf(alias);48 } catch (IllegalArgumentException e) {49 throw new BuilderException("Error resolving JdbcType. Cause: " + e, e);50 }51 }52 53 @SuppressWarnings("unchecked")54 public static Class > resolveTypeHandler(Field field) {55 Class > typeHandlerClass = null;56 if (field.getType().isEnum()) {57 typeHandlerClass = (Class >) EnumTypeHandler.class;58 if (field.isAnnotationPresent(Enumerated.class)) {59 // 获取注解对象60 Enumerated enumerated = field.getAnnotation(Enumerated.class);61 // 设置了value属性62 if (enumerated.value() == EnumType.ORDINAL) {63 typeHandlerClass = (Class >) EnumOrdinalTypeHandler.class;64 }65 }66 }67 68 if (field.getType().equals(Boolean.class)) {69 typeHandlerClass = (Class >) BooleanTypeHandler.class;70 }71 return typeHandlerClass;72 }73 }
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为例
1 public class InsertSelectiveBuilder implements StatementBuildable { 2 3 @Override 4 public String buildSQL(PersistentMeta persistentMeta, Method method) { 5 // columns 6 StringBuilder columns = new StringBuilder(); 7 columns.append(""); 8 // values 9 StringBuilder values = new StringBuilder();10 values.append(" ");11 for (MybatisColumnMeta columnMeta : persistentMeta.getColumnMetaMap().values()) {12 // columns13 columns.append(" ");14 columns.append(columnMeta.getColumnName() + ", ");15 columns.append(" ");16 // values17 values.append(" ");18 values.append(SqlAssistant.resolveSqlParameter(columnMeta) + ", ");19 values.append(" ");20 }21 22 columns.append(" ");23 values.append(" ");24 25 return " ";27 }28 29 @Override30 public void parseStatement(MybatisStatementAdapter adapter, PersistentMeta persistentMeta, Method method) {31 // 方法名32 adapter.setMethodName(method.getName());33 // 参数类型34 adapter.setParameterTypeClass(persistentMeta.getType());35 // sqlScript36 adapter.setSqlScript(buildSQL(persistentMeta, method));37 // 返回值类型38 adapter.setResultType(int.class);39 adapter.setResultMapId(null);40 41 adapter.setSqlCommandType(SqlCommandType.INSERT);42 43 // 主键策略44 adapter.setKeyGenerator(new NoKeyGenerator());45 adapter.setKeyProperty(null);46 adapter.setKeyColumn(null);47 48 adapter.parseStatement();49 50 }51 52 }
1 public class SelectBuilder implements StatementBuildable { 2 3 @Override 4 public String buildSQL(PersistentMeta persistentMeta, Method method) { 5 return "SELECT " + persistentMeta.getColumnNames() + " FROM " + persistentMeta.getTableName() 6 + SqlAssistant.buildSingleCondition(method, persistentMeta); 7 } 8 9 @Override10 public void parseStatement(MybatisStatementAdapter adapter, PersistentMeta persistentMeta, Method method) {11 // 方法名12 adapter.setMethodName(method.getName());13 // 参数类型14 if (method.getParameterTypes().length > 0) {15 // Mybatis mapper 方法最多支持一个参数,先设置成Object.class,mybatis会在sql中解析16 adapter.setParameterTypeClass(Object.class);17 } else {18 adapter.setParameterTypeClass(void.class);19 }20 21 String orderBy = " ";22 23 if (method.isAnnotationPresent(OrderBy.class)) {24 orderBy = " order by " + method.getAnnotation(OrderBy.class).value();25 }26 27 // sqlScript28 adapter.setSqlScript(buildSQL(persistentMeta, method) + orderBy);29 // 返回值类型30 adapter.setResultType(persistentMeta.getType());31 adapter.setResultMapId(persistentMeta.getType().getSimpleName() + "ResultMap");32 33 adapter.setSqlCommandType(SqlCommandType.SELECT);34 35 // 主键策略36 adapter.setKeyGenerator(new NoKeyGenerator());37 adapter.setKeyProperty(null);38 adapter.setKeyColumn(null);39 40 adapter.parseStatement();41 42 }