springboot 加载mybatis的流程
1.POM依賴包加載
<dependency><groupId>org.mybatis.spring.boot</groupId><artifactId>mybatis-spring-boot-starter</artifactId><version>1.3.2</version></dependency>2.自定義MYBATICS配置對象。
/*** mybatics的數(shù)據(jù)工廠配置。*/ @Configuration @MapperScan(basePackages = "com.tpw.summaryday.dao", sqlSessionFactoryRef = "test2_sqlSessionFactory") public class MybaticsSessionConf {//配置mybatis的分頁插件pageHelper@Beanpublic PageHelper pageHelper() {PageHelper pageHelper = new PageHelper();Properties props = new Properties();props.setProperty("dialect", "mysql");// 表示支持從接口中讀取pageNum和pageSizeprops.setProperty("supportMethodsArguments", "true");pageHelper.setProperties(props);return pageHelper;}@Bean(name = "test2_dataSource")@ConfigurationProperties(prefix = "spring.datasource.test2")public DataSource getDateSource1() {return DataSourceBuilder.create().build();}@Bean(name = "test2_sqlSessionFactory")public SqlSessionFactory getSqlSessionFactory(@Qualifier("test2_dataSource") DataSource test2DataSource)throws Exception {SqlSessionFactoryBean bean = new SqlSessionFactoryBean();bean.setDataSource(test2DataSource);bean.setConfigLocation(new PathMatchingResourcePatternResolver().getResource("classpath:mybatis-config.xml")); // bean.setMapperLocations( // // 設(shè)置mybatis的xml所在位置 // new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/localdb/*.xml"));return bean.getObject();}@Bean("test2_sqlSessionTemplate")public SqlSessionTemplate getSqlsessiontemplate(@Qualifier("test2_sqlSessionFactory") SqlSessionFactory sqlSessionFactory) {try {return new SqlSessionTemplate(sqlSessionFactory);} catch (Exception e) {e.printStackTrace();}return null;}}3.環(huán)境配置文件加載pageHelper配置
pagehelper.helperDialect = mysql pagehelper.reasonable= true pagehelper.supportMethodsArguments= true pagehelper.params= count=countSql4.mybatis-generator.xml配置,用來生成DAO,MAPPER,ENTITY
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfigurationPUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN""http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration><context id="DB2Tables" targetRuntime="MyBatis3"><commentGenerator><property name="suppressDate" value="true"/><property name="suppressAllComments" value="true"/></commentGenerator><!--數(shù)據(jù)庫鏈接地址賬號密碼--><jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://rm-wz96s5izji5099uz13o.mysql.rds.aliyuncs.com:3306/test2?"userId="" password=""></jdbcConnection><javaTypeResolver><property name="forceBigDecimals" value="false"/></javaTypeResolver><!--生成Model類存放位置--><javaModelGenerator targetPackage="com.tpw.summaryday.entity" targetProject="src/main/java"><property name="enableSubPackages" value="true"/><property name="trimStrings" value="true"/></javaModelGenerator><!--生成映射文件存放位置--><sqlMapGenerator targetPackage="com.tpw.summaryday.mapper" targetProject="src/main/java"><property name="enableSubPackages" value="true"/></sqlMapGenerator><!--生成Dao類存放位置--><!-- 客戶端代碼,生成易于使用的針對Model對象和XML配置文件 的代碼type="ANNOTATEDMAPPER",生成Java Model 和基于注解的Mapper對象type="MIXEDMAPPER",生成基于注解的Java Model 和相應(yīng)的Mapper對象type="XMLMAPPER",生成SQLMap XML文件和獨立的Mapper接口--><javaClientGenerator type="ANNOTATEDMAPPER" targetPackage="com.tpw.summaryday.dao" targetProject="src/main/java"><property name="enableSubPackages" value="true"/></javaClientGenerator><!--生成對應(yīng)表及類名--><table tableName="fanli_directional_goods" domainObjectName="DirectionalGoods"enableCountByExample="false" enableUpdateByExample="false" enableDeleteByExample="false"enableSelectByExample="false" selectByExampleQueryId="false"></table></context> </generatorConfiguration>5.mybatis-config.xml配置,開啟了二級緩存
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configurationPUBLIC "-//mybatis.org//DTD Config 3.0//EN""http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration><settings><setting name="logImpl" value="STDOUT_LOGGING"/><!--這個配置使全局的映射器(二級緩存)啟用或禁用緩存--><setting name="cacheEnabled" value="true" /></settings> </configuration>6.DAO層添加二級緩存配置
@CacheNamespace(implementation = RedisCache.class,flushInterval = 60L) public interface DirectionalGoodsMapper {@Delete({"delete from fanli_directional_goods","where id = #{id,jdbcType=INTEGER}"})int deleteByPrimaryKey(Integer id);@Select("select g.*,a.img,a.price from fanli_directional_goods g,fanli_akbgoods a where g.goods_id = a.data_id and g.goods_id = #{goodsId} limit 1")DirectGoodsDto selectByGoodsId(String goodsId);@Select("select g.*,a.img,a.price from fanli_directional_goods g,fanli_akbgoods a where g.goods_id = a.data_id and g.goods_id = #{goodsId}")List<DirectGoodsDto> listByGoodsId(String goodsId);@Select("select * from fanli_directional_goods where stime >= #{stime} and etime <= #{etime}")List<DirectionalGoods> pageByStimeAndEtime(String stime, String etime/*, @Param("pageNum")int startIndex, @Param("pageSize")int pageNumIn*/); }二、mybatics的DAO層對象加載流程
1.在CONFIG類中加入了@MapperScan(basePackages = "com.tpw.summaryday.dao", sqlSessionFactoryRef = "test2_sqlSessionFactory"),mapperScan對象引入了@Import(MapperScannerRegistrar.class)
org.mybatis.spring.annotation.MapperScannerRegistrarpublic void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));List<String> basePackages = new ArrayList<String>();for (String pkg : annoAttrs.getStringArray("value")) {if (StringUtils.hasText(pkg)) {basePackages.add(pkg);}}for (String pkg : annoAttrs.getStringArray("basePackages")) {if (StringUtils.hasText(pkg)) {basePackages.add(pkg);}}for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {basePackages.add(ClassUtils.getPackageName(clazz));}scanner.registerFilters();scanner.doScan(StringUtils.toStringArray(basePackages));}?2.然后跳到org.mybatis.spring.mapper.ClassPathMapperScanner.doScan 方法,去搜索DAO目錄下所有的MAPPER對象。
public Set<BeanDefinitionHolder> doScan(String... basePackages) {Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);if (beanDefinitions.isEmpty()) {logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");} else {processBeanDefinitions(beanDefinitions);}return beanDefinitions;}這里生成的BEANDEFINERD中的beanClass就是MapFactoryBean工廠類。
?3.初始化數(shù)據(jù)源,sqlSessionFactory,sqlSessionTemplate對象。
?4.開始創(chuàng)建DAO層代理對象。是根據(jù) org.mybatis.spring.mapper.MapperFactoryBean.getObject來通過工廠類BEAN創(chuàng)建對象。由于是工廠BEAN創(chuàng)建對象,首先要創(chuàng)建工廠對象。
這時在進行工廠對象的實例化和初始化操作。
?5.上面工廠對象的afterPropertiesSet方法中,接著會調(diào)用到org.apache.ibatis.binding.MapperRegistry中的addMapper方法,將mapper映射保存起來。
上面類中有這樣一個MAPPER映射哈希表。為一個****Mapper對象一個MAPPER代理工廠對象。
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>(); MapperRegistry為org.apache.ibatis.session.Configuration下面的一個成員變量。Configuration也為sqlSession下一個成員。 Configuration configuration = getSqlSession().getConfiguration(); public Configuration getConfiguration() {return this.sqlSessionFactory.getConfiguration(); }從上面可以看出,configuation,MapperRegistry為一個sqlSessionFactory全局共享。
public <T> void addMapper(Class<T> type) {if (type.isInterface()) {if (hasMapper(type)) {throw new BindingException("Type " + type + " is already known to the MapperRegistry.");}boolean loadCompleted = false;try {knownMappers.put(type, new MapperProxyFactory<T>(type));loadCompleted = true;} finally {if (!loadCompleted) {knownMappers.remove(type);}}}}?6.這個為 org.mybatis.spring.mapper.MapperFactoryBean.getObject方法,就是通過sqlSession去創(chuàng)建一個mapper對象,所以我們在外面也可以通過sqlSession獲取任意的mybatics的MAPPER對象。
public T getObject() throws Exception {return getSqlSession().getMapper(this.mapperInterface);}?
7.這里面的sqlSession其實為sqlSessionTemplate對象,然后會調(diào)用MapperRegistry的
getMapper方法來創(chuàng)建對象。 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 MapperRegistry.");}try {return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause: " + e, e);}}最終也就是調(diào)用org.apache.ibatis.binding.MapperProxyFactory.newInstance來創(chuàng)建代理對象。
public T newInstance(SqlSession sqlSession) {final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);}?8.我們可以看到,最終就是生成一個JDK的動態(tài)代理對象,真正的代理實現(xiàn)類為MapperProxy
protected T newInstance(MapperProxy<T> mapperProxy) {return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);}三、mybatics的DAO層接口調(diào)用流程
1.上面說過DAO層接口對象實際上是JDK生成的動態(tài)代理對象,dao層的任何接口都會走向代理對象的invoke方法。
org.apache.ibatis.binding.MapperProxy public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);} else if (isDefaultMethod(method)) {return invokeDefaultMethod(proxy, method, args);}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}final MapperMethod mapperMethod = cachedMapperMethod(method);return mapperMethod.execute(sqlSession, args);}2.首先創(chuàng)建mapperMethod對象,傳入sqlSession.getConfiguration()對象。
private MapperMethod cachedMapperMethod(Method method) {MapperMethod mapperMethod = methodCache.get(method);if (mapperMethod == null) {mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());methodCache.put(method, mapperMethod);}return mapperMethod;}3.然后會調(diào)用到MapperMethod.execute方法,從這里可以看到,都是通過sqlSession的接口轉(zhuǎn)發(fā)。
public Object execute(SqlSession sqlSession, Object[] args) {Object result;switch (command.getType()) {case INSERT: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.insert(command.getName(), param));break;}case UPDATE: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.update(command.getName(), param));break;}case DELETE: {Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.delete(command.getName(), param));break;}case SELECT:if (method.returnsVoid() && method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);result = null;} else if (method.returnsMany()) {result = executeForMany(sqlSession, args);} else if (method.returnsMap()) {result = executeForMap(sqlSession, args);} else if (method.returnsCursor()) {result = executeForCursor(sqlSession, args);} else {Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(command.getName(), param);}break;case FLUSH:result = sqlSession.flushStatements();break;default:throw new BindingException("Unknown execution method for: " + command.getName());}return result;}4.由于我們執(zhí)行的是返回一個list,所以執(zhí)行的是executeForMany方法
@Select("select g.*,a.img,a.price from fanli_directional_goods g,fanli_goods a where g.goods_id = a.data_id and g.goods_id = #{goodsId}") List<DirectGoodsDto> listByGoodsId(String goodsId); private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {List<E> result;Object param = method.convertArgsToSqlCommandParam(args);if (method.hasRowBounds()) {RowBounds rowBounds = method.extractRowBounds(args);result = sqlSession.<E>selectList(command.getName(), param, rowBounds);} else {result = sqlSession.<E>selectList(command.getName(), param);}// issue #510 Collections & arrays supportif (!method.getReturnType().isAssignableFrom(result.getClass())) {if (method.getReturnType().isArray()) {return convertToArray(result);} else {return convertToDeclaredCollection(sqlSession.getConfiguration(), result);}}return result;}5.這里面的sqlSession為sqlSessionTemplate對象,繼而調(diào)用此類的selectList方法
public <E> List<E> selectList(String statement, Object parameter) {return this.sqlSessionProxy.<E> selectList(statement, parameter);} 這里的sqlSessionProxy又是一個JDK動態(tài)代理對象,實際的實現(xiàn)類為SqlSessionInterceptor this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),new Class[] { SqlSession.class },new SqlSessionInterceptor());?6.接著就會調(diào)用SqlSessionInterceptor.invoke方法,進行反射調(diào)用。這里面會先獲取一個sqlSession(同一個事務(wù)就會共用,否則每次都會創(chuàng)建一個新的)
private class SqlSessionInterceptor implements InvocationHandler {@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {SqlSession sqlSession = getSqlSession(SqlSessionTemplate.this.sqlSessionFactory,SqlSessionTemplate.this.executorType,SqlSessionTemplate.this.exceptionTranslator);try {Object result = method.invoke(sqlSession, args);if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {// force commit even on non-dirty sessions because some databases require// a commit/rollback before calling close()sqlSession.commit(true);}return result;} catch (Throwable t) {} finally {if (sqlSession != null) {closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);}}}}7.創(chuàng)建session為org.apache.ibatis.session.defaults.DefaultSqlSessionFactory.openSessionFromDataSource,注意這里會先根據(jù)mybatics的config類以及數(shù)據(jù)源開啟一個JDBC的事務(wù),并且生成一個帶緩存的執(zhí)行器,然后會再生成一個SQL會話,傳入執(zhí)行器,配置類,默認自動提交為FALSE。
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {Transaction tx = null;try {final Environment environment = configuration.getEnvironment();final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);final Executor executor = configuration.newExecutor(tx, execType);return new DefaultSqlSession(configuration, executor, autoCommit);} catch (Exception e) {closeTransaction(tx); // may have fetched a connection so lets call close()throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);} finally {ErrorContext.instance().reset();}}?8.接著反射調(diào)用到org.apache.ibatis.session.defaults.DefaultSqlSession.selectList
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {try {MappedStatement ms = configuration.getMappedStatement(statement);return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);} catch (Exception e) {throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);} finally {ErrorContext.instance().reset();} }9.這里面的executor為帶緩存的執(zhí)行器,然后由于我們加入了分頁插件,所以加入了
com.github.pagehelper.pageInterceptor,?
public Object intercept(Invocation invocation) throws Throwable {Object var16;try {Object[] args = invocation.getArgs();MappedStatement ms = (MappedStatement)args[0];Object parameter = args[1];RowBounds rowBounds = (RowBounds)args[2];ResultHandler resultHandler = (ResultHandler)args[3];Executor executor = (Executor)invocation.getTarget();CacheKey cacheKey;BoundSql boundSql;this.checkDialectExists();List resultList;if (!this.dialect.skip(ms, parameter, rowBounds)) {resultList = ExecutorUtil.pageQuery(this.dialect, executor, ms, parameter, rowBounds, resultHandler, boundSql, cacheKey);} else {resultList = executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);}var16 = this.dialect.afterPage(resultList, parameter, rowBounds);} finally {this.dialect.afterAll();}return var16;}10.由于我們是普通查詢,沒有分頁,所以執(zhí)行executor.query(ms, parameter, rowBounds, resultHandler, cacheKey, boundSql);
執(zhí)行到org.apache.ibatis.executor.BaseExecutor.query方法
這里的cache只是在同一個sqlSession中有效,由于spring的特性,每次都會開啟一個新的會話,所以一級緩存無效。這里查詢的是數(shù)據(jù)庫。
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException {ErrorContext.instance().resource(ms.getResource()).activity("executing a query").object(ms.getId());if (queryStack == 0 && ms.isFlushCacheRequired()) {clearLocalCache();}List<E> list;try {queryStack++;list = resultHandler == null ? (List<E>) localCache.getObject(key) : null;if (list != null) {handleLocallyCachedOutputParameters(ms, key, parameter, boundSql);} else {list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql);}} finally {queryStack--;}return list;}11.然后會執(zhí)行到org.apache.ibatis.executor.SimpleExecutor.doQuery,這里最終會去創(chuàng)建數(shù)據(jù)源連接,然后生成一個預(yù)處理的語句,然后進行JDBC的數(shù)據(jù)源查詢 。
?
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {Statement stmt = null;try {Configuration configuration = ms.getConfiguration();StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);stmt = prepareStatement(handler, ms.getStatementLog());return handler.<E>query(stmt, resultHandler);} finally {closeStatement(stmt);}}12.執(zhí)行完查詢結(jié)果后,就會返回到SqlSessionInterceptor.invoke方法,最終發(fā)現(xiàn)本方法沒有開啟事務(wù),進行自動提交,返回結(jié)果,然后關(guān)閉會話。
?13.至此查詢流程完成。
四、mybatics的一級查詢緩存在SPRING中無效,上面已經(jīng)講過,二級緩存可以開啟,在同 一個命名空間中生效。
二級緩存,實現(xiàn)Cache接口,然后底層實現(xiàn)可以使用REDIS等各種分布式緩存。
?
總結(jié)
以上是生活随笔為你收集整理的springboot 加载mybatis的流程的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: springboot 读取nacos配置
- 下一篇: redis 主从哨兵模式搭建