手写MyBatis底层机制

手写MyBatis底层机制

读取配置文件,得到数据库连接

思路

  1. 引入必要的依赖
  2. 需要写一个自己的config.xml文件,在里面配置一些信息,driver,url ,password,username
  3. 需要编写Configuration类,对 自己的config.xml文件 进行解析,得到一个数据库连接

实现

  • 引入必要的依赖
<dependencies>
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.49</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
        </dependency>
    </dependencies>
  • 需要写一个自己的config.xml文件,在里面配置一些信息,driver,url ,password,username
<?xml version="1.0" encoding="UTF-8" ?>
<database>
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://localhost:3306/hsp_mybatis?
        useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
    <property name="username" value="root"/>
    <property name="password" value="zy"/>
</database>
  • 需要编写Configuration类,对 自己的config.xml文件 进行解析,得到一个数据库连接
public class ZyConfiguration {

    //属性 类加载器
    private ClassLoader classLoader =
            ClassLoader.getSystemClassLoader();

    //读取xml文件信息并处理
    public Connection build(String resource) {
        Connection connection = null;
        
        //加载配置文件,获取对应的InputStream流
        InputStream resourceAsStream =
                classLoader.getResourceAsStream(resource);

        //解析xml文件
        SAXReader reader = new SAXReader();
        try {
            Document document = reader.read(resourceAsStream);
            Element root = document.getRootElement();

            //解析rootElement
            System.out.println("root= "+root);
            return evalDataSource(root);

        } catch (DocumentException e) {
            throw new RuntimeException(e);
        }

    }


    //解析xml文件 并返回一个连接
    private Connection evalDataSource(Element node) {
        Iterator property = node.elementIterator("property");
        String driverClassName = null;
        String url = null;
        String username = null;
        String password = null;

        //遍历node子节点 获取属性值
        while(property.hasNext()){
            Element pro = (Element)property.next();
            String name = pro.attributeValue("name");
            String value = pro.attributeValue("value");

            //判断是否得到了name 和 value
            if (name == null || value == null){
                throw new RuntimeException("property 节点没有设置name 或 value属性");
            }
            switch (name){
                case "driverClassName":
                    driverClassName = value;
                    break;
                case "url":
                    url = value;
                    break;
                case "username":
                    username = value;
                    break;
                case "password":
                    password = value;
                    break;
                default:
                    throw new RuntimeException("属性名没有匹配到");
            }

        }
        Connection connection = null;
        try {
            Class.forName(driverClassName);
            connection = DriverManager.getConnection(url, username, password);

        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return connection;
    }

}

编写执行器,输入SQL语句,完成操作

思路

  1. 需要写一个实体类,对应monster表
  2. 编写接口executor
  3. 实现接口,编写自己的执行器
  4. 需要一个 自己的Configuration类 返回连接,通过连接对数据库进行操作

实现

  • 需要写一个实体类,对应monster表
@Setter
@Getter
@ToString
@NoArgsConstructor
@AllArgsConstructor
public class Monster {
    private Integer id;
    private Integer age;
    private String name;
    private String email;
    private Date birthday;
    private double salary;
    private Integer gender;
}
  • 编写接口executor
public interface Executor {
    public <T> T query(String statement,Object parameter);
}

  • 实现接口,编写自己的执行器
public class ZyExecutor implements Executor{

    private ZyConfiguration zyConfiguration = new ZyConfiguration();

    @Override
    public <T> T query(String sql, Object parameter) {
        Connection connection = getConnection();

        //查询返回的结果集
        ResultSet set = null;
        PreparedStatement pre = null;

        try {
            pre = connection.prepareStatement(sql);
            //设置参数,如果参数多,用数组处理
            pre.setString(1, parameter.toString());
            set = pre.executeQuery();

            //把set数据封装到对象 -- monster
            Monster monster = new Monster();//简化处理 认为返回的结果就是一个monster记录

            //遍历结果集
            while(set.next()){
                monster.setId(set.getInt("id"));
                monster.setName(set.getString("name"));
                monster.setEmail(set.getString("email"));
                monster.setAge(set.getInt("age"));
                monster.setGender(set.getInt("gender"));
                monster.setBirthday(set.getDate("birthday"));
                monster.setSalary(set.getDouble("salary"));
            }
            return (T)monster;

        } catch (SQLException e) {
            throw new RuntimeException(e);
        }finally {
            try {
                if (set != null) {
                    set.close();
                }
                if (pre != null) {
                    pre.close();
                }
                if (connection != null) {
                    connection.close();
                }
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }

        
    }

    public Connection getConnection(){//Configuration类 返回连接,通过连接对数据库进行操作
       return zyConfiguration.build("zy_mybatis.xml");
    }
}
  • 需要一个 自己的Configuration类 返回连接,通过连接对数据库进行操作

将Sqlsession封装到执行器

思路

  1. 需要写自己的Sqlsession类,它是搭建连接和执行器之间的桥梁,里面封装有 执行器 和 配置文件 以及 操作DB 的具体方法
  2. 写一个selectOne方法 ,SelectOne() 返回一条记录,一条记录对应一个Monster对象

实现

  • 需要写自己的Sqlsession类,它是搭建连接和执行器之间的桥梁,里面封装有 执行器 和 配置文件 以及 操作DB 的具体方法
public class ZySqlSession {//搭建连接和执行器之间的桥梁

    //执行器
    private Executor executor = new ZyExecutor();

    //配置
    private ZyConfiguration zyConfiguration = new ZyConfiguration();

    //操作DB 的具体方法
    //SelectOne 返回一条记录-对象
    public <T> T selectOne(String statement,Object parameter){
        return executor.query(statement,parameter);
    }
}
  • 写一个selectOne方法 ,SelectOne() 返回一条记录,一条记录对应一个Monster对象
//操作DB 的具体方法
    //SelectOne 返回一条记录-对象
    public <T> T selectOne(String statement,Object parameter){
        return executor.query(statement,parameter);
    }

开发Mapper接口和Mapper.xml

思路

  1. 编写MonsterMapper接口,里面有方法getMonsterById(Integer id)根据id返回一个monster对象
  2. 在resources下编写对应的monsterMapper.xml(简化:因为在resources 编译时会在类路径下比较好写)
  3. monsterMapper.xml 编写具体的sql语句,并指定语句类型,id,resultType(和原生Mybatis一样)

实现

  • 编写MonsterMapper接口,里面有方法getMonsterById(Integer id)根据id返回一个monster对象
public interface MonsterMapper {
    public Monster getMonsterById(Integer id);
}
  • 在resources下编写对应的monsterMapper.xml(简化:因为在resources 编译时会在类路径下比较好写)
  • monsterMapper.xml 编写具体的sql语句,并指定语句类型,id,resultType(和原生Mybatis一样)
<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="com.code_study.mapper.MonsterMapper">
    <!--    实现配置接口方法 getMonsterById-->
    <select id="getMonsterById" resultType="com.code_study.entity.Monster">
        SELECT * FROM monster WHERE id = ?
    </select>
</mapper>

开发MapperBean,可以和Mapper接口相映射

思路

  1. 开发 Function类 ,用于记录对应的Mapper的方法信息,比如sql类型,方法名,执行的sql语句,返回类型,入参类型
  2. 开发 MapperBean类,记录接口信息和接口下的所有方法
  3. Function类 对应 monsterMapper.xml中的信息
  4. MapperBean类 对应 MonsterMapper 接口中的信息

实现

  • 开发 Function类 ,用于记录对应的Mapper的方法信息,比如sql类型,方法名,执行的sql语句,返回类型,入参类型
//对应 monsterMapper.xml中的信息
public class Function {
    private String sqlType;//sql类型,比如select,insert,update,delete
    private String funcName;//方法名
    private String sql;//执行的sql语句
    private Object resultType;//返回类型
    private String parameterType;//入参类型
}
  • 开发 MapperBean类,记录接口信息和接口下的所有方法
//对应 MonsterMapper 接口中的信息
public class MapperBean {
    private String interfaceName;//接口名

    //    接口下的所有方法
    private List<Function> functions;
}
  • Function类 对应 monsterMapper.xml中的信息
  • MapperBean类 对应 MonsterMapper 接口中的信息

在Configuration中解析MapperXML获取MapperBean对象

思路

  1. 在Configuration 添加方法readMapper(String path)
  2. 通过 path 读取接口对应的Mapper方法
  3. 保存接口下所有的方法信息
  4. 封装成 MapperBean对象

实现

  • 在Configuration 添加方法readMapper(String path)
  • 通过 path 读取接口对应的Mapper方法
  • 保存接口下所有的方法信息
  • 封装成 MapperBean对象
 //解析MapperXML获取MapperBean对象
    //path = xml的路径+文件名 是从类的加载路径计算的(如果放在resource目录下 之间传xml文件名即可)
    public MapperBean readMapper(String path) {
        MapperBean mapperBean = new MapperBean();

        InputStream resourceAsStream = classLoader.getResourceAsStream(path);
        SAXReader reader = new SAXReader();
        try {
            Document document = reader.read(resourceAsStream);
            Element root = document.getRootElement();

            String namespace = root.attributeValue("namespace");
            mapperBean.setInterfaceName(namespace);

            List<Function> list = new ArrayList<>();//保存接口下所有的方法信息

            //得到root的迭代器
            Iterator iterator = root.elementIterator();
            while(iterator.hasNext()){
                Element e = (Element)iterator.next();
                String sqlType = e.getName().trim();
                String sql = e.getText().trim();
                String funcName = e.attributeValue("id");
                String resultType = e.attributeValue("resultType");

                //ResultType 返回的是一个Object对象 ->反射
                Object instance = Class.forName(resultType).newInstance();

                //封装 function 对象
                Function function = new Function();
                function.setSql(sql);
                function.setSqlType(sqlType);
                function.setFuncName(funcName);
                function.setResultType(instance);

                //将封装好的function对象 放入 list中
                list.add(function);


                mapperBean.setFunctions(list);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return mapperBean;
    }

动态代理Mapper方法

思路

  1. 在SqlSession中添加方法 getMapper 输入一个Class类型,返回mapper的动态代理对象
  2. 编写动态代理类 实现 InvocationHandler 接口
  3. 取出mapperBean的functions 遍历
  4. 判断 当前要执行的方法和function.getFunctionName是否一致
  5. 调用方法返回 动态代理对象
  6. 编写SqlSessionFactory 会话工厂,可以返回SqlSession

实现

  • 编写动态代理类 实现 InvocationHandler 接口

  • 在SqlSession中添加方法 getMapper 输入一个Class类型,返回mapper的动态代理对象

 //返回mapper的动态代理对象
    public <T> T getMapper(Class<T> clazz){
        return (T) Proxy.newProxyInstance(
                clazz.getClassLoader(),
                new Class[]{clazz},
                new ZyMapperProxy(zyConfiguration,clazz,this));
    }
  • 取出mapperBean的functions 遍历
  • 判断 当前要执行的方法和function.getFunctionName是否一致
  • 调用方法返回 动态代理对象
public class ZyMapperProxy implements InvocationHandler {
    private ZySqlSession zySqlSession;
    private String mapperFile;
    private ZyConfiguration zyConfiguration;

    public ZyMapperProxy(ZySqlSession zySqlSession, Class clazz, ZyConfiguration zyConfiguration) {
        this.zySqlSession = zySqlSession;
        this.zyConfiguration = zyConfiguration;
        this.mapperFile = clazz.getSimpleName() + ".xml";
    }

    //当执行Mapper接口的代理对象方法时,会执行到invoke方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        MapperBean mapperBean = zyConfiguration.readMapper(this.mapperFile);

        //判断是否为当前xml文件对应的接口
        if (!method.getDeclaringClass().getName().equals(mapperBean.getInterfaceName())){
            return null;
        }

        //取出mapperBean的functions
        List<Function> functions = mapperBean.getFunctions();
        //判断当前的mapperBean 解析对应的MapperXML后,有方法
        if (null != functions || 0 != functions.size()){
            for (Function function : functions) {
                //当前要执行的方法和function.getFunctionName
                if (method.getName().equals(function.getFuncName())){
                    if ("SELECT".equalsIgnoreCase(function.getSqlType())){
                        return zySqlSession.selectOne(function.getSql(),String.valueOf(args[0]));
                    }
                }
            }
        }

        return null;
    }
}
  • 编写SqlSessionFactory 会话工厂,可以返回SqlSession
public class ZySqlSessionFactory {
    
    public static ZySqlSession open(){
        return new ZySqlSession();
    }
}

测试

@Test
public void openSession(){
    ZySqlSession zySqlSession = ZySqlSessionFactory.openSession();
    System.out.println("zySqlSession= "+zySqlSession);
    MonsterMapper mapper = zySqlSession.getMapper(MonsterMapper.class);
    Monster monster = mapper.getMonsterById(1);
    System.out.println("monster= "+monster);
}
posted @ 2024-05-07 09:49  zy2596  阅读(122)  评论(0编辑  收藏  举报