MyBatis进阶(一)自定义持久层框架

一.自定义框架设计

(一)使用设计

1. 创建数据库及user表

 2. 创建pojo.User类,代码示例请点击

3. 创建核心配置文件

resources.sqlMapConfig.xml: 配置数据源信息,链接UserMapper.xml。 代码示例请点击

resources.UserMapper.xml: 配置要执行的SQL语句。代码示例请点击

4.创建dao层IUserDao接口,代码示例请点击

(二)框架设计

1.加载读取使用端配置文件-准备阶段

使用Resources类InputSteam getResourceAsSteam(String path)将配置文件加载成字节输入流,并创建两个JavaBean用来存储配置文件输入流信息。

(1)创建io.Resources类,代码示例请点击

(2)创建pojo.Configuration类 使用此JavaBean用来存储数据源相关信息及封装MappedStatement的SQL语句集合。代码示例请点击

(3)创建pojo.MappedStatement类,使用此JavaBean用来封装配置文件中单条SQL语句及输入、输出参数类型。代码示例请点击

2.解析配置文件-构建阶段

创建sqlSessionFactoryBuilder构建类,在此类中创建sqlSessionFactory   build(InputSteam in)方法,调用xmlConfigBuilder类的parseConfig(in)方法将dom4j解析出来的数据源配置文件信息封装到pojo.Configuration中,再在xmlConfigBuilder类中调用XMLMapperBuilder类,用来将SQL执行语句及输入输出参数类型封装到pojo.MappedStatement中,最后将.MappedStatement集合封装到pojo.Configuration中。

(1)创建sqlSession.sqlSessionFactoryBuilder类,代码示例请点击

(2)创建config.xmlConfigBuilder类,代码示例请点击

(3)创建config.XMLMapperBuilder类,代码示例请点击

3. 创建SqlSessionFactory工厂类,并生产sqlSession-生产阶段,代码示例点击

(1)创建SqlSessionFactory接口及实现类DefaultSqlSessionFacotry,使用openSession() 方法: 获取sqlSession接⼝的实现类DefaultSqlSession实例对象,也就是生产sqlSession。

(2)在DefaultSqlSession类中使用JDK动态代理模式,访问sqlSession.getMapper方法时,生成代理对象,实际访问:

new InvocationHandler()内部类中的invoke方法

在invoke方法中,通过判断选择要去执行哪条SQL语句对应的方法。这里使用动态代理机制,可以不用再写IUserDao实现类,如下图:

4.创建Executor接口及实现类SimpleExecutor实现类。

通过query()方法,执行底层JDBC代码

 (1)常规的JDBC代码执行如下:

public static void main(String[] args) {
    Connection connection = null;
    PreparedStatement preparedStatement = null;
    ResultSet resultSet = null;
    try {
        // 加载数据库驱动
        Class.forName("com.mysql.jdbc.Driver");
        // 通过驱动管理类获取数据库链接
        connection = DriverManager.getConnection("jdbc:mysql://localhost:3306    /mybatis?characterEncoding=utf-8", "root", "root");
        // 定义sql语句?表示占位符
        String sql = "select * from user where username = ?";
        // 获取预处理statement
        preparedStatement = connection.prepareStatement(sql);
        // 设置参数,第⼀个参数为sql语句中参数的序号(从1开始),第⼆个参数为设置的参数值
        preparedStatement.setString(1, "tom");
        // 向数据库发出sql执⾏查询,查询出结果集
        resultSet = preparedStatement.executeQuery();
        // 遍历查询结果集
        while (resultSet.next()) {
            int id = resultSet.getInt("id");
            String username = resultSet.getString("username");
            // 封装User
            user.setId(id);
            user.setUsername(username);
        }
        System.out.println(user);
    } catch (Exception e) {
        e.printStackTrace();
    } 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问题总结:

原始jdbc开发存在的问题如下:

1  数据库连接创建、释放频繁造成系统资源浪费,从⽽影响系统性能。

2  Sql语句在代码中硬编码,造成代码不易维护,实际应⽤中sql变化的可能较⼤,   sql变动需要改变java代码。

3  使⽤preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不⼀定,可能 多也可能 少,修改sql还要修改代码,系统不易维护。

4  对结果集解析存在硬编码(查询列名) sql变化导致解析代码变化,系统不易维护,如果能将数据 库记录封装成 pojo对象解析⽐较⽅便

(2)在生产出来的一个个的DefaultSqlSession中,我们将调用

 simpleExecutor.query(configuration, mappedStatement, params)方法来执行底层的JDBC代码。

Executor接口代码如下:

package com.lagou.sqlSession;

import com.lagou.pojo.Configuration;
import com.lagou.pojo.MappedStatement;

import java.util.List;

public interface Executor {

    public <E> List<E> query(Configuration configuration,MappedStatement mappedStatement,Object... params) throws Exception;

}

SimpleExecutor实现类代码如下:

package com.lagou.sqlSession;


import com.lagou.config.BoundSql;
import com.lagou.pojo.Configuration;
import com.lagou.pojo.MappedStatement;
import com.lagou.utils.GenericTokenParser;
import com.lagou.utils.ParameterMapping;
import com.lagou.utils.ParameterMappingTokenHandler;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.util.ArrayList;
import java.util.List;

public class simpleExecutor implements  Executor {


    @Override                                                                                //user
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        // 1. 注册驱动,获取连接
        Connection connection = configuration.getDataSource().getConnection();

        // 2. 获取sql语句 : select * from user where id = #{id} and username = #{username}
            //转换sql语句: select * from user where id = ? and username = ? ,转换的过程中,还需要对#{}里面的值进行解析存储
        String sql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSql(sql);

        // 3.获取预处理对象:preparedStatement
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());

        // 4. 设置参数
            //获取到了参数的全路径
         String paramterType = mappedStatement.getParamterType();
         //调用getClassType方法
         Class<?> paramtertypeClass = getClassType(paramterType);

        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String content = parameterMapping.getContent();

            //反射
            //获取指定名称的成员变量,不受修饰符限制
            Field declaredField = paramtertypeClass.getDeclaredField(content);
            //防止私有变量,开放该成员变量的访问权限,暴力访问
            declaredField.setAccessible(true);
            //通过参数对象获取指定的成员变量的值,o代表成员变量的值,declaredField代表成员变量,params[0]表示该成员变量所属的实例化对象
            Object o = declaredField.get(params[0]);
            //开始设置参数,i从0开始,因此用i+1
            preparedStatement.setObject(i+1,o);

        }


        // 5. 执行sql
        ResultSet resultSet = preparedStatement.executeQuery();
        String resultType = mappedStatement.getResultType();
        Class<?> resultTypeClass = getClassType(resultType);

        ArrayList<Object> objects = new ArrayList<>();

        // 6. 封装返回结果集
        while (resultSet.next()){
            //实例化反射的对象
            Object o =resultTypeClass.newInstance();
            //元数据
            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {

                // 字段名
                String columnName = metaData.getColumnName(i);
                // 字段的值
                Object value = resultSet.getObject(columnName);

                //使用反射或者内省,根据数据库表和实体的对应关系,完成封装
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(o,value);


            }
            objects.add(o);

        }
            return (List<E>) objects;

    }

    private Class<?> getClassType(String paramterType) throws ClassNotFoundException {
        if(paramterType!=null){
            Class<?> aClass = Class.forName(paramterType);
            return aClass;
        }
         return null;

    }


    /**
     * 完成对#{}的解析工作:1.将#{}使用?进行代替,2.解析出#{}里面的值进行存储
     * @param sql
     * @return
     */
    private BoundSql getBoundSql(String sql) {
        //标记处理类:配置标记解析器来完成对占位符的解析处理工作
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{", "}", parameterMappingTokenHandler);
        //解析出来的sql
        String parseSql = genericTokenParser.parse(sql);
        //#{}里面解析出来的参数名称
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();

        BoundSql boundSql = new BoundSql(parseSql,parameterMappings);
         return boundSql;

    }


}

(3)以上代码将配置文件中解析出来的SQL语句及参数格式如下:

<select id="findByCondition" resultType="com.lagou.pojo.User" paramterType="com.lagou.pojo.User">
        select * from user where id = #{id} and username = #{username}
    </select>

这个格式是无法被JDBC预处理对象PreparedStatement所识别,因此要通过转换格式,这里用到了mybatis框架的部分源码。我们通过SimpleExecutor实现类的query()方法中调用getBoundSql(sql)方法,在方法中先构建ParameterMappingTokenHandler对象将例如:#{id}格式转化成能被识别的参数格式为 :?符号;其次构建genericTokenParser对象,用来转换及存储转化后的SQL执行语句,然后将能被识别的SQL语句及参数封装到BoundSql对象中放回。

代码示例请点击

(三)项目结构及pom.xml

1. 使用端结构:

 2.使用端pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.lagou</groupId>
    <artifactId>IPersistence_test</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
        <java.version>1.8</java.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <!--引入自定义持久层框架端的依赖-->
    <dependencies>
        <dependency>
            <groupId>com.lagou</groupId>
            <artifactId>IPersistence</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
    </dependencies>



</project>

3. 自定义框架端结构:

4. 自定义框架端pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.lagou</groupId>
    <artifactId>IPersistence</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.encoding>UTF-8</maven.compiler.encoding>
        <java.version>1.8</java.version>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>


    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.17</version>
        </dependency>
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
        <dependency>
            <groupId>log4j</groupId>
            <artifactId>log4j</artifactId>
            <version>1.2.12</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.10</version>
        </dependency>
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.1.6</version>
        </dependency>
    </dependencies>




</project>

二. 自定义mybatis持久层框架所涉及的主要技术知识点

(一)工厂设计模式,请点击:

⼯⼚设计模式介绍及在MyBatis框架中的体现_舞鹤白沙编码日志-CSDN博客

《设计模式之抽象工厂模式:如何统一不同代码风格下的代码级别?》《设计模式之工厂方法模式:如何解决生成对象时的不确定性?》

(二)建造者设计模式,请点击:

《Builder构建者模式及在MyBatis框架中的体现_舞鹤白沙编码日志-CSDN博客》

《设计模式之 建造者模式:如何创建不同形式的复杂对象?》

(三)ResultSetMetaData类介绍,请点击:

《ResultSetMetaData类常用方法简介》

 (四)Properties类介绍,请点击:

《Java中的Properties类详解》

(五)JAVA反射机制,请点击:

《秒懂系列,深入理解Java反射机制》

(六)JDK动态代理机制,请点击:

动态代理模式的介绍及在MyBatis框架中的体现_舞鹤白沙编码日志-CSDN博客

《JDK动态代理的深入理解》

(七)JAVA中泛型介绍,请点击:

《Java泛型中的标记符含义》

(八)C3P0连接池介绍,请点击:

《C3P0连接池》

(九)JAVA内省机制介绍,请点击:

《什么是java内省_Java内省实例解析》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

enterpc

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值