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.手撕框架

前置知识:

序号

前置知识

是否必须

用途

1

动态代理

必须

代理模式,静态代理,基于JDK的动态代理,Mapper接口代理

2

泛型与反射

必须

动态获取类型,实体对象赋值

3

JDBC连接数据库

必须

连接数据库,执行SQL,返回结果集

4

自定义注解

非必须

基于注解实现SQL语句与接口方法映射

5

函数式

非必须

功能抽象

6

H2数据库

非必须

提供数据源

7

fastjson

非必须

json序列化

源码:

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

  • 2
    点赞
  • 7
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值