首先根据MyBatis Plus入门实践详解 搭建好工程。然后创建数据库表与相关的类。
表结构如下:
EmployeeMapper接口继承自BaseMapper<Employee>
public interface EmployeeMapper extends BaseMapper<Employee> { }
这个BaseMapper是com.baomidou.mybatisplus.mapper.BaseMapper。这里测试的MyBatis Plus版本是:
<dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus</artifactId> <version>2.3</version> </dependency>
BaseMapper定义了常用的增删改查接口,继承该接口后无需编写mapper.xml文件,即可获得通用的CRUD功能。
当然你可以在这个employeeMapper里面自定义方法,方法通过注解或者mapper.xml里面insert|update|select|delete实现。
【1】通用插入数据
① insert
测试代码如下:
@Test public void testCommonInsert() { //初始化Employee对象 Employee employee = new Employee(); employee.setLastName("MP"); employee.setEmail("mp@163.com"); employee.setSalary(20000.0); //插入到数据库 Integer result = employeeMapper.insert(employee); System.out.println("result: " + result ); //获取当前数据在数据库中的主键值 Integer key = employee.getId(); System.out.println("key:" + key ); }
测试结果如下:
结论:
insert方法在插入时, 会根据实体类的每个属性进行非空判断,只有非空的属性对应的字段才会出现到SQL语句中
② insertAllColumn
测试代码如下:
@Test public void testCommonInsert() { //初始化Employee对象 Employee employee = new Employee(); employee.setLastName("MP"); employee.setEmail("mp@163.com"); employee.setSalary(20000.0); //插入到数据库 Integer result = employeeMapper.insertAllColumn(employee); System.out.println("result: " + result ); //获取当前数据在数据库中的主键值 Integer key = employee.getId(); System.out.println("key:" + key ); }
测试结果如下:
结论
insertAllColumn方法在插入时, 不管属性是否非空, 属性所对应的字段都会出现到SQL语句中。
【2】通用更新数据
① updateById
测试代码如下:
@Test public void testCommonUpdate() { //初始化修改对象 Employee employee = new Employee(); employee.setId(7); employee.setLastName("小泽老师"); employee.setEmail("xz@sina.com"); employee.setGender(0); Integer result = employeeMapper.updateById(employee); System.out.println("result: " + result ); }
测试结果如下:
结论
updateById方法在更新时, 会根据实体类的每个属性进行非空判断,只有非空的属性对应的字段才会出现到SQL语句中
② updateAllColumnById
测试代码如下:
@Test public void testCommonUpdate() { //初始化修改对象 Employee employee = new Employee(); employee.setId(7); employee.setLastName("小泽老师"); employee.setEmail("xz@sina.com"); employee.setGender(0); Integer result = employeeMapper.updateAllColumnById(employee); System.out.println("result: " + result ); }
测试结果如下:
结论
updateAllColumnById方法在更新时, 不管属性是否非空, 属性所对应的字段都会出现到SQL语句中。
【3】通用查询数据
① selectById
测试代码如下:
Employee employee = employeeMapper.selectById(7); System.out.println(employee);
测试结果如下:
② selectOne多列查询
测试代码如下:
Employee employee = new Employee(); employee.setId(7); employee.setLastName("小泽老师"); employee.setGender(0); Employee result = employeeMapper.selectOne(employee); System.out.println("result: " +result );
测试结果如下:
这里需要注意的是使用selectOne时需要保证数据库中顶多查询出一条数据,否则就会报错nested exception is org.apache.ibatis.exceptions.TooManyResultsException: Expected one result (or null) to be returned by selectOne(), but found: 2
③ selectBatchIds
测试代码如下:
List<Integer> idList = new ArrayList<>(); idList.add(7); idList.add(8); idList.add(9); List<Employee> emps = employeeMapper.selectBatchIds(idList); System.out.println(emps);
测试结果如下:
④ selectByMap
测试代码如下:
Map<String,Object> columnMap = new HashMap<>(); //这里注意是列名, 非对象属性名 columnMap.put("last_name", "MP"); columnMap.put("gender", 1); List<Employee> emps = employeeMapper.selectByMap(columnMap); System.out.println(emps);
测试结果如下:
⑤ 分页查询selectPage
测试代码如下:
List<Employee> emps = employeeMapper.selectPage(new Page<>(2, 2), null); System.out.println(emps);
测试结果如下:
可以看到SQL语句上面并没有limit关键字进行分页,但是返回的数据结果确实是经过分页处理的。这说明其并非是真正的物理分页,而是借助于RowBounds实现的内存分页。如果想实现物理分页,可以考虑使用pageHelper插件。
故而上述分页实例并不推荐,推荐使用MyBatis Plus的分页插件。
【4】通用删除数据
① deleteById
测试代码如下:
Integer result = employeeMapper.deleteById(13); System.out.println("result: " + result );
测试结果如下:
将会返回删除条数,没有删除返回0。
② deleteByMap
根据组合条件删除,返回删除条数,没有删除返回0.
测试代码如下:
Map<String,Object> columnMap = new HashMap<>(); columnMap.put("last_name", "MP"); columnMap.put("email", "mp@163.com"); Integer result = employeeMapper.deleteByMap(columnMap); System.out.println("result: " + result );
测试结果如下:
③ deleteBatchIds
根据ID批量删除,返回删除的记录数,没有删除返回0。
测试代码如下:
vList<Integer> idList = new ArrayList<>(); idList.add(13); idList.add(14); idList.add(15); Integer result = employeeMapper.deleteBatchIds(idList); System.out.println("result: " + result );
测试结果如下:
【5】条件构造器使用
① 条件查询
如下,分页查询tbl_employee表中,年龄在18~50之间且性别为男且姓名为Tom的所有用户。
测试代码如下:
List<Employee> emps =employeeMapper.selectPage(new Page<Employee>(1, 2), new EntityWrapper<Employee>() .between("age", 18, 50) .eq("gender", 1) .eq("last_name", "Tom") ); System.out.println(emps);
这里使用new EntityWrapper
传一个Wrapper实例对象进去,使用between、eq这些关键字来封装查询条件(在JPA里面也可以看到这种思想)
。
查询SQL如下:
SELECT id,last_name AS lastName,email,gender,age FROM tbl_employee WHERE (age BETWEEN ? AND ? AND gender = ? AND last_name = ?)
还可以使用Wrapper的另外一个子类Condition来实现上述效果,condition和EntityWrapper都wrapper的子类:
List<Employee > emps = employeeMapper.selectPage( new Page<Employee>(1,2), Condition.create() .between("age", 18, 50) .eq("gender", "1") .eq("last_name", "Tom") ); System.out.println(emps);
查询SQL如下:
SELECT id,last_name AS lastName,email,gender,age FROM tbl_employee WHERE (age BETWEEN ? AND ? AND gender = ? AND last_name = ?)
② 组合条件模糊查询
查询tbl_employee表中, 性别为女并且名字中带有"老师" 或者 邮箱中带有"a"。
测试代码如下:
List<Employee> emps = employeeMapper.selectList( new EntityWrapper<Employee>() .eq("gender", 0) .like("last_name", "老师") .orNew() // SQL: (gender = ? AND last_name LIKE ?) OR (email LIKE ?) .like("email", "a") ); System.out.println(emps);
查询SQL如下:
SELECT id,last_name AS lastName,email,gender,age FROM tbl_employee WHERE (gender = ? AND last_name LIKE ?) OR (email LIKE ?) Parameters: 0(Integer), %老师%(String), %a%(String)
上面使用的是orNew,下面对比使用or的时候查询SQL:
List<Employee> emps = employeeMapper.selectList( new EntityWrapper<Employee>() .eq("gender", 0) .like("last_name", "老师") .or() // SQL: (gender = ? AND last_name LIKE ? OR email LIKE ?) .like("email", "a") ); System.out.println(emps); SELECT id,last_name AS lastName,email,gender,age FROM tbl_employee WHERE (gender = ? AND last_name LIKE ? OR email LIKE ?) Parameters: 0(Integer), %老师%(String), %a%(String)
可以发现OR只是作为一个查询条件,而orNew则封装了两个子查询。针对本例效果是一致的,但是在某些情况下结果会不一致。
③ 条件查询排序
查询性别为女的, 根据age进行排序(asc/desc), 简单分页。
测试代码如下:
List<Employee> emps = employeeMapper.selectList( new EntityWrapper<Employee>() .eq("gender", 0) .orderBy("age")//默认升序 可以使用orderAsc //.orderDesc(Arrays.asList(new String [] {"age"})) .last("desc limit 1,3") );
查询SQL如下:
SELECT id,last_name AS lastName,email,gender,age FROM tbl_employee WHERE (gender = ?) ORDER BY age desc limit 1,3
④ 更新数据
测试代码如下:
@Test public void testEntityWrapperUpdate() { Employee employee = new Employee(); employee.setLastName("苍老师"); employee.setEmail("cls@sina.com"); employee.setGender(0); employeeMapper.update(employee, new EntityWrapper<Employee>() .eq("last_name", "Tom") .eq("age", 44) ); }
更新SQL如下:
Preparing: UPDATE tbl_employee SET last_name=?, email=?, gender=? WHERE (last_name = ? AND age = ?) Parameters: 苍老师(String), cls@sina.com(String), 0(Integer), Tom(String), 44(Integer) Updates: 0
⑤ 删除数据
测试代码如下:
@Test public void testEntityWrapperDelete() { employeeMapper.delete( new EntityWrapper<Employee>() .eq("last_name", "Tom") .eq("age", 22) ); }
测试结果如下:
为什么继承了BaseMapper后,不需要写SQL,不需要mapper.xml配置文件,即可使用大多CRUD方法?
【6】MP 启动注入 SQL原理分析
以如下代码为例跟踪一下究竟发生了什么:
@Test public void testEntityWrapperUpdate() { Employee employee = new Employee(); employee.setLastName("苍老师"); employee.setEmail("cls@sina.com"); employee.setGender(0); Integer update = employeeMapper.update(employee, new EntityWrapper<Employee>() .eq("last_name", "Tom") .eq("age", 44) ); System.out.println(update); }
① 此时的employeeMapper对象是个什么?
此时的employeeMapper是个代理对象org.apache.ibatis.binding.MapperProxy@7728643a
,如下图所示其是一个代理对象。
这里sqlsessionTemplate是sqlsession实现类:
主要属性如下:
继续跟踪其sqlSessionFactory属性,即employeeMapper.sqlSession.sqlSessionFactory:
sqlSessionFactory只有一个属性configuration,其是MybatisConfiguration实例对象,该实例对象有MybatisMapperRegistry注册中心实例对象、 mappedStatements(Configuration$StrictMap)。那么什么是mappedStatements?我们看一下:
似乎找到了employeeMapper那么多默认功能的来源!项目启动的时候初始化employeeMapper对象时会自动注入这些方法!
那么具体如何注入的呢?我们跟下代码看看。
② debug运行代码
如下图所示,在启动的时候根据type注入了employeeMapper,在AbstractAutowireCapableBeanFactory类中调用了afterPropertiesSet方法,然后将一系列mapper的方法注入。
那么有两个问题:
- 1.调用了谁的afterPropertiesSet方法?afterPropertiesSet方法是InitializingBean接口的。
- 2.MappedStatement究竟是什么?
1.1 AbstractAutowireCapableBeanFactory.initializeBean
如下所示我们跟踪源码从这里开始,主要跟踪其invokeInitMethods方法。
protected Object initializeBean(final String beanName, final Object bean, RootBeanDefinition mbd) { if (System.getSecurityManager() != null) { AccessController.doPrivileged(new PrivilegedAction<Object>() { @Override public Object run() { invokeAwareMethods(beanName, bean); return null; } }, getAccessControlContext()); } else { invokeAwareMethods(beanName, bean); } Object wrappedBean = bean; if (mbd == null || !mbd.isSynthetic()) { //循环执行那些bean的后置处理器的postProcessBeforeInitialization方法 wrappedBean = applyBeanPostProcessorsBeforeInitialization(wrappedBean, beanName); } try { //调用初始化方法:如InitializingBean's {@code afterPropertiesSet} //or a custom init-method invokeInitMethods(beanName, wrappedBean, mbd); } catch (Throwable ex) { throw new BeanCreationException( (mbd != null ? mbd.getResourceDescription() : null), beanName, "Invocation of init method failed", ex); } //循环执行那些bean的后置处理器的postProcessBeforeInitialization方法 if (mbd == null || !mbd.isSynthetic()) { wrappedBean = applyBeanPostProcessorsAfterInitialization(wrappedBean, beanName); } return wrappedBean; }
1.2 AbstractAutowireCapableBeanFactory.invokeInitMethods
方法源码如下所示:
#这里的bean 是wrappedBean protected void invokeInitMethods(String beanName, final Object bean, RootBeanDefinition mbd) throws Throwable { boolean isInitializingBean = (bean instanceof InitializingBean); if (isInitializingBean && (mbd == null || !mbd.isExternallyManagedInitMethod("afterPropertiesSet"))) { if (logger.isDebugEnabled()) { logger.debug("Invoking afterPropertiesSet() on bean with name '" + beanName + "'"); } if (System.getSecurityManager() != null) { try { AccessController.doPrivileged(new PrivilegedExceptionAction<Object>() { @Override public Object run() throws Exception { ((InitializingBean) bean).afterPropertiesSet(); return null; } }, getAccessControlContext()); } catch (PrivilegedActionException pae) { throw pae.getException(); } } else { ((InitializingBean) bean).afterPropertiesSet(); } } if (mbd != null) { String initMethodName = mbd.getInitMethodName(); if (initMethodName != null && !(isInitializingBean && "afterPropertiesSet".equals(initMethodName)) && !mbd.isExternallyManagedInitMethod(initMethodName)) { invokeCustomInitMethod(beanName, bean, mbd); } } }
invokeInitMethods方法作用是什么?
在bean属性被设置后告诉bean的bean factory是什么并检测其是否实现了InitializingBean接口或者自定义了一个init method。如果是,则尝试调用其afterPropertiesSet以及invokeCustomInitMethod(调用自定义的init method方法)。
为什么要从这里开始?
这里是bean实例化过程的一步,执行栈信息如下(可以看到是spring初始化bean的过程):
这里方法参数具体值如下:
具体看下此时的包装bean:
MapperFactoryBean类继承图如下:
可以看到其实现了InitializingBean接口,afterPropertiesSet方法会在bean属性设置完后进行一系列操作。
我们跟踪进afterPropertiesSet方法,这里是DaoSupport.afterPropertiesSet方法,源码如下:
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException { //从这里跟进去 参考1.3 this.checkDaoConfig(); try { this.initDao(); } catch (Exception var2) { throw new BeanInitializationException("Initialization of DAO failed", var2); } }
1.3 MapperFactoryBean.checkDaoConfig
protected void checkDaoConfig() { super.checkDaoConfig(); notNull(this.mapperInterface, "Property 'mapperInterface' is required"); //获取到configuration --MyBatisConfiguration实例 Configuration configuration = getSqlSession().getConfiguration(); //如果该configuration没有该mapperInterface,则进行addMapper操作 if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) { try { //这个是核心,参考1.4 configuration.addMapper(this.mapperInterface); } //... } } }
configuration.addMapper(this.mapperInterface);直接调用了mybatisMapperRegistry的addMapper方法:
public <T> void addMapper(Class<T> type) { this.mybatisMapperRegistry.addMapper(type); }
1.4 MybatisMapperRegistry.addMapper
public <T> void addMapper(Class<T> type) { //判断是否接口 if (type.isInterface()) { //判断是否已经存在 if (this.hasMapper(type)) { return; } boolean loadCompleted = false; try { //type:new MapperProxyFactory(type) 放到knownMappers这个hashmap中 this.knownMappers.put(type, new MapperProxyFactory(type)); //获取一个解析器,进行对象方法注入 MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(this.config, type); //重点来了 参考1.5 parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { this.knownMappers.remove(type); } } } }
这里我们重点看下MybatisMapperAnnotationBuilder 是什么,其构造函数如下:
public MybatisMapperAnnotationBuilder(Configuration configuration, Class<?> type) { super(configuration, type); String resource = type.getName().replace('.', '/') + ".java (best guess)"; this.assistant = new MapperBuilderAssistant(configuration, resource); this.configuration = configuration; this.type = type; this.sqlAnnotationTypes.add(Select.class); this.sqlAnnotationTypes.add(Insert.class); this.sqlAnnotationTypes.add(Update.class); this.sqlAnnotationTypes.add(Delete.class); this.sqlProviderAnnotationTypes.add(SelectProvider.class); this.sqlProviderAnnotationTypes.add(InsertProvider.class); this.sqlProviderAnnotationTypes.add(UpdateProvider.class); this.sqlProviderAnnotationTypes.add(DeleteProvider.class); }
其首先调用了父类MapperAnnotationBuilder的构造函数,如下所示:
public MapperAnnotationBuilder(Configuration configuration, Class<?> type) { //资源符号路径转换 String resource = type.getName().replace('.', '/') + ".java (best guess)"; this.assistant = new MapperBuilderAssistant(configuration, resource); this.configuration = configuration; this.type = type; this.sqlAnnotationTypes.add(Select.class); this.sqlAnnotationTypes.add(Insert.class); this.sqlAnnotationTypes.add(Update.class); this.sqlAnnotationTypes.add(Delete.class); this.sqlProviderAnnotationTypes.add(SelectProvider.class); this.sqlProviderAnnotationTypes.add(InsertProvider.class); this.sqlProviderAnnotationTypes.add(UpdateProvider.class); this.sqlProviderAnnotationTypes.add(DeleteProvider.class); }
调用完父类的构造函数后,当前MybatisMapperAnnotationBuilder如下:
然后继续走MybatisMapperAnnotationBuilder的构造函数,最终如下所示:
1.5 MybatisMapperAnnotationBuilder.parse
public void parse() { //拿到接口完整路径 String resource = this.type.toString(); if (!this.configuration.isResourceLoaded(resource)) { //获取mapper.xml文件 这个很有意思,参考1.6 this.loadXmlResource(); this.configuration.addLoadedResource(resource); //设置当前的Namespace--命名空间 this.assistant.setCurrentNamespace(this.type.getName()); this.parseCache(); this.parseCacheRef(); //获取接口的方法 Method[] methods = this.type.getMethods(); //如果是BaseMapper的子类,则会调用inspectInject方法 if (BaseMapper.class.isAssignableFrom(this.type)) { //获取注入器进行注入 参考1.7 GlobalConfigUtils.getSqlInjector(this.configuration).inspectInject(this.assistant, this.type); } Method[] arr$ = methods; int len$ = methods.length; for(int i$ = 0; i$ < len$; ++i$) { Method method = arr$[i$]; try { if (!method.isBridge()) { this.parseStatement(method); } } catch (IncompleteElementException var8) { this.configuration.addIncompleteMethod(new MethodResolver(this, method)); } } } this.parsePendingMethods(); }
这里可以看下获取的接口的方法:
对比下我们在employeeMapper里面看到的方法(可以猜测,将会把这些方法一个个注入进去,具体注入什么进去到哪里呢?):
1.6 MybatisMapperAnnotationBuilder.loadXmlResource
private void loadXmlResource() { if (!this.configuration.isResourceLoaded("namespace:" + this.type.getName())) { // 这里xmlResource 是com/jane/mp/mapper/EmployeeMapper.xml String xmlResource = this.type.getName().replace('.', '/') + ".xml"; InputStream inputStream = null; try { //尝试读取文件流 具体可参考 inputStream = Resources.getResourceAsStream(this.type.getClassLoader(), xmlResource); } catch (IOException var4) { ; } if (inputStream != null) { XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, this.assistant.getConfiguration(), xmlResource, this.configuration.getSqlFragments(), this.type.getName()); xmlParser.parse(); } } }
1.7 AutoSqlInjector.inspectInject
public void inspectInject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) { //interface com.jane.mp.mapper.EmployeeMapper String className = mapperClass.toString(); Set<String> mapperRegistryCache = GlobalConfigUtils.getMapperRegistryCache(builderAssistant.getConfiguration()); //如果缓存中没有className 则调用inject,然后放到mapperRegistryCache if (!mapperRegistryCache.contains(className)) { //参考1.8 this.inject(builderAssistant, mapperClass); //将会放到GlobalConfiguration.mapperRegistryCache中(Set<String>),下次即可从其获取 mapperRegistryCache.add(className); } }
1.8 AutoSqlInjector.inject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass)
public void inject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass) { this.configuration = builderAssistant.getConfiguration(); this.builderAssistant = builderAssistant; this.languageDriver = this.configuration.getDefaultScriptingLanguageInstance(); Class<?> modelClass = this.extractModelClass(mapperClass); if (null != modelClass) { if (this.getGlobalConfig().isSqlParserCache()) { PluginUtils.initSqlParserInfoCache(mapperClass); } //获取表信息 TableInfo table = TableInfoHelper.initTableInfo(builderAssistant, modelClass); this.injectSql(builderAssistant, mapperClass, modelClass, table); } }
看下这里TableInfo 是个什么?
1.9 AutoSqlInjector.inject(MapperBuilderAssistant builderAssistant, Class<?> mapperClass, Class<?> modelClass, TableInfo table)
protected void injectSql(MapperBuilderAssistant builderAssistant, Class<?> mapperClass, Class<?> modelClass, TableInfo table) { /** * 表信息包含主键,注入主键相关方法 */ if (StringUtils.isNotEmpty(table.getKeyProperty())) { /** 删除 */ this.injectDeleteByIdSql(false, mapperClass, modelClass, table); this.injectDeleteByIdSql(true, mapperClass, modelClass, table); /** 修改 */ this.injectUpdateByIdSql(true, mapperClass, modelClass, table); this.injectUpdateByIdSql(false, mapperClass, modelClass, table); /** 查询 */ this.injectSelectByIdSql(false, mapperClass, modelClass, table); this.injectSelectByIdSql(true, mapperClass, modelClass, table); } else { // 表不包含主键时 给予警告 logger.warn(String.format("%s ,Not found @TableId annotation, Cannot use Mybatis-Plus 'xxById' Method.", modelClass.toString())); } /** * 正常注入无需主键方法 */ /** 插入 */ this.injectInsertOneSql(true, mapperClass, modelClass, table); this.injectInsertOneSql(false, mapperClass, modelClass, table); /** 删除 */ this.injectDeleteSql(mapperClass, modelClass, table); this.injectDeleteByMapSql(mapperClass, table); /** 修改 */ this.injectUpdateSql(mapperClass, modelClass, table); /** 修改 (自定义 set 属性) */ this.injectUpdateForSetSql(mapperClass, modelClass, table); /** 查询 */ this.injectSelectByMapSql(mapperClass, modelClass, table); this.injectSelectOneSql(mapperClass, modelClass, table); this.injectSelectCountSql(mapperClass, modelClass, table); this.injectSelectListSql(SqlMethod.SELECT_LIST, mapperClass, modelClass, table); this.injectSelectListSql(SqlMethod.SELECT_PAGE, mapperClass, modelClass, table); this.injectSelectMapsSql(SqlMethod.SELECT_MAPS, mapperClass, modelClass, table); this.injectSelectMapsSql(SqlMethod.SELECT_MAPS_PAGE, mapperClass, modelClass, table); this.injectSelectObjsSql(SqlMethod.SELECT_OBJS, mapperClass, modelClass, table); /** 自定义方法 */ this.inject(configuration, builderAssistant, mapperClass, modelClass, table); }
③ 以 injectDeleteByIdSql为例查看具体如何注入了进去
① 调用AutoSqlInjector.injectDeleteByIdSql方法:
protected void injectDeleteByIdSql(boolean batch, Class<?> mapperClass, Class<?> modelClass, TableInfo table) { SqlMethod sqlMethod = SqlMethod.DELETE_BY_ID; String idStr = table.getKeyProperty(); //判断是否批量删除,若是则封装foreach 语句 if (batch) { sqlMethod = SqlMethod.DELETE_BATCH_BY_IDS; StringBuilder ids = new StringBuilder(); ids.append("\n<foreach item=\"item\" index=\"index\" collection=\"coll\" separator=\",\">"); ids.append("#{item}"); ids.append("\n</foreach>"); idStr = ids.toString(); } //SQL格式化 <script>DELETE FROM tbl_employee WHERE id=#{id}</script> String sql = String.format(sqlMethod.getSql(), table.getTableName(), table.getKeyColumn(), idStr); //根据SQL创建sqlSource SqlSource sqlSource = this.languageDriver.createSqlSource(this.configuration, sql, modelClass); //封装一个个MappedStatement放到 this.addDeleteMappedStatement(mapperClass, sqlMethod.getMethod(), sqlSource); }
sqlMethod.getSql如下:
<script>DELETE FROM %s WHERE %s=#{%s}</script>
创建的SqlSource为:
② this.addDeleteMappedStatement
AutoSqlInjector.addDeleteMappedStatement方法源码如下:
public MappedStatement addDeleteMappedStatement(Class<?> mapperClass, String id, SqlSource sqlSource) { return this.addMappedStatement(mapperClass, id, sqlSource, SqlCommandType.DELETE, (Class)null, (String)null, Integer.class, new NoKeyGenerator(), (String)null, (String)null); }
其调用了addMappedStatement方法:
public MappedStatement addMappedStatement(Class<?> mapperClass, String id, SqlSource sqlSource, SqlCommandType sqlCommandType, Class<?> parameterClass, String resultMap, Class<?> resultType, KeyGenerator keyGenerator, String keyProperty, String keyColumn) { String statementName = mapperClass.getName() + "." + id; //判断是否已经存在 if (this.hasMappedStatement(statementName)) { System.err.println("{" + statementName + "} Has been loaded by XML or SqlProvider, ignoring the injection of the SQL."); return null; } else { boolean isSelect = false; //判断是否查询语句 if (sqlCommandType == SqlCommandType.SELECT) { isSelect = true; } //从这里继续跟 参考③ return this.builderAssistant.addMappedStatement(id, sqlSource, StatementType.PREPARED, sqlCommandType, (Integer)null, (Integer)null, (String)null, parameterClass, resultMap, resultType, (ResultSetType)null, !isSelect, isSelect, false, keyGenerator, keyProperty, keyColumn, this.configuration.getDatabaseId(), this.languageDriver, (String)null); } }
③ MapperBuilderAssistant.addMappedStatement(String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType, String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets)
是的,这个方法的参数多到了难以想象(xml中标签属性都有)。主要参数如下所示:
方法源码如下:
public MappedStatement addMappedStatement(String id, SqlSource sqlSource, StatementType statementType, SqlCommandType sqlCommandType, Integer fetchSize, Integer timeout, String parameterMap, Class<?> parameterType, String resultMap, Class<?> resultType, ResultSetType resultSetType, boolean flushCache, boolean useCache, boolean resultOrdered, KeyGenerator keyGenerator, String keyProperty, String keyColumn, String databaseId, LanguageDriver lang, String resultSets) { if (this.unresolvedCacheRef) { throw new IncompleteElementException("Cache-ref not yet resolved"); } else { //给方法名拼接上name space: com.jane.mp.mapper.EmployeeMapper.deleteById id = this.applyCurrentNamespace(id, false); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; //获取statementBuilder org.apache.ibatis.mapping.MappedStatement.Builder statementBuilder = (new org.apache.ibatis.mapping.MappedStatement.Builder(this.configuration, id, sqlSource, sqlCommandType)).resource(this.resource).fetchSize(fetchSize).timeout(timeout).statementType(statementType).keyGenerator(keyGenerator).keyProperty(keyProperty).keyColumn(keyColumn).databaseId(databaseId).lang(lang).resultOrdered(resultOrdered).resultSets(resultSets).resultMaps(this.getStatementResultMaps(resultMap, resultType, id)).resultSetType(resultSetType).flushCacheRequired((Boolean)this.valueOrDefault(flushCache, !isSelect)).useCache((Boolean)this.valueOrDefault(useCache, isSelect)).cache(this.currentCache); //获取参数对象 ParameterMap statementParameterMap = this.getStatementParameterMap(parameterMap, parameterType, id); if (statementParameterMap != null) { statementBuilder.parameterMap(statementParameterMap); } MappedStatement statement = statementBuilder.build(); //从这里跟进去,参考④ this.configuration.addMappedStatement(statement); return statement; } }
看一下创建的MappedStatement对象:
④ MybatisConfiguration.addMappedStatement(MappedStatement ms)
public void addMappedStatement(MappedStatement ms) { logger.debug("addMappedStatement: " + ms.getId()); //判断是否刷新,如果是就移除掉 if (GlobalConfigUtils.isRefresh(ms.getConfiguration())) { this.mappedStatements.remove(ms.getId()); } else if (this.mappedStatements.containsKey(ms.getId())) { logger.error("mapper[" + ms.getId() + "] is ignored, because it's exists, maybe from xml file"); return; } //从这里继续跟进去 super.addMappedStatement(ms); }
留意日志发现其打印了addMappedStatement: com.jane.mp.mapper.EmployeeMapper.deleteById
父类Configuration的addMappedStatement方法很简单:
public void addMappedStatement(MappedStatement ms) { //以 ms.getId():ms 放入MybatisConfiguration的mappedStatements属性中 this.mappedStatements.put(ms.getId(), ms); }
如下可以看到刚添加的deleteById :
同理注入其他:
在上面的图示中我们还看到了许多SqlRunner.XXX 方法,那么这些是什么时候注入的呢?是在实例化MybatisSqlSessionFactoryBean过程中注入的。这又是一个漫长的过程,参考【7】
【7】sqlSessionFactoryBean的实例化过程
如下图所示,我们这里仍旧从AbstractAutowireCapableBeanFactory.invokeInitMethods方法中的afterPropertiesSet方法开始开始:
① MybatisSqlSessionFactoryBean.afterPropertiesSet
方法源码如下:
public void afterPropertiesSet() throws Exception { //几个断言 notNull(dataSource, "Property 'dataSource' is required"); notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required"); state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null), "Property 'configuration' and 'configLocation' can not specified with together"); //创建一个sqlsessionfactory,从这里跟进去② this.sqlSessionFactory = buildSqlSessionFactory(); }
② MybatisSqlSessionFactoryBean.buildSqlSessionFactory
这个是核心方法,用来创建Configuration 实例并根据configuration实例获取sqlsessionFactory实例。
protected SqlSessionFactory buildSqlSessionFactory() throws Exception { Configuration configuration; // TODO 加载自定义 MybatisXmlConfigBuilder MybatisXMLConfigBuilder xmlConfigBuilder = null; if (this.configuration != null) { configuration = this.configuration; if (configuration.getVariables() == null) { configuration.setVariables(this.configurationProperties); } else if (this.configurationProperties != null) { configuration.getVariables().putAll(this.configurationProperties); } //这里,如果configLocation 不为null,则获取MybatisXMLConfigBuilder并得到Configuration } else if (this.configLocation != null) { xmlConfigBuilder = new MybatisXMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); configuration = xmlConfigBuilder.getConfiguration(); } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration"); } // TODO 使用自定义配置,注意这里直接new了一个MybatisConfiguration对象 configuration = new MybatisConfiguration(); if (this.configurationProperties != null) { configuration.setVariables(this.configurationProperties); } } if (this.objectFactory != null) { configuration.setObjectFactory(this.objectFactory); } if (this.objectWrapperFactory != null) { configuration.setObjectWrapperFactory(this.objectWrapperFactory); } if (this.vfs != null) { configuration.setVfsImpl(this.vfs); } if (hasLength(this.typeAliasesPackage)) { // TODO 支持自定义通配符 String[] typeAliasPackageArray; if (typeAliasesPackage.contains("*") && !typeAliasesPackage.contains(",") && !typeAliasesPackage.contains(";")) { typeAliasPackageArray = PackageHelper.convertTypeAliasesPackage(typeAliasesPackage); } else { typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); } if (typeAliasPackageArray == null) { throw new MybatisPlusException("not find typeAliasesPackage:" + typeAliasesPackage); } for (String packageToScan : typeAliasPackageArray) { configuration.getTypeAliasRegistry().registerAliases(packageToScan, typeAliasesSuperType == null ? Object.class : typeAliasesSuperType); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases"); } } } // TODO 自定义枚举类扫描处理 if (hasLength(this.typeEnumsPackage)) { Set<Class> classes = null; if (typeEnumsPackage.contains("*") && !typeEnumsPackage.contains(",") && !typeEnumsPackage.contains(";")) { classes = PackageHelper.scanTypePackage(typeEnumsPackage); } else { String[] typeEnumsPackageArray = tokenizeToStringArray(this.typeEnumsPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); if (typeEnumsPackageArray == null) { throw new MybatisPlusException("not find typeEnumsPackage:" + typeEnumsPackage); } classes = new HashSet<Class>(); for (String typePackage : typeEnumsPackageArray) { classes.addAll(PackageHelper.scanTypePackage(typePackage)); } } // 取得类型转换注册器 TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry(); for (Class cls : classes) { if (cls.isEnum()) { if (IEnum.class.isAssignableFrom(cls)) { typeHandlerRegistry.register(cls.getName(), com.baomidou.mybatisplus.handlers.EnumTypeHandler.class.getCanonicalName()); } else { // 使用原生 EnumOrdinalTypeHandler typeHandlerRegistry.register(cls.getName(), org.apache.ibatis.type.EnumOrdinalTypeHandler.class.getCanonicalName()); } } } } if (!isEmpty(this.typeAliases)) { for (Class<?> typeAlias : this.typeAliases) { configuration.getTypeAliasRegistry().registerAlias(typeAlias); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered type alias: '" + typeAlias + "'"); } } } if (!isEmpty(this.plugins)) { for (Interceptor plugin : this.plugins) { configuration.addInterceptor(plugin); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered plugin: '" + plugin + "'"); } } } if (hasLength(this.typeHandlersPackage)) { String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS); for (String packageToScan : typeHandlersPackageArray) { configuration.getTypeHandlerRegistry().register(packageToScan); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers"); } } } if (!isEmpty(this.typeHandlers)) { for (TypeHandler<?> typeHandler : this.typeHandlers) { configuration.getTypeHandlerRegistry().register(typeHandler); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Registered type handler: '" + typeHandler + "'"); } } } if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls try { configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource)); } catch (SQLException e) { throw new NestedIOException("Failed getting a databaseId", e); } } if (this.cache != null) { configuration.addCache(this.cache); } if (xmlConfigBuilder != null) { try { xmlConfigBuilder.parse(); if (LOGGER.isDebugEnabled()) { LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'"); } } catch (Exception ex) { throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex); } finally { ErrorContext.instance().reset(); } } if (this.transactionFactory == null) { this.transactionFactory = new SpringManagedTransactionFactory(); } configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource)); // 设置元数据相关 GlobalConfigUtils.setMetaData(dataSource, globalConfig); SqlSessionFactory sqlSessionFactory = this.sqlSessionFactoryBuilder.build(configuration); // TODO SqlRunner SqlRunner.FACTORY = sqlSessionFactory; // TODO 缓存 sqlSessionFactory globalConfig.setSqlSessionFactory(sqlSessionFactory); // TODO 设置全局参数属性 globalConfig.signGlobalConfig(sqlSessionFactory); if (!isEmpty(this.mapperLocations)) { if (globalConfig.isRefresh()) { //TODO 设置自动刷新配置 减少配置 new MybatisMapperRefresh(this.mapperLocations, sqlSessionFactory, 2, 2, true); } for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; } try { // 解析mapper.xml中的配置,并将CRUD等创建为一个个mappedstatement对象放到configuration中 XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); } catch (Exception e) { throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e); } finally { ErrorContext.instance().reset(); } if (LOGGER.isDebugEnabled()) { LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'"); } } } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found"); } } return sqlSessionFactory; }
其他咱们先不管,由于本文测试的时候使用了mybatis-config.xml,所以这里会走到xmlConfigBuilder = new MybatisXMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);,我们继续跟踪其构造函数:
public MybatisXMLConfigBuilder(InputStream inputStream, String environment, Properties props) { this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props); } #从这里继续跟踪进去 private MybatisXMLConfigBuilder(XPathParser parser, String environment, Properties props) { //TODO 自定义 Configuration super(new MybatisConfiguration()); ErrorContext.instance().resource("SQL Mapper Configuration"); this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser; } #可以看到首先调用了 super(new MybatisConfiguration()); #还是走到了new MybatisConfiguration()
③ new MybatisConfiguration()如此重要
那么我们看下MybatisConfiguration这个类,其实MyBatis 或者 MP全局配置对象,其属性如下:
public class MybatisConfiguration extends Configuration { private static final Log logger = LogFactory.getLog(MybatisConfiguration.class); /** * Mapper 注册 */ public final MybatisMapperRegistry mybatisMapperRegistry = new MybatisMapperRegistry(this); /** * 初始化调用 */ public MybatisConfiguration() { this.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class); logger.debug("Mybatis-plus init success."); } //... }
方法列表如下:
可以看到该类主要做三件事情:获取一个MybatisMapperRegistry注册中心、往mappedStatements中添加MappedStatement对象以及往mybatisMapperRegistry注册中心添加mapper: mybatisMapperRegistry.addMappers(packageName, superType);
需要擦亮眼睛的是,public final MybatisMapperRegistry mybatisMapperRegistry = new MybatisMapperRegistry(this);这是一个用final修饰的变量,也就是常量。那么常量是在何时被赋值的呢?
静态变量和常量(如果有初始值)在类加载过程中就已经初始化了,在编译的时候存储在class的常量池中,在加载后又被放进方法区的运行时常量池中。
这里常量mybatisMapperRegistry被赋予了初始值,那么在类MybatisConfiguration加载过程中的编译阶段就会将初始值存入constantValue属性(class文件的常量池)中,在准备阶段就将constantValue的值赋给mybatisMapperRegistry。
④ MybatisMapperRegistry
类源码如下:
public class MybatisMapperRegistry extends MapperRegistry { //如果一个mapper已经被加载过,将会被放到knownMappers 中 private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>(); //MybatisConfiguration实例对象的引用 private final Configuration config; public MybatisMapperRegistry(Configuration config) { super(config); this.config = config; // TODO注入SqlRunner GlobalConfigUtils.getSqlInjector(config).injectSqlRunner(config); } @Override public <T> T getMapper(Class<T> type, SqlSession sqlSession) { final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MybatisPlusMapperRegistry."); } try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception e) { throw new BindingException("Error getting mapper instance. Cause: " + e, e); } } @Override public <T> boolean hasMapper(Class<T> type) { return knownMappers.containsKey(type); } @Override public <T> void addMapper(Class<T> type) { if (type.isInterface()) { if (hasMapper(type)) { // TODO 如果之前注入 直接返回 return; // throw new BindingException("Type " + type + // " is already known to the MybatisPlusMapperRegistry."); } boolean loadCompleted = false; try { knownMappers.put(type, new MapperProxyFactory<>(type)); // It's important that the type is added before the parser is run // otherwise the binding may automatically be attempted by the // mapper parser. If the type is already known, it won't try. // TODO 自定义无 XML 注入---解析mapper中方法上面使用了注解SQL MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } } /** * @since 3.2.2 */ @Override public Collection<Class<?>> getMappers() { return Collections.unmodifiableCollection(knownMappers.keySet()); } }
核心在这里:
GlobalConfigUtils.getSqlInjector(config).injectSqlRunner(config);
这里将会调用AutoSqlInjector.injectSqlRunner(Configuration configuration)注入sqlrunner相关的mappedStatement对象。
@Override public void injectSqlRunner(Configuration configuration) { this.configuration = configuration; this.languageDriver = configuration.getDefaultScriptingLanguageInstance(); initSelectList(); initSelectObjs(); initInsert(); initUpdate(); initDelete(); initCount(); }
最后看下日志:
【8】mapperLocation设置的那些mapper.xml配置文件何时被解析?
同样是在MybatisSqlSessionFactoryBean实例化过程中。代码如下实例:
if (!isEmpty(this.mapperLocations)) { if (globalConfig.isRefresh()) { //TODO 设置自动刷新配置 减少配置 new MybatisMapperRefresh(this.mapperLocations, sqlSessionFactory, 2, 2, true); } for (Resource mapperLocation : this.mapperLocations) { if (mapperLocation == null) { continue; } try { // 获取一个XMLMapperBuilder 然后解析那些mapper.xml配置文件 XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(), configuration, mapperLocation.toString(), configuration.getSqlFragments()); xmlMapperBuilder.parse(); } //... } } //...
① XMLMapperBuilder.parse
方法源码如下所示:
public void parse() { //如果当前mapper.xml配置文件没有加载过,就会解析并加载 if (!configuration.isResourceLoaded(resource)) { //从这里跟进去,参考2 configurationElement(parser.evalNode("/mapper")); //解析完xml文件后,将该String resource放到loadedResources这个hashset中 configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }
② XMLMapperBuilder.configurationElement
方法源码如下:
private void configurationElement(XNode context) { try { //获取namespace String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); parameterMapElement(context.evalNodes("/mapper/parameterMap")); resultMapElements(context.evalNodes("/mapper/resultMap")); sqlElement(context.evalNodes("/mapper/sql")); //解析那些CRUD标签,并创建一个个MappedStatement,然后放到MappedStatements这个map中 参考3 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); } }
③ XMLMapperBuilder.buildStatementFromContext(List<XNode> list
)
如下图所示,这里每一个NODE就对应了了一条SQL。
其有继续调用了如下方法:
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) { for (XNode context : list) { //获取一个XMLStatementBuilder ,然后解析结点 final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId); try { //这里参考4 statementParser.parseStatementNode(); } catch (IncompleteElementException e) { configuration.addIncompleteStatement(statementParser); } } }
④ XMLStatementBuilder.parseStatementNode()
这个方法获取了标签(insert|update|delete|select)的相关属性,并在最后调用了builderAssistant这个助手将其添加MybatisConfiguration的 protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection"):
public void parseStatementNode() { String id = context.getStringAttribute("id"); String databaseId = context.getStringAttribute("databaseId"); if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } Integer fetchSize = context.getIntAttribute("fetchSize"); Integer timeout = context.getIntAttribute("timeout"); String parameterMap = context.getStringAttribute("parameterMap"); String parameterType = context.getStringAttribute("parameterType"); Class<?> parameterTypeClass = resolveClass(parameterType); String resultMap = context.getStringAttribute("resultMap"); String resultType = context.getStringAttribute("resultType"); String lang = context.getStringAttribute("lang"); LanguageDriver langDriver = getLanguageDriver(lang); Class<?> resultTypeClass = resolveClass(resultType); String resultSetType = context.getStringAttribute("resultSetType"); StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); String nodeName = context.getNode().getNodeName(); SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); boolean useCache = context.getBooleanAttribute("useCache", isSelect); boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // Include Fragments before parsing XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); // Parse selectKey after includes and remove them. processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); String resultSets = context.getStringAttribute("resultSets"); String keyProperty = context.getStringAttribute("keyProperty"); String keyColumn = context.getStringAttribute("keyColumn"); KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; } //看到这个是不是很熟悉?ok下面就不再重复过程了 builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }
【9】如果我在mapper接口中放上自定义方法并使用注解呢?
如下所示:
public interface EmployeeMapper extends BaseMapper<Employee> { @Select("select * from tbl_employee") List<Employee> selectEmployee(); }
注解在哪里被解析然后注入?
① 当你没有xml配置文件时
① MapperFactoryBean.checkDaoConfig
protected void checkDaoConfig() { super.checkDaoConfig(); notNull(this.mapperInterface, "Property 'mapperInterface' is required"); Configuration configuration = getSqlSession().getConfiguration(); if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) { try { configuration.addMapper(this.mapperInterface); } catch (Exception e) { logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e); throw new IllegalArgumentException(e); } finally { ErrorContext.instance().reset(); } } }
这个核心入口configuration.addMapper(this.mapperInterface);,会调用如下方法:
@Override public <T> void addMapper(Class<T> type) { mybatisMapperRegistry.addMapper(type); }
② MybatisMapperRegistry.addMapper(Class<T> type
)
@Override public <T> void addMapper(Class<T> type) { if (type.isInterface()) { if (hasMapper(type)) { // TODO 如果之前注入 直接返回 return; // throw new BindingException("Type " + type + // " is already known to the MybatisPlusMapperRegistry."); } boolean loadCompleted = false; try { knownMappers.put(type, new MapperProxyFactory<>(type)); //这里会对方法上面注解进行解析 MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
③ MybatisMapperAnnotationBuilder.parse
public void parse() { String resource = type.toString(); if (!configuration.isResourceLoaded(resource)) { loadXmlResource(); configuration.addLoadedResource(resource); assistant.setCurrentNamespace(type.getName()); parseCache(); parseCacheRef(); Method[] methods = type.getMethods(); // TODO 注入 CURD 动态 SQL (应该在注解之前注入) if (BaseMapper.class.isAssignableFrom(type)) { GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type); } for (Method method : methods) { try { // issue #237 if (!method.isBridge()) { parseStatement(method); } } catch (IncompleteElementException e) { configuration.addIncompleteMethod(new MethodResolver(this, method)); } } } parsePendingMethods(); }
② 如果有mapper.xml配置文件时(!isEmpty(this.mapperLocations))
在sqlSessionFactoryBean实例化过程中,会调用XMLMapperBuilder的parse方法。
public void parse() { if (!configuration.isResourceLoaded(resource)) { //解析那些mapper.xml配置文件 configurationElement(parser.evalNode("/mapper")); //标记资源已经被解析 configuration.addLoadedResource(resource); //这里这里这里! bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }
这里我们重点看下bindMapperForNamespace方法:
private void bindMapperForNamespace() { //获取命名空间 String namespace = builderAssistant.getCurrentNamespace(); if (namespace != null) { Class<?> boundType = null; try { boundType = Resources.classForName(namespace); } catch (ClassNotFoundException e) { //ignore, bound type is not required } if (boundType != null) { if (!configuration.hasMapper(boundType)) { //如果没有被解析过就执行如下方法 configuration.addLoadedResource("namespace:" + namespace); //这里是核心入口 configuration.addMapper(boundType); } } } }
configuration.addMapper(boundType);
会调用MybatisMapperRegistry.addMapper(Class<T> type)
:
@Override public <T> void addMapper(Class<T> type) { if (type.isInterface()) { if (hasMapper(type)) { // TODO 如果之前注入 直接返回 return; } boolean loadCompleted = false; try { knownMappers.put(type, new MapperProxyFactory<>(type)); // TODO 自定义无 XML 注入---解析方法上面的注解!!! MybatisMapperAnnotationBuilder parser = new MybatisMapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
当然,因为首先会做判断是否已经解析。所以当你配置了mapperLocations且其不为空的时候,在实例化sqlSessionFactoryBean的过程中就会完成mappedStatement对象的注入。当你实例化employeeMapper时调用其包装类的afterPropertiesSet方法以及其后一系列过程最终走到上面方法的时候,就不会再次注入!
③ MybatisMapperAnnotationBuilder.parse
为什么要看这个方法呢?上面我们分析了没有mapper.xml、有mapper.xml(配置了mapperLocations属性)的情况,但是如果有mapper.xml但是没有配置mapperLocations属性呢?
public void parse() { String resource = type.toString(); if (!configuration.isResourceLoaded(resource)) { //在这里,会尝试加载接口下面的同名xml文件进行解析!!! loadXmlResource(); configuration.addLoadedResource(resource); assistant.setCurrentNamespace(type.getName()); parseCache(); parseCacheRef(); Method[] methods = type.getMethods(); // TODO 注入 CURD 动态 SQL (应该在注解之前注入) if (BaseMapper.class.isAssignableFrom(type)) { GlobalConfigUtils.getSqlInjector(configuration).inspectInject(assistant, type); } for (Method method : methods) { try { // issue #237 if (!method.isBridge()) { parseStatement(method); } } catch (IncompleteElementException e) { configuration.addIncompleteMethod(new MethodResolver(this, method)); } } } parsePendingMethods(); }
MybatisMapperAnnotationBuilder.loadXmlResource
private void loadXmlResource() { //判断xml是否已经加载过 if (!configuration.isResourceLoaded("namespace:" + type.getName())) { //接口同名xml文件 String xmlResource = type.getName().replace('.', '/') + ".xml"; InputStream inputStream = null; try { inputStream = Resources.getResourceAsStream(type.getClassLoader(), xmlResource); } catch (IOException e) { // ignore, resource is not required } if (inputStream != null) { //获取XMLMapperBuilder进行解析 XMLMapperBuilder xmlParser = new XMLMapperBuilder(inputStream, assistant.getConfiguration(), xmlResource, configuration.getSqlFragments(), type.getName()); xmlParser.parse(); } } }
【10】什么是mappedStatements?
其是Configuration的一个属性,类型为StrictMap:
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");
一 个 MappedStatement对象对应 Mapper配置文件中的一个select/update/insert/delete节点,主要描述的是一条 SQL语句。
其主要属性如下:
看到这些属性是否很熟悉?我们对照下mapper.xml中一个select标签的属性:
也就是说,一个MappedStatement代表一个增删改查标签的详细信息。
【11】SqlMethod是什么?
这是一个SQL模板的枚举类,根据这些模板解析出一个个具体的SQL
public enum SqlMethod { /** * 插入 */ INSERT_ONE("insert", "插入一条数据(选择字段插入)", "<script>INSERT INTO %s %s VALUES %s</script>"), INSERT_ONE_ALL_COLUMN("insertAllColumn", "插入一条数据(全部字段插入)", "<script>INSERT INTO %s %s VALUES %s</script>"), /** * 删除 */ DELETE_BY_ID("deleteById", "根据ID 删除一条数据", "<script>DELETE FROM %s WHERE %s=#{%s}</script>"), DELETE_BY_MAP("deleteByMap", "根据columnMap 条件删除记录", "<script>DELETE FROM %s %s</script>"), DELETE("delete", "根据 entity 条件删除记录", "<script>DELETE FROM %s %s</script>"), DELETE_BATCH_BY_IDS("deleteBatchIds", "根据ID集合,批量删除数据", "<script>DELETE FROM %s WHERE %s IN (%s)</script>"), /** * 逻辑删除 */ LOGIC_DELETE_BY_ID("deleteById", "根据ID 逻辑删除一条数据", "<script>UPDATE %s %s WHERE %s=#{%s}</script>"), LOGIC_DELETE_BY_MAP("deleteByMap", "根据columnMap 条件逻辑删除记录", "<script>UPDATE %s %s %s</script>"), LOGIC_DELETE("delete", "根据 entity 条件逻辑删除记录", "<script>UPDATE %s %s %s</script>"), LOGIC_DELETE_BATCH_BY_IDS("deleteBatchIds", "根据ID集合,批量逻辑删除数据", "<script>UPDATE %s %s WHERE %s IN (%s)</script>"), /** * 修改 */ UPDATE_BY_ID("updateById", "根据ID 选择修改数据", "<script>UPDATE %s %s WHERE %s=#{%s} %s</script>"), UPDATE_ALL_COLUMN_BY_ID("updateAllColumnById", "根据ID 修改全部数据", "<script>UPDATE %s %s WHERE %s=#{%s} %s</script>"), UPDATE("update", "根据 whereEntity 条件,更新记录", "<script>UPDATE %s %s %s</script>"), UPDATE_FOR_SET("updateForSet", "根据 whereEntity 条件,自定义Set值更新记录", "<script>UPDATE %s %s %s</script>"), /** * 逻辑删除 -> 修改 */ LOGIC_UPDATE_BY_ID("updateById", "根据ID 修改数据", "<script>UPDATE %s %s WHERE %s=#{%s} %s</script>"), LOGIC_UPDATE_ALL_COLUMN_BY_ID("updateAllColumnById", "根据ID 选择修改数据", "<script>UPDATE %s %s WHERE %s=#{%s} %s</script>"), /** * 查询 */ SELECT_BY_ID("selectById", "根据ID 查询一条数据", "SELECT %s FROM %s WHERE %s=#{%s}"), SELECT_BY_MAP("selectByMap", "根据columnMap 查询一条数据", "<script>SELECT %s FROM %s %s</script>"), SELECT_BATCH_BY_IDS("selectBatchIds", "根据ID集合,批量查询数据", "<script>SELECT %s FROM %s WHERE %s IN (%s)</script>"), SELECT_ONE("selectOne", "查询满足条件一条数据", "<script>SELECT %s FROM %s %s</script>"), SELECT_COUNT("selectCount", "查询满足条件总记录数", "<script>SELECT COUNT(1) FROM %s %s</script>"), SELECT_LIST("selectList", "查询满足条件所有数据", "<script>SELECT %s FROM %s %s</script>"), SELECT_PAGE("selectPage", "查询满足条件所有数据(并翻页)", "<script>SELECT %s FROM %s %s</script>"), SELECT_MAPS("selectMaps", "查询满足条件所有数据", "<script>SELECT %s FROM %s %s</script>"), SELECT_MAPS_PAGE("selectMapsPage", "查询满足条件所有数据(并翻页)", "<script>SELECT %s FROM %s %s</script>"), SELECT_OBJS("selectObjs", "查询满足条件所有数据", "<script>SELECT %s FROM %s %s</script>"), /** * 逻辑删除 -> 查询 */ LOGIC_SELECT_BY_ID("selectById", "根据ID 查询一条数据", "SELECT %s FROM %s WHERE %s=#{%s} %s"), LOGIC_SELECT_BATCH_BY_IDS("selectBatchIds", "根据ID集合,批量查询数据", "<script>SELECT %s FROM %s WHERE %s IN (%s) %s</script>"); private final String method; private final String desc; private final String sql; SqlMethod(final String method, final String desc, final String sql) { this.method = method; this.desc = desc; this.sql = sql; } public String getMethod() { return this.method; } public String getDesc() { return this.desc; } public String getSql() { return this.sql; } }