• mybatis(八)手写简易版mybatis


    一、画出流程图

     二、设计核心类

    二、V1.0 的实现
    创建一个全新的 maven 工程,命名为 mebatis,引入 mysql 的依赖。
    <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.21</version>
    </dependency>
    1、SqlSession
    我们已经分析了 MeBatis 的主要对象和操作流程,应该从哪里入手?
    当我们在 psvm 操作的时候,第一个需要的对象是 SqlSession。所以我们从应用层
    的接口 SqlSession 入手。
    那么我们先来创建一个 package,它是我们手写的 MeBatis ,我们建一个包叫
    mebatis。
    首先我们创建一个自己的 SqlSession,叫 GPSqlSession。
    根据我们刚才总结的流程图,SqlSession 需要有一个获取代理对象的方法,那么这
    个代理对象是从哪里获取到的呢?是从我们的配置类里面获取到的,因为配置类里面有
     
    接口和它要产生的代理类的对应关系。
    所以,我们要先持有一个 Configuration 对象,叫 GPConfiguration,我们也创建
    这个类。除了获取代理对象之外,Configuration 里面还存储了我们的接口方法(也就是
    statementId)和 SQL 语句的绑定关系。
    第二个,我们在 SqlSession 中定义的操作数据库的方法,最后都会调用 Executor
    去操作数据库,所以我们还要持有一个 Executor 对象,叫 GPExecutor,我们也创建它。
    // GPSqlSession.java
    private GPConfiguration configuration;
    private GPExecutor executor;
    除了这两个属性之外,我们还要定义 SqlSession 的行为,也就是它的主要的方法。
    第一个方法是查询方法,selectOne(),由于它可以返回任意类型,我们把返回值定
    义成<T> T 泛型。selectOne()有两个参数,一个是 String 类型的 statementId,我们会
    根据它找到 SQL 语句。一个是 Object 类型的 parameter 参数(可以是 Integer 也可以
    是 String 等等,任意类型),用来填充 SQL 里面的占位符。
    它会调用 Executor 的 query()方法,所以我们创建 Executor 类,传入这两个参数,
    一样返回一个泛型。Executor 里面要传入 SQL,但是我们还没拿到,先用 statementId
    代替。
    // GPSqlSession.java
    public <T> T selectOne(String statementId, Object parameter){
    String sql = statementId; // 先用 statementId 代替 SQL
    return executor.query(sql, parameter);
    }
    // GPExecutor.java
    public <T> T query(String sql, Object paramater ) {
    return null;
    }
     
    第二个方法是获取代理对象的方法,我们通过这种方式去避免了 statementId 的硬
    编码。
    我们在 SqlSession 中创建一个 getMapper()的方法,由于可以返回任意类型的代理
    类,所以我们把返回值也定义成泛型<T> T。我们是根据接口类型获取到代理对象的,所
    以传入参数要用类型 Class。
    // GPSqlSession.java
    public <T> T getMapper(Class clazz){
    return null;
    }
    2、Configuration
    代理对象我们不是在 SqlSession 里面获取到的,要进一步调用 Configuration 的
    getMapper()方法。返回值需要强转成(T)。
    // GPSqlSession.java
    public <T> T getMapper(Class<T> clazz){
    return (T)configuration.getMapper(clazz);
    }
    我们先在 Configuration 创建这个方法,返回类型一样是泛型<T> T,先返回空。
    // GPConfiguration.java
    public <T> T getMapper(Class clazz) {
    return null;
    }
    3、MapperProxy
    我们要在 Configuration 中通过 getMapper()方法拿到这个代理对象,必须要有一
    个实现了 InvocationHandler 的代理类。我们来创建它:GPMapperProxy。
    提供一个 invoke()方法。
     
    // GPMapperProxy.java
    public class GPMapperProxy implements InvocationHandler {
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    return null;
    }
    }
    invoke()的实现我们先留着,先返回 null。MapperProxy 已经有了,我们回到
    Configuration.getMapper()完成获取代理对象的逻辑。
    返回代理对象,直接使用 JDK 的动态代理:第一个参数是类加载器,第二个参数是
    被代理类,第三个参数是代理类。
    把返回结果强转为(T):
    // GPConfiguration.java
    public <T> T getMapper(Class<T> clazz, GPSqlSession sqlSession) {
    return (T)Proxy.newProxyInstance(this.getClass().getClassLoader(),
    new Class[]{clazz},
    new GPMapperProxy());
    }
    获取代理类的逻辑已经实现完了,我们可以在 SqlSession 中通过 getMapper()拿到
    代理对象了,也就是可以调用 invoke()方法了。接下来去完成 MapperProxy 的 invoke()
    方法。
    在 MapperProxy 的 invoke()方法里面又调用了 SqlSession 的 selectOne()方法。
    一个问题出现了:在 MapperProxy 里面根本没有 SqlSession 对象?
    这两个对象的关系怎么建立起来?MapperProxy 怎么拿到一个 SqlSession 对象?
    很简单,我们可通过构造函数传入它。
    先定义一个属性,然后在 MapperProxy 的构造函数里面赋值:
    // GPMapperProxy.java
    private GPSqlSession sqlSession;
     
    public GPMapperProxy(GPSqlSession sqlSession) {
    this.sqlSession = sqlSession;
    }
    因为修改了代理类的构造函数,这个时候 Configuration 创建代理类的方法
    getMapper()也要修改。
    问题:Configuration 的 getMapper()方法参数中也没有 SqlSession,没办法传给
    MapperProxy 的构造函数。怎么拿到 SqlSession 呢?是直接 new 一个吗?
    不需要,可以在 SqlSession 调用它的时候直接把自己传进来(红色是修改的地方):
    // GPConfiguration.java
    public <T> T getMapper(Class clazz, GPSqlSession sqlSession) {
    return (T)Proxy.newProxyInstance(this.getClass().getClassLoader(),
    new Class[]{clazz},
    new GPMapperProxy(sqlSession));
    }
    那么 SqlSession 的 getMapper()方法也要修改(红色是修改的地方):
    // GPSqlSession.java
    public <T> T getMapper(Class clazz){
    return configuration.getMapper(clazz, this);
    }
    现在在 MapperProxy 里面已经就可以拿到 SqlSession 对象了,在 invoke()方法里
    面我们会调用 SqlSession 的 selectOne()方法。我们继续来完成 invoke()方法。
    selectOne()方法有两个参数, statementId 和 paramater,这两个我们怎么拿到
    呢?
    statementId 其实就是接口的全路径+方法名,中间加一个英文的点。
    paramater 可以从方法参数中拿到,这里我们只传了一个参数,用 args[0]。
    它要把 statementId 和参数传给 SqlSession:
    // GPMapperProxy.java
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
    String mapperInterface = method.getDeclaringClass().getName();
     
    String methodName = method.getName();
    String statementId = mapperInterface + "." + methodName;
    return sqlSession.selectOne(statementId, args[0]);
    }
    4、Executor
    到了 sqlSession 的 selectOne()方法,这里我们要去调用 Executor 的 query()方法,
    这个时候我们必须传入 SQL 语句和参数(根据 statementId 获取)。
    问题来了:我们怎么根据 StatementId 找到我们要执行的 SQL 语句呢?他们之间的
    绑定关系我们配置在哪里?
    为了简便,免去读取文件流和解析 XML 标签的麻烦,我们把我们的 SQL 语句放在
    Properties 文件里面。
    我们在 resources 目录下创建一个 mesql.properties 文件。key 就是接口全路径+
    方法名称,SQL 是我们的查询 SQL。
    参数这里,因为我们要传入一个整数,所以先用一个%d 的占位符代替:
    (这里直接把 standalone 工程的实体类 Blog 和 BlogMapper 接口复制过来)
    com.gupaoedu.mebatis.BlogMapper.selectBlogById=select * from blog where bid = %d
    这个绑定关系是放在配置类 Configuration 里面的。
    为了避免重复解析,我们在 Configuration 创建一个静态属性和静态方法,直接解
    析 mesql.properties 文件里面的所有 KV 键值对:
    // GPConfiguration.java
    public static final ResourceBundle sqlMappings;
    static{
    sqlMappings = ResourceBundle.getBundle("mesql");
    }
     
    这样就可以通过 Configuration 拿到 SQL 了。
    如果 SQL 语句拿不到,说明不存在映射关系(或者不是接口中定义的操作数据的方
    法,比如 toString()),我们返回空。
    // GPSqlSession.java
    public <T> T selectOne(String statement, String parameter){
    String sql = GPConfiguration.sqlMappings.getString(statement);
    if( null != sql && !"".equals(sql)){
    return executor.query(sql, parameter);
    }
    return null;
    }
    SQL 语句已经拿到了,接下来就是 Executor 类的 query()方法,Executor 是数据库
    操作的真正执行者。它里面应该做什么事情?
    我们干脆直接把 JDBC 的代码全部复制过来,职责先不用细分。
    参数用传入的参数替换%d 占位符,需要 format 一下。
    // GPExecutor.java
    ResultSet rs = stmt.executeQuery(String.format(sql, paramater));
    最后我们把结果强转一下。
    // GPExecutor.java
    return (T)blog;
    写一个测试类:
    // MeBatisTest.java
    public class MeBatisTest {
    public static void main(String[] args) {
    GPSqlSession sqlSession = new GPSqlSession();
    BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
    blogMapper.selectBlogById(1);
     
    }
    }
    跑一下:
    Exception in thread "main" java.lang.NullPointerException
    at com.gupaoedu.hand.GPSqlSession.getMapper(GPSqlSession.java:23)
    at com.gupaoedu.hand.MyBatisBoot.main(MyBatisBoot.java:13)
    configuration 是空的,忘记拿到 Configuration 了!那么 Executor 肯定也是空的。
    构造函数里面要给他们俩加上:
    // GPSqlSession.java
    public GPSqlSession(GPConfiguration configuration, GPExecutor executor){
    this.configuration = configuration;
    this.executor = executor;
    }
    改一下我们的测试类(红色是修改部分):
    // MeBatisTest.java
    public class MeBatisTest {
    public static void main(String[] args) {
    GPSqlSession sqlSession = new GPSqlSession(new GPConfiguration(), new GPExecutor());
    BlogMapper blogMapper = sqlSession.getMapper(BlogMapper.class);
    blogMapper.selectBlogById(1);
    }
    }
    测试通过,MeBatis 1.0 的版本完成了:
    Blog{bid=1, name='MyBatis 源码分析', authorId='1001'}
     
    三、1.0 的不足
    MeBatis1.0 的功能完成了,在拿给老王看之前,我抽了根烟思考了一下:
    1、在 Executor 中,对参数、语句和结果集的处理是耦合的,没有实现职责分离;
    2、参数:没有实现对语句的预编译,只有简单的格式化(format),效率不高,
    还存在 SQL 注入的风险;
    3、语句执行:数据库连接硬编码;
    4、结果集:还只能处理 Blog 类型,没有实现根据实体类自动映射。
    确实有点搓,拿不出手。
    V1.0 的优化目标
    支持参数预编译;
    支持结果集的自动处理(通过反射);
    对 Executor 的职责进行细化。
    V1.0 的功能增强目标
    在方法上使用注解配置 SQL;
    查询带缓存功能;
    支持自定义插件。
     
    代码 源码见下面链接:
  • 相关阅读:
    CentOS 安装Python3, pip3
    Pyinstaller打包python程序,以及遇到的问题:Cannot find existing PyQt5 plugin directories
    [Python] fetchone()和fetchall()
    在Linux命令行下,如何登录mysql server
    管理MySQL的客户端软件-MySQL Workbench
    在win10修改jupyter notebook(Anaconda安装)工作路径
    安装spark过程中出现Exception in thread "main" java.lang.UnsupportedClassVersionError错误的解决办法
    如何在win10本地主机操作系统和virtualBox 的Ubuntu之间设置共享文件夹
    支持向量机(SVM)
    特征工程(python)
  • 原文地址:https://www.cnblogs.com/flgb/p/12688464.html
Copyright © 2020-2023  润新知