手动实现一个Mybatis

Mybatis是Java很常用的一个持久层的框架,减少了很多的开发,但是同时应该给初学者留下了不少的问题:为什么mapper中的接口不需要实现类就会调用sql语句, 怎么样和spring整和的。

首先第一步,我们先纯Mybatis看看,他是怎么实现接口不需要实现类而实现一系列操作的。
在官方文档的快速开始Mybatis的介绍中,有这样一行代码

BlogMapper mapper = session.getMapper(BlogMapper.class);

这行代码应该很熟悉,获取一个Mapper对象,然后调用mapper接口中的相关函数实现对数据库的操作,我们点进getMapper方法的源码一直向下点我们会发现这些代码。

 protected T newInstance(MapperProxy<T> mapperProxy) {
        return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
    }

熟悉代理的发现,这TM不就是动态代理吗,没错Mybatis底层调用了jdk动态代理技术。知道了这个,我们模拟一个Mybatis炒鸡轻松。

既然我们知道了它是动态代理,那么我们很轻易就能模拟一个出来,主要就模拟getMapper()。剩下的Mybatis的其他类我们不予模拟因为主要是这个类起了关键作用。

准备我们的包和类

UserDao

public interface UserDao {
    @Select("select * from u_order")
    public List<Map<String, Object>> list();

}

重点来了:我们既然需要代理,那么我们的代理等等需要自己写,mybatis叫SqlSession,我们可以叫MySession,就是在这里返回我们的代理对象,我们也叫getMapper方法。

不会代理的可以看我写过的另一篇文章:动态代理与静态代理

public class MySession {
    public static <T> T getMapper(Class cla){
        Class []classes = new Class[]{cla};
        return (T) Proxy.newProxyInstance(MySession.class.getClassLoader(), classes, new MyInvocationHandler());
    }
}

jdk动态代理我们还需要实现InvocationHandler,所以我们单独写一个类实现这个接口:

public class MyInvocationHandler implements InvocationHandler {
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println("连接数据库");//这里就是JDBC的代码我就不写了
        Select annotation = method.getAnnotation(Select.class);//拿到注解
        if(annotation != null){//
            String s = annotation.value()[0];
            System.out.println(s);
        }
        return null;
    }
}

编写测试类:

public class Test {
    public static void main(String[] args) {
        UserDao userDao = MySession.getMapper(UserDao.class);
        userDao.list();
    }
}

执行结果:

连接数据库
select * from u_order 

这里我们就实现了Mybatis重要的功能,这就是为什么实现类不用你写,因为你写无非都是固定的东西Mybatis在invoke方法中进行了封装使得实现类不用再去你写。

成功打印了sql语句,那么好,我们立刻发现了问题,当我有多个mapper的时候,这种方法就显的十分的乏力,我们还需要对上面进行一下修改。

我们也只是模拟多个mapper的时候,官网推荐@MapperScan注解,我们就是模拟这个注解的原理,于是我们新增两个类:这里要开始熟悉spring的原理了。我们现在的需求就是,如何动态实现类的代理,程序员有10个mapper,那么我们怎么把这10个mapper读取到,这里整合spring时要用到FactoryBean这个接口,这是一个特殊的bean这里不细讲。Mybatis中有MapperFactoryBean,我们叫MyMapperFactoryBean,实现FactoryBean接口会覆盖三个方法,但是你只需要覆盖两个方法就行,那个mapperInterface就是你动态输入的mapper接口

public class MyMapperFactoryBean implements FactoryBean {

      Class mapperInterface;

      public MyMapperFactoryBean(Class mapperInterface) {
          this.mapperInterface = mapperInterface;
      }

      public Object getObject() throws Exception {
          return MySession.getMapper(mapperInterface);
      }

      public Class<?> getObjectType() {
          return mapperInterface;
      }
  }

下一步我们是要将MyMapperFactoryBean注册进spring容器,@Component在这里是不行,因为你是动态注入,你加了这个注解你就没办法传参数,所以我们使用spring的另一个注解@Import,我们需要新建一个类实现一个接口ImportBeanDefinitionRegistrar

public class MyImportBeanDefinitionRegistart implements ImportBeanDefinitionRegistrar {
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyMapperFactoryBean.class);
        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
        beanDefinition.getConstructorArgumentValues().addGenericArgumentValue("com.ysu.mapper.UserDao");
        registry.registerBeanDefinition("myImportBeanDefinitionRegistart", beanDefinition);
    }
}

这里就不细讲了,就是把MyMapperFactoryBean注册进spring容器,并且可以传参的那种。

AppConfig类

@ComponentScan("com.ysu")
@Configuration
@Import(MyImportBeanDefinitionRegistart.class)//把这个类加进去
public class AppConfig {
}

service层

@Service
public class UserService {

    @Autowired
    private UserDao dao;

    public List<Map<String, Object>> list(){
        return dao.list();
    }

}

Test测试类:

public class Test {
    public static void main(String[] args) {
        AnnotationConfigApplicationContext an = new AnnotationConfigApplicationContext(AppConfig.class);
        an.getBean(UserService.class).list();

    }
}

一样的打印结果,service层不影响什么,我加上纯粹就是为了更真实,看完有些懵逼的,需要补一补spring和动态代理了,因为Mybatis本身是没什么的,但是spring确实有些难

这段代码我要再说一下,Mybatis源码比这个多很多,我们这里还是没有实现你写多个接口我们就可以扫描到,Mybatis是怎么实现的呢,其实就是一个循环,通过反射把mapper包或者dao包下你的所有的接口拿出来,循环加进去,但是大体思路就是下面中间这四行。看不懂的还是恶补一下spring和动态代理吧

public class MyImportBeanDefinitionRegistart implements ImportBeanDefinitionRegistrar {
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
        BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MyMapperFactoryBean.class);
        AbstractBeanDefinition beanDefinition = builder.getBeanDefinition();
        beanDefinition.getConstructorArgumentValues().addGenericArgumentValue("com.ysu.mapper.UserDao");
        registry.registerBeanDefinition("myImportBeanDefinitionRegistart", beanDefinition);
    }
}
  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值