MyBatis如何解析mapper和Spring中@MapperScan原理

咱們在啓動項目的時候,spring就會幫咱們實例化好bean,那咱們使用mybatis-spring的時候,編 寫的maper是怎麼交給spring容器的呢?這就是今天要探討的問題。

1、MyBatis

1.1 MyBatis簡介

MyBatis 是一款優秀的持久層框架,它支持自定義 SQL、存儲過程以及高級映射。MyBatis 免除了幾乎全部的 JDBC 代碼以及設置參數和獲取結果集的工做。MyBatis 能夠經過簡單的 XML 或註解來配置和映射原始類型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 對象)爲數據庫中的記錄。html

1.2 MyBatis使用
1.2.1 安裝
<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis</artifactId>
  <version>x.x.x</version>
</dependency>
1.2.2 從 XML 中構建 SqlSessionFactory

​ 每一個基於 MyBatis 的應用都是以一個 SqlSessionFactory 的實例爲核心的。SqlSessionFactory 的實例能夠經過 SqlSessionFactoryBuilder 得到。而 SqlSessionFactoryBuilder 則能夠從 XML 配置文件或一個預先配置的 Configuration 實例來構建出 SqlSessionFactory 實例。java

​ 從 XML 文件中構建 SqlSessionFactory 的實例很是簡單,建議使用類路徑下的資源文件進行配置。 但也可使用任意的輸入流(InputStream)實例,好比用文件路徑字符串或 file:// URL 構造的輸入流。MyBatis 包含一個名叫 Resources 的工具類,它包含一些實用方法,使得從類路徑或其它位置加載資源文件更加容易。mysql

/**
 * @author wangjun
 * @date 2020-08-01
 */
public class SqlSessionFactoryWithXml {
    public static void main(String[] args) {
        String resource = "mybatis-config.xml";
        ClassLoader defaultClassLoader = ClassUtils.getDefaultClassLoader();
        if (defaultClassLoader == null) {
            return;
        }
        InputStream inputStream = defaultClassLoader.getResourceAsStream(resource);
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(inputStream);
        System.out.println(build);
    }
}

XML 配置文件中包含了對 MyBatis 系統的核心設置,包括獲取數據庫鏈接實例的數據源(DataSource)以及決定事務做用域和控制方式的事務管理器(TransactionManager)。後面會再探討 XML 配置文件的詳細內容,這裏先給出一個簡單的示例:git

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/zp"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mappers/NewsMapper.xml"/>
    </mappers>
</configuration>
1.2.3 不使用 XML 構建 SqlSessionFactory

若是你更願意直接從 Java 代碼而不是 XML 文件中建立配置,或者想要建立你本身的配置建造器,MyBatis 也提供了完整的配置類,提供了全部與 XML 文件等價的配置項。github

/**
 * @author wangjun
 * @date 2020-08-01
 */
public class SqlSessionFactoryWithoutXml {
    public static void main(String[] args) {
        DataSource dataSource = DataSourceFactory.getDataSource();
        JdbcTransactionFactory jdbcTransactionFactory = new JdbcTransactionFactory();
        Environment development = new Environment("development", jdbcTransactionFactory, dataSource);
        Configuration configuration = new Configuration(development);
        configuration.addMapper(NewsMapper.class);
        SqlSessionFactory build = new SqlSessionFactoryBuilder().build(configuration);
        System.out.println(build);
    }
}

注意該例中,configuration 添加了一個映射器類(mapper class)。映射器類是 Java 類,它們包含 SQL 映射註解從而避免依賴 XML 文件。不過,因爲 Java 註解的一些限制以及某些 MyBatis 映射的複雜性,要使用大多數高級映射(好比:嵌套聯合映射),仍然須要使用 XML 配置。有鑑於此,若是存在一個同名 XML 配置文件,MyBatis 會自動查找並加載它spring

1.2.4 從 SqlSessionFactory 中獲取 SqlSession

既然有了 SqlSessionFactory,顧名思義,咱們能夠從中得到 SqlSession 的實例。SqlSession 提供了在數據庫執行 SQL 命令所需的全部方法。你能夠經過 SqlSession 實例來直接執行已映射的 SQL 語句。例如:sql

例如:數據庫

try (SqlSession session = sqlSessionFactory.openSession()) {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  Blog blog = mapper.selectBlog(101);
}
1.2.5 探究已映射的 SQL 語句

在上面提到的例子中,一個語句既能夠經過 XML 定義,也能夠經過註解定義。咱們先看看 XML 定義語句的方式,事實上 MyBatis 提供的全部特性均可以利用基於 XML 的映射語言來實現,這使得 MyBatis 在過去的數年間得以流行。若是你用過舊版本的 MyBatis,你應該對這個概念比較熟悉。 但相比於以前的版本,新版本改進了許多 XML 的配置,後面咱們會提到這些改進。這裏給出一個基於 XML 映射語句的示例,它應該能夠知足上個示例中 SqlSession 的調用。apache

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.tim.wang.sourcecode.mybatis.spring.mapper.NewsMapper">

    <select id="selectIds" resultType="java.lang.Integer">
        SELECT ID FROM t_news LIMIT 0,1
    </select>

</mapper>
1.2.6 對命名空間的一點補充

在以前版本的 MyBatis 中,命名空間(Namespaces)的做用並不大,是可選的。 但如今,隨着命名空間愈加重要,你必須指定命名空間。緩存

命名空間的做用有兩個,一個是利用更長的全限定名來將不一樣的語句隔離開來,同時也實現了你上面見到的接口綁定。就算你以爲暫時用不到接口綁定,你也應該遵循這裏的規定,以防哪天你改變了主意。 長遠來看,只要將命名空間置於合適的 Java 包命名空間之中,你的代碼會變得更加整潔,也有利於你更方便地使用 MyBatis。

命名解析:爲了減小輸入量,MyBatis 對全部具備名稱的配置元素(包括語句,結果映射,緩存等)使用了以下的命名解析規則。

  • 全限定名(好比 「com.mypackage.MyMapper.selectAllThings)將被直接用於查找及使用。
  • 短名稱(好比 「selectAllThings」)若是全局惟一也能夠做爲一個單獨的引用。 若是不惟一,有兩個或兩個以上的相同名稱(好比 「com.foo.selectAllThings」 和 「com.bar.selectAllThings」),那麼使用時就會產生「短名稱不惟一」的錯誤,這種狀況下就必須使用全限定名。
1.3 做用域(Scope)和生命週期

依賴注入框架能夠建立線程安全的、基於事務的 SqlSession 和映射器,並將它們直接注入到你的 bean 中,所以能夠直接忽略它們的生命週期。

1.3.1 SqlSessionFactoryBuilder

這個類能夠被實例化、使用和丟棄,一旦建立了 SqlSessionFactory,就再也不須要它了。 所以 SqlSessionFactoryBuilder 實例的最佳做用域是方法做用域(也就是局部方法變量)。 你能夠重用 SqlSessionFactoryBuilder 來建立多個 SqlSessionFactory 實例,但最好仍是不要一直保留着它,以保證全部的 XML 解析資源能夠被釋放給更重要的事情。

1.3.2 SqlSessionFactory

SqlSessionFactory 一旦被建立就應該在應用的運行期間一直存在,沒有任何理由丟棄它或從新建立另外一個實例。 使用 SqlSessionFactory 的最佳實踐是在應用運行期間不要重複建立屢次,屢次重建 SqlSessionFactory 被視爲一種代碼「壞習慣」。所以 SqlSessionFactory 的最佳做用域是應用做用域。 有不少方法能夠作到,最簡單的就是使用單例模式或者靜態單例模式。

1.3.3 SqlSession

每一個線程都應該有它本身的 SqlSession 實例。SqlSession 的實例不是線程安全的,所以是不能被共享的,因此它的最佳的做用域是請求或方法做用域。 絕對不能將 SqlSession 實例的引用放在一個類的靜態域,甚至一個類的實例變量也不行。 也毫不能將 SqlSession 實例的引用放在任何類型的託管做用域中,好比 Servlet 框架中的 HttpSession。 若是你如今正在使用一種 Web 框架,考慮將 SqlSession 放在一個和 HTTP 請求類似的做用域中。 換句話說,每次收到 HTTP 請求,就能夠打開一個 SqlSession,返回一個響應後,就關閉它。 這個關閉操做很重要,爲了確保每次都能執行關閉操做,你應該把這個關閉操做放到 finally 塊中。 下面的示例就是一個確保 SqlSession 關閉的標準模式:

try (SqlSession session = sqlSessionFactory.openSession()) {
  // 你的應用邏輯代碼
}
1.3.4 映射器實例

映射器是一些綁定映射語句的接口。映射器接口的實例是從 SqlSession 中得到的。雖然從技術層面上來說,任何映射器實例的最大做用域與請求它們的 SqlSession 相同。但方法做用域纔是映射器實例的最合適的做用域。 也就是說,映射器實例應該在調用它們的方法中被獲取,使用完畢以後便可丟棄。 映射器實例並不須要被顯式地關閉。儘管在整個請求做用域保留映射器實例不會有什麼問題,可是你很快會發現,在這個做用域上管理太多像 SqlSession 的資源會讓你忙不過來。 所以,最好將映射器放在方法做用域內。就像下面的例子同樣:

try (SqlSession session = sqlSessionFactory.openSession()) {
  BlogMapper mapper = session.getMapper(BlogMapper.class);
  // 你的應用邏輯代碼
}
1.4 MyBatis-Spring
1.4.1 MyBatis-Spring應用

咱們在接入mybatis-spring的時候會在相應的配置類增長這樣的註解

@MapperScan(basePackages = "com.test.**.mapper")

用MyBatis-Spring會將MyBatis整合到Spring當中,我這裏用的版本Spring 5.2.1,MyBatis 3.5.3,MyBatis-Spring 2.0.3,知足官方的要求,用的註解而不是XML

具體的使用,須要配置兩樣東西,一個是SqlSessionFactory,用來建立SqlSession的Factory,SqlSession用來對接數據庫;另外一個是數據映射器Mapper接口

一、在MyBatis-Spring中,能夠經過SqlSessionFactoryBean來建立SqlSessionFactory,這裏還須要一個DataSource,和JDBC等其它DataSource同樣便可

@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
    SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
    factoryBean.setDataSource(dataSource());
    return factoryBean.getObject();
}

大體流程SqlSessionFactoryBean->getObject()->afterPropertiesSet()->buildSqlSessionFactory()->Configuration->SqlSessionFactory

二、映射器是一個接口,定義一個Mapper接口,一個簡單的查詢方法,@Select指定具體的SQL

public interface UserMapper {
    
    @Select("select * from user_info where id = #{id}")
    User getUserById(@Param("id") int id);
}

三、對應ORM實體類

public class User {
    
    private int id;
    private String name;
    private String phone;
    private int age;
    
    @Override
    public String toString() {
        return "id=" + id + ", name=" + name + ", phone=" + phone + ", age=" + age;
    }
}

四、映射器的註冊,經過MapperFactoryBean能夠將映射器註冊到Spring

import com.lihuia.mybatis.mapper.UserMapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.mapper.MapperFactoryBean;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.PropertySource;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.datasource.DriverManagerDataSource;

/**
 * Copyright (C), 2018-2019
 * FileName: MyBatisConfig
 * Author:   lihui
 * Date:     2019/11/15
 */

@PropertySource(value = {"classpath:config/db.properties"})
@Configuration
public class MyBatisConfig {
      @Value("${jdbc.driver}")
    private String driver;
    @Value("${jdbc.url}")
    private String url;
    @Value("${jdbc.username}")
    private String username;
    @Value("${jdbc.password}")
    private String password;
    
    @Bean
    public DriverManagerDataSource dataSource() {
        DriverManagerDataSource dataSource = new DriverManagerDataSource();
        dataSource.setDriverClassName(driver);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource());
        return factoryBean.getObject();
    }
    
      @Bean
    public JdbcTemplate jdbcTemplate() {
        return new JdbcTemplate(dataSource());
    }
    
    @Bean
    public MapperFactoryBean<UserMapper> userMapper() throws Exception {
        MapperFactoryBean<UserMapper> factoryBean = new MapperFactoryBean<>(UserMapper.class);
        factoryBean.setSqlSessionFactory(sqlSessionFactory());
        return factoryBean;
    }

}

五、測試類

@ContextConfiguration(classes = {MyBatisConfig.class})
@Slf4j
public class UserTest extends AbstractTestNGSpringContextTests {

    @Resource
    private JdbcTemplate jdbcTemplate;
    
    @Resource
    private UserMapper userMapper;
    
    
    @Test(description = "測試JDBC")
    public void jdbcTest() {
        String sql = "select * from user_info";
        System.out.println(jdbcTemplate.queryForList(sql));
    }
    
    @Test(description = "測試MyBatis")
    public void myBatisTest() {
        log.info(userMapper.getUserById(1).toString());
    }
}

若是是直接經過注入Bean的方式注入UserMapper,那麼假若有一大堆的映射器,一個一個的註冊注入十分麻煩,所以就和@ComponentScan註解同樣有一個掃描映射器的註解@MapperScan,大體以下

@PropertySource(value = {"classpath:config/db.properties"})
@Configuration
@MapperScan("com.lihuia.mybatis.mapper")
public class MyBatisConfig {
1.4.2 @MapperScan註解
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MapperScannerRegistrar.class)
@Repeatable(MapperScans.class)
public @interface MapperScan {

裏面的@Import註解,能夠導入一個類,定義以下

/**
 * A {@link ImportBeanDefinitionRegistrar} to allow annotation configuration of MyBatis mapper scanning. Using
 * an @Enable annotation allows beans to be registered via @Component configuration, whereas implementing
 * {@code BeanDefinitionRegistryPostProcessor} will work for XML configuration.
 *
 * @author Michael Lanyon
 * @author Eduardo Macarron
 * @author Putthiphong Boonphong
 *
 * @see MapperFactoryBean
 * @see ClassPathMapperScanner
 * @since 1.2.0
 */
public class MapperScannerRegistrar implements ImportBeanDefinitionRegistrar, ResourceLoaderAware {

根據註釋裏能夠看到ClassPathMapperScanner,繼承了ClassPathBeanDefinitionScanner類是Spring提供的一個用於掃描Bean定義配置的基礎類,這裏覆蓋了基類的doScan()方法

/**
 * Calls the parent search that will search and register all the candidates. Then the registered objects are post
 * processed to set them as MapperFactoryBeans
 */
@Override
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;
}

DEBUG一下,能夠看到這個就是掃描Mapper接口的方法,返回@MapperScan註解的value

2、參考連接

https://juejin.im/post/5d8e06...

https://mybatis.org/mybatis-3...

http://lihuia.com/mybatis/

https://juejin.im/post/5db3bc...

https://my.oschina.net/xiaoly...

https://www1350.github.io/hex...

https://cofcool.github.io/tech/2018/06/20/mybatis-sourcecode-1#21-%E9%85%8D%E7%BD%AE%E7%B1%BB

https://objcoding.com/2018/06...

http://www.songshuiyang.com/2018/12/18/backend/framework/mybatis/sourceCodeAnalysis/Mybatis%E6%BA%90%E7%A0%81(%E5%8D%81%E4%B9%9D)Spring%20Mybatis%E9%9B%86%E6%88%90%E4%B9%8B%E5%9F%BA%E4%BA%8E%E6%B3%A8%E8%A7%A3%E7%9A%84%E9%85%8D%E7%BD%AE%E5%8E%9F%E7%90%86%E8%A7%A3%E6%9E%90/Spring Mybatis集成之基於註解的配置原理解析/)

相關文章
相關標籤/搜索