如何自定义MyBatis框架

第一个 MyBatis 程序(XML配置)

在上一篇中,简单总结了一下原生 JDBC 的一些局限性,同时引出了 MyBatis 这个框架,算较为详细的整理如何搭建 MyBatis 的工做环境java

这一篇,咱们在开篇,如今搭建好工做环境的基础上,开始咱们的第一个例程,可是,简单的让程序跑起来之后,咱们却要讲解如何自定义 MyBatis 框架,它的意义是什么呢?mysql

虽然第一个例程虽然比较简单,可是其中有不少点倒是容易引发疑惑的,例如为何用工厂模式后还有构建者对象,经过自定义框架,可让本身对于 MyBatis 的理解更加深入,从而更好的应用这个框架spring

首先,咱们想让咱们的第一个程序运行起来sql

一、搭建好环境,在主配置文件 (SqlMapConfig.xml) 中指定映射配置文件的位置数据库

<!-- 指定映射配置文件的位置 -->
<mappers>
    <mapper resource="cn/ideal/mapper/UserMapper.xml"/>
</mappers>

二、在test文件夹下,建立一个如图结构测试类apache

因为咱们的 mapper 接口中写的方法是一个 查询全部信息的方法,因此咱们直接以下图所写就好了,这就是第一个例程,后面咱们会详细的讲到其中的点,先让本身的程序跑起来看看设计模式

public class MyBatisTest {
    public static void main(String[] args) throws Exception {
        //读取配置文件
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //建立SqlSessionFactory工厂
        SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = factoryBuilder.build(in);
        //使用工厂生产SqlSession对象
        SqlSession session = factory.openSession();
        //使用SqlSession建立Mapper接口的代理对象
        UserMapper userMapper = session.getMapper(UserMapper.class);
        //使用代理对象执行方法
        List<User> users = userMapper.findAllUserInfo();
        for (User user : users) {
            System.out.println(user);
        }
        //释放资源
        session.close();
        in.close();
    }
}

第一个 MyBatis 程序(注解配置)

注解配置,天然咱们刚才的 UserMapper.xml 就能够删除了掉了,同时咱们须要在mapper接口方法中添加注解如图所示数组

public interface UserMapper {
    /**
     * 查询全部用户信息
     *
     * @return
     */
    @Select("select * from user")
    List<User> findAllUserInfo();
}

而后在主配置文件中,使用class属性指定被注解的mapper全限定类名微信

<mappers>
    <mapper class="cn.ideal.mapper.UserMapper"/>
</mappers>

两种方式执行结果都是一致的,以下图session

自定义 MyBatis 框架 (首先使用XML)

首先咱们建立一个 Maven 工程,修改其 pom.xml 文件 增长一些必要依赖的坐标,因为咱们使用dom4j的方式解析 xml 文件因此,须要引入 dom4j 和 jaxen

<?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>cn.ideal</groupId>
    <artifactId>code_02_user_defined</artifactId>
    <version>1.0-SNAPSHOT</version>
    
    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.6</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 框架 进行一个简单的查询,即查询表中的全部用户信息,因此咱们须要根据数据库内容,对应的建立出其 User 类实体

CREATE DATABASE ideal_mybatis; -- 建立数据库

CREATE TABLE USER (
  id        INT(11)NOT NULL AUTO_INCREMENT,
  username     VARCHAR(32) NOT NULL COMMENT '用户名',
  telephone     VARCHAR(11) NOT NULL COMMENT '手机',
  birthday    DATETIME DEFAULT NULL COMMENT '生日',
  gender     CHAR(1) DEFAULT NULL COMMENT '性别',
  address    VARCHAR(256) DEFAULT NULL COMMENT '地址',
  PRIMARY KEY  (`id`)
) ENGINE=INNODB DEFAULT CHARSET=utf8;

建立出UserMapper接口,并书写相关的方法

package cn.ideal.mapper;

public interface UserMapper {
    /**
     * 查询全部用户信息
     */
    List<User> findAllUserInfo();
}

设置好其主配置文件(SqlMapConfig.xml)须要说明的是:因为咱们是要本身模拟设计一个 MyBatis 框架,因此不要习惯性的加上对应的 DTD 规范约束,如下是具体代码

<?xml version="1.0" encoding="UTF-8"?>
<!-- mybatis 主配置文件 -->
<configuration>
    <!-- 配置环境,和spring整合后 environments配置将会被废除 -->
    <environments default="development">
        <environment id="development">
            <!-- 使用JDBC事务管理 -->
            <transactionManager type="JDBC"></transactionManager>
            <!-- 数据库链接池 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/ideal_mybatis"/>
                <property name="username" value="root"/>
                <property name="password" value="root99"/>
            </dataSource>
        </environment>
    </environments>

    <!-- 指定映射配置文件的位置 -->
    <mappers>
        <mapper resource="cn/ideal/mapper/UserMapper.xml"/>
    </mappers>
</configuration>

修改其SQL映射配置文件

<?xml version="1.0" encoding="UTF-8"?>
<mapper namespace="cn.ideal.mapper.UserMapper">
    <select id="findAllUserInfo" resultType="cn.ideal.domain.User">
        select * from user
    </select>
</mapper>

测试类,咱们的今天用的测试类,与直接使用MyBatis 框架使用的测试类是一致的,只不过咱们须要本身实现其中的一些类和方法

package cn.ideal.test;

import cn.ideal.domain.User;
import cn.ideal.mapper.UserMapper;
import cn.ideal.mybatis.io.Resources;
import cn.ideal.mybatis.sqlsession.SqlSession;
import cn.ideal.mybatis.sqlsession.SqlSessionFactory;
import cn.ideal.mybatis.sqlsession.SqlSessionFactoryBuilder;

import java.io.InputStream;
import java.util.List;

public class MyBatisTest {
    public static void main(String[] args) throws Exception {
        //读取配置文件
        InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
        //建立SqlSessionFactory工厂
        SqlSessionFactoryBuilder factoryBuilder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = factoryBuilder.build(in);
        //使用工厂生产SqlSession对象
        SqlSession session = factory.openSession();
        //使用SqlSession建立Mapper接口的代理对象
        UserMapper userMapper = session.getMapper(UserMapper.class);
        //使用代理对象执行方法
        List<User> users = userMapper.findAllUserInfo();
        for (User user : users) {
            System.out.println(user);
        }
        //释放资源
        session.close();
        in.close();
    }
}

咱们参考一下真正的 MyBatis 先规整一下,咱们须要,Resources 以及 SqlSessionFactoryBuilder 类须要 SqlSessionFactory 、SqlSession 这样两个接口,而后咱们还须要建立出其对应的实现类,其他的咱们根据须要再进行建立,而后根据测试类中的须要,为建立出来的类加上对应须要的方法

(一) Resources类

首先先建立一个 Resources类,而且在中增长一个,getResourceAsStream 方法,参数类型天然是一个字符串类型,根据测试类中的接收类型,判断出返回值类型为 InputStream,也所以能够写出具体的方法体了,以下图

package cn.ideal.mybatis.io;

import java.io.InputStream;

public class Resources {
    /**
     * 根据传入的参数,获取一个字节输入流
     * @param filePath
     * @return
     */
    public static InputStream getResourceAsStream(String filePath){
        // 获取当前类的字节码,获取字节码的类加载器,根据类加载器读取配置
        return Resources.class.getClassLoader().getResourceAsStream(filePath);
    }
}

获取到主配置文件的字节输入流后,咱们就须要将其中的 XML 解析出来,这里就用到了 Dom4j 的XML解析方式,在Pom.xml中咱们已经引入了它的坐标,咱们须要建立其工具类,固然咱们能够找一份现成的工具类,也能够本身手动的去写一份,咱们的重心仍是在 MyBatis 的执行流程上

(二) XMLConfigBuilder(解析XML工具类,理解便可)

因为咱们首先使用的 XML 配置的方式,因此咱们暂时先无论 XML 中的注解部分,因此注释掉的部分,暂时先不用理会

package cn.ideal.mybatis.utils;

import cn.ideal.mybatis.cfg.Configuration;
import cn.ideal.mybatis.cfg.Mapper;
import cn.ideal.mybatis.io.Resources;
//import com.itheima.mybatis.annotations.Select;

import org.dom4j.Attribute;
import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Method;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 *  用于解析配置文件
 */
public class XMLConfigBuilder {
    /**
     * 解析主配置文件,把里面的内容填充到DefaultSqlSession所须要的地方
     * 使用的技术:
     *      dom4j+xpath,因此须要导入dom4j和jaxen的坐标
     */
    public static Configuration loadConfiguration(InputStream config){
        try{
            //定义封装链接信息的配置对象(mybatis的配置对象)
            Configuration cfg = new Configuration();

            //1.获取SAXReader对象
            SAXReader reader = new SAXReader();
            //2.根据字节输入流获取Document对象
            Document document = reader.read(config);
            //3.获取根节点
            Element root = document.getRootElement();
            //4.使用xpath中选择指定节点的方式,获取全部property节点
            List<Element> propertyElements = root.selectNodes("//property");
            //5.遍历节点
            for(Element propertyElement : propertyElements){
                //判断节点是链接数据库的哪部分信息
                //取出name属性的值
                String name = propertyElement.attributeValue("name");
                if("driver".equals(name)){
                    //表示驱动
                    //获取property标签value属性的值
                    String driver = propertyElement.attributeValue("value");
                    cfg.setDriver(driver);
                }
                if("url".equals(name)){
                    //表示链接字符串
                    //获取property标签value属性的值
                    String url = propertyElement.attributeValue("value");
                    cfg.setUrl(url);
                }
                if("username".equals(name)){
                    //表示用户名
                    //获取property标签value属性的值
                    String username = propertyElement.attributeValue("value");
                    cfg.setUsername(username);
                }
                if("password".equals(name)){
                    //表示密码
                    //获取property标签value属性的值
                    String password = propertyElement.attributeValue("value");
                    cfg.setPassword(password);
                }
            }
            //取出mappers中的全部mapper标签,判断他们使用了resource仍是class属性
            List<Element> mapperElements = root.selectNodes("//mappers/mapper");
            //遍历集合
            for(Element mapperElement : mapperElements){
                //判断mapperElement使用的是哪一个属性
                Attribute attribute = mapperElement.attribute("resource");
                if(attribute != null){
                    System.out.println("XML方式");
                    //表示有resource属性,用的是XML
                    //取出属性的值
                    String mapperPath = attribute.getValue();//获取属性的值"com/itheima/dao/IUserDao.xml"
                    //把映射配置文件的内容获取出来,封装成一个map
                    Map<String, Mapper> mappers = loadMapperConfiguration(mapperPath);
                    //给configuration中的mappers赋值
                    cfg.setMappers(mappers);
                }else{
//                    System.out.println("注解方式");
//                    //表示没有resource属性,用的是注解
//                    //获取class属性的值
//                    String daoClassPath = mapperElement.attributeValue("class");
//                    //根据daoClassPath获取封装的必要信息
//                    Map<String,Mapper> mappers = loadMapperAnnotation(daoClassPath);
//                    //给configuration中的mappers赋值
//                    cfg.setMappers(mappers);
                }
            }
            //返回Configuration
            return cfg;
        }catch(Exception e){
            throw new RuntimeException(e);
        }finally{
            try {
                config.close();
            }catch(Exception e){
                e.printStackTrace();
            }
        }

    }

    /**
     * 根据传入的参数,解析XML,而且封装到Map中
     * @param mapperPath    映射配置文件的位置
     * @return  map中包含了获取的惟一标识(key是由dao的全限定类名和方法名组成)
     *          以及执行所需的必要信息(value是一个Mapper对象,里面存放的是执行的SQL语句和要封装的实体类全限定类名)
     */
    private static Map<String,Mapper> loadMapperConfiguration(String mapperPath)throws IOException {
        InputStream in = null;
        try{
            //定义返回值对象
            Map<String,Mapper> mappers = new HashMap<String,Mapper>();
            //1.根据路径获取字节输入流
            in = Resources.getResourceAsStream(mapperPath);
            //2.根据字节输入流获取Document对象
            SAXReader reader = new SAXReader();
            Document document = reader.read(in);
            //3.获取根节点
            Element root = document.getRootElement();
            //4.获取根节点的namespace属性取值
            String namespace = root.attributeValue("namespace");//是组成map中key的部分
            //5.获取全部的select节点
            List<Element> selectElements = root.selectNodes("//select");
            //6.遍历select节点集合
            for(Element selectElement : selectElements){
                //取出id属性的值      组成map中key的部分
                String id = selectElement.attributeValue("id");
                //取出resultType属性的值  组成map中value的部分
                String resultType = selectElement.attributeValue("resultType");
                //取出文本内容            组成map中value的部分
                String queryString = selectElement.getText();
                //建立Key
                String key = namespace+"."+id;
                //建立Value
                Mapper mapper = new Mapper();
                mapper.setQueryString(queryString);
                mapper.setResultType(resultType);
                //把key和value存入mappers中
                mappers.put(key,mapper);
            }
            return mappers;
        }catch(Exception e){
            throw new RuntimeException(e);
        }finally{
            in.close();
        }
    }

    /**
     * 根据传入的参数,获得dao中全部被select注解标注的方法。
     * 根据方法名称和类名,以及方法上注解value属性的值,组成Mapper的必要信息
     * @param daoClassPath
     * @return
     */
//    private static Map<String,Mapper> loadMapperAnnotation(String daoClassPath)throws Exception{
//        //定义返回值对象
//        Map<String,Mapper> mappers = new HashMap<String, Mapper>();
//
//        //1.获得dao接口的字节码对象
//        Class daoClass = Class.forName(daoClassPath);
//        //2.获得dao接口中的方法数组
//        Method[] methods = daoClass.getMethods();
//        //3.遍历Method数组
//        for(Method method : methods){
//            //取出每个方法,判断是否有select注解
//            boolean isAnnotated = method.isAnnotationPresent(Select.class);
//            if(isAnnotated){
//                //建立Mapper对象
//                Mapper mapper = new Mapper();
//                //取出注解的value属性值
//                Select selectAnno = method.getAnnotation(Select.class);
//                String queryString = selectAnno.value();
//                mapper.setQueryString(queryString);
//                //获取当前方法的返回值,还要求必须带有泛型信息
//                Type type = method.getGenericReturnType();//List<User>
//                //判断type是否是参数化的类型
//                if(type instanceof ParameterizedType){
//                    //强转
//                    ParameterizedType ptype = (ParameterizedType)type;
//                    //获得参数化类型中的实际类型参数
//                    Type[] types = ptype.getActualTypeArguments();
//                    //取出第一个
//                    Class domainClass = (Class)types[0];
//                    //获取domainClass的类名
//                    String resultType = domainClass.getName();
//                    //给Mapper赋值
//                    mapper.setResultType(resultType);
//                }
//                //组装key的信息
//                //获取方法的名称
//                String methodName = method.getName();
//                String className = method.getDeclaringClass().getName();
//                String key = className+"."+methodName;
//                //给map赋值
//                mappers.put(key,mapper);
//            }
//        }
//        return mappers;
//    }
}

咱们能够大体看一下代码,首先获取全部property节点,将驱动,用户名,密码等的值获取出来,而后

取出mappers中的全部mapper标签,判断他们使用了resource仍是class属性,这就判断出使用了XML仍是注解的方式,例如本例中的XML方式,就获取到了主配置文件中 <mapper resource="cn/ideal/mapper/UserMapper.xml"/> 这一句中cn/ideal/mapper/UserMapper.xml 而后对这个 SQL映射的配置文件进行解析,一样将其中一些必要的信息提取出来

可是咱们若是想要使用这个工具类,能够看到仍是有一些报错的地方,这就是由于咱们缺乏一些必要的类,咱们须要本身,补充一下

首先咱们须要建立一个 Configuration 实体类,用来传递咱们获取到的 一些连接信息

public class Configuration {
    
    private String driver;
    private String url;
    private String username;
    private String password;
    private Map<String, Mapper> mappers = new HashMap<String, Mapper>();
    
     补充其对应get set方法   
    //特别说明:setMappers 须要使用putALL的追加写入方式,不能直接赋值,否则旧的就会被新的覆盖掉
    public void setMappers(Map<String, Mapper> mappers) {
        this.mappers.putAll(mappers); // 追加的方式
    }
}

其次咱们能够看到,对 SQL 映射文件进行解析的时候,咱们将其封装成一个 Map 集合,其中key是由 mapper 的全限定类名和方法名组成的, 即经过分别获取二者的值,而后进行字符串拼接成key,value值是一个 Mapper对象,它的key为 id ,而value值为 SQL 语句 以及 resultType

public class Mapper {

    private String queryString; //SQL
    private String resultType; //实体类的全限定类名
    
    补充其对应get set方法 
}

(三) SqlSessionFactoryBuilder 类

咱们继续回到测试类中,咱们须要建立一个 SqlSessionFactoryBuilder 类 ,根据测试类看到,咱们将 Recourse 类中获取到的流文件,做为参数传递到了本类中的 build 方法中,这也就是说,咱们在build方法中,须要对 XML 进行解析,而后使用刚才建立的 Configuration 类进行接收,可是根据测试类中,咱们能够知道,测试中使用的是一个 SqlSessionFactory 类型来接收build的返回,而经过 真正MyBatis可知,SqlSessionFactory是一个接口,这里使用了建造者设计模式,因此咱们真正要返回的就是其对应的实现类,而且将 Configuration 的对象传进去,代码以下所示,

public class SqlSessionFactoryBuilder {
    public  SqlSessionFactory build(InputStream config){
        Configuration cfg = XMLConfigBuilder.loadConfiguration(config);
        return new DefaultSqlSessionFactory(cfg);
    }
}

(四) SqlSessionFactory接口

public interface SqlSessionFactory {
    /**
     * 用来打开一个新的SqlSession对象
     * @return
     */
    SqlSession openSession();
}

(五) DefaultSqlSessionFactor实现类

因为咱们将配置传入了方法中,因此必要的,咱们须要先建立一个 Configuration 成员,而后建立一个带参构造,一样根据测试类建立其 openSession 方法,用于建立操做数据的对象,这里是一样的思路,SqlSession是一个接口,因此咱们真正须要返回的仍是其接口

public class DefaultSqlSessionFactory implements SqlSessionFactory {

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

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

(六) SqlSession 接口

在这里咱们须要使用SqlSession建立Mapper接口的代理对象

public interface SqlSession {
    /**
     * 根据参数建立一个代理对象
     * @param mapperInterfaceClass mapper的接口字节码
     * @param <T>
     * @return
     */
    <T> T getMapper(Class<T> mapperInterfaceClass);

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

(七) DefaultSqlSession实现类

首先依旧是建立成员以及带参构造方法,咱们须要建立一个工具类,用来建立数据源,起名为DataSourceUtil,同时这个类中比较重要的是建立咱们的 getMapper方法,咱们使用Proxy.newProxyInstance 这个方法进行,其中的参数,

  • 第一个参数即咱们须要代理的类加载器,也就是代理谁,就用谁的类加载器
  • 第二个参数就是动态代理类须要实现的接口
  • 第三个参数 动态代理方法在执行时,会调用里面的方法去执行 (自创)

    • 其中的参数,就是将咱们配置中的信息传入
public class DefaultSqlSession implements SqlSession {

    private Configuration cfg;
    private Connection connection;

    public DefaultSqlSession(Configuration cfg) {
        this.cfg = cfg;

        connection = DataSourceUtil.getConnection(cfg);
    }

    /**
     * 用于建立代理对象
     *
     * @param mapperInterfaceClass mapper的接口字节码
     * @param <T>
     * @return
     */
    public <T> T getMapper(Class<T> mapperInterfaceClass) {
        return (T) Proxy.newProxyInstance(mapperInterfaceClass.getClassLoader(),
                new Class[]{mapperInterfaceClass},new MapperProxy(cfg.getMappers(),connection));
    }

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

(八) DataSourceUtil 工具类

public class DataSourceUtil {
    public static Connection getConnection(Configuration cfg){
        try {
            Class.forName(cfg.getDriver());
            return DriverManager.getConnection(cfg.getUrl(),cfg.getUsername(),cfg.getPassword());
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
}

(九) MapperProxy

咱们开始实现刚才自定义的一个 MapperProxy 类,常规的建立成员以及构造函数,其中传入Connection 的缘由是,为了最后执行 Executor() 的须要, 这个类还有一个重要的就是使用 invoke 对方法进行加强,将其获取并组合,

public class MapperProxy implements InvocationHandler {

    //map的key是全限定类名 + 方法名
    private Map<String, Mapper> mappers;
    private Connection connection;

    public MapperProxy(Map<String, Mapper> mappers,Connection connection) {
        this.mappers = mappers;
        this.connection = connection;
    }

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

(十) Executor 工具类

接着就是使用一个现成的工具类进行执行咱们的 SQL,由于 SQL语句,以及resultType已经被封装在了mapper中被传递了进来

/**
 * 负责执行SQL语句,而且封装结果集
 */
public class Executor {

    public <E> List<E> selectList(Mapper mapper, Connection conn) {
        PreparedStatement pstm = null;
        ResultSet rs = null;
        try {
            //1.取出mapper中的数据
            String queryString = mapper.getQueryString();//select * from user
            String resultType = mapper.getResultType();//com.itheima.domain.User
            Class domainClass = Class.forName(resultType);
            //2.获取PreparedStatement对象
            pstm = conn.prepareStatement(queryString);
            //3.执行SQL语句,获取结果集
            rs = pstm.executeQuery();
            //4.封装结果集
            List<E> list = new ArrayList<E>();//定义返回值
            while(rs.next()) {
                //实例化要封装的实体类对象
                E obj = (E)domainClass.newInstance();

                //取出结果集的元信息:ResultSetMetaData
                ResultSetMetaData rsmd = rs.getMetaData();
                //取出总列数
                int columnCount = rsmd.getColumnCount();
                //遍历总列数
                for (int i = 1; i <= columnCount; i++) {
                    //获取每列的名称,列名的序号是从1开始的
                    String columnName = rsmd.getColumnName(i);
                    //根据获得列名,获取每列的值
                    Object columnValue = rs.getObject(columnName);
                    //给obj赋值:使用Java内省机制(借助PropertyDescriptor实现属性的封装)
                    PropertyDescriptor pd = new PropertyDescriptor(columnName,domainClass);//要求:实体类的属性和数据库表的列名保持一种
                    //获取它的写入方法
                    Method writeMethod = pd.getWriteMethod();
                    //把获取的列的值,给对象赋值
                    writeMethod.invoke(obj,columnValue);
                }
                //把赋好值的对象加入到集合中
                list.add(obj);
            }
            return list;
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            release(pstm,rs);
        }
    }


    private void release(PreparedStatement pstm,ResultSet rs){
        if(rs != null){
            try {
                rs.close();
            }catch(Exception e){
                e.printStackTrace();
            }
        }

        if(pstm != null){
            try {
                pstm.close();
            }catch(Exception e){
                e.printStackTrace();
            }
        }
    }
}

到这里咱们就能够来测试一下了,结果是没有什么问题的

如何使用注解方式 自定MyBatis

首先咱们须要在主配置文件中修改成注解的形式,即

<mappers>
    <mapper class="cn.ideal.mapper.UserMapper"/>
</mappers>

而后建立一个自定义的Select注解

@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface Select {
    /**
     * 配置SQL语句的
     * @return
     */
    String value();
}

而后到咱们的 UserMapper 接口方法上添加注解,将SQL语句写在其中

public interface UserMapper {
    /**
     * 查询全部用户信息
     *
     * @return
     */
    @Select("select * from user")
    List<User> findAllUserInfo();
}

最后咱们将 XMLConfigBuilder 工具类中 注释掉注解的部分取消注释就能够了

运行结果以下:

最后,给你们程序结构图,方便你们建立包,结构为 cn.ideal.xxxx

总结

我从新捋一下,整个流程

  • MyBatis 相关的配置文件,例中的 SqlMapConfig.xml 被 Rosource 类中的方法读取,获得一个流文件
  • 在 SqlSessionFactoryBuilder 中经过 XMLConfigBuilder 对配置文件进行解析,同时使用 Configuration 类,存取提取出来具体的一些配置信息
  • 经过 SqlSessionFactory 建立新的操做数据库的对象
  • 获取到 SqlSession
  • 使用代理模式 MapperProxy 执行 SQL,本质是调用 Executor执行器
  • 测试运行

自制一个简单的流程图

说明:此文章代码来自某马,此文章是我根据其思路,从新整理,总结了一下,固然不是照搬,我也切实的敲了一次,简单的作了说明与分析,天然没什么高深技术可言,或许能对一些朋友有一些帮助,不管如何,仍是感谢支持,若是真有须要原代码的朋友,我后期更个连接,方便你们参考

结尾

若是文章中有什么不足,或者错误的地方,欢迎你们留言分享想法,感谢朋友们的支持!

若是能帮到你的话,那就来关注我吧!若是您更喜欢微信文章的阅读方式,能够关注个人公众号

在这里的咱们素不相识,却都在为了本身的梦而努力 ❤

一个坚持推送原创开发技术文章的公众号:理想二旬不止

相关文章
相关标签/搜索