一、Mybatis回顧
1、MyBatis介紹
Mybatis是一個半ORM框架,它使用簡單的 XML 或註解用於配置和原始對映,將介面和Java的POJOs(普通的Java 物件)對映成資料庫中的記錄。
2、Mybatis整體架構
二、Spring Boot整合Mybatis + Druid
1、在應用中匯入maven依賴如下:
<!-- mybatis --> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <!--database pool--> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid</artifactId> <version>1.1.8</version> </dependency> <!--mysql資料庫--> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <scope>runtime</scope> </dependency>
2、在應用中加配置
① 配置Druid資料來源引數:
#配置資料來源 spring.datasource.druid.url=jdbc:mysql://127.0.0.1:3306/demo_db?useUnicode=true&characterEncoding=utf8&useSSL=false spring.datasource.druid.username=root spring.datasource.druid.password=123qwe spring.datasource.druid.driverClassName=com.mysql.jdbc.Driver spring.datasource.druid.initialSize: 5 spring.datasource.druid.minIdle: 5 spring.datasource.druid.maxActive: 20 spring.datasource.druid.maxWait: 60000 spring.datasource.druid.timeBetweenEvictionRunsMillis: 60000 spring.datasource.druid.minEvictableIdleTimeMillis: 300000 spring.datasource.druid.validationQuery: SELECT 1 FROM DUAL spring.datasource.druid.testWhileIdle: true spring.datasource.druid.testOnBorrow: false spring.datasource.druid.testOnReturn: false spring.datasource.druid.poolPreparedStatements: true spring.datasource.druid.filters: stat,wall spring.datasource.druid.maxPoolPreparedStatementPerConnectionSize: 20 spring.datasource.druid.useGlobalDataSourceStat: true spring.datasource.druid.connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500
② 編寫Druid資料來源屬性接收類:
/** * @desc: 自定義druid的屬性 * @author: toby */ @ConfigurationProperties(prefix = "spring.datasource.druid") @Data public class DruidDataSourceProperties { private String username; private String password; private String url; private String driverClassName; private Integer initialSize; private Integer maxActive; private Integer minIdle; private Long maxWait; ...... }
③ 編寫Druid資料來源配置類:
/** * @desc: 自定義druid配置,如不自定義,配置檔案設定的屬性不生效自行測試 * @author: toby */ @Configuration @EnableConfigurationProperties(value = DruidDataSourceProperties.class) @MapperScan(basePackages="com.toby.mapper", value="sqlSessionFactory") public class DruidDataSourceConfig { @Autowired private DruidDataSourceProperties druidDataSourceProperties; @Bean public DataSource dataSource() throws SQLException { DruidDataSource druidDataSource = new DruidDataSource(); druidDataSource.setUsername(druidDataSourceProperties.getUsername()); druidDataSource.setPassword(druidDataSourceProperties.getPassword()); druidDataSource.setUrl(druidDataSourceProperties.getUrl()); druidDataSource.setDriverClassName(druidDataSourceProperties.getDriverClassName()); druidDataSource.setInitialSize(druidDataSourceProperties.getInitialSize()); druidDataSource.setMinIdle(druidDataSourceProperties.getMinIdle()); druidDataSource.setMaxActive(druidDataSourceProperties.getMaxActive()); druidDataSource.setMaxWait(druidDataSourceProperties.getMaxWait()); druidDataSource.setFilters(druidDataSourceProperties.getFilters()); druidDataSourceProperties.setPoolPreparedStatements(druidDataSourceProperties.getPoolPreparedStatements()); return druidDataSource; } }
3、在應用中加@MapperScan註解,他的主要作用就是掃描basePackage包下面的TobyMapper介面,然後getBean的時候通過JDK的動態代理,生成代理物件,所以我們程式中看到的是TobyMapper介面,其實是被動態代理過的。驗證方式很簡單,DEUBG到TobyMapper的方法裡面,就可以發現其就是個動態代理
/** * @desc: spring boot 啟動類 * @author: toby */ @SpringBootApplication @MapperScan(basePackages="com.toby.mapper") public class MybatisApplication { public static void main(String[] args) { SpringApplication.run(MybatisApplication.class, args); } }
三、原始碼解析
1、Mybatis自動裝配
自動裝配的流程圖:
具體詳細見Spring Boot系列(二):Spring Boot自動裝配原理解析中的Spring Boot自動裝配流程圖。Mybatis的自動配置類給我們配置了什麼元件,我們接下來看下MybatisAutoConfiguration類
① SqlSessionFactory:當容器中沒有SqlSessionFactory這個型別的Bean的時候,Spring就載入該元件。
② SqlSessionTemplate:同樣當容器中沒有SqlSessionTemplate這個型別的Bean的時候,Spring就載入該元件。
到此SqlSessionFactory和SqlSessionTemplate元件有了。
2、@MapperScan註解
@MapperScan註解:他的作用就是掃描basePackages包下面的TobyMapper介面,然後getBean("tobyMapper")的時候把該介面通過JDK的動態代理,生成代理物件,用於和資料庫打交道。
① 從@MapperScan入手:
② 匯入了MapperScanner註冊類MapperScannerRegistrar:
它是一個ImportBeanDefinitionRegistrar,Spring在通過@Import匯入Bean的時候,會呼叫其registerBeanDefinitions,往Spring容器中註冊bean定義資訊,以便後面可以通過getBean獲取到被註冊進行的bean的定義資訊所對應的Bean,其往Spring容器中註冊的是MapperFactoryBean型別的Bean定義資訊,為什麼是MapperFactoryBean型別,而不是TobyMapper型別?原因很簡單,Spring容器在getBean的時候,會忽略掉介面,介面是不能new的,而Spring容器預設在例項化的時候就是通過呼叫beanDefinition的beanClass屬性所對應的類的無參構造方法。
③ 進去到註冊bean定義資訊的registerBeanDefinitions方法如下:
④ 進入到ClassPathMapperScanner的doScan,其作用是掃描basePackages所有的包,這裡ClassPathMapper Scanner繼承了Spring的包掃描ClassPathBeanDefinitionScanner
重寫(覆蓋)了判斷是否是候選的Component方法isCandidateComponent,因為Spring預設的isCandidateComponent是會過濾掉介面的,顯然不滿足,所以重寫了該方法
/** * Spring預設的,獨立的非介面,非抽象類 */ protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { AnnotationMetadata metadata = beanDefinition.getMetadata(); return (metadata.isIndependent() && (metadata.isConcrete() || (metadata.isAbstract() && metadata.hasAnnotatedMethods(Lookup.class.getName())))); } /** * ClassPathMapperScanner的可以是獨立的介面 */ @Override protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) { return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent(); }
接下來進入到處理TobyMapper的bean的定義資訊方法:
⑤ 處理TobyMapper的bean定義資訊,主要由3個重要改動:
//第一:修改建構函式為有參的建構函式; definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); //第二:修改bean的class為MapperFactoryBean,該MapperFactoryBean是FactoryBean; definition.setBeanClass(this.mapperFactoryBean.getClass()); //第三:修改注入型別為按照型別注入; definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
到此,掃描TobyMapper的時候,往Spring容器中註冊的是beanClass為MapperFactoryBean,一個有引數的建構函式,按照型別注入的這麼一個Bean定義資訊。
3、通過getBean("tobyMapper")獲取TobyMapper的動態代理類
我們知道,此時的tobyMapper的bean定義資訊中的beanClass的屬性是MapperFactoryBean.class,而MapperFactoryBean又是一個FactoryBean,FactoryBean的特點就是在getBean的時候會呼叫其getObject方法;
① 我們找到MapperFactoryBean的getObject方法:
② 我們再進入org.apache.ibatis.session.defaults.DefaultSqlSession#getMapper方法,其實已經到了Mybatis的邏輯了。
③ 最後呼叫到org.apache.ibatis.binding.MapperRegistry#getMapper,其實就是建立JDK的動態代理了。
@SuppressWarnings("unchecked") 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); } }
代理的邏輯在org.apache.ibatis.binding.MapperProxy#invoke方法,到此tobyMapper建立完成,可以運算元據庫。
四、掃描Mapper和Mapper動態代理物件生成流程圖