模仿Mybatis入门案例实现一个ORM框架

入门案例参见:

Mybatis使用与入门案例

设计分析

main函数里都在干什么?

// 1.读取配置文件
// 这是相对类的路径,读取配置文件时通常使用两个方法:
// a. 类加载器 b.使用ServletContext对象的getRealPath()
InputStream in = Resources.getResourceAsStream("mybatis.xml");
// 2.创建SqlSessionFactory工厂,构建者模式
SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builder.build(in);
// 3.使用工厂生成SqlSession对象,工厂模式
SqlSession session = factory.openSession();
// 4.使用SqlSession创建Dao接口的代理,代理模式
IUserDao userDao = session.getMapper(IUserDao.class);
// 5.使用代理对象执行方法
List<User> users = userDao.findAll();
for(User user:users){
    System.out.println(user);
}
// 6.释放资源
session.close();
in.close();

自定义ORM框架的思路:

  1. 根据配置文件的信息创建Connection对象
    注册驱动,获取连接
  2. 获取预处理对象PreparedStatement
    此时需要SQL语句
    conn.prepareStatement(sql)
  3. 执行查询
    ResultSet set = psm.executeQuery()
  4. 遍历结果集用于封装
    List list = new ArrayList();
    while(resultSet.next()){
    E element = xxxx;(通过反射)
    //进行封装
    list.add(element);
    }
  5. 返回list
    return list;

模仿Mybatis入门案例实现一个ORM框架

实现

仓库地址:

http://git.codetool.top/RhettPeng/mybatis_design/src/master

使用类加载器读取配置文件

public class Resources {

    /**
     * 根据传入的参数,获取一个字节输入流
     */
    public static InputStream getResourceAsStream(String filePath){
        return Resources.class.getClassLoader().getResourceAsStream(filePath);
    }
}

使用输入流构建工厂

public class SqlSessionFactoryBuilder {
    /**
     * 根据参数的字节输入流来构建一个SqlSessionFactory工厂
     * @param config
     * @return
     */
    public SqlSessionFactory build(InputStream config){
        //XMLConfigBuilder用于解析xml配置文件并生成Configuration对象
        Configuration cfg = XMLConfigBuilder.loadConfiguration(config);

        return new DefaultSqlSessionFactory(cfg);
    }
}

Configuration:

public class Configuration {
    private String driver;
    private String url;
    private String username;
    private String password;

    private Map<String,Mapper> mappers = new HashMap<>();
    //getter & setter...
}

Configuration里面包含了所有读出来的mapper映射,key为全限定类名.方法名,value为Mapper对象,Mapper类:

/**
 * 用于封装执行的SQL语句和结果类型的全限定类名
 */
public class Mapper {
    private String queryString;//SQL
    private String resultType;//返回类型

    //getter & setter...
}

工厂接口:

public interface SqlSessionFactory {
    /**
     * 用于创建一个SqlSession对象
     * @return
     */
    SqlSession openSession();
}

使用工厂获取session对象

/**
 * SqlSessionFactory接口的实现类
 */
public class DefaultSqlSessionFactory implements SqlSessionFactory {
    private Configuration cfg;

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

    /**
     * 用于创建一个新的操作数据库对象
     * @return
     */
    @Override
    public SqlSession openSession() {
        return new DefaultSqlSession(cfg);
    }
}

SqlSession接口:

/**
 * 自定义Mybatis和数据库交互的核心类
 * 它里面可以创建dao接口的代理对象
 */
public interface SqlSession {
    /**
     * 根据参数创建一个代理对象
     * @param daoInterfaceClass dao的字节码
     * @param <T>
     * @return
     */
    <T> T getMapper(Class<T> daoInterfaceClass);

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

使用Session获取一个mapper对象

这里使用了动态代理,默认SqlSession实现:

public class DefaultSqlSession implements SqlSession {
    private Configuration cfg;
    private Connection connection;

    public DefaultSqlSession(Configuration cfg){
        this.cfg = cfg;
        this.connection = DataSourceUtil.getConnection(cfg);
    }
    /**
     * 用于创建代理对象
     * @param daoInterfaceClass dao的字节码
     * @param <T>
     * @return
     */

    //通过传过来的接口找到Mapper映射中对应的Mapper对象,然后使用InvocationHandler构建动态代理对象
    @Override
    public <T> T getMapper(Class<T> daoInterfaceClass) {
        T mapper = (T)Proxy.newProxyInstance(daoInterfaceClass.getClassLoader(), new Class[]{daoInterfaceClass}, new MapperProxy(cfg.getMappers(), connection));
        return mapper;
    }

    /**
     * 用于释放资源
     */
    @Override
    public void close() {
        if(connection!=null){
            try {
                connection.close();
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}

MapperProxy:

public class MapperProxy implements InvocationHandler {
    //key:全限定类名+方法名
    private Map<String, Mapper> mappers;
    private Connection conn;

    public MapperProxy(Map mappers,Connection conn){
        this.mappers = mappers;
        this.conn = conn;
    }

    /**
     * 用于对方法进行增强,这里就是调用selectList方法
     * @param proxy
     * @param method
     * @param args
     * @return
     * @throws Throwable
     */
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //1. 获取方法名
        String methodName = method.getName();
        //2. 获取方法所在类的名称
        String className = method.getDeclaringClass().getName();
        //3. 组合key
        String key = className+"."+methodName;
        //4. 获取mappers中的mapper对象
        Mapper mapper = mappers.get(key);
        //5. 判断是否有mapper
        if(mapper==null){
            throw new IllegalArgumentException("传入的参数有误");
        }
        //6. 调用工具类执行查询所有
        return new Executor().selectList(mapper,conn);
    }
}

用户定义的Mapper:

public interface IUserDao {
    /**
     * 查询所有操作
     */
    List<User> findAll();
}

总结

模仿Mybatis入门案例实现一个ORM框架

原创文章,作者:彭晨涛,如若转载,请注明出处:https://www.codetool.top/article/%e6%a8%a1%e4%bb%bfmybatis%e5%85%a5%e9%97%a8%e6%a1%88%e4%be%8b%e5%ae%9e%e7%8e%b0%e4%b8%80%e4%b8%aaorm%e6%a1%86%e6%9e%b6/