mybatis源码深入学习-02(手撸mybatis框架)

mybatis源码深入学习-01

如果自己设计mybatis架构

在这里插入图片描述
首先创建 SqlSessionFactory 实例,SqlSessionFactory 就是创建 SqlSession 的工厂类。
加载配置文件创建 Configuration 对象,配置文件包括数据库相关配置文件以及我们在 XML 文件中写的 SQL。
通过 SqlSessionFactory 创建 SqlSession。
通过 SqlSession 获取 mapper 接口动态代理。
动态代理回调 SqlSession 中某查询方法。
SqlSession 将查询方法转发给 Executor。
Executor 基于 JDBC 访问数据库获取数据,最后还是通过 JDBC 操作数据库。
Executor 通过反射将数据转换成 POJO 并返回给 SqlSession。
将数据返回给调用者。

传统jdbc

加载驱动
创建连接,Connection 对象
根据 Connection 创建 Statement 或者 PreparedStatement 来执行 SQL 语句
返回结果集到 ResultSet 中
手动将 ResultSet 映射到 JavaBean 中

MyBatis 操作数据库总结

使用配置文件构建 SqlSessionFactory
使用 SqlSessionFactory 获得 SqlSession,SqlSession 相当于传统 JDBC 的 Conection
使用 SqlSession 得到 Mapper
用 Mapper 来执行 SQL 语句,并返回结果直接封装到 JavaBean 中
在这里插入图片描述

自己写一个mybatis框架

1)加载配置文件配置信息

import java.util.HashMap;
import java.util.Map;

/**
 * Configuration 总配置类,来保存 db.propeties 里面的属性和 XML 文件的 SQL 信息
 */
public class Configuration {

    private String jdbcDriver;

    private String jdbcUrl;

    private String jdbcPassword;

    private String jdbcUsername;

    private Map<String, MappedStatement> mappedStatement = new HashMap<>();

    public Map<String, MappedStatement> getMappedStatement() {
        return mappedStatement;
    }

    public void setMappedStatement(Map<String, MappedStatement> mappedStatement) {
        this.mappedStatement = mappedStatement;
    }

    public String getJdbcDriver() {
        return jdbcDriver;
    }

    public void setJdbcDriver(String jdbcDriver) {
        this.jdbcDriver = jdbcDriver;
    }

    public String getJdbcUrl() {
        return jdbcUrl;
    }

    public void setJdbcUrl(String jdbcUrl) {
        this.jdbcUrl = jdbcUrl;
    }

    public String getJdbcPassword() {
        return jdbcPassword;
    }

    public void setJdbcPassword(String jdbcPassword) {
        this.jdbcPassword = jdbcPassword;
    }

    public String getJdbcUsername() {
        return jdbcUsername;
    }

    public void setJdbcUsername(String jdbcUsername) {
        this.jdbcUsername = jdbcUsername;
    }
}
import com.java.mybatishandwriting.utis.Configuration;

import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;

/**
 * 加载配置文件配置信息
 */

public class GetProperties {
    private Configuration configuration;

    public GetProperties(Configuration configuration) {
        this.configuration = configuration;
    }

    public GetProperties() {
    }

    // 数据库信息存放的位置
    public Properties loadDBInfo() {
        InputStream db = this.getClass().getClassLoader().getResourceAsStream("application.properties");
        Properties p = new Properties();
        try {
            p.load(db);
        } catch (IOException e) {
            e.printStackTrace();
        }
        System.out.println("driver:" + p.get("jdbc.driver").toString());
        System.out.println("url:" + p.get("jdbc.url").toString());
        System.out.println("username:" + p.get("jdbc.username").toString());
        System.out.println("password:" + p.get("jdbc.password").toString());
        //将配置信息写入Configuration 对象
        configuration.setJdbcDriver(p.get("jdbc.driver").toString());
        configuration.setJdbcUrl(p.get("jdbc.url").toString());
        configuration.setJdbcUsername(p.get("jdbc.username").toString());
        configuration.setJdbcPassword(p.get("jdbc.password").toString());

        return p;
    }
}

2)解析mapper xml文件

/**
 * 加载写 SQL 语句的 XML 文件,上面我们说过要注意四个点,namespace、id、resultType、SQL 语句,我们写对应的属性来保存它
 */
public final class MappedStatement {

    private String namespace;

    private String id;

    private String resultType;

    private String sql;

    public String getNamespace() {
        return namespace;
    }

    public void setNamespace(String namespace) {
        this.namespace = namespace;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getResultType() {
        return resultType;
    }

    public void setResultType(String resultType) {
        this.resultType = resultType;
    }

    public String getSql() {
        return sql;
    }

    public void setSql(String sql) {
        this.sql = sql;
    }

}
import java.io.File;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;

import com.java.mybatishandwriting.utis.Configuration;
import com.java.mybatishandwriting.utis.MappedStatement;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

/**
 * 解析mapper  xml文件
 */
public class GetMapperInfo {
    // xml 文件存放的位置
    private static final String MAPPER_CONFIG_LOCATION = "mappers";
    private Configuration configuration;

    public GetMapperInfo(Configuration configuration) {
        this.configuration = configuration;
    }

    public GetMapperInfo() {
    }

    //解析并加载xml文件
    public void loadMapperInfo() {
        URL resources = null;
        resources = this.getClass().getClassLoader().getResource(MAPPER_CONFIG_LOCATION);
        File mappers = new File(resources.getFile());
        //读取文件夹下面的文件信息
        if (mappers.isDirectory()) {
            File[] files = mappers.listFiles();
            for (File file : files) {
                loadMapperInfo(file);
            }
        }
    }

    public void loadMapperInfo(File file) {
        SAXReader reader = new SAXReader();
        //通过read方法读取一个文件转换成Document 对象
        Document document = null;
        try {
            document = reader.read(file);
        } catch (DocumentException e) {
            e.printStackTrace();
        }
        //获取根结点元素对象<mapper>
        Element e = document.getRootElement();
        //获取命名空间namespace
        String namespace = e.attribute("namespace").getData().toString();
        //获取select,insert,update,delete子节点列表
        List<Element> selects = e.elements("select");
        List<Element> inserts = e.elements("insert");
        List<Element> updates = e.elements("update");
        List<Element> deletes = e.elements("delete");

        List<Element> all = new ArrayList<>();
        all.addAll(selects);
        all.addAll(inserts);
        all.addAll(updates);
        all.addAll(deletes);
        for (Element ele : all) {
            String id = ele.attribute("id").getData().toString();
            String resultType = ele.attribute("resultType").getData().toString();
            String sql = ele.getData().toString();

            System.out.println("id:" + id);
            System.out.println("resultType:" + resultType);
            System.out.println("sql:" + sql);
            MappedStatement mappedStatement = new MappedStatement();

            mappedStatement.setId(namespace + "." + id);
            mappedStatement.setResultType(resultType);
            mappedStatement.setNamespace(namespace);
            mappedStatement.setSql(sql);
            // xml 文件中的每个 sql 方法都组装成 mappedStatement 对象,以 namespace+"."+id 为 key, 放入
            // configuration 配置类中。
            configuration.getMappedStatement().put(namespace + "." + id, mappedStatement);

        }
    }

}

3)构建 SqlSessionFactory

public interface SqlSessionFactory {
    SqlSession openSession();
}
import com.java.mybatishandwriting.utis.Configuration;

/**
 * 构建 SqlSessionFactory
 */
public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private final Configuration configuration = new Configuration();

    // xml 文件存放的位置
    private static final String MAPPER_CONFIG_LOCATION = "mappers";

    // 数据库信息存放的位置
    private static final String DB_CONFIG_FILE = "db.properties";


    public DefaultSqlSessionFactory() {
        loadDBInfo();
        loadMapperInfo();
    }

    private void loadDBInfo() {
        GetProperties getProperties = new GetProperties(configuration);
        getProperties.loadDBInfo();
    }

    //解析并加载xml文件
    private void loadMapperInfo() {
        GetMapperInfo getMapperInfo = new GetMapperInfo(configuration);
        getMapperInfo.loadMapperInfo();

    }

    @Override
    public SqlSession openSession() {
        // openSession 方法创建一个 DefaultSqlSession,configuration 配置类作为 构造函数参数传入
        return new DefaultSqlSession(configuration);
    }
}

4)通过SqlSessionFactory 获得 SqlSession

import java.util.List;

public interface SqlSession {
    /**
     * 根据传入的条件查询单一结果
     *
     * @param statement namespace+id,可以用做 key,去 configuration 里面获取 sql 语句,resultType
     * @param parameter 要传入 sql 语句中的查询参数
     * @param <T>       返回指定的结果对象
     * @return
     */
    <T> T selectOne(String statement, Object parameter);

    <T> List<T> selectList(String statement, Object parameter);

    <T> T getMapper(Class<T> type);
}
import com.java.mybatishandwriting.utis.Configuration;
import com.java.mybatishandwriting.utis.MappedStatement;

import java.util.List;

import java.lang.reflect.Proxy;

public class DefaultSqlSession implements SqlSession {

    private final Configuration configuration;

    private Executor executor;

    public DefaultSqlSession(Configuration configuration) {
        super();
        this.configuration = configuration;
        executor = new SimpleExecutor(configuration);
    }

    @Override
    public <T> T selectOne(String statement, Object parameter) {
        List<T> selectList = this.selectList(statement, parameter);
        if (selectList == null || selectList.size() == 0) {
            return null;
        }
        if (selectList.size() == 1) {
            return (T) selectList.get(0);
        } else {
            throw new RuntimeException("too many result");
        }
    }

    @Override
    public <T> List<T> selectList(String statement, Object parameter) {
        MappedStatement ms = configuration.getMappedStatement().get(statement);
        // 我们的查询方法最终还是交给了 Executor 去执行,Executor 里面封装了 JDBC 操作。传入参数包含了 sql 语句和 sql 语句需要的参数。
        return executor.query(ms, parameter);
    }

    @Override
    public <T> T getMapper(Class<T> type) {
        //通过动态代理生成了一个实现类,我们重点关注,动态代理的实现,它是一个 InvocationHandler,传入参数是 this,就是 sqlSession 的一个实例。
        MapperProxy mp = new MapperProxy(this);
        //给我一个接口,还你一个实现类
        return (T) Proxy.newProxyInstance(type.getClassLoader(), new Class[]{type}, mp);
    }
}

5)定义了数据库操作的最基本的方法

import com.java.mybatishandwriting.utis.MappedStatement;

import java.util.List;

/**
 * mybatis 核心接口之一,定义了数据库操作的最基本的方法,JDBC,sqlSession的所有功能都是基于它来实现的
 */
public interface Executor {
    /**
     * 查询接口
     *
     * @param ms        封装sql 语句的 mappedStatemnet 对象,里面包含了 sql 语句,resultType 等。
     * @param parameter 传入sql 参数
     * @param <E>       将数据对象转换成指定对象结果集返回
     * @return
     */
    <E> List<E> query(MappedStatement ms, Object parameter);
}
import com.java.mybatishandwriting.utis.Configuration;
import com.java.mybatishandwriting.utis.MappedStatement;
import com.java.mybatishandwriting.utis.ReflectionUtil;

import java.sql.*;
import java.util.ArrayList;
import java.util.List;

public class SimpleExecutor implements Executor {

    private final Configuration configuration;

    public SimpleExecutor(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public <E> List<E> query(MappedStatement ms, Object parameter) {
        System.out.println(ms.getSql().toString());

        List<E> ret = new ArrayList<>(); //返回结果集
        try {
            Class.forName(configuration.getJdbcDriver());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }

        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            connection = DriverManager.getConnection(configuration.getJdbcUrl(), configuration.getJdbcUsername(), configuration.getJdbcPassword());
            String regex = "#\\{([^}])*\\}";
            // 将 sql 语句中的 #{userId} 替换为 ?
            String sql = ms.getSql().replaceAll(regex, "");
            preparedStatement = connection.prepareStatement(sql);
            //处理占位符,把占位符用传入的参数替换
            parametersize(preparedStatement, parameter);
            resultSet = preparedStatement.executeQuery();
            handlerResultSet(resultSet, ret, ms.getResultType());
        } catch (SQLException e) {
            e.printStackTrace();
        } finally {
            try {
                resultSet.close();
                preparedStatement.close();
                connection.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        return ret;
    }


    private void parametersize(PreparedStatement preparedStatement, Object parameter) throws SQLException {
        if (parameter instanceof Integer) {
            preparedStatement.setInt(1, (int) parameter);
        } else if (parameter instanceof Long) {
            preparedStatement.setLong(1, (Long) parameter);
        } else if (parameter instanceof String) {
            preparedStatement.setString(1, (String) parameter);
        }
    }

    private <E> void handlerResultSet(ResultSet resultSet, List<E> ret, String className) {
        Class<E> clazz = null;
        //通过反射获取类对象
        try {
            clazz = (Class<E>) Class.forName(className);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        try {
            while (resultSet.next()) {
                Object entity = clazz.newInstance();
                //通过反射工具 将 resultset 中的数据填充到 entity 中
                ReflectionUtil.setPropToBeanFromResultSet(entity, resultSet);
                ret.add((E) entity);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

    }
}

6)将请求转发给 sqlSession

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Collections;

/**
 * 将请求转发给 sqlSession
 */
public class MapperProxy implements InvocationHandler {

    private SqlSession sqlSession;

    public MapperProxy(SqlSession sqlSession) {
        this.sqlSession = sqlSession;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(method.getDeclaringClass().getName() + "." + method.getName());
        //最终还是将执行方法转给 sqlSession,因为 sqlSession 里面封装了 Executor
        //根据调用方法的类名和方法名以及参数,传给 sqlSession 对应的方法
        if (Collection.class.isAssignableFrom(method.getReturnType())) {
            return sqlSession.selectList(method.getDeclaringClass().getName() + "." + method.getName(), args == null ? null : args[0]);
        } else {
            return sqlSession.selectOne(method.getDeclaringClass().getName() + "." + method.getName(), args == null ? null : args[0]);
        }
    }
}

7)反射,返回结果直接封装到 JavaBean 中

import com.java.mybatishandwriting.pojo.User;

import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.SQLException;

/**
 * 反射工具类
 *
 * @author ys
 */
public class ReflectionUtil {
    /**
     * 为指定的beanpropName属性的值设置为value
     *
     * @param bean    目标对象
     * @param proName 对象的属性名
     * @param value   值
     */
    public static void   setPropToBean(Object bean, String proName, Object value) {
        Field f;
        try {
            //获取对象指定的属性
            f = bean.getClass().getDeclaredField(proName);
            //将字段设置为可通过反射访问
            f.setAccessible(true);
            //为属性赋值
            f.set(bean, value);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    /**
     * 从resultSet中读取一行数据,并填充到指定的实体bean
     *
     * @param entity    待填充的实体bean
     * @param resultSet 从数据库中加载的数据
     * @throws SQLException
     */
    public static void setPropToBeanFromResultSet(Object entity, ResultSet resultSet) throws SQLException {
        //通过反射获取对象的所有字段
        Field[] declaredFields = entity.getClass().getDeclaredFields();
        //遍历所有的字段,从resultSet中读取相应的电话机,并填充至对象的属性中
        for (int i = 0; i < declaredFields.length; i++) {
            //如果是字符串类型的数据
            if (declaredFields[i].getType().getSimpleName().equals("String")) {
                setPropToBean(entity, declaredFields[i].getName(), resultSet.getString(declaredFields[i].getName()));
                //如果是int类型
            } else if (declaredFields[i].getType().getSimpleName().equals("Integer")) {
                setPropToBean(entity, declaredFields[i].getName(), resultSet.getInt(declaredFields[i].getName()));
                //如果是long类型数据
            } else if (declaredFields[i].getType().getSimpleName().equals("Long")) {
                setPropToBean(entity, declaredFields[i].getName(), resultSet.getLong(declaredFields[i].getName()));
            }
        }
    }


    //测试
    public static void main(String[] args) {
        User user = new User();
        ReflectionUtil.setPropToBean(user, "userName", "ys");
        System.out.println(user.getUserName());
    }
}

8)测试

import org.junit.Test;

import java.util.Arrays;
import java.util.List;

public class MybatisTest {

    @Test
    public void loadProperties() {
//        getPropertiestest();
//        getMapperInfo();
        SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory();

        SqlSession sqlSession = sqlSessionFactory.openSession();

        UserMapper mapper = sqlSession.getMapper(UserMapper.class);

        List<User> users = mapper.selectUserList();

        System.out.println(Arrays.asList(users));
    }

    private void getMapperInfo() {
        GetMapperInfo getMapperInfo = new GetMapperInfo();
        getMapperInfo.loadMapperInfo();
    }

    private void getPropertiestest() {
        GetProperties getProperties = new GetProperties();
        getProperties.loadDBInfo();
    }
}

参考文章
https://www.cnblogs.com/paulwang92115/p/12130224.html

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值