自定义mybatis

架构分析

  1. Configuration类:

    1. 得到数据源对象

    2. 加载其它的实体类映射文件:UserMapper.xml,使用DOM4J

  2. Mapper类只是一个实体类:POJO,用来封装数据

  3. SqlSession类:

    1. 生成了UserMapper接口的代理对象,JDK代理。

    2. 访问数据库:JDBC

    3. 封装查询的结果集,使用反射

《自定义mybatis》

使用到的技术

《自定义mybatis》

 

添加所需的所有依赖,做好准备工作。

pom.xml文件

《自定义mybatis》
《自定义mybatis》

 <properties>
        <!--dom4j版本-->
        <dom4j.vesrion>1.6.1</dom4j.vesrion>
        <!--dom4j依赖包版本-->
        <jaxen.version>1.1.6</jaxen.version>
        <!--mysql驱动版本-->
        <mysql.version>5.1.30</mysql.version>
        <!--c3p0版本-->
        <c3p0.version>0.9.2.1</c3p0.version>
        <!-- junit版本 -->
        <junit.version>4.12</junit.version>
    </properties>

    <dependencies>
        <!--dom4j依赖-->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>${dom4j.vesrion}</version>
        </dependency>
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>${jaxen.version}</version>
        </dependency>
        <!-- mysql数据库依赖 -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>${mysql.version}</version>
        </dependency>
        <!--c3p0依赖-->
        <dependency>
            <groupId>com.mchange</groupId>
            <artifactId>c3p0</artifactId>
            <version>${c3p0.version}</version>
        </dependency>
        <!-- junit依赖 -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>${junit.version}</version>
        </dependency>
    </dependencies>

pox.xml

自定义mybatis:Mapper封装数据类

分析映射配置:UserMapper.xml 

《自定义mybatis》

 

步骤

  1. 创建包com.it.mybatis

  2. 创建实体类:Mapper包含4个属性:namespace,id,resultType,sql

  3. 重写toString()方法,方便后期测试看到封装的结果

  4. 生成get和set方法

  5. 一个Mapper对象代表一条要操作的查询语句对象

代码

《自定义mybatis》
《自定义mybatis》

package com.it.mybatis;

/**
 用来封装映射文件的实体类
 */
public class Mapper {

    private String namespace;  //接口类全名
    private String id;   //接口中方法名
    private String resultType;  //封装的数据类型
    private String sql;   //要执行的SQL语句

    @Override
    public String toString() {
        return "Mapper{" +
                "namespace='" + namespace + '\'' +
                ", id='" + id + '\'' +
                ", resultType='" + resultType + '\'' +
                ", sql='" + sql + '\'' +
                '}';
    }

    public String getNamespace() {
        return namespace;
    }

    public void setNamespace(String namespace) {
        this.namespace = namespace;
    }

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getResultType() {
        return resultType;
    }

    public void setResultType(String resultType) {
        this.resultType = resultType;
    }

    public String getSql() {
        return sql;
    }

    public void setSql(String sql) {
        this.sql = sql;
    }
}

Mapper

自定义mybatis:设计Configuration的基本属性

  1. 设计Configuration的基本属性

  2. 产生get和set方法

  3. 产生toString()方法

分析

sqlMapConfig.xml文件

《自定义mybatis》

 

  1. 创建loadSqlMapConfig()方法,它的作用:

    1. 解析sqlMapConfig.xml配置文件,给Configuration中的属性赋值

    2. 解析UserMapper.xml配置文件,给Mapper中的属性赋值

  2. 在构造方法中调用方法: loadSqlMapConfig()

  3. 作用:使用dom4j解析sqlMapConfig.xml文件,给数据库有关的属性赋值

    1. 从类路径加载/sqlMapConfig.xml配置文件,创建输入流

    2. 使用dom4j得到文档对象

    3. 使用XPath读取所有property元素

    4. 遍历每个property元素,读取它的name和value属性值

    5. 判断name的字符串,如果与类中的属性名相同,则赋值到相应属性中

  4.Configuration解析实体类映射文件

         解析UserMapper.xml并且封装到Mapper类中

    1. 创建新的方法loadMapper(Document document),将当前的文档对象传递给方法

    2. 读取<mapper>中的resource属性值

    3. 通过resource读取它对应的XML文件

    4. 得到namespace,id,resultType,sql的值,封装成Mapper对象

    5. 在loadSqlMapConfig()中调用此方法

《自定义mybatis》

 

 

loadMapper(Document document)方法开发步骤

作用:进一步解析其它的XML文件,给mappers属性赋值

  1. 读取mapper中的resource属性值

    1. 使用XPath读取所有mapper元素

    2. 遍历每个mapper元素

    3. 读取mapper的resource属性值

  2. 通过resource读取它对应的XML文件,得到namespace,id,resultType,sql的值

    1. 使用类对象,读取输入流下面resource,注:要加上/

    2. 创建文档对象

    3. 读取根元素mapper

    4. 读取namespace属性

    5. 读取根元素下的一个select标签

    6. 得到id,resultType,sql内容

  3. 封装成Mapper对象

    1. 创建一个自定义的Mapper对象,封装上面三个属性

    2. 再封装namespace属性

    3. 将封装好的mapper对象添加到this的mappers属性中,其中键是namespace+”.”+id,值是自定义的mapper对象。

代码

《自定义mybatis》
《自定义mybatis》

package com.it.mybatis;

import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import javax.sql.DataSource;
import java.beans.PropertyVetoException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 1. 封装sqlMapConfig.xml配置信息
 2. 得到数据源
 3. 加载UserMapper.xml配置信息
 */
public class Configuration {

    //数据源的四个属性
    private String username;
    private String password;
    private String url;
    private String driver;

    //封装其它的映射文件中属性
    private Map<String, Mapper> mappers = new HashMap<>();

    private DataSource dataSource;  //数据源

    //在构造方法中调用
    public Configuration() {
        try {
            loadSqlMapConfig();  //加载配置文件
            createDataSource();  //创建数据源
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /*
        解析sqlMapConfig.xml文件,封装上面的数据源的四个属性
         */
    private void loadSqlMapConfig() throws Exception {
        //得到输入流
        InputStream inputStream = Configuration.class.getResourceAsStream("/sqlMapConfig.xml");
        //得到文档对象
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(inputStream);
        //读取property
        List<Element> list = document.selectNodes("//property");
        for (Element property : list) {
            //读取name属性
            String name = property.attributeValue("name");
            //读取value属性
            String value = property.attributeValue("value");
            //判断是哪个属性
            switch (name) {
                case "username":
                    this.username = value;
                    break;
                case "password":
                    this.password = value;
                    break;
                case "driver":
                    this.driver = value;
                    break;
                case "url":
                    this.url = value;
                    break;
            }
        }
        //读取UserMapper.xml文件
        loadMapper(document);
    }

    /**
    解析其它的实体类映射文件
    @param document 上面已经得到的文档对象
     */
    private void loadMapper(Document document) throws Exception {
        //读取mapper中resource属性
        List<Element> list = document.selectNodes("//mapper");
        for (Element mapperElement : list) {
            //读取mapper中resource属性
            String resource = mapperElement.attributeValue("resource");
            //再次读取新的XML文件
            InputStream in = Configuration.class.getResourceAsStream("/" + resource);
            //创建文档对象
            Document doc = new SAXReader().read(in);
            //得到根元素
            Element rootElement = doc.getRootElement();
            String namespace = rootElement.attributeValue("namespace");
            //得到mapper下select元素
            Element select = rootElement.element("select");
            //得到id属性
            String id = select.attributeValue("id");
            String resultType = select.attributeValue("resultType");
            String sql = select.getTextTrim();
            //创建Mapper对象
            Mapper mapper = new Mapper();
            mapper.setId(id);
            mapper.setNamespace(namespace);
            mapper.setSql(sql);
            mapper.setResultType(resultType);
            //键=namespace + "." + "id";
            String key = namespace + "." + id;
            //将创建好的mapper对象加到Map集合中
            mappers.put(key,mapper);
        }
    }

    /**
     创建数据源
     */
    private void createDataSource() throws PropertyVetoException {
        //使用c3p0的数据源
        ComboPooledDataSource ds = new ComboPooledDataSource();
        //设置数据库访问属性
        ds.setUser(username);
        ds.setPassword(password);
        ds.setJdbcUrl(url);
        ds.setDriverClass(driver);
        this.dataSource = ds;
    }

    @Override
    public String toString() {
        return "Configuration{" +
                "username='" + username + '\'' +
                ", password='" + password + '\'' +
                ", url='" + url + '\'' +
                ", driver='" + driver + '\'' +
                ", mappers=" + mappers +
                ", dataSource=" + dataSource +
                '}';
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getDriver() {
        return driver;
    }

    public void setDriver(String driver) {
        this.driver = driver;
    }

    public Map<String, Mapper> getMappers() {
        return mappers;
    }

    public void setMappers(Map<String, Mapper> mappers) {
        this.mappers = mappers;
    }

    public DataSource getDataSource() {
        return dataSource;
    }

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }
}

Configuration

 

核心组件SqlSession:编写getMapper方法

《自定义mybatis》

 

步骤

《自定义mybatis》

得到SQL语句和返回类型

  1. 得到Configuration中Map集合

    1. 实例化Configuration对象

    2. 通过Configuration得到Mapper对象的集合

  2. 得到Map中的键:类全名.方法名

    1. 通过方法对象->得到声明的接口->得到名称:即类全名 com.it.dao.UserMapper

    2. 获取当前执行的方法名称:findAllUsers

    3. 通过类全名+方法名得到键

  3. 得到Mapper中相应的属性

    1. 通过类全名+”.”+方法名,从mappers中得到映射的mapper对象

    2. 从mapper中获取查询的sql语句

    3. 从mapper中获取返回值类型resultType

    4. 通过反射将上面的resultType字符串转成类对象,供后面的方法使用

得到Connection对象访问数据库

  1. 通过Configuration得到数据源,通过数据源得到连接对象

  2. 调用List queryForList(Connection connection, String sql, Class clazz)方法

    1. 参数:连接对象,SQL语句,结果集的类型。 直接创建一个List集合,添加3个User对象到集合中,暂时不访问数据库。

    2. 返回封装好的集合

 

《自定义mybatis》
《自定义mybatis》

package com.it.mybatis;

import com.itheima.entity.User;

import javax.sql.DataSource;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

/**
 核心类
 1. 生成了UserMapper接口的代理对象,JDK代理。
 2. 访问数据库:JDBC
 3. 封装查询的结果集,使用反射。
 */
public class SqlSession {

    public <T> T getMapper(Class<T> clazz) {
        /*
        参数1:类加载器
        参数2:接口数组
        参数3:每个方法调用一次
         */
        return (T) Proxy.newProxyInstance(
                SqlSession.class.getClassLoader(),
                new Class[]{clazz},
                new InvocationHandler() {
                    @Override
                    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                       //1. 通过键得到Mapper对象
                        //创建Configuration对象
                        Configuration configuration = new Configuration();
                        //得到集合
                        Map<String, Mapper> mappers = configuration.getMappers();
                        String id = method.getName();  //方法名
                        //getDeclaringClass得到method所在类全名
                        String namespace = method.getDeclaringClass().getName();
                        //如何得到键
                        String key = namespace + "." + id;
                        //通过键得到值
                        Mapper mapper = mappers.get(key);

                        //2. 从Mapper对象中得到SQL语句执行,并且封装成对象返回
                        String sql = mapper.getSql();
                        //resultType = com.itheima.entity.User
                        String resultType = mapper.getResultType();
                        //将字符串转成Class
                        Class type = Class.forName(resultType);

                        //3.查询数据库,必须要连接对象
                        //连接对象从数据源中得到
                        DataSource dataSource = configuration.getDataSource();
                        Connection connection = dataSource.getConnection();

                        //使用JDBC访问数据库得到封装好的结果
                        List list = queryForList(connection, sql, type);
                        return list;
                    }
                });
    }

    /**
     通过JDBC来访问数据库
     @param connection 连接对象
     @param sql 语句
     @param type 返回类型
     @return
     */
    private List queryForList(Connection connection, String sql, Class type) throws Exception {
        List users = new ArrayList<>();

        //1. 通过Connection创建语句对象:PreparedStatement
        PreparedStatement ps = connection.prepareStatement(sql);

        //2. 通过语句对象执行SQL语句
        ResultSet rs = ps.executeQuery();

        //3. 执行完毕以后得到结果集ResultSet
        while(rs.next()) {
            //4. 将ResultSet进行遍历,封装成实体类对象,添加到集合中
            //创建对象
            Object user = type.getConstructor().newInstance();
            //得到类中所有的属性
            Field[] fields = type.getDeclaredFields();
            for (Field field : fields) {
                //得到属性名
                String name = field.getName();
                //得到相应的值
                Object value = rs.getObject(name);
                //暴力反射
                field.setAccessible(true);
                //给每个属性赋值
                field.set(user, value);
            }
            users.add(user);
        }

        //5.释放资源
        rs.close();
        ps.close();
        connection.close();

        return users;
    }

}

SqlSession

 

点赞