ORM 思想及相关框架[Mytatis]实现原理

一.ORM 思想

目前,通过 Java 语言连接并操作数据库的技术或方式已经有很多了,例如:JDBC,
Hibernate,MyBatis,TopLink 等等。其中 JDBC 是 Java 原生的 API,支持连接并操作各种关系型数据库。相信每个程序员都是从 JDBC 开始学起的,然后才接触到各种持久层框架。
JDBC 作为 Java 原生 API,有优点,也有缺点,这里主要说一下缺点:

  1. 编码繁琐,效率低
  2. 数据库连接的创建和释放比较重复,也造成了系统资源的浪费
  3. 大量硬编码,缺乏灵活性,不利于后期维护
  4. 参数的赋值和数据的封装全是手动进行

可能有些程序员还可以再列出一些 JDBC 的缺点,如果你已经很久没有使用过 JDBC 了,印象已经不深刻了,那么相信下面的代码能勾引起你的些许回忆。

public class Demo {
    public List findAll() {
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        List bookList = null;
        try {
            //加载数据库驱动
            Class.forName("com.mysql.jdbc.Driver");
            //通过驱动管理类获取数据库链接
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test", "root", "123");
            //定义 sql 语句 ?表示占位符
            String sql = "select * from t_book where author = ?";
            //获取预处理 statement
            preparedStatement = connection.prepareStatement(sql);
            //设置参数,第一个参数为 sql 语句中参数的序号(从 1 开始),第二个参数为设置的参数值
            preparedStatement.setString(1, "张三");
            //向数据库发出 sql 执行查询,查询出结果集
            resultSet = preparedStatement.executeQuery();
            //遍历查询结果集
            bookList = new ArrayList<>();
            while (resultSet.next()) {
                Book book = new Book();
                book.setId(resultSet.getInt("id"));
                book.setName(resultSet.getString("bname"));
                book.setAuthor(resultSet.getString("author"));
                book.setPrice(resultSet.getDouble("price"));
                bookList.add(book);
            }
            return bookList;
        } catch (Exception e) {
            e.printStackTrace();
            return null;
        } finally {
//释放资源
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (preparedStatement != null) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}

正是因为 JDBC 存在着各种问题,所以才导致很多持久层框架应运而生,例如:Hibernate和 MyBatis,这两个都是目前比较流行的持久层框架,都对 JDBC 进行了更高级的封装和优化

很多程序员其实都亲自尝试过自己对 JDBC 进行封装和优化,设计并编写过一些 API,每个程序员在做这个事情时,可能设计以及实现的思想都是不一样的,这些思想各有特点,各有千秋,可以分为两大类:
第一类:着重对 JDBC 进行 API 层的抽取和封装,以及功能的增强,典型代表是 Apache 的DbUtils


ORM 思想及相关框架[Mytatis]实现原理_第1张图片
Apache Dbutils

使用 DbUtils 时仍然需要编写 sql 语句并手动进行数据封装,但是 API 的使用比JDBC方便了很多,下面是使用 DbUtils 的代码片段

@Test
public void testQuery() {
    //创建 queryRunner 对象,用来操作 sql 语句
    QueryRunner qr = new QueryRunner(JDBCUtils.getDataSource());
    //编写 sql 语句
    String sql = "select * from user";
    //执行 sql 语句
    try {
        List list = qr.query(sql, new BeanListHandler(User.class));
        System.out.println(list);
    } catch (SQLException e) {
        e.printStackTrace();
    }
}

第二类:借鉴面向对象的思想,让程序员以操作对象的方式操作数据库,无需编写 sql 语句,典型代表是 ORM。ORM(Object Relational Mapping)吸收了面向对象的思想,把对 sql 的操作转换为对象的操作,从而让程序员使用起来更加方便和易于接受。这种转换是通过对象和表之间的元数据映射实现的,这是实现 ORM 的关键,如下图所示


ORM 思想及相关框架[Mytatis]实现原理_第2张图片
ORM思想

由于类和表之间以及属性和字段之间建立起了映射关系,所以,通过 sql 对表的操作就可以转换为对象的操作,程序员从此无需编写 sql 语句,由框架根据映射关系自动生成,这就是 ORM 思想。

目前比较流行的 Hibernate 和 MyBatis 都采用了 ORM 思想,一般我们把 Hibernate 称之为全自动的 ORM 框架,把 MyBatis 称之为半自动的 ORM 框架。使用过这两个框架的程序员,对于 ORM 一定不会陌生。同时,ORM 也是 JPA(SUN 推出的持久层规范)的核心内容

二.ORM 的经典应用:MyBatis

MyBatis框架也应用了ORM思想,一般我们把它称之为半自动的ORM框架,跟Hibernate相比,MyBatis 更加轻量,更加灵活,为了保证这一点,程序员在使用 MyBatis 时需要自己编写 sql 语句,但是 API 的使用依然像 Hibernate 一样简单方便

2.1 MyBatis 案例

1.创建Maven项目 添加依赖

        
        
            mysql
            mysql-connector-java
            5.1.47
        
        
        
        
            org.mybatis
            mybatis
            3.4.5
        
        
        
        
            com.alibaba
            druid
            1.1.12
        
        

2. MyBatis 核心配置文件 SqlMapConfig.xml




    
        
        
    

    
        
            
            
                
                
                
                
            
        
    
    

        
    

3.数据库表和对应实体类

CREATE TABLE `user_info` (
  `id` int(11) NOT NULL AUTO_INCREMENT COMMENT 'id 无意义',
  `username` varchar(16) DEFAULT NULL COMMENT '用户名',
  `password` varchar(20) DEFAULT NULL COMMENT '密码',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
public class UserInfo {
    private Integer id;
    private String username;
    private String password;
    //省略getter setter 方法
}

4. Mapper 接口 UserInfoMapper.java

public interface UserInfoMapper {
    /**
     * 查询所有数据
     * @return 如果找到了,返回泛型为UserInfo的List集合,如果没有找到,就返回null
     */
    List findAll();
}

MyBatis 其实会采用默认规则自动建立表和实体类之间,属性和字段之间的映射关系

5. UserInfo对应的xml配置文件





    

6. 测试

package cn.icanci.test;

import cn.icanci.dao.IUserDao;
import cn.icanci.domain.UserInfo;
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.InputStream;
import java.util.List;

/**
 * @Author: icanci
 * @ProjectName: mybatis
 * @PackageName: cn.icanci.test
 * @Date: Created in 2020/2/20 13:26
 * @ClassAction: 测试
 */
public class Test {
    public static void main(String[] args) throws Exception {
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
        SqlSessionFactory build = builder.build(inputStream);

        SqlSession session = build.openSession();
        IUserDao mapper = session.getMapper(IUserDao.class);
        List all = mapper.findAll();
        all.forEach(System.out::println);
        session.close();
    }
}

打印

UserInfo{id=1, username='haxi', password='456456'}
UserInfo{id=2, username='阿斯顿', password='2134fds'}
UserInfo{id=3, username='kkq', password='asdasd'}
2.2 MyBatis 的 的 ORM 实现原理

通过上述案例来讲解一下 MyBatis 框架是如何应用 ORM 思想的,一起剖析一下 MyBatis 3.x 的内部实现原理

SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");
SqlSessionFactory build = builder.build(inputStream);

这三行代码先是创建了 SqlSessionFactoryBuilder 对象,然后获得了 MyBatis 核心配置文件的输入流,最后调用了 build 方法,我们接下来跟踪一下该方法的源码

/**
 *    Copyright 2009-2016 the original author or authors.
 *
 *    Licensed under the Apache License, Version 2.0 (the "License");
 *    you may not use this file except in compliance with the License.
 *    You may obtain a copy of the License at
 *
 *       http://www.apache.org/licenses/LICENSE-2.0
 *
 *    Unless required by applicable law or agreed to in writing, software
 *    distributed under the License is distributed on an "AS IS" BASIS,
 *    WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *    See the License for the specific language governing permissions and
 *    limitations under the License.
 */
package org.apache.ibatis.session;

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

import org.apache.ibatis.builder.xml.XMLConfigBuilder;
import org.apache.ibatis.exceptions.ExceptionFactory;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.session.defaults.DefaultSqlSessionFactory;

/**
 * Builds {@link SqlSession} instances.
 *
 * @author Clinton Begin
 */
public class SqlSessionFactoryBuilder {

  public SqlSessionFactory build(Reader reader) {
    return build(reader, null, null);
  }

  public SqlSessionFactory build(Reader reader, String environment) {
    return build(reader, environment, null);
  }

  public SqlSessionFactory build(Reader reader, Properties properties) {
    return build(reader, null, properties);
  }

  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        reader.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }

  public SqlSessionFactory build(InputStream inputStream) {
    return build(inputStream, null, null);
  }

  public SqlSessionFactory build(InputStream inputStream, String environment) {
    return build(inputStream, environment, null);
  }

  public SqlSessionFactory build(InputStream inputStream, Properties properties) {
    return build(inputStream, null, properties);
  }

  public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
    try {
      XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
      return build(parser.parse());
    } catch (Exception e) {
      throw ExceptionFactory.wrapException("Error building SqlSession.", e);
    } finally {
      ErrorContext.instance().reset();
      try {
        inputStream.close();
      } catch (IOException e) {
        // Intentionally ignore. Prefer previous error.
      }
    }
  }
    
  public SqlSessionFactory build(Configuration config) {
    return new DefaultSqlSessionFactory(config);
  }
}

在上述源码中

XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);

创建了一个 xml 解析器对象,并在下面

 return build(parser.parse());

对 MyBatis 核心配置文件进行了解析,拿到了数据库连接数据以及映射配置文件中的数据(包括我们编写的 sql 语句和自定义的resultMap)

在Mybatis中,通过读取 Configuration 对象中的数据分别创建了 Environment对象,事务对象,Executor 对象等,并最终直接 new 了一个 DefaultSalSession 对象(SqlSession 接口的实现类),该对象是 MyBatis API 的核心对象

通过代理工厂最终把我们自定义的 Mapper 接口的代理对象创建了出来
当调用代理对象的 getEmpTotalByDept()方法时,框架内部会调用 MapperProxy 的 invoke 方法

public  T getMapper(Class type, SqlSession sqlSession) {
    final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
}

最后 通过调用 queryFromDatabase 方法最终执行了 sql 语句并将查询结果按照自定义的 resultMap 进行了封装

三.自定义一个简单的 ORM 框架

自己定义一个简单的 ORM 框架(名为 MiniORM),希望能通过这种方式亲自零距离的去应用一下 ORM

3.1 MiniORM 框架的结构设计
ORM 思想及相关框架[Mytatis]实现原理_第3张图片
miniORM框架整体设计.

1. 第一层为配置层:
 miniORM.cfg.xml 是框架的核心配置文件,主要用来设置数据库连接信息和映射配置文件路径信息
 Xxx.mapper.xml 是框架的映射配置文件,主要用来设置类和表之间以及属性和字段之间的映射关系
 Xxx.java 是带有映射注解的实体类,主要用来设置类和表之间以及属性和字段之间的映射关系,和 Xxx.mapper.xml 的作用一样,只不过采用的是注解方式,两者二选一

2. 第二层为解析层:
 Dom4jUtil 类用来解析 miniORM.cfg.xml 和 Xxx.mapper.xml 两个配置文件的数据
 AnnotationUtil 类用来解析实体类中的映射注解

3. 第三层为封装层:
 ORMConfig 类用来封装和存储从 miniORM.cfg.xml 文件中解析得到的数据
 Mapper 类用来封装和存储从 Xxx.mapper.xml 或实体类中解析得到的映射数据

4. 第四层为功能层:
 ORMSession 类主要用来从 ORMConfig 和 Mapper 中获取相关数据,然后生成 sql 语句,
最后通过对 JDBC 的封装最终实现增删改查功能

3.2 MiniORM 框架的代码实现

1.pom.xml配置文件



    4.0.0

    cn.icanci
    miniorm
    1.0-SNAPSHOT
    jar

    
        
            org.dom4j
            dom4j
            2.1.1
        
    

    
        
            
                org.apache.maven.plugins
                maven-compiler-plugin
                3.2
                
                    1.8
                    1.8
                    UTF-8
                
            
        
    

2. 自定义三个注解

package cn.icanci.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Author: icanci
 * @ProjectName: miniorm
 * @PackageName: cn.icanci.annotation
 * @Date: Created in 2020/2/20 19:20
 * @ClassAction: 注解 用来设置表明
 */

//运行期间保留注解的信息
@Retention(RetentionPolicy.RUNTIME)
//设置注解用到什么地方
@Target({ElementType.TYPE})
public @interface ORMTable {
    public String name() default "";
}
package cn.icanci.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Author: icanci
 * @ProjectName: miniorm
 * @PackageName: cn.icanci.annotation
 * @Date: Created in 2020/2/20 19:22
 * @ClassAction: 用来设置id
 */

//运行期间保留注解的信息
@Retention(RetentionPolicy.RUNTIME)
//设置注解用到什么地方
@Target({ElementType.FIELD})
public @interface ORMId {
}
package cn.icanci.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * @Author: icanci
 * @ProjectName: miniorm
 * @PackageName: cn.icanci.annotation
 * @Date: Created in 2020/2/20 19:23
 * @ClassAction: 注解 用来设置属性是哪个字段
 */
//运行期间保留注解的信息
@Retention(RetentionPolicy.RUNTIME)
//设置注解用到什么地方
@Target({ElementType.FIELD})
public @interface ORMColumn {
    public String name() default "";
}

3. 解析数据工具类
Dom4jUtil

package cn.icanci.utils;

import java.io.File;
import java.util.*;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

/**
 * 基于dom4j的工具类
 */
public class Dom4jUtil {

    /**
     * 通过文件的路径获取xml的document对象
     *
     * @param path 文件的路径
     * @return 返回文档对象
     */
    public static Document getXMLByFilePath(String path) {
        if (null == path) {
            return null;
        }
        Document document = null;
        try {
            SAXReader reader = new SAXReader();
            document = reader.read(new File(path));
        } catch (Exception e) {
            e.printStackTrace();
        }
        return document;
    }

    /**
     * 获得某文档中某元素内某属性的值和元素的文本信息
     *
     * @param document    xml文档对象
     * @param elementName 元素名
     * @param attrName    属性名
     * @return 返回一个Map集合
     */
    public static Map Elements2Map(Document document, String elementName, String attrName) {
        List propList = document.getRootElement().elements(elementName);
        Map propConfig = new HashMap<>();
        for (Element element : propList) {
            String key = element.attribute(attrName).getValue();
            String value = element.getTextTrim();
            propConfig.put(key, value);
        }
        return propConfig;
    }

    /**
     * 针对mapper.xml文件,获得映射信息并存到Map集合中
     *
     * @param document xml文档对象
     * @return 返回一个Map集合
     */
    public static Map Elements2Map(Document document) {
        Element classElement = document.getRootElement().element("class");
        Map mapping = new HashMap<>();

        Element idElement = classElement.element("id");
        String idKey = idElement.attribute("name").getValue();
        String idValue = idElement.attribute("column").getValue();
        mapping.put(idKey, idValue);

        List propElements = classElement.elements("property");
        for (Element element : propElements) {
            String propKey = element.attribute("name").getValue();
            String propValue = element.attribute("column").getValue();
            mapping.put(propKey, propValue);
        }
        return mapping;
    }

    /**
     * 针对mapper.xml文件,获得主键的映射信息并存到Map集合中
     *
     * @param document xml文档对象
     * @return 返回一个Map集合
     */
    public static Map ElementsID2Map(Document document) {
        Element classElement = document.getRootElement().element("class");
        Map mapping = new HashMap<>();

        Element idElement = classElement.element("id");
        String idKey = idElement.attribute("name").getValue();
        String idValue = idElement.attribute("column").getValue();
        mapping.put(idKey, idValue);

        return mapping;
    }

    /**
     * 获得某文档中某元素内某属性的值
     *
     * @param document    xml文档对象
     * @param elementName 元素名
     * @param attrName    属性名
     * @return 返回一个Set集合
     */
    public static Set Elements2Set(Document document, String elementName, String attrName) {
        List mappingList = document.getRootElement().elements(elementName);
        Set mappingSet = new HashSet<>();
        for (Element element : mappingList) {
            String value = element.attribute(attrName).getValue();
            mappingSet.add(value);
        }
        return mappingSet;
    }

    /**
     * 获得某文档中某元素内某属性的值
     *
     * @param document    xml文档对象
     * @param elementName 元素名
     * @param attrName    属性名
     * @return 返回一个Set集合
     */
    public static String getPropValue(Document document, String elementName, String attrName) {
        Element element = (Element) document.getRootElement().elements(elementName).get(0);
        return element.attribute(attrName).getValue();
    }
}

AnnotationUtil

package cn.icanci.utils;

import cn.icanci.annotation.ORMColumn;
import cn.icanci.annotation.ORMId;
import cn.icanci.annotation.ORMTable;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.lang.reflect.Field;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;

/**
 * 使用反射解析实体类中注解的工具类
 */
public class AnnotationUtil {

    /**
     * 得到的类名
     */
    public static String getClassName(Class clz) {
        return clz.getName();
    }

    /**
     * 得到ORMTable注解中的表名
     */
    public static String getTableName(Class clz) {
        if (clz.isAnnotationPresent(ORMTable.class)) {
            ORMTable ormTable = (ORMTable) clz.getAnnotation(ORMTable.class);
            return ormTable.name();
        } else {
            System.out.println("缺少ORMTable注解");
            return null;
        }
    }

    /**
     * 得到主键属性和对应的字段
     */
    public static Map getIdMapper(Class clz) {
        boolean flag = true;
        Map map = new HashMap<>();
        Field[] fields = clz.getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(ORMId.class)) {
                flag = false;
                String fieldName = field.getName();
                if (field.isAnnotationPresent(ORMColumn.class)) {
                    ORMColumn ormColumn = field.getAnnotation(ORMColumn.class);
                    String columnName = ormColumn.name();
                    map.put(fieldName, columnName);
                    break;
                } else {
                    System.out.println("缺少ORMColumn注解");
                }
            }
        }
        if (flag) {
            System.out.println("缺少ORMId注解");
        }
        return map;
    }

    /**
     * 得到类中所有属性和对应的字段
     */
    public static Map getPropMapping(Class clz) {
        Map map = new HashMap<>();
        map.putAll(getIdMapper(clz));
        Field[] fields = clz.getDeclaredFields();
        for (Field field : fields) {
            if (field.isAnnotationPresent(ORMColumn.class)) {
                ORMColumn ormColumn = field.getAnnotation(ORMColumn.class);
                String fieldName = field.getName();
                String columnName = ormColumn.name();
                map.put(fieldName, columnName);
            }
        }
        return map;
    }

    /**
     * 获得某包下面的所有类名
     */
    public static Set getClassNameByPackage(String packagePath) {
        Set names = new HashSet<>();
        String packageFile = packagePath.replace(".", "/");
        String classpath = Thread.currentThread().getContextClassLoader().getResource("").getPath();
        if (classpath == null) {
            classpath = Thread.currentThread().getContextClassLoader().getResource("/").getPath();
        }
        try {
            classpath = java.net.URLDecoder.decode(classpath, "utf-8");
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
        }
        File dir = new File(classpath + packageFile);
        if (dir.exists()) {
            File[] files = dir.listFiles();
            for (File f : files) {
                String name = f.getName();
                if (f.isFile() && name.endsWith(".class")) {
                    name = packagePath + "." + name.substring(0, name.lastIndexOf("."));
                    names.add(name);
                }
            }
        } else {
            System.out.println("包路径不存在");
        }
        return names;
    }
}

4. miniORM.cfg.xml 是框架的核心配置文件,主要用来设置数据库连接信息和映射配置文件路径信息,源码如下所示:




    jdbc:mysql://localhost:3306/test
    com.mysql.jdbc.Driver
    root
    ok
    
    

5.Mapper用来封装和存储映射信息
package cn.icanci.core;

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

/**
 * @Author: icanci
 * @ProjectName: miniorm
 * @PackageName: cn.icanci.core
 * @Date: Created in 2020/2/20 19:39
 * @ClassAction: 用来封装和存储映射信息
 */
public class Mapper {
    /**
     * 类名
     */
    private String className;

    /**
     * 表名
     */
    private String tableName;

    /**
     * 主键信息
     */
    private Map idMapper = new HashMap<>();

    /**
     * 普通属性和字段信息
     */
    private Map propMapper = new HashMap<>();

    @Override
    public String toString() {
        return "Mapper{" +
                "className='" + className + '\'' +
                ", tableName='" + tableName + '\'' +
                ", idMapper=" + idMapper +
                ", propMapper=" + propMapper +
                '}';
    }
}
6.ORMConfig 用来解析和封装框架的配置文件
package cn.icanci.core;

import cn.icanci.utils.AnnotationUtil;
import cn.icanci.utils.Dom4jUtil;
import org.dom4j.Document;

import java.io.File;
import java.net.URLDecoder;
import java.sql.Connection;
import java.sql.DriverManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @Author: icanci
 * @ProjectName: miniorm
 * @PackageName: cn.icanci.core
 * @Date: Created in 2020/2/20 19:45
 * @ClassAction: 用来解析和封装框架的配置文件
 */
public class ORMConfig {
    /**
     * classpath 路径
     */
    private static String classpath;

    /**
     * 核心配置文件
     */
    private static File cfgFile;

    /**
     * 标签中的数据
     */
    private static Map propConfig;

    /**
     * 映射文件路径
     */
    private static Set mappingSet;

    /**
     * 实体类
     */
    private static Set entitySet;

    /**
     * 映射信息
     */
    public static List mapperList;

    static {
        //得到的classpath路径
        classpath=Thread.currentThread().getContextClassLoader().getResource("").getPath();
        //针对中文路径进行转码
        try {
            classpath = URLDecoder.decode(classpath, "utf-8");
        }catch (Exception e){
            e.printStackTrace();
        }
        //得到核心配置文件
        System.out.println(classpath);
        cfgFile=new File(classpath + "miniORM.cfg.xml");
        if(cfgFile.exists()){
            // 解析核心配置文件中的数据
            Document document=Dom4jUtil.getXMLByFilePath(cfgFile.getPath());
            propConfig=Dom4jUtil.Elements2Map(document,"property","name");
            mappingSet=Dom4jUtil.Elements2Set(document,"mapping","resource");
            entitySet=Dom4jUtil.Elements2Set(document,"entity","package");
        }
        else {
            cfgFile = null;
            System.out.println("未找到核心配置文件miniORM.cfg.xml");
        }

    }

    //从propConfig集合中获取数据并连接数据库
    private Connection getConnection() throws  Exception{
        String url=propConfig.get("connection.url");
        String driverClass=propConfig.get("connection.driverClass");
        String username=propConfig.get("connection.username");
        String password=propConfig.get("connection.password");

        Class.forName(driverClass);
        Connection connection=DriverManager.getConnection(url,username,password);
        connection.setAutoCommit(true);
        return  connection;
    }

    private void getMapping () throws  Exception{

        mapperList = new ArrayList<>();

        //1. 解析xxx.mapper.xml文件拿到映射数据
        for (String xmlPath:mappingSet){
            Document document=Dom4jUtil.getXMLByFilePath(classpath+xmlPath);
            String className=Dom4jUtil.getPropValue(document,"class","name");
            String tableName=Dom4jUtil.getPropValue(document,"class","table");
            Map id_id=Dom4jUtil.ElementsID2Map(document);
            Map mapping = Dom4jUtil.Elements2Map(document);

            Mapper mapper=new Mapper();
            mapper.setTableName(tableName);
            mapper.setClassName(className);
            mapper.setIdMapper(id_id);
            mapper.setPropMapper(mapping);

            mapperList.add(mapper);

        }

        //2. 解析实体类中的注解拿到映射数据
        for(String packagePath:entitySet){
            Set nameSet=AnnotationUtil.getClassNameByPackage(packagePath);
            for(String name:nameSet){
                Class clz=Class.forName(name);
                String className=AnnotationUtil.getClassName(clz);
                String tableName=AnnotationUtil.getTableName(clz);
                Map id_id=AnnotationUtil.getIdMapper(clz);
                Map mapping=AnnotationUtil.getPropMapping(clz);

                Mapper mapper=new Mapper();
                mapper.setTableName(tableName);
                mapper.setClassName(className);
                mapper.setIdMapper(id_id);
                mapper.setPropMapper(mapping);

                mapperList.add(mapper);
            }
        }

    }


    //创建ORMSession对象
    public ORMSession buildORMSession() throws  Exception{
        //1. 连接数据库
        Connection connection=getConnection();

        //2. 得到映射数据
        getMapping();

        //3. 创建ORMSession对象
        return  new ORMSession(connection);

    }
}
7. ORMSession 实现增删查
package cn.icanci.core;

import java.lang.reflect.Field;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

/**
 * @Author: icanci
 * @ProjectName: miniorm
 * @PackageName: cn.icanci.core
 * @Date: Created in 2020/2/20 21:33
 * @ClassAction:
 */
//该类生成sql并实现增删改查功能
public class ORMSession {

    private Connection connection;

    public ORMSession(Connection conn) {
        this.connection = conn;
    }

    //保存数据
    public void save(Object entity) throws Exception {
        String insertSQL = "";

        //1. 从ORMConfig中获得保存有映射信息的集合
        List mapperList = ORMConfig.mapperList;

        //2. 遍历集合,从集合中找到和entity参数相对应的mapper对象
        for (Mapper mapper : mapperList) {
            if (mapper.getClassName().equals(entity.getClass().getName())) {
                String tableName = mapper.getTableName();
                String insertSQL1 = "insert into " + tableName + "( ";
                String insertSQL2 = " ) values ( ";

                //3. 得到当前对象所属类中的所有属性
                Field[] fields = entity.getClass().getDeclaredFields();
                for (Field field : fields) {
                    field.setAccessible(true);
                    //4. 遍历过程中根据属性得到字段名
                    String columnName = mapper.getPropMapper().get(field.getName());
                    //5. 遍历过程中根据属性得到它的值
                    String columnValue = field.get(entity).toString();
                    //6. 拼接sql语句
                    insertSQL1 += columnName + ",";
                    insertSQL2 += "'" + columnValue + "',";
                }
                insertSQL = insertSQL1.substring(0, insertSQL1.length() - 1) + insertSQL2.substring(0, insertSQL2.length() - 1) + " )";
                break;
            }
        }

        // 把sql语句打印到控制台
        System.out.println("MiniORM-save: " + insertSQL);

        //7. 通过JDBC发送并执行sql
        PreparedStatement statement = connection.prepareStatement(insertSQL);
        statement.executeUpdate();
        statement.close();
    }

    //根据主键进行数据删除  delete from  表名  where 主键 = 值
    public void delete(Object entity) throws Exception {
        String delSQL = "delete from ";

        //1. 从ORMConfig中获得保存有映射信息的集合
        List mapperList = ORMConfig.mapperList;

        //2. 遍历集合,从集合中找到和entity参数相对应的mapper对象
        for (Mapper mapper : mapperList) {
            if (mapper.getClassName().equals(entity.getClass().getName())) {
                // 3. 得到我们想要的mapper对象,并得到表名
                String tableName = mapper.getTableName();
                delSQL += tableName + " where ";
                // 4. 得到主键的字段名和属性名
                Object[] idProp = mapper.getIdMapper().keySet().toArray(); //idProp[0]
                Object[] idColumn = mapper.getIdMapper().values().toArray(); //idColumn[0]

                // 5. 得到主键的值
                Field field = entity.getClass().getDeclaredField(idProp[0].toString());
                field.setAccessible(true);
                String idVal = field.get(entity).toString();

                // 6. 拼接sql
                delSQL += idColumn[0].toString() + " = " + idVal;
                // 把sql语句打印到控制台
                System.out.println("MiniORM-delete: " + delSQL);

                break;
            }

        }
        //7. 通过JDBC发送并执行sql
        PreparedStatement statement = connection.prepareStatement(delSQL);
        statement.executeUpdate();
        statement.close();
    }

    // 根据主键进行查询  select * from 表名 where  主键字段 = 值
    public Object findOne(Class clz, Object id) throws  Exception{

        String querySQL = "select * from ";

        //1. 从ORMConfig中得到存有映射信息的集合
        List mapperList=ORMConfig.mapperList;

        //2. 遍历集合拿到我们想要的mapper对象
        for (Mapper mapper : mapperList) {
            if (mapper.getClassName().equals(clz.getName())) {

                // 3. 获得表名
                String tableName = mapper.getTableName();

                //4. 获得主键字段名
                Object[] idColumn = mapper.getIdMapper().values().toArray(); //idColumn[0]

                //5. 拼接sql
                querySQL += tableName + " where " + idColumn[0].toString() + " = " + id;

                break;
            }
        }

        System.out.println("MiniORM-findOne:" +querySQL);

        //6. 通过jdbc发送并执行sql, 得到结果集
        PreparedStatement statement=connection.prepareStatement(querySQL);
        ResultSet rs=statement.executeQuery();

        //7. 封装结果集,返回对象
        if(rs.next()){
            // 查询到一行数据
            // 8.创建一个对象,目前属性的值都是初始值
            Object obj=clz.newInstance();
            // 9. 遍历mapperList集合找到我们想要的mapper对象
            for(Mapper mapper:mapperList){
                if (mapper.getClassName().equals(clz.getName())) {
                    //10. 得到存有属性-字段的映射信息
                    Map propMap = mapper.getPropMapper();
                    //11. 遍历集合分别拿到属性名和字段名
                    Set keySet = propMap.keySet();
                    for(String prop:keySet){  //prop就是属性名
                        String column = propMap.get(prop);   //column就是和属性对应的字段名
                        Field field = clz.getDeclaredField(prop);
                        field.setAccessible(true);
                        field.set(obj,rs.getObject(column));
                    }
                    break;
                }
            }
            //12. 释放资源
            statement.close();
            rs.close();

            //13. 返回查询出来的对象
            return obj;

        }else {
            // 没有查到数据
            return null;
        }

    }

    //关闭连接,释放资源
    public void close() throws  Exception{
        if(connection!=null){
            connection.close();
            connection = null;
        }
    }

}

执行测试

1.测试保存
@org.junit.Test
public void test1() throws Exception {
    ormConfig = new cn.icanci.core.ORMConfig();
    System.out.println(ormConfig);
    UserInfo userInfo = new UserInfo();
    userInfo.setUsername("哈希哈系");
    userInfo.setPassword("demodemo");
    System.out.println(userInfo);
    cn.icanci.core.ORMSession ormSession = ormConfig.buildORMSession();
    ormSession.save(userInfo);
    ormSession.close();
}
ORM 思想及相关框架[Mytatis]实现原理_第4张图片
测试通过
2.测试查询
@org.junit.Test
public void test2() throws Exception {
    ormConfig = new cn.icanci.core.ORMConfig();
    System.out.println(ormConfig);
    cn.icanci.core.ORMSession ormSession = ormConfig.buildORMSession();
    UserInfo one = (UserInfo) ormSession.findOne(UserInfo.class, 2);
    System.out.println(one);
    ormSession.close();
}
测试通过
3.测试删除
@org.junit.Test
public void test3() throws Exception {
    ormConfig = new cn.icanci.core.ORMConfig();
    System.out.println(ormConfig);
    cn.icanci.core.ORMSession ormSession = ormConfig.buildORMSession();
    UserInfo one = (UserInfo) ormSession.findOne(UserInfo.class, 2);
    ormSession.delete(one);
    ormSession.close();
}
ORM 思想及相关框架[Mytatis]实现原理_第5张图片
测试通过

ORM 思想及相关框架[Mytatis]实现原理_第6张图片
测试通过

你可能感兴趣的:(ORM 思想及相关框架[Mytatis]实现原理)