绝对肝货,超全的 MyBatis 动态代理原理讲解。

news2025/1/15 12:44:46

1.MyBatis简介

MyBatis是一个ORM工具,封装了JDBC的操作,简化业务编程;

Mybatis在web工程中,与Spring集成,提供业务读写数据库的能力。

2.使用步骤

1.引入依赖

采用Maven包依赖管理,mybatis-3.5.5版本;同时需要数据库连接驱动

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.5</version>
</dependency>
<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.49</version>
</dependency>

2.配置文件

配置文件配置数据库连接源,及映射文件。

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
 
<configuration>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <!-- 数据库连接方式 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://localhost/user" />
                <property name="username" value="root" />
                <property name="password" value="123456" />
            </dataSource>
        </environment>
    </environments>
 
    <!-- 注册表映射文件 -->
    <mappers>
        <mapper resource="mybatis/User.xml"/>
    </mappers>
 
</configuration>

3.接口定义

定义实体

package com.xiongxin.mybatis.entity;
 
public class User {
 
    private String username;
    private String password;
  ...getter&&setter
}

接口定义

package com.xiongxin.mybatis.mapper;
import com.xiongxin.mybatis.entity.User;
import java.util.List;
public interface UserMapper {
    List<User> queryUser();
}

定义映射文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
 
<mapper namespace="com.xiongxin.mybatis.mapper.UserMapper">
 
    <select id="queryUser" resultType="com.xiongxin.mybatis.entity.User">
        select * from tbl_user
    </select>
 
</mapper>

4.加载执行

package com.xiongxin.mybatis;
 
import com.alibaba.fastjson.JSON;
import com.xiongxin.mybatis.entity.User;
import com.xiongxin.mybatis.mapper.UserMapper;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
 
import java.io.IOException;
import java.io.Reader;
import java.util.List;
 
public class TestMain {
 
    public static void main(String[] args) throws IOException {
        String resource = "mybatis-config.xml";
        //加载 mybatis 的配置文件(它也加载关联的映射文件)
        Reader reader = Resources.getResourceAsReader(resource);
        //构建 sqlSession 的工厂
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
        //创建能执行映射文件中 sql 的 sqlSession
        SqlSession session = sessionFactory.openSession();
        UserMapper userMapper = session.getMapper(UserMapper.class);
        List<User> users = userMapper.queryUser();
        System.out.println(JSON.toJSONString(users));
    }
 
}
---------------------------------
..consule print..
[{"password":"password","username":"xiongxin"}]

到这里,这个Mybatis的使用环节结束。

整个实现过程中,我们并未编写Mapper的实现类,框架是如何在无实现类的场景下实现接口方法返回的呢?

这里就不得不说到接口的动态代理方法了。

3.原理解析

SqlSession接口的实现中,获取Mapper的代理实现类

使用了JDK动态代理的功能

代理类执行方法调用

方法调用中执行MethodInvoker

最终执行execue方法。

获取返回结果Result

4.手撕框架

前置知识:

 

源码:

<dependencies>
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>fastjson</artifactId>
        <version>1.2.74</version>
    </dependency>
    <dependency>
        <groupId>com.h2database</groupId>
        <artifactId>h2</artifactId>
        <version>1.4.199</version>
    </dependency>
</dependencies>
package com.dbutil.session;
 
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
import java.lang.reflect.*;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.ArrayList;
import java.util.List;
 
import static java.lang.annotation.ElementType.*;
import static java.lang.annotation.RetentionPolicy.RUNTIME;
 
/**
 * @author xiongxin
 */
public class SqlSession {
 
    public static Connection getConnH2() throws Exception {
        String url = "jdbc:h2:mem:db_h2;MODE=MYSQL;INIT=RUNSCRIPT FROM './src/main/resources/schema.sql'";
        String user = "root";
        String password = "123456";
        //1.加载驱动程序
        Class.forName("org.h2.Driver");
        //2.获得数据库链接
        Connection conn = DriverManager.getConnection(url, user, password);
        return conn;
    }
 
 
 
    /**
     * 自定义注解
     */
    @Target({TYPE, FIELD, METHOD})
    @Retention(RUNTIME)
    public @interface QueryList {
        public String value();
    }
 
    /**
     * 动态代理
     *
     * @param mapperInterface
     * @param <T>
     * @return
     */
    public static <T> T getMapper(Class<T> mapperInterface) {
        return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, new MapperInvocationHandler());
    }
 
    /**
     * 代理类方法
     */
    public static class MapperInvocationHandler implements InvocationHandler {
 
        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            String sql = method.getAnnotation(QueryList.class).value();
            Class<?> returnType = method.getReturnType();
            //返回类型为List
            if (returnType == List.class) {
                Type genericReturnType = method.getGenericReturnType();
                String typeName = genericReturnType.getTypeName();
                String replace = typeName.replace("java.util.List<", "").replace(">", "");
                //获取泛型类型
                Class<?> forName = Class.forName(replace);
                return SqlSession.queryList(sql, forName);
            }
            return null;
        }
    }
 
    /**
     * 结果集转换
     *
     * @param <T>
     */
    public interface ResultMap<T> {
        T convert(ResultSet resultSet) throws Exception;
    }
 
 
    /**
     * 创建连接并执行
     *
     * @param sql
     * @param resultMap
     * @param <T>
     * @return
     * @throws Exception
     */
    public static <T> List<T> queryList(String sql, ResultMap<T> resultMap) throws Exception {
        //jdbc数据库
        Connection conn = getConnH2();
        //3.通过数据库的连接操作数据库,实现增删改查(使用Statement类)
        Statement st = conn.createStatement();
        ResultSet rs = st.executeQuery(sql);
        List<T> list = new ArrayList<>();
        //4.处理数据库的返回结果(使用ResultSet类)
        while (rs.next()) {
            T convert = resultMap.convert(rs);
            list.add(convert);
        }
        //关闭资源
        rs.close();
        st.close();
        conn.close();
        return list;
    }
 
    /**
     * 查询数据集
     *
     * @param sql
     * @param returnType
     * @param <T>
     * @return
     * @throws Exception
     */
    public static <T> List<T> queryList(String sql, Class<T> returnType) throws Exception {
        List<T> list = SqlSession.queryList(sql, rs -> {
            T obj = returnType.newInstance();
            Field[] declaredFields = returnType.getDeclaredFields();
            for (Field declaredField : declaredFields) {
                Class<?> type = declaredField.getType();
                //类型为String时的处理方法
                if (type == String.class) {
                    String value = rs.getString(declaredField.getName());
                    String fieldName = declaredField.getName();
                    Method method = returnType.getDeclaredMethod(
                            "set".concat(fieldName.substring(0, 1).toUpperCase().concat(fieldName.substring(1))),
                            String.class);
                    method.invoke(obj, value);
                }
                if (type == Long.class) {
                    Long value = rs.getLong(declaredField.getName());
                    String fieldName = declaredField.getName();
                    Method method = returnType.getDeclaredMethod(
                            "set".concat(fieldName.substring(0, 1).toUpperCase().concat(fieldName.substring(1))),
                            Long.class);
                    method.invoke(obj, value);
                }
                //其他类型处理方法
            }
            return obj;
        });
        return list;
    }
}

schema.sql文件

drop table if exists user;
CREATE TABLE user
(
  id       int(11) NOT NULL AUTO_INCREMENT,
  username varchar(255) DEFAULT NULL,
  password varchar(255) DEFAULT NULL,
  PRIMARY KEY (id)
);
 
insert into user(id,username,password) values(1,'xiongxina','123456');
insert into user(id,username,password) values(2,'xiongxinb','123456');
insert into user(id,username,password) values(3,'xiongxinc','123456');

mapper定义

package com.dbutil.mapper;
 
import com.dbutil.entity.UserEntity;
import com.dbutil.session.SqlSession;
 
import java.util.List;
 
public interface UserMapper {
 
    @SqlSession.QueryList("select * from user")
    List<UserEntity> queryUser();
}

使用:

package com.dbutil;
 
import com.dbutil.entity.UserEntity;
import com.dbutil.mapper.UserMapper;
import com.dbutil.session.SqlSession;
 
import java.util.List;
 
public class UserService {
 
    public static void main(String[] args) throws Exception {
        UserMapper userMapper = SqlSession.getMapper(UserMapper.class);
        List<UserEntity> userEntities = userMapper.queryUser();
        for (UserEntity userEntity : userEntities) {
            System.out.println(userEntity);
        }
    }
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/111959.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

MyBatis学习 | 动态SQL

文章目录一、简介二、if标签2.1 if标签的简单使用2.2 where标签2.3 trim标签&#xff08;了解&#xff09;三、choose标签 & set标签3.1 choose标签3.2 set标签四、foreach标签4.1 foreach标签的简单使用4.2 批量插入五、内置参数六、bind标签七、sql标签 & include标签…

2163. 删除元素后和的最小差值 堆解法解析

2163. 删除元素后和的最小差值 给你一个下标从 0 开始的整数数组 nums &#xff0c;它包含 3 * n 个元素。 你可以从 nums 中删除 恰好 n 个元素&#xff0c;剩下的 2 * n 个元素将会被分成两个相同大小的部分。 前面 n 个元素属于第一部分&#xff0c;它们的和记为 sumfirs…

Fabric.js 保存自定义属性

本文简介 点赞 关注 收藏 学会了 之前有些工友留言&#xff1a;在 fabric.js 中怎么保存元素的自定义属性&#xff1f; 比如&#xff0c;创建一个矩形&#xff0c;这个矩形有自己的 ID 属性&#xff0c;在执行序列化操作出来的结果却看不到 ID 属性了。 如何在序列化时输出…

项目实战之旅游网(三)后台用户管理(下)

目录 一.查询用户角色 二.修改用户角色 三.修改用户状态 一.查询用户角色 一个用户可以有多个角色&#xff0c;我们也可以给某个用户分配某些角色&#xff0c;所以我们还需要新建一个实体类&#xff08;这个实体类需要放到bean下&#xff0c;因为这个实体类和数据据库不是对…

【Effective_Objective-C_3接口与API设计】

文章目录前言15.用前缀避免命名空间冲突要点总结16.提供全能初始化方法全能初始化要点17.实现description方法description以字典形式输出descriptiondebugDescription要点18.尽量使用不可变对象要点19.使用清晰协调的命名方式方法命名类与协议命名要点20.为私有方法名加前缀21.…

人-机器人交互导论

【编者按&#xff1a;变主体性才是智能的真正厉害之处&#xff0c;能够设身处地地转换角色、角度、视角看待交互对象中的不确定性&#xff0c;并使用相应的同理同情共主观性机制机理进行处理&#xff0c;例如梅西过人中的真假变化。在个体/群体智能中的变主体性控制也是人机环境…

【圣诞节】飘雪圣诞树

一、前言 马上2023年的圣诞节&#x1f384;要到了&#xff0c;作为一个程序员&#xff0c;没什么可以送给大家的&#xff0c;就给大家画一个圣诞树&#x1f384;&#xff0c;作为礼物来送给大家吧。 二、创意名 明月当空飘雪圣诞树 三、效果展示 四、实现步骤 主要是利用three.…

详细设计说明书(GB8567——88)基于协同的在线表格forture-sheet

详细设计说明书 1引言 1.1编写目的 该文档在概要设计的基础上&#xff0c;进一步的细化系统结构&#xff0c;展示了软件结构的图标&#xff0c;物理设计、数据结构设计、及算法设计、详细的介绍了系统各个模块是如何实现的&#xff0c;包括涉及到的算法&#xff0c;逻辑流程…

【LeetCode每日一题:1754. 构造字典序最大的合并字符串~~~双指针+贪心算法】

题目描述 给你两个字符串 word1 和 word2 。你需要按下述方式构造一个新字符串 merge &#xff1a;如果 word1 或 word2 非空&#xff0c;选择 下面选项之一 继续操作&#xff1a; 如果 word1 非空&#xff0c;将 word1 中的第一个字符附加到 merge 的末尾&#xff0c;并将其…

《Unified Structure Generation for Universal Information Extraction》论文阅读

文章目录文章介绍文章方案用于统一结构编码的结构化抽取语言&#xff08;SEL&#xff09;用于可控IE结构生成的结构模式指导使用UIE生成预训练任务微调任务总结参考文章地址&#xff1a; https://arxiv.org/abs/2203.12277文章介绍 目前对于自然语言处理中的信息抽取任务如关系…

业聚医疗港交所上市:市值76亿港元 为钱永勋家族企业

雷递网 雷建平 12月23日血管介入器械公司――业聚医疗集团有限公司&#xff08;OrbusNeich Medical Group Limited&#xff09;&#xff08;简称“业聚医疗”&#xff0c;股票代码为&#xff1a;6929 &#xff09;今日在港交所上市。业聚医疗发行价为8.8港元&#xff0c;募资净…

SpringCloudGateway源码(四)限流组件

前言 如果不使用Alibaba Sentinel的网关流控规则&#xff0c; 是否可以选择使用SpringCloudGateway基于Redis的限流组件&#xff1f; 基于这个问题&#xff0c;笔者想了解一下scg自带限流组件的实现原理。 一、使用案例 1、pom 注意要加入redis-reactive依赖。 <depe…

OA系统遇到的问题

目录 一、开始时间与结束时间之差 二、弹出层的大小以及位置设置 2.1、高度设置body-style 2.2、位置设置dialogStyle 三、vue2安装引入Ant Design Vue 四、按钮控制盒子的显示与隐藏 五、表单生成器思想 5.1、点击左侧控件库生成中间的控件元素 5.2、点击中间的控件&…

Flink-状态编程的基本概念

文章目录Flink 中的状态1.1 有状态算子1.2 状态的管理1.3 状态的分类&#x1f48e;&#x1f48e;&#x1f48e;&#x1f48e;&#x1f48e; 更多资源链接&#xff0c;欢迎访问作者gitee仓库&#xff1a;https://gitee.com/fanggaolei/learning-notes-warehouse/tree/master Fli…

springcloud-gateway简介

目录 1. gateway简介 1.1 是什么 1.2 作用 1.3 主要特征 1.4 与zuul的主要区别 1.5 主要组件 1.6 架构图 2. 开发示例 2.1 创建一个gateway模块 2.2 与nacos结合使用 2.2.1 默认规则 2.2.2 通过配置文件配置路由 2.2.3 动态路由 1. gateway简介 1.1 是什么 SpringC…

一文带你深入理解【Java基础】· 网络编程(上)

写在前面 Hello大家好&#xff0c; 我是【麟-小白】&#xff0c;一位软件工程专业的学生&#xff0c;喜好计算机知识。希望大家能够一起学习进步呀&#xff01;本人是一名在读大学生&#xff0c;专业水平有限&#xff0c;如发现错误或不足之处&#xff0c;请多多指正&#xff0…

Kali Linux中安装IDLE的方法

1 IDLE简介 IDLE是Integrated Development and Learning Enviroment即集成开发和学习环境的简称&#xff0c;是Python的集成开发环境。在Kali Linux中&#xff0c;可以通过IDLE进行Python编程。 2 Kali Linux中安装IDLE 2.1 查看Kali Linux中是否安装IDLE 在Kali Linux终端…

WEB1.0起源:全球首个网站info.cern.ch

伯纳斯李&#xff08;图&#xff09;1990年创立第一个网站。 info.cern.ch是世上第一个网站&#xff0c;提供有关万维网的资料。 info.cern.ch这个网站依然运作如常。 英国科学家蒂姆伯纳斯-李 (Tim Berners-Lee) 于 1989 年在 CERN 工作期间发明了万维网 (WWW)。Web 最初的构思…

基于Vue+SpringBoot智慧校园疫情防控系统(PC端、手机端)--附源码

介绍 智慧校园疫情防控系统——PC 手机端 多端并行 项目源码下载&#xff1a;https://download.csdn.net/download/DeepLearning_/87340321 软件架构 手机端信息系统——日常健康信息填报系统&#xff08;前端手机端 文件夹&#xff09;电脑端智疫图 —— 数据可视化界面 &…

一种新的语义分割思路

这两天看到一篇挺有意思的论文&#xff0c;虽然不是语义分割方面的但是挺有意思的&#xff0c;因此在这里跟大家分享一下&#xff0c;这个也是一种语义分割的思路和方法。 Paper&#xff1a;Fully-Convolutional Siamese Networks for Object Tracking. SiamFC是深度学习目标…