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的目的是:使得程序員能夠以調(diào)用方法的方式執(zhí)行某個指定的sql,將執(zhí)行sql的底層邏輯進行了封裝。 這里重點思考以下mapper這個對象,當調(diào)用SqlSession的getMapper方法時,會對傳入的接口生成一個 代理對象,而程序要真正用到的就是這個代理對象,在調(diào)用代理對象的方法時,Mybatis會取出該方法所對應的sql語句,然后利用JDBC去執(zhí)行sql語句,最終得到結(jié)果。
分析需要解決的問題
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中的代理對象。如果你基于一個已經(jīng)完成整合的項目去調(diào)試即可發(fā)現(xiàn),userMapper的類型為: org.apache.ibatis.binding.MapperProxy@41a0aa7d。證明確實是Mybatis中的代理對象。 好,那么現(xiàn)在我們要解決的問題的就是:如何能夠把Mybatis的代理對象作為一個bean放入Spring容器中?要解決這個,我們需要對Spring的bean生成過程有一個了解。
Spring中Bean的產(chǎn)生過程
Spring啟動過程中,大致會經(jīng)過如下步驟去生成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"));
輸出結(jié)果為:com.luban.util.A@6acdbdf5 A類對應的bean對象類型仍然為A類。但是這個結(jié)論是不確定的,我們可以利用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類。此時,調(diào)用如下代碼會報錯:
context.getBean(A.class);
但是調(diào)用如下代碼不會報錯,盡管B類上沒有@Component注解:
context.getBean(B.class);
并且,下面代碼返回的結(jié)果是:
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容器中?
總結(jié)上文的推理:我們想通過設置BeanDefinition的class類型,然后由Spring自動的幫助我們?nèi)ド蓪腷ean,但是這條路是行不通的。 終極解決方案 那么我們還有沒有其他辦法,可以去生成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 從結(jié)果我們可以看到,從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變得靈活了,可以在構(gòu)造LubanFactoryBean時,通 過構(gòu)造傳入不同的Mapper接口。 實際上LubanFactoryBean也是一個Bean,我們也可以通過生成一個BeanDefinition來生成一個 LubanFactoryBean,并給構(gòu)造方法的參數(shù)設置不同的值,比如偽代碼如下:
BeanDefinition bd = new BeanDefinitoin(); // 注意一:設置的是LubanFactoryBean bd.setBeanClassName(LubanFactoryBean.class.getName()); // 注意二:表示當前BeanDefinition在生成bean對象時,會通過調(diào)用LubanFactoryBean 的構(gòu)造方法來生成,并傳入UserMapper bd.getConstructorArgumentValues().addGenericArgumentValue(UserMapper. class.getName()) SpringContainer.addBd(bd)
特別說一下注意二,表示表示當前BeanDefinition在生成bean對象時,會通過調(diào)用LubanFactoryBean的 構(gòu)造方法來生成,并傳入UserMapper的Class對象。那么在生成LubanFactoryBean時就會生成一個 UserMapper接口對應的代理對象作為bean了。 到此為止,其實就完成了我們要解決的問題:把Mybatis中的代理對象作為一個bean放入Spring容器中。
只是我們這是用簡單的JDK代理對象模擬的Mybatis中的代理對象,如果有時間,我們完全可以調(diào)? 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內(nèi)部 的邏輯,相當于會自動生產(chǎn)一個UserMapper接口的代理對象作為一個bean。
總結(jié)
總結(jié)一下,通過我們的分析,我們要整合Spring和Mybatis,需要我們做的事情如下:
- 定義一個
LubanFactoryBean - 定義一個
LubanImportBeanDefinitionRegistrar - 在AppConfig上添加一個注解
@Import(LubanImportBeanDefinitionRegistrar.class)
到此這篇關于Spring整合Mybatis實操分享的文章就介紹到這了,更多相關Spring整合Mybatis內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
mybatis plus 自動轉(zhuǎn)駝峰配置小結(jié)
SpringBoot提供兩種配置Mybatis的方式,第一種是通過yml或application.properties文件開啟配置,第二種是使用自定義配置類,通過給容器添加一個ConfigurationCustomizer來實現(xiàn)更靈活的配置,這兩種方法可以根據(jù)項目需求和個人喜好選擇使用2024-10-10
MyBatis后端對數(shù)據(jù)庫進行增刪改查等操作實例
Mybatis是appach下開源的一款持久層框架,通過xml與java文件的緊密配合,避免了JDBC所帶來的一系列問題,下面這篇文章主要給大家介紹了關于MyBatis后端對數(shù)據(jù)庫進行增刪改查等操作的相關資料,需要的朋友可以參考下2022-08-08
詳解eclipse下創(chuàng)建第一個spring boot項目
本文詳細介紹了創(chuàng)建第一個基于eclipse(eclipse-jee-neon-3-win32-x86_64.zip)+spring boot創(chuàng)建的項目。2017-04-04
Java thread.isInterrupted() 返回值不確定結(jié)果分析解決
這篇文章主要介紹了Java thread.isInterrupted() 返回值不確定結(jié)果分析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習吧2022-12-12

