模拟Mybatis"无实现类"调用接口

2021-02-21 10:19:17 828

Mybatis让我们通过接口(interface)就能调用到对应sql, 看起来好像没有实现类, 貌似不符合"常识"

通过这个简易demo, 带你大致了解mybatis背后所做的事

先建立maven工程

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.0.RELEASE</version>
        <relativePath/>
    </parent>
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
            <exclusions>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-logging</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-tomcat</artifactId>
                </exclusion>
            </exclusions>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-undertow</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-log4j2</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-devtools</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-configuration-processor</artifactId>
            <optional>true</optional>
        </dependency>

        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>${lombok.version}</version>
        </dependency>
    </dependencies>

application.yml

server:
  port: 8088

启动类

@SpringBootApplication
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
}

数据对象

@AllArgsConstructor
@NoArgsConstructor
@Data
@ToString
public class User {

    private Long id;

    private String username;

    private String password;
}

定义一个接口

UserDAO

public interface UserDAO {
    User getOne();

    List<User> getAll();
}

以下是重要的部分

实现FactoryBean接口
重写getObject方法, 该方法返回的是代理后的对象
实现InvocationHandler接口
重写invoke方法, 编写代理逻辑
public class UserDaoFactoryBean<T> implements FactoryBean<T>, InvocationHandler {


    @Override
    public T getObject() throws Exception {
        @SuppressWarnings("unchecked")
        T t = (T)Proxy.newProxyInstance(UserDAO.class.getClassLoader(), new Class[]{UserDAO.class}, this);
        return t;
    }

    @Override
    public Class<?> getObjectType() {
        return UserDAO.class;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        if (Object.class.equals(method.getDeclaringClass())) {
            return method.invoke(this, args);
        } else {
            if ("getOne".equals(method.getName())) {
                return new User(1L, "zhangsan", "123456");
            } else if ("getAll".equals(method.getName())) {
                List<User> userList = new ArrayList<>(2);
                userList.add(new User(1L, "zhangsan", "123456"));
                userList.add(new User(2L, "lisi", "09876"));
                return userList;
            }
            throw new NoSuchMethodException();
        }
    }
}

动态注册bean

实现BeanDefinitionRegistryPostProcessor接口并加入容器

@Component
public class InterfaceBeanFactoryPostProcessor implements BeanDefinitionRegistryPostProcessor {

    @Override
    public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
        BeanDefinitionBuilder definitionBuilder =
                BeanDefinitionBuilder.genericBeanDefinition(UserDAO.class);
        AbstractBeanDefinition beanDefinition = definitionBuilder.getBeanDefinition();
        //将beanClass替换为UserDaoFactoryBean, spring在生成对象时就会获取到UserDaoFactoryBean#getObject生成的代理对象
        beanDefinition.setBeanClass(UserDaoFactoryBean.class);
        registry.registerBeanDefinition("userDAO", beanDefinition);
    }

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

    }

}

启动测试

@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    UserDAO userDAO;

    @GetMapping("/getAll")
    public List<User> getAll() {
        return userDAO.getAll();
    }

    @GetMapping("/getOne")
    public User getOne() {
        return userDAO.getOne();
    }
}

访问两个接口

getAll返回

[
    {
        "id": 1,
        "username": "zhangsan",
        "password": "123456"
    },
    {
        "id": 2,
        "username": "lisi",
        "password": "09876"
    }
]

getOne返回

{
    "id": 1,
    "username": "zhangsan",
    "password": "123456"
}

实际上mybatis的生成代理过程更为复杂, 这里只是简略写出, 有兴趣的可以翻翻源码

MyBatis处理一对多关系时的性能考虑

Mybatis框架对于处理一对多的情况有两种方法查询的时候JOIN子表, 然后交给MyBatis拼装数据查询的时候不JOIN子表, 主表查询完成后发起select再查询关联表数据, 还可以配置fetchType=lazy进行懒加载这两种方法各有问题:第一种方案有两个缺陷: 1) 做分页查询的时候不准
2022-02-11

模拟Mybatis"无实现类"调用接口

Mybatis让我们通过接口(interface)就能调用到对应sql, 看起来好像没有实现类, 貌似不符合"常识"通过这个简易demo, 带你大致了解mybatis背后所做的事先建立maven工程 <parent> <groupId>org.springframework.b
2021-02-21

MyBatis是如何让我们通过接口就能调用到SQL的

大致可分为如下几个步骤1. 动态注册bean1.1 根据配置mapperScan, 扫描对应的包, 将对应的类解析成BeanDefinition1.2 通过替换BeanDefinition中的BeanClass为MapperFactoryBean, (原来的BeanClass是Mapper接口) 实
2021-02-13

MyBatis拦截器实现SQL打印

mybatis有自带的sql打印, 但只会出现在抛异常的时候, 或者配置日志输出, 但是输出的日志较为冗长像这样### Error querying database. Cause: java.lang.ArithmeticException: / by zero ### The error ma
2021-02-08
MyBatis拦截器执行顺序

MyBatis拦截器执行顺序

最近项目用上了mybatis, 但是想像hibernate那样能打印sql, 于是写了个基于mybatis拦截器的sql打印, 参考这个https://blog.22xcode.com/post/78然后, 碰到了问题, 拦截器会重复输出一句sqlmybatis sql: SELECT id, na
2021-02-08

freemarker 时间显示不正常 设置时区

项目在本地开发的时候显示正常,部署上服务器就一直差8个小时,最后发现freemarker官方文档有这样的说明time_zone:时区的名称来显示并格式化时间。 默认情况下,使用JVM的时区。 也可以是 Java 时区 API 接受的值,或者 "JVM default" (从 FreeMarker 2
2020-03-28
IDEA 2019.1 xml 不高亮

IDEA 2019.1 xml 不高亮

前几天更新了idea后,发现xml里的代码都没有了高亮,变得跟记事本一个德性了打开setting ,搜索 File Types,找到xml项, 查看下方的匹配格式,果然没有xml,(idea真是厉害)点击右方的+,输入*.xml,点击ok,解决问题
2020-03-28

npm install 淘宝镜像

npm install --registry=https://registry.npm.taobao.org
2020-03-28
Java中方法的参数传递机制

Java中方法的参数传递机制

来看一段代码 public class Man { private String name; private Integer age; public String getName() { return name; } publi
2020-03-28
基于自定义注解手写权限控制

基于自定义注解手写权限控制

方法一: AOP 方法二: 拦截器项目结构项目依赖<dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-w
2020-03-28

Docker 部署 详细全过程 附代码

Docker 部署本站 全过程环境:CentOS7.61. 安装Docker其他版本CentOS可以参考这个https://help.aliyun.com/document_detail/187598.html查看本机内核版本,内核版本需高于 3.10uname -r 确保 yum 包最新yum u
2020-03-28

SpringBoot 启动普通java工程

引入依赖<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter</artifactId> <version>2.0.9</version> </dependency>
2020-03-28

Vue.js DOM操作

<template> <input type="button" @click="reply($event)" value="回复"> </template> export default { methods: { replyFun(e) {
2020-03-29
CentOS7编译调试OpenJDK12

CentOS7编译调试OpenJDK12

1. 下载源码https://hg.openjdk.java.net/jdk/jdk12点击左侧的browse,再点击zip,就可以下载zip格式的源码压缩包。unzip xxx.zip 解压文件2. 安装jdkyum install java-11-openjdk-devel -y3. 运行con
2020-04-23
编写自己的Spring Boot Starter

编写自己的Spring Boot Starter

1.新建一个maven项目命名规则统一是xxx-spring-boot-starter完整pom.xml<?xml version="1.0" encoding="UTF-8"?> <project xmlns="http://maven.apache.org/POM/4.0.0"
2020-06-29