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:
- 如果是
UserDao
能通過@Autowired
注入實(shí)例派敷,那么首先需要把UserDao
放入spring容器中蛹批,但是UserDao
是一個(gè)接口,所以放入spring容器的是一個(gè)代理對象,這里我們可以使用FactoryBean
诊沪。 - 從最后的執(zhí)行方法可以看到被啼,只要我們通過
sqlSession.getMapper(UserDao.class)
拿到mybatis中UserDao
的代理對象那么我們就可以通過這個(gè)代理對象執(zhí)行方法。 - 通過上面分析可以知道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工廠后置處理器中注冊FactoryBean
的BeanDefinition
捅伤。(如果對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é)果如下
spring集成mybatis第一篇到這里就分析結(jié)束了,大家可能覺得有點(diǎn)奇怪姨谷,在MybatisImportBeanDefinitionRegistrar
類中可以直接掃描贰逾,為什么還要注冊一個(gè)MybatisBeanDefinitionRegistryPostProcessor
,在mybatis中整合到spring的項(xiàng)目中有兩個(gè)版本菠秒,第一個(gè)版本是在ImportBeanDefinitionRegistrar
子類中實(shí)現(xiàn)掃描疙剑,第二個(gè)版本才改為通過實(shí)現(xiàn)BeanDefinitionRegistryPostProcesso
r在進(jìn)行掃描,至于原因践叠,我們在第二篇源碼分析的時(shí)候在討論言缤。