聊聊、手写Mybatis 注解配置方式
导航:
聊聊、手写Mybatis SpringBoot Starter
在《聊聊、手写Mybatis XML配置方式》中聊了通过 XML配置方式 来实现 Mybatis,也聊到了 Mybatis 中用到的动态代理技术。我手动实现了 AccountMapperFactoryBean,但是有一个缺点,需要 XML 配置才可以用。类似 MapperFactoryBean。
这篇文章主要是聊聊通过注解方式来手写 Mybatis。我们向 Spring 注册 Bean 的方式有很多种,这里会用到 ImportBeanDefinitionRegistrar,它就是今天的主角。
自定义 AccountImportBeanDefinitionRegistrar 实现 ImportBeanDefinitionRegistrar
AccountImportBeanDefinitionRegistrar
package org.rockcode.factory;
import org.rockcode.mappers.AccountMapper;
import org.springframework.beans.factory.support.AbstractBeanDefinition;
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
import org.springframework.core.type.AnnotationMetadata;
public class AccountImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
BeanDefinitionBuilder beanDefinitionBuilder = BeanDefinitionBuilder.genericBeanDefinition(AccountMapperFactoryBean.class);
AbstractBeanDefinition beanDefinition = beanDefinitionBuilder.getBeanDefinition();
beanDefinition.getPropertyValues().add("mapperInterface", AccountMapper.class);
beanDefinitionRegistry.registerBeanDefinition("accountMapperFactoryBean",beanDefinition);
}
}
AccountConfig
package org.rockcode.config;
import com.alibaba.druid.pool.DruidDataSource;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.rockcode.factory.AccountImportBeanDefinitionRegistrar;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Import;
import javax.sql.DataSource;
@Configuration
@Import(AccountImportBeanDefinitionRegistrar.class)
public class AccountConfig {
@Bean
public SqlSessionFactoryBean sqlSessionFactoryBean(DataSource dataSource){
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setDataSource(dataSource);
return sqlSessionFactoryBean;
}
@Bean
public DataSource dataSource(){
DruidDataSource driverManagerDataSource = new DruidDataSource();
driverManagerDataSource.setDriverClassName("com.mysql.jdbc.Driver");
driverManagerDataSource.setUrl("jdbc:mysql://127.0.0.1:3306/test?useSSL=false");
driverManagerDataSource.setUsername("root");
driverManagerDataSource.setPassword("root");
return driverManagerDataSource;
}
}
AccountMapperFactoryBean
package org.rockcode.factory;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.support.SqlSessionDaoSupport;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import static org.springframework.util.Assert.notNull;
public class AccountMapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean {
private Class mapperInterface;
public void setMapperInterface(Class mapperInterface) {
this.mapperInterface = mapperInterface;
}
@Autowired
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
super.setSqlSessionFactory(sqlSessionFactory);
}
@Override
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
configuration.addMapper(this.mapperInterface);
}
@Override
public Object getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
@Override
public Class<?> getObjectType() {
return this.mapperInterface;
}
}
Main方法
package org.rockcode.config;
import org.rockcode.mappers.AccountMapper;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
public class Main {
public static void main(String[] args) {
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AccountConfig.class);
AccountMapper accountMapper = (AccountMapper) ac.getBean("accountMapperFactoryBean");
System.out.println(accountMapper.queryAll());
}
}
上面代码的重点在于 @Import(AccountImportBeanDefinitionRegistrar.class),这是 Spring 很重要的一个扩展点,与 ImportBeanDefinitionRegistrar 相同的扩展点还有 ImportSelector,它们都可以实现 Bean 的注册。
但是这里有一个不足,没有实现包扫描功能,在《聊聊、Mybatis集成Spring XML方式》里聊到了
<mybatis:scan base-package="org.rockcode.mappers" />
或者
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="org.rockcode.mappers" />
</bean>
既然是注解方式,上面这两种肯定不行,那就实现自己的 @MapperScan 吧
@AccountMapperScan
package org.rockcode.annotations;
import org.rockcode.factory.AccountImportBeanDefinitionRegistrar;
import org.springframework.context.annotation.Import;
import java.lang.annotation.*;
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(AccountImportBeanDefinitionRegistrar.class)
public @interface AccountMapperScan {
String[] value() default {};
String[] basePackages() default {};
}
修改上面的 AccountConfig
AccountConfig
内容和上面一样,只需要修改成
@Configuration
@AccountMapperScan("org.rockcode.mappers")
public class AccountConfig { }
实现自己的 AccountClassPathMapperScanner
AccountClassPathMapperScanner
public class AccountClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
public AccountClassPathMapperScanner(BeanDefinitionRegistry registry) {
super(registry, false);
}
public void registerFilters() {
addIncludeFilter(new TypeFilter() {
@Override
public boolean match(MetadataReader metadataReader, MetadataReaderFactory metadataReaderFactory) throws IOException {
return true;
}
});
}
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
Set<BeanDefinitionHolder> beanDefinitionHolders = super.doScan(basePackages);
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitionHolders) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
String beanClassName = definition.getBeanClassName();// 获取到 org.rockcode.mappers.AccountMapper 名称
try {
Class<?> clazz = Class.forName(beanClassName);// 获取到 AccountMapper.class
definition.setBeanClass(AccountMapperFactoryBean.class); // 这里注意了,beanClass 设置为 AccountMapperFactoryBean
definition.getPropertyValues().add("mapperInterface", clazz);// 注册 mapperInterface 属性
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
return beanDefinitionHolders;
}
//这个方法非常重要,不然是获取不到 AccountMapper 接口的 BeanDefinitionHolder
@Override
protected boolean isCandidateComponent(AnnotatedBeanDefinition beanDefinition) {
return beanDefinition.getMetadata().isInterface() && beanDefinition.getMetadata().isIndependent();
}
}
修改上面的 AccountImportBeanDefinitionRegistrar
AccountImportBeanDefinitionRegistrar
public class AccountImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
@Override
public void registerBeanDefinitions(AnnotationMetadata annotationMetadata, BeanDefinitionRegistry beanDefinitionRegistry) {
Map<String, Object> annotationAttributes = annotationMetadata.getAnnotationAttributes(AccountMapperScan.class.getName());
String[] value = (String[]) annotationAttributes.get("value");
String packages = value[0]; //获取到 @AccountMapperScan("org.rockcode.mappers") 中的 value,也就是 org.rockcode.mappers
// 下面就是扫描包
AccountClassPathMapperScanner myClassPathBeanDefinitionScanner = new AccountClassPathMapperScanner(beanDefinitionRegistry);
myClassPathBeanDefinitionScanner.registerFilters();
myClassPathBeanDefinitionScanner.scan(packages);
}
}
Main方法
AnnotationConfigApplicationContext ac = new AnnotationConfigApplicationContext(AccountConfig.class);
AccountMapper accountMapper = (AccountMapper) ac.getBean("accountMapper");// 扫描 org.rockcode.mappers 包,AccountMapper 接口类被注册为 "accountMapper" 名称
System.out.println(accountMapper.queryAll());
到这里,Mybatis 聊得差不多了,最后一篇聊聊 Mybatis SpringBoot Starter 。