Spring整合Mybatis實(shí)操分享
在介紹Spring整合Mybatis原理之前,我們得先來(lái)稍微介紹Mybatis的工作原理。
Mybatis的基本工作原理
在Mybatis
中,我們可以使用一個(gè)接口去定義要執(zhí)行sql,簡(jiǎn)化代碼如下: 定義一個(gè)接口,@Select表示要執(zhí)行查詢(xún)sql語(yǔ)句。
public interface UserMapper { @Select("select * from user where id = #{id}") User selectById(Integer id); }
執(zhí)行代碼:
InputStream inputStream = Resources.getResourceAsStream("mybatis.xml" ); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder(). build(inputStream); SqlSession sqlSession = sqlSessionFactory.openSession(); // 以下使我們需要關(guān)注的重點(diǎn) UserMapper mapper = sqlSession.getMapper(UserMapper.class); Integer id = 1; User user = mapper.selectById(id);
Mybatis的目的是:使得程序員能夠以調(diào)用方法的方式執(zhí)行某個(gè)指定的sql,將執(zhí)行sql的底層邏輯進(jìn)行了封裝。 這里重點(diǎn)思考以下mapper這個(gè)對(duì)象,當(dāng)調(diào)用SqlSession的getMapper方法時(shí),會(huì)對(duì)傳入的接口生成一個(gè) 代理對(duì)象,而程序要真正用到的就是這個(gè)代理對(duì)象,在調(diào)用代理對(duì)象的方法時(shí),Mybatis會(huì)取出該方法所對(duì)應(yīng)的sql語(yǔ)句,然后利用JDBC去執(zhí)行sql語(yǔ)句,最終得到結(jié)果。
分析需要解決的問(wèn)題
Spring和Mybatis時(shí),我們重點(diǎn)要關(guān)注的就是這個(gè)代理對(duì)象。因?yàn)檎系哪康木褪牵喊涯硞€(gè)Mapper的代理 對(duì)象作為一個(gè)bean放入Spring容器中,使得能夠像使用一個(gè)普通bean一樣去使用這個(gè)代理對(duì)象,比如能 被@Autowire自動(dòng)注入。 比如當(dāng)Spring和Mybatis整合之后,我們就可以使用如下的代碼來(lái)使用Mybatis中的代理對(duì)象了:
@Component public class UserService { @Autowired private UserMapper userMapper; public User getUserById(Integer id) { return userMapper.selectById(id); } }
UserService
中的userMapper屬性就會(huì)被自動(dòng)注入為Mybatis中的代理對(duì)象。如果你基于一個(gè)已經(jīng)完成整合的項(xiàng)目去調(diào)試即可發(fā)現(xiàn),userMapper的類(lèi)型為: org.apache.ibatis.binding.MapperProxy@41a0aa7d。證明確實(shí)是Mybatis中的代理對(duì)象。 好,那么現(xiàn)在我們要解決的問(wèn)題的就是:如何能夠把Mybatis的代理對(duì)象作為一個(gè)bean放入Spring容器中?要解決這個(gè),我們需要對(duì)Spring的bean生成過(guò)程有一個(gè)了解。
Spring中Bean的產(chǎn)生過(guò)程
Spring啟動(dòng)過(guò)程中,大致會(huì)經(jīng)過(guò)如下步驟去生成bean
- 掃描指定的包路徑下的class文件
- 根據(jù)class信息生成對(duì)應(yīng)的
BeanDefinition
- 在此處,程序員可以利用某些機(jī)制去修改
BeanDefinition
- 根據(jù)BeanDefinition生成bean實(shí)例
- 把生成的bean實(shí)例放入Spring容器中
假設(shè)有一個(gè)A類(lèi),假設(shè)有如下代碼: 一個(gè)A類(lèi)
@Component public class A { }
一個(gè)B類(lèi),不存在@Component注解
public class B { }
執(zhí)行如下代碼:
AnnotationConfigApplicationContext context = new AnnotationConfigAppl icationContext(AppConfig.class); System.out.println(context.getBean("a"));
輸出結(jié)果為:com.luban.util.A@6acdbdf5
A類(lèi)對(duì)應(yīng)的bean對(duì)象類(lèi)型仍然為A類(lèi)。但是這個(gè)結(jié)論是不確定的,我們可以利用BeanFactory后置處理器來(lái) 修改BeanDefinition,我們添加一個(gè)BeanFactory后置處理器:
@Component public class LubanBeanFactoryPostProcessor implements BeanFactoryPost Processor { @Override public void postProcessBeanFactory(ConfigurableListableBeanFactor y beanFactory) throws BeansException { BeanDefinition beanDefinition = beanFactory.getBeanDefinition ("a"); beanDefinition.setBeanClassName(B.class.getName()); } }
這樣就會(huì)導(dǎo)致,原本的A類(lèi)對(duì)應(yīng)的BeanDefiniton被修改了,被修改成了B類(lèi),那么后續(xù)正常生成的bean對(duì) 象的類(lèi)型就是B類(lèi)。此時(shí),調(diào)用如下代碼會(huì)報(bào)錯(cuò):
context.getBean(A.class);
但是調(diào)用如下代碼不會(huì)報(bào)錯(cuò),盡管B類(lèi)上沒(méi)有@Component注解:
context.getBean(B.class);
并且,下面代碼返回的結(jié)果是:
com.luban.util.B@4b1c1ea0
AnnotationConfigApplicationContext context = new AnnotationConfigAppl icationContext(AppConfig.class); System.out.println(context.getBean("a"));
之所以講這個(gè)問(wèn)題,是想說(shuō)明?個(gè)問(wèn)題:在Spring中,bean對(duì)象跟class沒(méi)有直接關(guān)系,跟 BeanDefinition才有直接關(guān)系。 那么回到我們要解決的問(wèn)題:如何能夠把Mybatis的代理對(duì)象作為一個(gè)bean放入Spring容器中? 在Spring中,如果你想生成一個(gè)bean,那么得先生成一個(gè)BeanDefinition,就像你想new一個(gè)對(duì)象實(shí) 例,得先有一個(gè)class。
解決問(wèn)題
繼續(xù)回到我們的問(wèn)題,我們現(xiàn)在想自己生成一個(gè)bean,那么得先生成一個(gè)BeanDefinition
,只要有了 BeanDefinition
,通過(guò)在BeanDefinition中設(shè)置bean對(duì)象的類(lèi)型,然后把BeanDefinition添加給 Spring,Spring就會(huì)根據(jù)BeanDefinition
?動(dòng)幫我們?成?個(gè)類(lèi)型對(duì)應(yīng)的bean對(duì)象。
所以,現(xiàn)在我們要解決兩個(gè)問(wèn)題:
- Mybatis的代理對(duì)象的類(lèi)型是什么?因?yàn)槲覀円O(shè)置給
BeanDefinition
- 我們?cè)趺窗?code>BeanDefinition添加給Spring容器?
注意:上文中我們使用的BeanFactory后置處理器,他只能修改BeanDefinition
,并不能新增一個(gè) BeanDefinition。我們應(yīng)該使用Import技術(shù)來(lái)添加一個(gè)BeanDefinition。后面再詳細(xì)介紹如果使用Import 技術(shù)來(lái)添加一個(gè)BeanDefinition,可以先看一下偽代碼實(shí)現(xiàn)思路。
假設(shè):我們有一個(gè)UserMapper接口,他的代理對(duì)象的類(lèi)型為UserMapperProxy
。 那么我們的思路就是這樣的,
偽代碼如下:
BeanDefinitoin bd = new BeanDefinitoin(); bd.setBeanClassName(UserMapperProxy.class.getName()); SpringContainer.addBd(bd);
但是,這里有一個(gè)嚴(yán)重的問(wèn)題,就是上文中的UserMapperProxy是我們假設(shè)的,他表示一個(gè)代理類(lèi)的類(lèi) 型,然而Mybatis中的代理對(duì)象是利用的JDK的動(dòng)態(tài)代理技術(shù)實(shí)現(xiàn)的,也就是代理對(duì)象的代理類(lèi)是動(dòng)態(tài)生成的,我們根本方法確定代理對(duì)象的代理類(lèi)到底是什么。 所以回到我們的問(wèn)題:Mybatis的代理對(duì)象的類(lèi)型是什么? 本來(lái)可以有兩個(gè)答案: 1. 代理對(duì)象對(duì)應(yīng)的代理類(lèi) 2. 代理對(duì)象對(duì)應(yīng)的接口 那么答案1就相當(dāng)于沒(méi)有了,因?yàn)槭谴眍?lèi)是動(dòng)態(tài)生成的,那么我們來(lái)看答案2:代理對(duì)象對(duì)應(yīng)的接口如果我們采用答案2,那么我們的思路就是:
BeanDefinition bd = new BeanDefinitoin(); // 注意這?,設(shè)置的是UserMapper bd.setBeanClassName(UserMapper.class.getName()); SpringContainer.addBd(bd);
但是,實(shí)際上給BeanDefinition
對(duì)應(yīng)的類(lèi)型設(shè)置為一個(gè)接口是行不通的,因?yàn)镾pring沒(méi)有辦法根據(jù)這個(gè) BeanDefinition去new出對(duì)應(yīng)類(lèi)型的實(shí)例,接口是沒(méi)法直接new出實(shí)例的。 那么現(xiàn)在問(wèn)題來(lái)了,我要解決的問(wèn)題:Mybatis的代理對(duì)象的類(lèi)型是什么? 兩個(gè)答案都被我們否定了,所以這個(gè)問(wèn)題是無(wú)解的,所以我們不能再沿著這個(gè)思路去思考了,只能回到最 開(kāi)始的問(wèn)題:如何能夠把Mybatis的代理對(duì)象作為一個(gè)bean放入Spring容器中?
總結(jié)上文的推理:我們想通過(guò)設(shè)置BeanDefinition的class類(lèi)型,然后由Spring自動(dòng)的幫助我們?nèi)ド蓪?duì)應(yīng)的bean,但是這條路是行不通的。 終極解決方案 那么我們還有沒(méi)有其他辦法,可以去生成bean呢?并且生成bean的邏輯不能由Spring來(lái)幫我們做了,得 由我們自己來(lái)做。 FactoryBean 有,那就是Spring中的FactoryBean。我們可以利用FactoryBean去自定義我們要生成的bean對(duì)象,比如
@Component public class LubanFactoryBean implements FactoryBean { @Override public Object getObject() throws Exception { Object proxyInstance = Proxy.newProxyInstance(LubanFactoryBe an.class. getClassLoader(), new Class[]{UserMapper.class}, new Invoca tionHandler() { @Override public Object invoke(Object proxy, Method method, Object [] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else { // 執(zhí)?代理邏輯 return null; } } }); return proxyInstance; } @Override public Class<?> getObjectType() { return UserMapper.class; } }
我們定義了一個(gè)LubanFactoryBean
,它實(shí)現(xiàn)了FactoryBean,getObject方法就是用來(lái)自定義生成bean 對(duì)象邏輯的。 執(zhí)行如下代碼:
public class Test { public static void main(String[] args) { AnnotationConfigApplicationContext context = new AnnotationCo nfigApplicationContext(AppConfig.class); System.out.println("lubanFactoryBean: " + context.getBean ("lu banFactoryBean")); System.out.println("&lubanFactoryBean: " + context.getBean ("& lubanFactoryBean")); System.out.println("lubanFactoryBean-class: " + context.getBe an("lubanFactoryBean").getClass()); } }
將打印: lubanFactoryBean: com.luban.util.LubanFactoryBean1@4d41cee &lubanFactoryBean: com.luban.util.LubanFactoryBean@3712b94 lubanFactoryBean-class: class com.sun.proxy.Proxy20 從結(jié)果我們可以看到,從Spring容器中拿名字為"lubanFactoryBean"的bean對(duì)象,就是我們所自定義的 jdk動(dòng)態(tài)代理所生成的代理對(duì)象。
所以,我們可以通過(guò)FactoryBean來(lái)向Spring容器中添加一個(gè)自定義的bean對(duì)象。上文中所定義的 LubanFactoryBean對(duì)應(yīng)的就是UserMapper,表示我們定義了一個(gè)LubanFactoryBean,相當(dāng)于把 UserMapper對(duì)應(yīng)的代理對(duì)象作為一個(gè)bean放入到了容器中。 但是作為程序員,我們不可能每定義了一個(gè)Mapper,還得去定義一個(gè)LubanFactoryBean,這是很麻煩的 事情,我們改造一下LubanFactoryBean,讓他變得更通用,
比如:
@Component public class LubanFactoryBean implements FactoryBean { // 注意這里 private Class mapperInterface; public LubanFactoryBean(Class mapperInterface) { this.mapperInterface = mapperInterface; } @Override public Object getObject() throws Exception { Object proxyInstance = Proxy.newProxyInstance(LubanFactoryBe an.class.getClassLoader(), new Class[]{mapperInterface}, new Invocat ionHandler() { @Override public Object invoke(Object proxy, Method method, Object [] args) throws Throwable { if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); } else { // 執(zhí)行代理邏輯 return null; } } }); return proxyInstance; } @Override public Class<?> getObjectType() { return mapperInterface; } }
改造LubanFactoryBean
之后,LubanFactoryBean
變得靈活了,可以在構(gòu)造LubanFactoryBean時(shí),通 過(guò)構(gòu)造傳入不同的Mapper接口。 實(shí)際上LubanFactoryBean也是一個(gè)Bean,我們也可以通過(guò)生成一個(gè)BeanDefinition來(lái)生成一個(gè) LubanFactoryBean,并給構(gòu)造方法的參數(shù)設(shè)置不同的值,比如偽代碼如下:
BeanDefinition bd = new BeanDefinitoin(); // 注意一:設(shè)置的是LubanFactoryBean bd.setBeanClassName(LubanFactoryBean.class.getName()); // 注意二:表示當(dāng)前BeanDefinition在生成bean對(duì)象時(shí),會(huì)通過(guò)調(diào)用LubanFactoryBean 的構(gòu)造方法來(lái)生成,并傳入U(xiǎn)serMapper bd.getConstructorArgumentValues().addGenericArgumentValue(UserMapper. class.getName()) SpringContainer.addBd(bd)
特別說(shuō)一下注意二,表示表示當(dāng)前BeanDefinition
在生成bean對(duì)象時(shí),會(huì)通過(guò)調(diào)用LubanFactoryBean的 構(gòu)造方法來(lái)生成,并傳入U(xiǎn)serMapper的Class對(duì)象。那么在生成LubanFactoryBean時(shí)就會(huì)生成一個(gè) UserMapper接口對(duì)應(yīng)的代理對(duì)象作為bean了。 到此為止,其實(shí)就完成了我們要解決的問(wèn)題:把Mybatis中的代理對(duì)象作為一個(gè)bean放入Spring容器中。
只是我們這是用簡(jiǎn)單的JDK代理對(duì)象模擬的Mybatis中的代理對(duì)象,如果有時(shí)間,我們完全可以調(diào)? Mybatis中提供的方法區(qū)生成一個(gè)代理對(duì)象。這里就不花時(shí)間去介紹了。 Import 到這里,我們還有一個(gè)事情沒(méi)有做,就是怎么真正的定義一個(gè)BeanDefinition,并把它添加到Spring中, 上文說(shuō)到我們要利用Import技術(shù),比如可以這么實(shí)現(xiàn):
定義如下類(lèi):
public class LubanImportBeanDefinitionRegistrar implements ImportBea nDefinitionRegistrar { @Override public void registerBeanDefinitions(AnnotationMetadata importing ClassMetadata, BeanDefinitionRegistry registry) { BeanDefinitionBuilder builder = BeanDefinitionBuilder.generi cBeanDefinition(); AbstractBeanDefinition beanDefinition = builder.getBeanDefin ition(); beanDefinition.setBeanClass(LubanFactoryBean.class); beanDefinition.getConstructorArgumentValues().addGenericArgu mentValue(UserMapper.class); // 添加beanDefinition registry.registerBeanDefinition("luban"+UserMapper.class.get SimpleName(), beanDefinition); } }
并且在AppConfig上添加@Import注解:
@Import(LubanImportBeanDefinitionRegistrar.class) public class AppConfig {
這樣在啟動(dòng)Spring時(shí)就會(huì)新增一個(gè)BeanDefinition,該BeanDefinition會(huì)生成一個(gè)LubanFactoryBean對(duì) 象,并且在生成LubanFactoryBean對(duì)象時(shí)會(huì)傳入U(xiǎn)serMapper.class對(duì)象,通過(guò)LubanFactoryBean內(nèi)部 的邏輯,相當(dāng)于會(huì)自動(dòng)生產(chǎn)一個(gè)UserMapper接口的代理對(duì)象作為一個(gè)bean。
總結(jié)
總結(jié)一下,通過(guò)我們的分析,我們要整合Spring和Mybatis,需要我們做的事情如下:
- 定義一個(gè)
LubanFactoryBean
- 定義一個(gè)
LubanImportBeanDefinitionRegistrar
- 在AppConfig上添加一個(gè)注解
@Import
(LubanImportBeanDefinitionRegistrar.class)
到此這篇關(guān)于Spring整合Mybatis實(shí)操分享的文章就介紹到這了,更多相關(guān)Spring整合Mybatis內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java實(shí)現(xiàn)二分搜索樹(shù)的示例代碼
二分搜索樹(shù)是一顆二叉樹(shù),二分搜索樹(shù)每個(gè)節(jié)點(diǎn)的左子樹(shù)的值都小于該節(jié)點(diǎn)的值,每個(gè)節(jié)點(diǎn)右子樹(shù)的值都大于該節(jié)點(diǎn)的值。本文將利用Java實(shí)現(xiàn)二分搜索樹(shù),需要的可以參考一下2022-03-03Java中的FutureTask實(shí)現(xiàn)異步任務(wù)代碼實(shí)例
這篇文章主要介紹了Java中的FutureTask實(shí)現(xiàn)異步任務(wù)代碼實(shí)例,普通的線程執(zhí)行是無(wú)法獲取到執(zhí)行結(jié)果的,FutureTask?間接實(shí)現(xiàn)了?Runnable?和?Future?接口,可以得到子線程耗時(shí)操作的執(zhí)行結(jié)果,AsyncTask?異步任務(wù)就是使用了該機(jī)制,需要的朋友可以參考下2024-01-01詳解SpringBoot初始教程之Tomcat、Https配置以及Jetty優(yōu)化
本篇文章主要介紹了詳解SpringBoot初始教程之Tomcat、Https配置以及Jetty優(yōu)化,具有一定的參考價(jià)值,有興趣的可以了解一下2017-09-09SpringBoot實(shí)現(xiàn)列表數(shù)據(jù)導(dǎo)出為Excel文件
這篇文章主要為大家詳細(xì)介紹了在Spring?Boot框架中如何將列表數(shù)據(jù)導(dǎo)出為Excel文件,文中的示例代碼講解詳細(xì),感興趣的小伙伴可以了解下2024-02-02Java數(shù)據(jù)結(jié)構(gòu)之棧與隊(duì)列實(shí)例詳解
這篇文章主要給大家介紹了關(guān)于Java數(shù)據(jù)結(jié)構(gòu)之棧與隊(duì)列的相關(guān)資料,算是作為用java描述數(shù)據(jù)結(jié)構(gòu)的一個(gè)開(kāi)始,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2021-11-11關(guān)于Springboot2.x集成lettuce連接redis集群報(bào)超時(shí)異常Command timed out afte
這篇文章主要介紹了Springboot2.x集成lettuce連接redis集群報(bào)超時(shí)異常Command timed out after 6 second(s),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2021-03-03Java Web程序?qū)崿F(xiàn)返回JSON字符串的方法總結(jié)
Java Web服務(wù)器端只要把Java對(duì)象數(shù)據(jù)轉(zhuǎn)成JSON字符串,把JSON字符串以文本的形式通過(guò)response輸出即可,2016-05-05Java新特性之Nashorn_動(dòng)力節(jié)點(diǎn)Java學(xué)院整理
這篇文章主要介紹了Java新特性之Nashorn的相關(guān)資料,需要的朋友可以參考下2017-06-06java對(duì)接微信支付之JSAPI支付(微信公眾號(hào)支付)
這篇文章主要給大家介紹了關(guān)于java對(duì)接微信支付之JSAPI支付(微信公眾號(hào)支付)的相關(guān)資料,微信JSAPI支付是近年來(lái)非常流行的一種支付方式,它使用了微信支付的SDK和demo來(lái)實(shí)現(xiàn)支付接口的對(duì)接,需要的朋友可以參考下2023-07-07SpringMVC 參數(shù)綁定之視圖傳參到控制器的實(shí)現(xiàn)代碼
這篇文章主要介紹了SpringMVC 參數(shù)綁定之視圖傳參到控制器的相關(guān)知識(shí),本文通過(guò)示例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2023-03-03