Spring源碼深度解析之spring整合mybatis原理分析(上)

spring整合mybatis原理分析浪南,分為兩篇笼才,上篇從零開始寫一個(gè)spring整合mybatis的代碼漱受,帶大家分析spring整合mybatis的思路。下篇分析spring整合mybatis的源碼骡送。

1 原生mybatis中執(zhí)行SQL

我們先從原生mybatis執(zhí)行SQL的例子昂羡。代碼目錄如下。

│  pom.xml
└─src
    ├─main
    │  ├─java
    │  │  └─com
    │  │      │  Main.java
    │  │      │
    │  │      ├─entity
    │  │      │      User.java
    │  │      │
    │  │      └─mapper
    │  │              BaseDao.java
    │  │              UserDao.java
    │  │
    │  └─resources
    │      │  configuration.xml
    │      │
    │      └─mapper
    │              UserMapper.xml
    │
    └─test
        └─java


實(shí)體類

public class User {
    private Integer id;
    private String userName;
    private int age;
    private String address;
.........
}

DAO類

public interface BaseDao<T>{
     T findById(Integer id);
}
public interface UserDao extends BaseDao<User>{
}

mapper文件UserMapper.xml

<?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">
<!-- 指明select * from user where id=#{id}這條sql對應(yīng)test.dao下的UserDao.java下的findById方法 -->
<!-- 同時(shí)findById的返回是一個(gè)User類型的類 -->
<mapper namespace="com.mapper.UserDao">
    <select id="findById" parameterType="HashMap" resultType="com.entity.User">
        select id, user_name userName, age, address from user where id=#{id}
    </select>
</mapper>

mybatis配置

<?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>
    <!-- 寫明關(guān)于mysql的連接信息 -->
    <environments default="development">
        <environment id="development">
            <transactionManager type="jdbc" />
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis" />
                <property name="username" value="root" />
                <property name="password" value="6128109" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="./mapper/UserMapper.xml" />
    </mappers>
</configuration>

表結(jié)構(gòu)

CREATE TABLE `mybatis`.`Untitled`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `user_name` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  `age` int(11) NULL DEFAULT NULL,
  `address` varchar(255) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 3 CHARACTER SET = utf8mb4 COLLATE = utf8mb4_general_ci ROW_FORMAT = Dynamic;

maven配置

    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.9</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-context</artifactId>
            <version>5.2.19.RELEASE</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis-spring</artifactId>
            <version>2.0.7</version>
        </dependency>
    </dependencies>

最后是執(zhí)行方法摔踱。

public class Main {
    public static void main(String[] args) throws IOException {
 SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(Resources
                .getResourceAsReader("configuration.xml"));
        SqlSession sqlSession = sessionFactory.openSession();
        UserDao userMapper = sqlSession.getMapper(UserDao.class);
        User user = userMapper.findById(1);
        System.out.println(user.toString());
    }
}
User{id=1, userName='zzl', age=18, address='法守法'}

從上面一個(gè)簡單的mybatis的例子虐先,大家對spring整合mybatis是否有靈感,我們以UserDao 來分析一下spirng如何整合mybatis:

  1. 如果是UserDao 能通過@Autowired注入實(shí)例派敷,那么首先需要把UserDao放入spring容器中蛹批,但是UserDao是一個(gè)接口,所以放入spring容器的是一個(gè)代理對象,這里我們可以使用FactoryBean诊沪。
  2. 從最后的執(zhí)行方法可以看到被啼,只要我們通過sqlSession.getMapper(UserDao.class)拿到mybatis中UserDao的代理對象那么我們就可以通過這個(gè)代理對象執(zhí)行方法。
  3. 通過上面分析可以知道UserDao 其實(shí)有兩個(gè)代理對象猪勇,第一次是在mybatis中生成的代理對象设褐,第二次放入spring容器的生產(chǎn)代理對象,也就是我們通過FactoryBean生成的泣刹。

spring整合mybatis

按上面分析的思路助析,我們新建一個(gè)FactoryBean類。


@Component
public class UserDaoFactoryBean implements FactoryBean {

    @Override
    public Object getObject() throws Exception {
        Object proxyInstance = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{UserDao.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

                SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(Resources
                        .getResourceAsReader("configuration.xml"));
                SqlSession sqlSession = sessionFactory.openSession();
                 Object result = method.invoke(userMapper, args);
                return result;
            }
        });
        return proxyInstance;
    }

    @Override
    public Class<?> getObjectType() {
        return UserDao.class;
    }
}

新建一個(gè)UserService并入住UserDao

@Service
public class UserService {

    @Autowired
    UserDao userDao;

    public User getUser(int id){
      return userDao.findById(id);
    }
}

新建spring配置類

@ComponentScan("com")
public class AppConfig {
}
public class Main {
    public static void main(String[] args) throws IOException {
//        String resource = "configuration.xml";
//
//        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(Resources
//                .getResourceAsReader(resource));
//        SqlSession sqlSession = sessionFactory.openSession();
//        UserDao userMapper = sqlSession.getMapper(UserDao.class);
//        User user = userMapper.findById(1);
//        System.out.println(user.toString());

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        UserService userService = context.getBean(UserService.class);
        System.out.println(userService.getUser(1));

    }
}

執(zhí)行結(jié)果

User{id=1, userName='zzl', age=18, address='法守法'}

從上結(jié)果可以看到我們已經(jīng)把userDao整合到spring中椅您。但是獲取SqlSessionFactory 是公共代碼我們把它抽取出來外冀。重構(gòu)后的代碼如下。

@ComponentScan("com")
public class AppConfig {

    @Bean
    SqlSessionFactory getSessionFactory() throws IOException {
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(Resources
                .getResourceAsReader("configuration.xml"));
        return sessionFactory;
    }

}
@Component
public class UserDaoFactoryBean implements FactoryBean {

   private SqlSession sqlSession;

    public UserDaoFactoryBean(SqlSessionFactory sessionFactory) {
        this.sqlSession = sessionFactory.openSession();
    }

    @Override
    public Object getObject() throws Exception {
        Object proxyInstance = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{UserDao.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                UserDao userMapper = sqlSession.getMapper(UserDao.class);
                Object result = method.invoke(userMapper, args);
                return result;
            }
        });
        return proxyInstance;
    }

    @Override
    public Class<?> getObjectType() {
        return UserDao.class;
    }
}

重構(gòu)后大家對整合mybatis是不是感覺有點(diǎn)熟悉了掀泳。

通過spirng 的bean工廠后置處理器BeanFactoryPostProcessor注冊bean

UserDaoFactoryBean 中的接口類是寫死的锥惋,我們不可能為每一個(gè)接口都建一個(gè)FactoryBean,所以我們FactoryBean需要變的更通用开伏。重構(gòu)UserDaoFactoryBean 膀跌。

構(gòu)造函數(shù)改為傳入接口類,新增一個(gè)setSqlSessionFactory用于注入SqlSessionFactory



public class MybatisFactoryBean implements FactoryBean {

   private SqlSession sqlSession;
   private Class mapperInterface;
  

    public MybatisFactoryBean(Class mapperInterface) {
        this.mapperInterface = mapperInterface;
    }

    
    @Override
    public Object getObject() throws Exception {
        Object proxyInstance = Proxy.newProxyInstance(this.getClass().getClassLoader(), new Class[]{mapperInterface}, (proxy, method, args) -> {
            UserDao userMapper = sqlSession.getMapper(UserDao.class);
            Object result = method.invoke(userMapper, args);
            return result;
        });
        return proxyInstance;
    }

    @Override
    public Class<?> getObjectType() {
        return mapperInterface;
    }

    public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
        this.sqlSession = sqlSessionFactory.openSession();
    }
}

為了方便理解和對比固灵,我們在新增一個(gè)OrderDao

public interface OrderDao extends BaseDao<Order>{
}

在bean工廠后置處理器中注冊FactoryBeanBeanDefinition捅伤。(如果對bean工廠后置處理器不了解,只需知道在spring啟動(dòng)的時(shí)候會(huì)自動(dòng)調(diào)用postProcessBeanDefinitionRegistry方法即可)


@Component
public class MybatisBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
    private Class<? extends MybatisFactoryBean> mapperFactoryBeanClass = MybatisFactoryBean.class;
    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

        AbstractBeanDefinition beanDefinition1 = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
         beanDefinition1.setBeanClass(mapperFactoryBeanClass);
        ConstructorArgumentValues constructorArgumentValues = beanDefinition1.getConstructorArgumentValues();
        constructorArgumentValues.addGenericArgumentValue(UserDao.class);
        //自動(dòng)注入BY TYPE
        beanDefinition1.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        registry.registerBeanDefinition("userDao",beanDefinition1);

        AbstractBeanDefinition beanDefinition2 = BeanDefinitionBuilder.genericBeanDefinition().getBeanDefinition();
        beanDefinition2.setBeanClass(mapperFactoryBeanClass);
        beanDefinition2.getConstructorArgumentValues().addGenericArgumentValue(OrderDao.class);
        //自動(dòng)注入BY TYPE
        beanDefinition2.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
        registry.registerBeanDefinition("orderDao",beanDefinition2);
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

    }
}

通過這種方式我們不用再為每一個(gè)接口都建一個(gè)FactoryBean巫玻,bean的注入模式為by type用于SqlSessionFactory的注入丛忆。

掃描

通過上面的兩個(gè)可以看出,其實(shí)除了構(gòu)造函數(shù)傳入的接口類型不一樣仍秤,其他都是一致的熄诡,那么我們是否可以約定一個(gè)目錄,我們掃描這個(gè)目錄把需要代理的接口讀進(jìn)來即可诗力。

新增一個(gè)掃描注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Import(MybatisImportBeanDefinitionRegistrar.class)
public @interface MybatisMapperScan {
    String value();
}

MybatisMapperScan 注解上通過@Import導(dǎo)入MybatisImportBeanDefinitionRegistrar用于注冊一個(gè)MybatisBeanDefinitionRegistryPostProcessor凰浮。

public class MybatisImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry, BeanNameGenerator importBeanNameGenerator) {

        Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(MybatisMapperScan.class.getName());
        String basePackage =(String) annotationAttributes.get("value");
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MybatisBeanDefinitionRegistryPostProcessor.class);
        //傳入要掃描的包路徑
        builder.addPropertyValue("basePackage", basePackage);
        builder.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        registry.registerBeanDefinition(generateBaseBeanName(importingClassMetadata,0), builder.getBeanDefinition());
    }

    private static String generateBaseBeanName(AnnotationMetadata importingClassMetadata, int index) {
        return importingClassMetadata.getClassName() + "#" + MybatisImportBeanDefinitionRegistrar.class.getSimpleName() + "#" + index;
    }
}

MybatisBeanDefinitionRegistryPostProcessor 邏輯中關(guān)鍵是MybatisClassPathBeanDefinitionScanner類,
它是ClassPathBeanDefinitionScanner的子類苇本。(ClassPathBeanDefinitionScanner是用來掃描@Component袜茧,@Service等注解的,所以我們需要繼承這個(gè)類瓣窄,并重寫相關(guān)方法笛厦,只掃描接口),另外它還實(shí)現(xiàn)BeanNameAware接口用于注入bean名字俺夕。


public class MybatisBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor, BeanNameAware {

    private String basePackage;
    private String beanName;

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {

        BeanDefinition mapperScannerBean = registry.getBeanDefinition(beanName);
        PropertyValues propertyValues = mapperScannerBean.getPropertyValues();
        basePackage =  propertyValues.getPropertyValue("basePackage").getValue().toString();
        // mybatis接口掃描器
        MybatisClassPathBeanDefinitionScanner scanner = new MybatisClassPathBeanDefinitionScanner(registry);
        //這個(gè)過濾器在spring中是用來判斷是否有@Component等注解的裳凸,覆蓋默認(rèn)值贱鄙,所有情況都返回true,
        scanner.addIncludeFilter((metadataReader, metadataReaderFactory) -> true);
        scanner.scan(basePackage);
        
    }

    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
      
    }

    public void setBasePackage(String basePackage) {
        this.basePackage = basePackage;
    }

    @Override
    public void setBeanName(String name) {
        this.beanName = name;
    }
}

MybatisClassPathBeanDefinitionScanner 實(shí)現(xiàn)如下


public class MybatisClassPathBeanDefinitionScanner  extends ClassPathBeanDefinitionScanner {
    private Class<? extends MybatisFactoryBean> mapperFactoryBeanClass = MybatisFactoryBean.class;

    public MybatisClassPathBeanDefinitionScanner(BeanDefinitionRegistry registry) {
        super(registry);
    }

    @Override
    protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
        Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
        for (BeanDefinitionHolder beanDefinitionHolder : beanDefinitionHolders) {
            AbstractBeanDefinition beanDefinition =(AbstractBeanDefinition) beanDefinitionHolder.getBeanDefinition();
            beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(beanDefinition.getBeanClassName());
            //類型注入
            beanDefinition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);
            beanDefinition.setBeanClass(this.mapperFactoryBeanClass);
        }
        return beanDefinitionHolders;
    }

    @Override
    protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
        //只掃描接口
        return beanDefinition.getMetadata().isInterface();
    }
}

在AppConfig中使用@MybatisMapperScan注解

@ComponentScan("com")
@MybatisMapperScan("com.mapper")
public class AppConfig {

    @Bean
    SqlSessionFactory getSessionFactory() throws IOException {
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(Resources
                .getResourceAsReader("configuration.xml"));
        return sessionFactory;
    }

}

執(zhí)行

   public static void main(String[] args) throws IOException {

        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        UserService userService = context.getBean(UserService.class);
        System.out.println(userService.getUser(1));

    }

測試結(jié)果如下


image.png
image.png

spring集成mybatis第一篇到這里就分析結(jié)束了,大家可能覺得有點(diǎn)奇怪姨谷,在MybatisImportBeanDefinitionRegistrar類中可以直接掃描贰逾,為什么還要注冊一個(gè)MybatisBeanDefinitionRegistryPostProcessor,在mybatis中整合到spring的項(xiàng)目中有兩個(gè)版本菠秒,第一個(gè)版本是在ImportBeanDefinitionRegistrar子類中實(shí)現(xiàn)掃描疙剑,第二個(gè)版本才改為通過實(shí)現(xiàn)BeanDefinitionRegistryPostProcessor在進(jìn)行掃描,至于原因践叠,我們在第二篇源碼分析的時(shí)候在討論言缤。

附源碼spring整合mybatis源碼地址

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市禁灼,隨后出現(xiàn)的幾起案子管挟,更是在濱河造成了極大的恐慌,老刑警劉巖弄捕,帶你破解...
    沈念sama閱讀 218,682評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件僻孝,死亡現(xiàn)場離奇詭異,居然都是意外死亡守谓,警方通過查閱死者的電腦和手機(jī)穿铆,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,277評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來斋荞,“玉大人荞雏,你說我怎么就攤上這事∑侥穑” “怎么了凤优?”我有些...
    開封第一講書人閱讀 165,083評論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蜈彼。 經(jīng)常有香客問我筑辨,道長,這世上最難降的妖魔是什么幸逆? 我笑而不...
    開封第一講書人閱讀 58,763評論 1 295
  • 正文 為了忘掉前任棍辕,我火速辦了婚禮,結(jié)果婚禮上秉颗,老公的妹妹穿的比我還像新娘痢毒。我一直安慰自己送矩,他們只是感情好蚕甥,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,785評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著栋荸,像睡著了一般菇怀。 火紅的嫁衣襯著肌膚如雪凭舶。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,624評論 1 305
  • 那天爱沟,我揣著相機(jī)與錄音帅霜,去河邊找鬼。 笑死呼伸,一個(gè)胖子當(dāng)著我的面吹牛身冀,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播括享,決...
    沈念sama閱讀 40,358評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼搂根,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了铃辖?” 一聲冷哼從身側(cè)響起剩愧,我...
    開封第一講書人閱讀 39,261評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎娇斩,沒想到半個(gè)月后仁卷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,722評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡犬第,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,900評論 3 336
  • 正文 我和宋清朗相戀三年锦积,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片歉嗓。...
    茶點(diǎn)故事閱讀 40,030評論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡充包,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出遥椿,到底是詐尸還是另有隱情基矮,我是刑警寧澤,帶...
    沈念sama閱讀 35,737評論 5 346
  • 正文 年R本政府宣布冠场,位于F島的核電站家浇,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏碴裙。R本人自食惡果不足惜钢悲,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,360評論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望舔株。 院中可真熱鬧莺琳,春花似錦、人聲如沸载慈。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,941評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽办铡。三九已至辞做,卻和暖如春琳要,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背秤茅。 一陣腳步聲響...
    開封第一講書人閱讀 33,057評論 1 270
  • 我被黑心中介騙來泰國打工稚补, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人框喳。 一個(gè)月前我還...
    沈念sama閱讀 48,237評論 3 371
  • 正文 我出身青樓课幕,卻偏偏與公主長得像,于是被迫代替她去往敵國和親五垮。 傳聞我的和親對象是個(gè)殘疾皇子撰豺,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,976評論 2 355

推薦閱讀更多精彩內(nèi)容