面试官你好,我自己手写实现过Mybatis(超详细注释) - Go语言中文社区

面试官你好,我自己手写实现过Mybatis(超详细注释)


一、MyBatis核心组件

在开始实现我们的mybatis框架之前我觉得有必要先学习一下MyBatis核心组件,如下示意图(出自前文),在前面这个链接中可以了解到更多的细节。这里附上代码的github链接:github源码

在这里插入图片描述

二、MyBatis手写实现

1. 从测试用例作为入口

/**
 * 测试用例,将整个工程串联起来
 */
public class MybatisTest {
    public static void main(String[] args) throws IOException {
        //1.读取配置文件,将配置文件转换为输入流
        InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
        //2.创建SqlSessionFactory工厂
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        //返回DefaultSqlSessionFactory对象,利用XMLConfigBuilder类从输入流中读取配置信息。
        // 封装成Configuration对象传入DefaultSqlSessionFactory的构造方法
        SqlSessionFactory factory = builder.build(in);
        //3.使用工厂(DefaultSqlSessionFactory)生产SqlSession对象,
        SqlSession session = factory.openSession();
        //4.使用SqlSession创建Dao接口的代理对象
        IUserMapper mapper = session.getMapper(IUserMapper.class);
        //5.使用代理对象执行方法
        List<User> users = mapper.findAll();
        for (User user : users) {
            System.out.println(user);
        }
        //6.释放资源
        session.close();
        try {
            in.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
}

我们根据这个测试用例将整个mybatis执行sql过程分为几步去实现它:

2.整个项目结构

在这里插入图片描述

3. 实现代码

可以打开右边的目录栏来快速跳转

(1)、Resources

/**
 * 使用类加载器读取配置文件的类
 */
public class Resources {
    public static InputStream getResourceAsStream(String filePath) {
        return Resources.class.getClassLoader().getResourceAsStream(filePath);
    }
}

(2)、SqlSessionFactoryBuilder

/**
 * 根据输入流构建SqlSessionFactory对象
 */
public class SqlSessionFactoryBuilder {
    public SqlSessionFactory build(InputStream inputStream) {
        return new DefaultSqlSessionFactory(
                XMLConfigBuilder.loadConfiguration(inputStream)
        );
    }
}

(3)、SqlSessionFactory

/**
 * 创建SqlSession对象的工厂类
 */
public interface SqlSessionFactory {
    SqlSession openSession();
}


/**
 * 根据传入的Configuration创建SqlSession
 */
public class DefaultSqlSessionFactory implements SqlSessionFactory {

    private Configuration cfg;

    @Override
    public SqlSession openSession() {
        return new DefaultSqlSession(cfg.getMappers(), DataSourceUtil.getConnection(cfg));
    }

    public DefaultSqlSessionFactory(Configuration cfg) {
        this.cfg = cfg;
    }
}

(4)、SqlSession

/**
 * 自定义Mybatis中和数据库交互的核心类,可以创建dao包中Mapper接口的代理对象
 */
public interface SqlSession {
    /**
     * 通过mapper字节码获取代理对象
     *
     * @param mapperClass 被代理对象字节码对象
     * @param <T>         被代理类型
     * @return 代理对象
     */
    <T> T getMapper(Class<T> mapperClass);

    /**
     * 释放资源
     */
    void close();
}

/**
 * 获取Mapper代理对象执行查询
 */
public class DefaultSqlSession implements SqlSession {


   private Map<String, SqlMapper> sqlMappers;

    private Connection connection;

    public DefaultSqlSession(Map<String, SqlMapper> sqlMappers, Connection connection) {
        this.sqlMappers = sqlMappers;
        this.connection = connection;
    }

    @Override
    public <T> T getMapper(Class<T> mapperClass) {
        return (T) Proxy.newProxyInstance(mapperClass.getClassLoader(),
                new Class[]{mapperClass},
                new MapperProxy(sqlMappers, connection));
    }

    @Override
    public void close() {
        if (connection != null) {
            try {
                connection.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

(5)、MapperProxy

/**
 * 增强Mapper,生成代理对象
 */
public class MapperProxy implements InvocationHandler {

    private Map<String, SqlMapper> sqlMappers;

    private Connection connection;

    public MapperProxy(Map<String, SqlMapper> sqlMappers, Connection connection) {
        this.sqlMappers = sqlMappers;
        this.connection = connection;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //获取方法名和全限名,根据方法名得到Mapper对象
        String methodName = method.getName();
        String className = method.getDeclaringClass().getName();
        SqlMapper sqlMapper = sqlMappers.get(className + "." + methodName);
        return new Executor().selectList(sqlMapper, connection);
    }
}

(6)、SqlMapper

/**
 * 要查询的sql语句和返回类型的全限定名,封装dao包类的每一个接口的每一个方法
 */
public class SqlMapper {
    /**
     * 要查询的sql语句
     */
    private String queryString;
    /**
     * 查询后要封装的类型
     */
    private String resultType;

    public String getQueryString() {
        return queryString;
    }

    public void setQueryString(String queryString) {
        this.queryString = queryString;
    }

    public String getResultType() {
        return resultType;
    }

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

(7)、Configuration

/**
 * 数据库连接的配置信息
 */
public class Configuration {
    private String driver;

    private String url;

    private String password;

    private String username;

    private Map<String, SqlMapper> sqlMappers = new HashMap<>();

    public Map<String, SqlMapper> getMappers() {
        return sqlMappers;
    }

    public void setMappers(Map<String, SqlMapper> mappers) {
        this.sqlMappers.putAll(mappers);
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getDriver() {
        return driver;
    }

    public void setDriver(String driver) {
        this.driver = driver;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }
}

(8)、Select注解

/**
 * mybatis自定义注解
 */
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select {

    /**
     * 配置SQL语句的
     */
    String value();
}

(9)、User

/**
 * 实体类
 */
public class User implements Serializable {
    private Integer id;

    private String username;

    private Date birthday;

    private String sex;

    private String address;


    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + ''' +
                ", birthday=" + birthday +
                ", sex='" + sex + ''' +
                ", address='" + address + ''' +
                '}';
    }

    public Integer getId() {
        return id;
    }

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

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public Date getBirthday() {
        return birthday;
    }

    public void setBirthday(Date birthday) {
        this.birthday = birthday;
    }

    public String getSex() {
        return sex;
    }

    public void setSex(String sex) {
        this.sex = sex;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}

(10)、IUserMapper

/**
 * Dao类接口
 */
public interface IUserMapper {
    @Select("select * from user")
    List<User> findAll();
}

(10)、工具类(不是重点,理解即可)

  • DataSourceUtil
/**
 * 根据Configuration创建连接
 */
public class DataSourceUtil {

    /**
     * 通过配置类用于获取一个连接
     *
     * @param cfg 配置类
     * @return 根据配置类读取的配置信息创建的连接
     */
    public static Connection getConnection(Configuration cfg) {
        try {
            Class.forName(cfg.getDriver());
            return DriverManager.getConnection(cfg.getUrl(), cfg.getUsername(), cfg.getPassword());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}
  • Executor
/**
 * 根据mapper和connection封装查询结果
 */
public class Executor {

    public <E> List<E> selectList(SqlMapper sqlMapper, Connection conn) {
        PreparedStatement pstm = null;
        ResultSet rs = null;
        try {
            //1.取出mapper中的数据
            String queryString = sqlMapper.getQueryString();
            String resultType = sqlMapper.getResultType();
            Class domainClass = Class.forName(resultType);
            //2.获取PreparedStatement对象
            pstm = conn.prepareStatement(queryString);
            //3.执行SQL语句,获取结果集
            rs = pstm.executeQuery();
            //4.封装结果集
            List<E> list = new ArrayList<E>();
            while (rs.next()) {
                //实例化要封装的实体类对象
                E obj = (E) domainClass.newInstance();

                //取出结果集的元信息:ResultSetMetaData
                ResultSetMetaData rsmd = rs.getMetaData();
                //取出总列数
                int columnCount = rsmd.getColumnCount();
                //遍历总列数
                for (int i = 1; i <= columnCount; i++) {
                    //获取每列的名称,列名的序号是从1开始的
                    String columnName = rsmd.getColumnName(i);
                    //根据得到列名,获取每列的值
                    Object columnValue = rs.getObject(columnName);
                    //给obj赋值:使用Java内省机制(借助PropertyDescriptor实现属性的封装)
                    //要求:实体类的属性和数据库表的列名保持一种
                    PropertyDescriptor pd = new PropertyDescriptor(columnName, domainClass);
                    //获取它的写入方法
                    Method writeMethod = pd.getWriteMethod();
                    //把获取的列的值,给对象赋值
                    writeMethod.invoke(obj, columnValue);
                }
                //把赋好值的对象加入到集合中
                list.add(obj);
            }
            return list;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            release(pstm, rs);
        }
    }


    private void release(PreparedStatement pstm, ResultSet rs) {
        if (rs != null) {
            try {
                rs.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }

        if (pstm != null) {
            try {
                pstm.close();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }
    }
}

  • XMLConfigBuilder
/**
 * 解析xml封装Configuration对象
 */
public class XMLConfigBuilder {


    /**
     * 解析主配置文件,把里面的内容填充到DefaultSqlSession所需要的地方
     * 使用的技术:
     * dom4j+xpath
     */
    public static Configuration loadConfiguration(InputStream in) {
        try {
            //定义封装连接信息的配置对象(mybatis的配置对象)
            Configuration cfg = new Configuration();

            //1.获取SAXReader对象
            SAXReader reader = new SAXReader();
            //2.根据字节输入流获取Document对象
            Document document = reader.read(in);
            //3.获取根节点
            Element root = document.getRootElement();
            //4.使用xpath中选择指定节点的方式,获取所有property节点
            List<Element> propertyElements = root.selectNodes("//property");
            //5.遍历节点
            for (Element propertyElement : propertyElements) {
                //判断节点是连接数据库的哪部分信息
                //取出name属性的值
                String name = propertyElement.attributeValue("name");
                if ("driver".equals(name)) {
                    //表示驱动
                    //获取property标签value属性的值
                    String driver = propertyElement.attributeValue("value");
                    cfg.setDriver(driver);
                }
                if ("url".equals(name)) {
                    //表示连接字符串
                    //获取property标签value属性的值
                    String url = propertyElement.attributeValue("value");
                    cfg.setUrl(url);
                }
                if ("username".equals(name)) {
                    //表示用户名
                    //获取property标签value属性的值
                    String username 
                            
                            版权声明:本文来源CSDN,感谢博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
原文链接:https://blog.csdn.net/weixin_42762133/article/details/105198657
站方申明:本站部分内容来自社区用户分享,若涉及侵权,请联系站方删除。

0 条评论

请先 登录 后评论

官方社群

GO教程

推荐文章

猜你喜欢