实现简易版的MyBatis-Spring中间件

MyBatis-Spring的基本使用

MyBatis-Spring中间件 会帮助你将 MyBatis 代码无缝地整合到 Spring 中。它将允许 MyBatis 参与到 Spring 的事务管理之中,创建映射器 mapper 和 SqlSession 并注入到 bean 中,以及将 Mybatis 的异常转换为 Spring 的 DataAccessException。最终,可以做到应用代码不依赖于 MyBatis,Spring 或 MyBatis-Spring

构建MyBatis和Spring整合的首先需要引入对应的依赖

<dependency>
  <groupId>org.mybatis</groupId>
  <artifactId>mybatis-spring</artifactId>
  <version>2.0.4</version>
</dependency>

要整合Spring和MyBatis需要俩个东西:
1:一个 SqlSessionFactory
2:至少一个数据映射器类

SqlSessionFactory

@Configuration
public class MyConfig {

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource());
        return factoryBean.getObject();
    }

    private DataSource dataSource() {
        DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
        driverManagerDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8");
        driverManagerDataSource.setUsername("root");
        driverManagerDataSource.setPassword("admin");
        return driverManagerDataSource;
    }

}

配置SqlSessionFactory 可以通过Java代码的方式,也可以通过XML配置文件的方式:

	<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">  
	  <property name="driverClassName">  
	      <value>com.mysql.jdbc.Driver</value>  
	  </property>  
	  <property name="url">  
	      <value>jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8</value>
	
	  </property>  
	  <property name="username">  
	      <value>root</value>  
	  </property>  
	  <property name="password">  
	      <value>admin</value>  
	  </property>  	
	</bean>
	
	
	<bean id="sqlSession" class="org.mybatis.spring.SqlSessionFactoryBean">
		<property name="typeAliasesPackage" value="com.octopus.pojo" />
		<property name="dataSource" ref="dataSource"/>
		<property name="mapperLocations" value="classpath:com/octopus/mapper/*.xml"/>
		<property name="plugins">
		    <array>
		      <bean class="com.github.pagehelper.PageInterceptor">
		        <property name="properties">
		          <!--使用下面的方式配置参数,一行配置一个 -->
		          <value>
		          </value>
		        </property>
		      </bean>
		    </array>
		  </property>		
	</bean>

如此,便是完成了IOC容器中注入SqlSessionFactory 的实例对象

Mapper是一个接口,可以通过它来操作数据,第一种通过在方法上添加注解,第二种创建对应的xml文件,第一种适合于SQL相对简单的操作,XML适合于相对复杂的SQL语句

此时,当我们完成了Mapper类的编写后,现在他只是一个接口,Spring如何去管理这些对象了,这里肯定不能通过@Component注解,这个原理待会在详细说,先看下Spring提供给开发人员的配置方法:
方法一:通过@Bean或者XML指定某个Mapper,这种不适合实际开发
方法二:通过@MapperScan(“XXX”)注解扫描
方法三:多个包可以通过,;分割

<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
		<property name="basePackage" value="com.octopus.XXXX1,;com.octopus.XXXX1"/>
	</bean>
构建自己的Mapper
  public static void main(String[] args) {
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        UserMapper userMapper = (UserMapper)context.getBean("userMapper");
        System.out.println(userMapper);

    }

在这里插入图片描述
通过Spring和MyBatis整合后,发现注入到IOC容器中的Mapper对象,其实是一个代理类对象,即Spring是通过动态代理,构建一个代理类,然后反射获取对应的方法注解,执行注解上的SQL语句,关联到SqlSession对象最后执行并得到数据库数据记录的

public interface UserMapper {

    @Select("SELECT * FROM users ")
    public List<User> findUsers();
}
@Test
    public void proxyTest() {

        UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(UserMapper.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                //通过反射获取注解
                System.out.println("注解标注的SQL语句为:" + method.getAnnotation(Select.class).value()[0]);
                return null;
            }
        });
        userMapper.findUsers();
    }
代理类生成的对象如何添加到单例池中呢?
方式一:BeanFactoryPostProcessor
@Component
public class MyBeanFactoryPostProcessor  implements BeanFactoryPostProcessor {


    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
        BeanDefinition beanDefinition = beanFactory.getBeanDefinition("user");
        beanDefinition.setBeanClassName("com.octopus.mapper.UserMapper");
    }
}

XXXPostProcessor阅读过Spring都知道这是Spring的后置处理器会在IOC容器初始化的时候被调用,对相关的Bean进行处理
通过构建自己的后置处理器可以操作对应的Bean,这种方式无法直接添加Bean,只能做原有Bean的修改,可以扩展Bean对象功能
上面是通过先获取某个Bean的方式,通过该方式,我们可以操作BeanFactory来往单例池添加一个对象,但是这种方式不适合开发中实际使用,无法进一步扩展

@Component
public class MyBeanFactoryPostProcessor implements BeanFactoryPostProcessor {


    @Override
    public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {

        UserMapper userMapper = (UserMapper) Proxy.newProxyInstance(UserMapper.class.getClassLoader(), new Class[]{UserMapper.class}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                return null;
            }
        });
        beanFactory.registerSingleton("userMapper", userMapper);

    }
}
方式二:FactoryBean

Spring提供了一个org.springframework.bean.FactoryBean的工厂接口,用户可以通过实现该接口定制实例化的bean。

FactoryBean接口对于Spring框架来说占用重要的地位,Spring本身就提供了特别多的FactoryBean的实现。它们隐藏了实例化复杂bean的细节,给上层应用带来了便利。FactoryBean的源码如下

public interface FactoryBean<T> {

    //返回由FactoryBean创建的bean实例,如果isSingleton()返回true,
	//则该实例会放到Spring容器中实例缓存池中
    @Nullable
    T getObject() throws Exception;

	//返回FactoryBean创建的 bean的类型。
    @Nullable
    Class<?> getObjectType();

    //返回有FactoryBean创建的bean的实例的作用域是singleton还是prototype,
	//这里默认返回的是true,也就是默认是singleton bean
    default boolean isSingleton() {
        return true;
    }

}

&factoryBean——代表实现FactoryBean的普通类
factoryBean——表示返回getObject中的Bean对象

@Component
public class MyFactoryBean implements FactoryBean {

    @Override
    public Object getObject() throws Exception {
        return new User();
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}
 @Test
    public  void  testFactoryBean(){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        System.out.println(context.getBean("&myFactoryBean"));
    }

在这里插入图片描述

 @Test
    public  void  testFactoryBean(){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        System.out.println(context.getBean("myFactoryBean"));
    }

在这里插入图片描述
Spring源码中会去判断实现了FactoryBean 接口的类,如果BeanName前缀是&则表示的为一个普通的Bean,如果不带&则构建的Bean就是调用getObject()方法,将这个方法的得到的对象加入到单例池中

<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
  <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
  <property name="sqlSessionFactory" ref="sqlSessionFactory" />
</bean>

上面是Spring中管理MyBatis的方式,可以看到对应的MapperFactoryBean

public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {

  private Class<T> mapperInterface;

  private boolean addToConfig = true;


  public MapperFactoryBean(Class<T> mapperInterface) {
    this.mapperInterface = mapperInterface;
  }

  public MapperFactoryBean() {
  }

  /**
   * {@inheritDoc}
   */
  @Override
  protected void checkDaoConfig() {
    super.checkDaoConfig();

    notNull(this.mapperInterface, "Property 'mapperInterface' is required");

    Configuration configuration = getSqlSession().getConfiguration();
    if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
      try {
        configuration.addMapper(this.mapperInterface);
      } catch (Exception e) {
        logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
        throw new IllegalArgumentException(e);
      } finally {
        ErrorContext.instance().reset();
      }
    }
  }
}

该类实现了FactoryBean接口
通过这个可以发现,Spring就是通过这个实现中间件的类注册,但是现在还存在一个问题,就是自定义的MyFactoryBean如何注册到IOC容中,上面使用的是@Component但是这个肯定不行的,Spring默认扫描的包@ComponentScan肯定不会扫描到这些中间件的具体路径的,所以现在需要解决的问题就是如何注册当期的MyFactoryBean

@Import
@Target(ElementType.TYPE)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface Import {

	/**
	 * The @{@link Configuration}, {@link ImportSelector} and/or
	 * {@link ImportBeanDefinitionRegistrar} classes to import.
	 */
	Class<?>[] value();
}

@Import为我们提供俩种实现方式:
ImportSelector
ImportBeanDefinitionRegistrar

现在去除掉上面MyFactoryBean中的@component注解,创建下面对象

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {
    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyFactoryBean.class);
        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
        registry.registerBeanDefinition("userMapper",beanDefinition);

    }
}

然后在配置类上添加@Import注解即可

@Configuration
@Import(MyImportBeanDefinitionRegistrar.class)
public class MyConfig {

    @Bean
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource());
        return factoryBean.getObject();
    }

    private DataSource dataSource() {
        DriverManagerDataSource driverManagerDataSource = new DriverManagerDataSource();
        driverManagerDataSource.setDriverClassName("com.mysql.jdbc.Driver");
        driverManagerDataSource.setUrl("jdbc:mysql://localhost:3306/test?characterEncoding=UTF-8");
        driverManagerDataSource.setUsername("root");
        driverManagerDataSource.setPassword("admin");
        return driverManagerDataSource;
    }

}
创建自定义的@MapperScan

经过上面的步骤已经实现用户可以通过@Import(MyImportBeanDefinitionRegistrar.class)注册Mapper,但是我们知道MyBatis-Spring提供了@MapperScan注解来简化这些操作,现在我们也同样需要构建一个注解@MyMapperScan,并且该注解可以提供扫描包的功能,需要将包下所有的Mapper都添加到单例池中

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.TYPE)
@Documented
@Import(MyImportBeanDefinitionRegistrar.class)
public @interface MyMapperScan {

    String[] value() default {};

}

上面就是自定义的一个注解,可以看到注解类导入了
@Import(MyImportBeanDefinitionRegistrar.class)

通过上面构建后,还需要对原来的ImportBeanDefinitionRegistrar进行修改,使得它具有如下功能
1:注入Bean
2:获取自定义注解@MyMapperScan(“xxx”)下的所有的Mapper接口
3:将Mapper以首字母小写作为BeanName,Mapper接口遍历注入

public class MyImportBeanDefinitionRegistrar implements ImportBeanDefinitionRegistrar {


    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        //获取注解信息
        AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MyMapperScan.class.getName()));
        //存储包路径
        List<String> basePackages = new ArrayList<String>();
        for (String pkg : annoAttrs.getStringArray("value")) {
            if (StringUtils.hasText(pkg)) {
                basePackages.add(pkg);
            }
        }
        List<Class<?>> classes = null;
        try {
            //工具类,根据包名获取包下所有的类限定名
            classes = PackageScanner.PackageScanner(basePackages);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        //遍历每个获取到的Mapper接口,注入IOC中
        for (Class<?> aClass : classes) {
            BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyFactoryBean.class);
            AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();

            //构造器传参
            beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(aClass.getName());
            System.out.println(aClass.getName());
            String[] split = aClass.getName().split("\\.");
            String beanName = split[split.length - 1];
            String name = beanName.substring(0, 1).toLowerCase() + beanName.substring(1);
            registry.registerBeanDefinition(name, beanDefinition);
        }
    }

}

由于MyFactoryBean 需要动态的创建Bean,因此里面的动态代理创建的对象我们需要通过外部传入进去,不能写死,这里通过构造器传入一个Class类型的对象,就是一个接口类型,然后在 BeanDefinitionRegistrar中通过构造器创建FactoryBean对象:
beanDefinition.getConstructorArgumentValues().addGenericArgumentValue(aClass.getName());

public class MyFactoryBean implements FactoryBean {

    private Class mapper;

    public MyFactoryBean(Class mapper) {
        this.mapper = mapper;
    }


    @Override
    public Object getObject() throws Exception {
        Object object = Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{mapper}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                System.out.println("注解标注的SQL语句为:" + method.getAnnotation(Select.class).value()[0]);
                return null;
            }
        });
        return object;
    }

    @Override
    public Class<?> getObjectType() {
        return null;
    }

    @Override
    public boolean isSingleton() {
        return false;
    }
}

截止目前已经基本完成了中间件的简单构建,现在创建俩个Mapper接口,然后进行测试

public interface EmpMapper {

    @Select("SELECT * FROM emp ")
    public List<Emp> findEmps();

}
public interface UserMapper {

    @Select("SELECT * FROM users ")
    public List<User> findUsers();

}

测试类:

  @Test
    public  void  testMyImportBeanDefinitionRegistrar(){
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(MyConfig.class);
        UserMapper  userMapper = (UserMapper) context.getBean("userMapper");
        userMapper.findUsers();
        EmpMapper  empMapper = (EmpMapper) context.getBean("empMapper");
        empMapper.findEmps();
    }

在这里插入图片描述

参考:
http://mybatis.org/spring/zh/index.html

https://www.jianshu.com/p/afd2c49394c2

原创文章 105 获赞 33 访问量 1万+

猜你喜欢

转载自blog.csdn.net/Octopus21/article/details/105743502