绝对干货,超全的 MyBatis 动态代理原理讲解!

Posted Java知音_

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了绝对干货,超全的 MyBatis 动态代理原理讲解!相关的知识,希望对你有一定的参考价值。

点击关注公众号,实用技术文章及时了解

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);
        
    

来源:blog.csdn.net/tsxiong123/article/

details/119057825

推荐

Java面试题宝典

技术内卷群,一起来学习!!

PS:因为公众号平台更改了推送规则,如果不想错过内容,记得读完点一下“在看”,加个“星标”,这样每次新文章推送才会第一时间出现在你的订阅列表里。点“在看”支持我们吧!

以上是关于绝对干货,超全的 MyBatis 动态代理原理讲解!的主要内容,如果未能解决你的问题,请参考以下文章

收藏 | 超全的 100 个 Pandas 函数

超全的 100 个 Pandas 函数汇总

超全MyBatis动态SQL详解!( 看完SQL爽多了)

Mybatis常用类原理

MyBatis之Mapper动态代理

MyBATIS插件原理第一篇——技术基础(反射和JDK动态代理)(转)