Spring整合Mybatis實操分享
在介紹Spring整合Mybatis原理之前,我們得先來稍微介紹Mybatis的工作原理。
Mybatis的基本工作原理
在Mybatis
中,我們可以使用一個接口去定義要執(zhí)行sql,簡化代碼如下: 定義一個接口,@Select表示要執(zhí)行查詢sql語句。
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(); // 以下使我們需要關注的重點 UserMapper mapper = sqlSession.getMapper(UserMapper.class); Integer id = 1; User user = mapper.selectById(id);
Mybatis的目的是:使得程序員能夠以調用方法的方式執(zhí)行某個指定的sql,將執(zhí)行sql的底層邏輯進行了封裝。 這里重點思考以下mapper這個對象,當調用SqlSession的getMapper方法時,會對傳入的接口生成一個 代理對象,而程序要真正用到的就是這個代理對象,在調用代理對象的方法時,Mybatis會取出該方法所對應的sql語句,然后利用JDBC去執(zhí)行sql語句,最終得到結果。
分析需要解決的問題
Spring和Mybatis時,我們重點要關注的就是這個代理對象。因為整合的目的就是:把某個Mapper的代理 對象作為一個bean放入Spring容器中,使得能夠像使用一個普通bean一樣去使用這個代理對象,比如能 被@Autowire自動注入。 比如當Spring和Mybatis整合之后,我們就可以使用如下的代碼來使用Mybatis中的代理對象了:
@Component public class UserService { @Autowired private UserMapper userMapper; public User getUserById(Integer id) { return userMapper.selectById(id); } }
UserService
中的userMapper屬性就會被自動注入為Mybatis中的代理對象。如果你基于一個已經完成整合的項目去調試即可發(fā)現(xiàn),userMapper的類型為: org.apache.ibatis.binding.MapperProxy@41a0aa7d。證明確實是Mybatis中的代理對象。 好,那么現(xiàn)在我們要解決的問題的就是:如何能夠把Mybatis的代理對象作為一個bean放入Spring容器中?要解決這個,我們需要對Spring的bean生成過程有一個了解。
Spring中Bean的產生過程
Spring啟動過程中,大致會經過如下步驟去生成bean
- 掃描指定的包路徑下的class文件
- 根據(jù)class信息生成對應的
BeanDefinition
- 在此處,程序員可以利用某些機制去修改
BeanDefinition
- 根據(jù)BeanDefinition生成bean實例
- 把生成的bean實例放入Spring容器中
假設有一個A類,假設有如下代碼: 一個A類
@Component public class A { }
一個B類,不存在@Component注解
public class B { }
執(zhí)行如下代碼:
AnnotationConfigApplicationContext context = new AnnotationConfigAppl icationContext(AppConfig.class); System.out.println(context.getBean("a"));
輸出結果為:com.luban.util.A@6acdbdf5
A類對應的bean對象類型仍然為A類。但是這個結論是不確定的,我們可以利用BeanFactory后置處理器來 修改BeanDefinition,我們添加一個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()); } }
這樣就會導致,原本的A類對應的BeanDefiniton被修改了,被修改成了B類,那么后續(xù)正常生成的bean對 象的類型就是B類。此時,調用如下代碼會報錯:
context.getBean(A.class);
但是調用如下代碼不會報錯,盡管B類上沒有@Component注解:
context.getBean(B.class);
并且,下面代碼返回的結果是:
com.luban.util.B@4b1c1ea0
AnnotationConfigApplicationContext context = new AnnotationConfigAppl icationContext(AppConfig.class); System.out.println(context.getBean("a"));
之所以講這個問題,是想說明?個問題:在Spring中,bean對象跟class沒有直接關系,跟 BeanDefinition才有直接關系。 那么回到我們要解決的問題:如何能夠把Mybatis的代理對象作為一個bean放入Spring容器中? 在Spring中,如果你想生成一個bean,那么得先生成一個BeanDefinition,就像你想new一個對象實 例,得先有一個class。
解決問題
繼續(xù)回到我們的問題,我們現(xiàn)在想自己生成一個bean,那么得先生成一個BeanDefinition
,只要有了 BeanDefinition
,通過在BeanDefinition中設置bean對象的類型,然后把BeanDefinition添加給 Spring,Spring就會根據(jù)BeanDefinition
?動幫我們?成?個類型對應的bean對象。
所以,現(xiàn)在我們要解決兩個問題:
- Mybatis的代理對象的類型是什么?因為我們要設置給
BeanDefinition
- 我們怎么把
BeanDefinition
添加給Spring容器?
注意:上文中我們使用的BeanFactory后置處理器,他只能修改BeanDefinition
,并不能新增一個 BeanDefinition。我們應該使用Import技術來添加一個BeanDefinition。后面再詳細介紹如果使用Import 技術來添加一個BeanDefinition,可以先看一下偽代碼實現(xiàn)思路。
假設:我們有一個UserMapper接口,他的代理對象的類型為UserMapperProxy
。 那么我們的思路就是這樣的,
偽代碼如下:
BeanDefinitoin bd = new BeanDefinitoin(); bd.setBeanClassName(UserMapperProxy.class.getName()); SpringContainer.addBd(bd);
但是,這里有一個嚴重的問題,就是上文中的UserMapperProxy是我們假設的,他表示一個代理類的類 型,然而Mybatis中的代理對象是利用的JDK的動態(tài)代理技術實現(xiàn)的,也就是代理對象的代理類是動態(tài)生成的,我們根本方法確定代理對象的代理類到底是什么。 所以回到我們的問題:Mybatis的代理對象的類型是什么? 本來可以有兩個答案: 1. 代理對象對應的代理類 2. 代理對象對應的接口 那么答案1就相當于沒有了,因為是代理類是動態(tài)生成的,那么我們來看答案2:代理對象對應的接口如果我們采用答案2,那么我們的思路就是:
BeanDefinition bd = new BeanDefinitoin(); // 注意這?,設置的是UserMapper bd.setBeanClassName(UserMapper.class.getName()); SpringContainer.addBd(bd);
但是,實際上給BeanDefinition
對應的類型設置為一個接口是行不通的,因為Spring沒有辦法根據(jù)這個 BeanDefinition去new出對應類型的實例,接口是沒法直接new出實例的。 那么現(xiàn)在問題來了,我要解決的問題:Mybatis的代理對象的類型是什么? 兩個答案都被我們否定了,所以這個問題是無解的,所以我們不能再沿著這個思路去思考了,只能回到最 開始的問題:如何能夠把Mybatis的代理對象作為一個bean放入Spring容器中?
總結上文的推理:我們想通過設置BeanDefinition的class類型,然后由Spring自動的幫助我們去生成對應的bean,但是這條路是行不通的。 終極解決方案 那么我們還有沒有其他辦法,可以去生成bean呢?并且生成bean的邏輯不能由Spring來幫我們做了,得 由我們自己來做。 FactoryBean 有,那就是Spring中的FactoryBean。我們可以利用FactoryBean去自定義我們要生成的bean對象,比如
@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; } }
我們定義了一個LubanFactoryBean
,它實現(xiàn)了FactoryBean,getObject方法就是用來自定義生成bean 對象邏輯的。 執(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 從結果我們可以看到,從Spring容器中拿名字為"lubanFactoryBean"的bean對象,就是我們所自定義的 jdk動態(tài)代理所生成的代理對象。
所以,我們可以通過FactoryBean來向Spring容器中添加一個自定義的bean對象。上文中所定義的 LubanFactoryBean對應的就是UserMapper,表示我們定義了一個LubanFactoryBean,相當于把 UserMapper對應的代理對象作為一個bean放入到了容器中。 但是作為程序員,我們不可能每定義了一個Mapper,還得去定義一個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
變得靈活了,可以在構造LubanFactoryBean時,通 過構造傳入不同的Mapper接口。 實際上LubanFactoryBean也是一個Bean,我們也可以通過生成一個BeanDefinition來生成一個 LubanFactoryBean,并給構造方法的參數(shù)設置不同的值,比如偽代碼如下:
BeanDefinition bd = new BeanDefinitoin(); // 注意一:設置的是LubanFactoryBean bd.setBeanClassName(LubanFactoryBean.class.getName()); // 注意二:表示當前BeanDefinition在生成bean對象時,會通過調用LubanFactoryBean 的構造方法來生成,并傳入UserMapper bd.getConstructorArgumentValues().addGenericArgumentValue(UserMapper. class.getName()) SpringContainer.addBd(bd)
特別說一下注意二,表示表示當前BeanDefinition
在生成bean對象時,會通過調用LubanFactoryBean的 構造方法來生成,并傳入UserMapper的Class對象。那么在生成LubanFactoryBean時就會生成一個 UserMapper接口對應的代理對象作為bean了。 到此為止,其實就完成了我們要解決的問題:把Mybatis中的代理對象作為一個bean放入Spring容器中。
只是我們這是用簡單的JDK代理對象模擬的Mybatis中的代理對象,如果有時間,我們完全可以調? Mybatis中提供的方法區(qū)生成一個代理對象。這里就不花時間去介紹了。 Import 到這里,我們還有一個事情沒有做,就是怎么真正的定義一個BeanDefinition,并把它添加到Spring中, 上文說到我們要利用Import技術,比如可以這么實現(xiàn):
定義如下類:
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 {
這樣在啟動Spring時就會新增一個BeanDefinition,該BeanDefinition會生成一個LubanFactoryBean對 象,并且在生成LubanFactoryBean對象時會傳入UserMapper.class對象,通過LubanFactoryBean內部 的邏輯,相當于會自動生產一個UserMapper接口的代理對象作為一個bean。
總結
總結一下,通過我們的分析,我們要整合Spring和Mybatis,需要我們做的事情如下:
- 定義一個
LubanFactoryBean
- 定義一個
LubanImportBeanDefinitionRegistrar
- 在AppConfig上添加一個注解
@Import
(LubanImportBeanDefinitionRegistrar.class)
到此這篇關于Spring整合Mybatis實操分享的文章就介紹到這了,更多相關Spring整合Mybatis內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
Java中的FutureTask實現(xiàn)異步任務代碼實例
這篇文章主要介紹了Java中的FutureTask實現(xiàn)異步任務代碼實例,普通的線程執(zhí)行是無法獲取到執(zhí)行結果的,FutureTask?間接實現(xiàn)了?Runnable?和?Future?接口,可以得到子線程耗時操作的執(zhí)行結果,AsyncTask?異步任務就是使用了該機制,需要的朋友可以參考下2024-01-01詳解SpringBoot初始教程之Tomcat、Https配置以及Jetty優(yōu)化
本篇文章主要介紹了詳解SpringBoot初始教程之Tomcat、Https配置以及Jetty優(yōu)化,具有一定的參考價值,有興趣的可以了解一下2017-09-09SpringBoot實現(xiàn)列表數(shù)據(jù)導出為Excel文件
這篇文章主要為大家詳細介紹了在Spring?Boot框架中如何將列表數(shù)據(jù)導出為Excel文件,文中的示例代碼講解詳細,感興趣的小伙伴可以了解下2024-02-02關于Springboot2.x集成lettuce連接redis集群報超時異常Command timed out afte
這篇文章主要介紹了Springboot2.x集成lettuce連接redis集群報超時異常Command timed out after 6 second(s),本文通過實例代碼給大家介紹的非常詳細,需要的朋友可以參考下2021-03-03Java Web程序實現(xiàn)返回JSON字符串的方法總結
Java Web服務器端只要把Java對象數(shù)據(jù)轉成JSON字符串,把JSON字符串以文本的形式通過response輸出即可,2016-05-05Java新特性之Nashorn_動力節(jié)點Java學院整理
這篇文章主要介紹了Java新特性之Nashorn的相關資料,需要的朋友可以參考下2017-06-06SpringMVC 參數(shù)綁定之視圖傳參到控制器的實現(xiàn)代碼
這篇文章主要介紹了SpringMVC 參數(shù)綁定之視圖傳參到控制器的相關知識,本文通過示例代碼給大家介紹的非常詳細,對大家的學習或工作具有一定的參考借鑒價值,需要的朋友可以參考下2023-03-03