聊聊手写Mybatis 注解配置方式

Posted 香农随笔

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了聊聊手写Mybatis 注解配置方式相关的知识,希望对你有一定的参考价值。

导航:  

聊聊、Mybatis API  

聊聊、Mybatis XML 

聊聊、Mybatis集成Spring XML 方式

聊聊、Mybatis集成Spring 注解方式

聊聊、手写Mybatis XML配置方式

聊聊、手写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 。

 

以上是关于聊聊手写Mybatis 注解配置方式的主要内容,如果未能解决你的问题,请参考以下文章

mybatis--使用注解方式配置sql语句

mybatis执行CRUD操作的两种方式配置和注解

Spring Boot中使用MyBatis注解配置详解

mybatis 注解方式

Mybatis(dao层)基于注解模式的配置方式

MyBatis学习:与Spring整合(非注解方式配置MyBatis)