手写MyBatis开发框架

运行环境

JDK :17
IntelliJ IDEA : 2022.3
Datagrip64: 2022.2

前提工作

  1. 首先在datagrip中创建一个新的schema:hand-write-mybatis,方便我们后续做测试使用~

    表结构如下:
create table `hand-write-mybatis`.user
(
    id       int auto_increment
        primary key,
    username varchar(255) collate utf8_bin null
);

table

  1. 掌握JDBC基础知识和JDK动态代理知识:
    本项目中TestJdbc回顾JDBCTestUserDao回顾JDK动态代理

项目结构

项目类继承关系:

项目类图

main结构:

在这里插入图片描述
java
com.bruce.mapper:用户自定义的Mapper接口(UserMapper)
com.bruce.pojo:用户自定义的对象(User)

org.apache.ibatis.configuration:MyBatis框架的SQL配置封装类
org.apache.ibatis.executor:SQl语句执行器
org.apache.ibatis.io:获取一个类加载器
org.apache.ibatis.session:SqlSessionFactory和SqlSession的实现

resources
mappers:存放的是用户自定义的Mapper.xml文件


test结构:

在这里插入图片描述
java
com.bruce.dao:回顾动态代理,测试生成一个代理对象
com.bruce.test:最终的一些测试类

resources
目前只存放了测试Dom4j解析的book.xml


三个重要的XML文件:

  • UserMapper.xml:用户自定义的Mapper文件
  • mybatis-config.xml: MyBatis的配置文件
  • book.xml : 测试 Dom4j解析功能的xml文件 (非必须)

请添加图片描述


项目流程

MyBatis运行总流程:

原生MyBatis执行流程

  1. 读取配置:读取MyBatis的核心配置文件:mybatis-config.xml,MyBatis针对每个接口还有一个Mapper映射文件:UserMapper.xml。底层使用XML解析技术读取配置文件(本项目使用的是Dom4j技术),封装了Configuration对象。加载MyBatis的配置文件,返回inputstream字节流,使用SqlSessionFactoryBuilder().build()方法构建SqlSessionFactory,通过SqlSessionFactory获取SqlSession对象,SqlSession对象.getMapper()底层使用JDK动态代理生成了接口实现类,也就是代理类对象!
  2. 加载Mapper:解析XML文件,获取SQL语句,并且映射到Configuration对象中的mapperStatemengMap中。
  3. 构造SqlSession:通过传入的Configuration构造一个SqlSessionFactory,并传入SqlSessionFactory中。
  4. 执行SQL:SimpleExecutor开始执行,根据id取出一个MappedStatement对象并执行,然后执行SQL语句。
  5. 返回结果:一条条取出来交给ResultSet结果集中,然后遍历ResultSet每行取出的值封装到objects列表中,最后返回list。
  6. 测试框架:在TestMybatis测试类中测试,执行成功后前往数据库查看。

注意:整体的流程最好自己debug一遍,印象会更深刻!


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>org.example</groupId>
    <artifactId>Handwriting-MyBatis</artifactId>
    <version>1.0-SNAPSHOT</version>

    <dependencies>
        <!--单元测试-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>

        <!--lombok依赖-->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.8</version>
        </dependency>

        <!--MySQL驱动-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.26</version>
        </dependency>

        <!--Dom4j依赖,比较流行的解析XML工具-->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>

        <!--XPath(XML Path Language)的解析和查询库-->
        <!--使用XPath表达式来查询和操作XML文档中的节点,从而实现对XML数据的处理和分析-->
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.2.0</version>
        </dependency>

        <!--数据库连接池-->
        <dependency>
            <groupId>c3p0</groupId>
            <artifactId>c3p0</artifactId>
            <version>0.9.1.2</version>
        </dependency>
    </dependencies>

    <!--控制JDK版本,防止每次refresh maven时自动跳转到JDK5-->
    <build>
        <plugins>
            <plugin>
                <groupId>org.apache.maven.plugins</groupId>
                <artifactId>maven-compiler-plugin</artifactId>
                <version>3.8.1</version>
                <configuration>
                    <source>17</source>
                    <target>17</target>
                </configuration>
            </plugin>
        </plugins>
    </build>
    
</project>

main


com.bruce.mapper包:

UserMapper接口:

package com.bruce.mapper;

import com.bruce.pojo.User;

import java.util.List;

/**
 * @Author: Juechen
 * @Date: 2024/4/21
 * @Description: TODO
 * @Version: 1.0
 **/
public interface UserMapper {

    List<User> list();

    User findById(User user);

    Integer insert(User user);

    Integer updateUser(User user);

    Integer delete(User user);


}


com.bruce.pojo包:

User类:

package com.bruce.pojo;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @Author: Juechen
 * @Date: 2024/4/21
 * @Description: TODO
 * @Version: 1.0
 **/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {

    /**
     * 根据对象的属性名与数据库表的列名进行自动映射。如果属性名和列名一致,可以减少配置和映射的工作量。
     * 尽管可以通过一些框架提供的注解或配置来指定属性和列的映射关系,但是保持一致性通常是推荐的做法,因为它能够简化代码,并使代码更易于理解。
     */

    private Integer id;
    private String username;

}


org.apache.ibatis.configuration包:

BoundSql类:作为SQL的封装

package org.apache.ibatis.configuration;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import java.util.ArrayList;
import java.util.List;

/**
 * @Author: Juechen
 * @Date: 2024/4/22
 * @Description: 作为SQL的封装
 * @Version: 1.0
 **/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class BoundSql {

    //要执行的SQL语句,也就是转换完毕之后的SQL语句
    private String sqlText;

    // 执行SQL语句参数的集合
    private List<String> parameterMappingList = new ArrayList<>();
}

Configuration类:MyBatis框架的SQL配置封装类,后期使用Dom4j进行解析

package org.apache.ibatis.configuration;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

import javax.sql.DataSource;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

/**
 * @Author: Juechen
 * @Date: 2024/4/21
 * @Description: MyBatis框架的SQL配置封装类,后期使用Dom4j进行解析
 * @Version: 1.0
 **/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class Configuration {

    /**
     * 数据源
     */
    private DataSource dataSource;

    /**
     * 封装UserMapper.xml文件中的SQL语句
     */
    Map<String,MappedStatement> mapperStatemengMap = new ConcurrentHashMap<String,MappedStatement>();
}

MappedStatement类:MyBatis-config.xml配置文件的映射类,封装UserMapper.xml文件解析之后的SQL语句信息,底层使用Dom4j进行解析!

package org.apache.ibatis.configuration;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

/**
 * @Author: Juechen
 * @Date: 2024/4/21
 * @Description: MyBatis-config.xml配置文件的映射类,封装UserMapper.xml文件解析之后的SQL语句信息,底层使用Dom4j进行解析!
 * @Version: 1.0
 **/
@Data
@NoArgsConstructor
@AllArgsConstructor
public class MappedStatement {

    /**
     * id标识
     */
    private String id;

    /**
     * SQL语句的返回值
     */
    private String resultType;

    /**
     * 参数类型
     */
    private String parameterType;

    /**
     * SQL语句
     */
    private String sql;

    /**
     * SQL语句的类型(UserMapper标签里的)
     */
    private String sqlType;
}

XmlConfigBuilder类:解析MyBatis核心配置文件(mybatis-config.xml)

package org.apache.ibatis.configuration;

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

import java.beans.PropertyVetoException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;

/**
 * @Author: Juechen
 * @Date: 2024/4/21
 * @Description: 解析MyBatis核心配置文件(mybatis-config.xml)
 * @Version: 1.0
 **/
public class XmlConfigBuilder {

    /**
     * 配置数据封装对象
     */
    private Configuration configuration;

    /**
     * 有参的构造方法
     */
    public XmlConfigBuilder(Configuration configuration) {
        this.configuration = configuration;
    }

    public Configuration parseMyBatisConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(inputStream);
        Element rootElement = document.getRootElement();
        List<Element> propertyList = rootElement.selectNodes("//property");
        Properties properties = new Properties();
        for (Element element : propertyList) {
            String name = element.attributeValue("name");
            String value = element.attributeValue("value");
            properties.setProperty(name,value);
        }

        // 初始化数据库连接池,这个地方导入依赖,不需要手写
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
        comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
        comboPooledDataSource.setUser(properties.getProperty("username"));
        comboPooledDataSource.setPassword(properties.getProperty("password"));

        // 设置数据源
        configuration.setDataSource(comboPooledDataSource);

        // MyBatis的核心配置文件中,映射 XxxMapper.xml文件
        List<Element> mapperList = rootElement.selectNodes("//mapper");
        for (Element element : mapperList) {
            String resource = element.attributeValue("resource");
            InputStream resourceAsStream = Resources.getResourceAsStream(resource);
            XmlMapperBuilder xmlMapperBuilder = new XmlMapperBuilder(configuration);
            xmlMapperBuilder.parse(resourceAsStream);
        }
        return configuration;
    }

}

XmlMapperBuilder类:使用Dom4j解析 UserMapper.xml配置文件

package org.apache.ibatis.configuration;

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

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

/**
 * @Author: Juechen
 * @Date: 2024/4/21
 * @Description: 使用Dom4j解析 UserMapper.xml配置文件
 * @Version: 1.0
 **/
public class XmlMapperBuilder {

    /**
     * 配置数据封装对象
     */
    private Configuration configuration;

    /**
     * 有参的构造方法
     */
    public XmlMapperBuilder(Configuration configuration) {
        this.configuration = configuration;
    }

    /**
     * 传入配置文件的字节流,解析配置文件
     */
    public void parse(InputStream inputStream) throws DocumentException {

        SAXReader saxReader = new SAXReader();
        // XML文档对象
        Document document = saxReader.read(inputStream);
        // 获取文档的根节点
        Element rootElement = document.getRootElement();
        // 获取根节点的属性
        Attribute attributeNamespace = rootElement.attribute("namespace");
        // com.bruce.mapper.UserMapper
        String namespace = attributeNamespace.getValue();

        //xpath解析,解析XML配置文件,获取所有查询相关配置节点
        List<Element> selectList = rootElement.selectNodes("//select");
        //xpath解析,解析XML配置文件,获取所有新增相关配置节点
        List<Element> insertList = rootElement.selectNodes("//insert");
        //xpath解析,解析XML配置文件,获取所有更新相关配置节点
        List<Element> updateList = rootElement.selectNodes("//update");
        //xpath解析,解析XML配置文件,获取所有删除相关配置节点
        List<Element> deleteList = rootElement.selectNodes("//delete");

        List<Element> allList = new ArrayList<>();
        allList.addAll(selectList);
        allList.addAll(insertList);
        allList.addAll(updateList);
        allList.addAll(deleteList);

        for (Element element : allList) {
            String id = element.attributeValue("id");
            // 获取返回值类型
            String resultType = element.attributeValue("resultType");
            // 获取输入参数类型
            String parameterType = element.attributeValue("parameterType");
            // 获取每个mapper节点中的SQL语句
            String sqlText = element.getTextTrim();

            // 封装对象
            MappedStatement mappedStatement = new MappedStatement();
            mappedStatement.setId(id);
            mappedStatement.setResultType(resultType);
            mappedStatement.setParameterType(parameterType);
            mappedStatement.setSql(sqlText);
            mappedStatement.setSqlType(element.getName());

            String key = namespace + "." + id;
            configuration.getMapperStatemengMap().put(key,mappedStatement);
        }

    }
}


org.apache.ibatis.executor包:

Executor接口:SQl语句执行器

package org.apache.ibatis.executor;

import org.apache.ibatis.configuration.Configuration;
import org.apache.ibatis.configuration.MappedStatement;

import java.util.List;

/**
 * @Author: Juechen
 * @Date: 2024/4/22
 * @Description: SQL语句执行器
 * @Version: 1.0
 **/
public interface Executor {

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

}

SimpleExecutor类:SQl语句执行器的实现类

package org.apache.ibatis.executor;

import org.apache.ibatis.configuration.BoundSql;
import org.apache.ibatis.configuration.Configuration;
import org.apache.ibatis.configuration.MappedStatement;

import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.*;

/**
 * @Author: Juechen
 * @Date: 2024/4/22
 * @Description: SQl语句执行器
 * @Version: 1.0
 **/
public class SimpleExecutor implements Executor {

    /**
     * 执行SQL核心方法,最终执行JDBC的方法
     *
     * @param configuration
     * @param mappedStatement
     * @param params
     * @param <E>
     * @return
     */
    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object... params) throws Exception {
        // 1.获取数据库连接
        Connection connection = configuration.getDataSource().getConnection();
        // 2.获取要执行的SQL语句
        String sql = mappedStatement.getSql();  // 原始的SQL语句
        BoundSql boundSql = this.getBoundSql(sql); // 解析后的SQL语句
        System.out.println("要执行的SQL语句是:" + boundSql.getSqlText());

        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
        // 设置参数
        String parameterType = mappedStatement.getParameterType(); // com.bruce.pojo.User
        Class<?> parameterTypeClass = this.getClassType(parameterType);
        // 获取SQL语句参数集合
        List<String> parameterMappingList = boundSql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            String content = parameterMappingList.get(i);
            // 暴力反射
            Field declaredField = parameterTypeClass.getDeclaredField(content);
            declaredField.setAccessible(true);
            // 取出参数
            Object data = declaredField.get(params[0]);
            System.out.println("参数" + content + "    SQL语句的参数值:" + data);

            preparedStatement.setObject(i + 1, data);
        }

        // 执行SQL
        ResultSet resultSet = null;
        if ("select".equals(mappedStatement.getSqlType())) {
            // 查询
            resultSet = preparedStatement.executeQuery();
        } else {
            // 增删改
            Integer result = preparedStatement.executeUpdate();
            List<Integer> resultList = new ArrayList<>();
            resultList.add(result);
            return (List<E>) resultList;
        }
        // 获取返回值类型
        String returnType = mappedStatement.getResultType();
        Class<?> returnTypeClass = this.getClassType(returnType);
        List<Object> objects = new ArrayList<>();

        // 查询的结果集封装
        while (resultSet.next()) {
            // 调用无参数的构造方法生产对象
            Object o = returnTypeClass.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, returnTypeClass);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(o, value);

                //方法二:反射给返回对象赋值
                //Field declaredField = returnTypeClass.getDeclaredField(columnName);
                //declaredField.setAccessible(true);
                //declaredField.set(o,value);
            }
            objects.add(o);
        }
        return (List<E>) objects;
    }

    /**
     * 根据类的全名称获取Class
     *
     * @param parameterType
     * @return
     */
    public Class<?> getClassType(String parameterType) throws ClassNotFoundException {
        if (parameterType != null) {
            Class<?> aClass = Class.forName(parameterType);
            return aClass;
        }
        return null;
    }

    Map<Integer, Integer> map = new TreeMap<>();
    int findPosition = 0;
    // SQL语句的参数
    List<String> parameterMappings = new ArrayList<>();

    /**
     * 完成对#{}的解析工作:
     * 1.将#{}使用?占位符代替
     * 2.解析出#{}里面的值进行存储
     *
     * @return 解析后的sql
     */
    private BoundSql getBoundSql(String sql) {
        this.parserSql(sql);
        Set<Map.Entry<Integer, Integer>> entries = map.entrySet();
        for (Map.Entry<Integer, Integer> entry : entries) {
            Integer key = entry.getKey() + 2;
            Integer value = entry.getValue();
            parameterMappings.add(sql.substring(key, value));
        }
        for (String s : parameterMappings) {
            sql = sql.replace("#{" + s + "}", "?");
        }
        BoundSql boundSql = new BoundSql(sql, parameterMappings);
        return boundSql;
    }

    /**
     * 递归算法 select * from user where id=#{id} and username=#{username}
     *
     * @param sql
     */
    private void parserSql(String sql) {
        int openIndex = sql.indexOf("#{", findPosition);
        if (openIndex != -1) {
            int endIndex = sql.indexOf("}", findPosition + 1);
            if (endIndex != -1) {
                map.put(openIndex, endIndex);
                findPosition = endIndex + 1;
                parserSql(sql);
            } else {
                System.out.println("SQL语句中参数错误..");
            }
        }
    }
}

org.apache.ibatis.io包:

Resources类:类加载器

package org.apache.ibatis.io;

import java.io.InputStream;

/**
 * @Author: Juechen
 * @Date: 2024/4/21
 * @Description: 类加载器
 * @Version: 1.0
 **/
public class Resources {

    /**
     * 根据配置文件的路径,将配置文件加载为字节流形式,存储在内存中
     * @param path 配置文件的位置
     * @return 返回的字节流
     */
    public static InputStream getResourceAsStream(String path) {

        // 加载类路径下的配置文件,以字节流形式返回
        return Resources.class.getClassLoader().getResourceAsStream(path);
    }
}


org.apache.ibatis.session包:

SqlSession接口:SqlSession 对象

package org.apache.ibatis.session;

import java.util.List;

/**
 * @Author: Juechen
 * @Date: 2024/4/22
 * @Description: SqlSession 对象
 * @Version: 1.0
 **/
public interface SqlSession {

    /**
     * 查询所有数据
     * @param statemenntId sql语句的唯一id
     * @param params 查询sql需要的可变参数
     * @return
     * @param <E>
     * @throws Exception
     */
    <E> List<E> selectList(String statemenntId,Object... params) throws Exception;

    /**
     * 根据条件查询单个对象
     */
    <E> E selectOne(String statemenntId,Object... params) throws Exception;

    /**
     * 新增
     */
    <E> E insert(String statemenntId,Object... params) throws Exception;

    /**
     * 更新
     */
    <E> E update(String statemenntId,Object... params) throws Exception;

    /**
     * 删除
     */
    <E> E delete(String statemenntId,Object... params) throws Exception;

    /**
     * 为mapper层的接口JDK动态代理生成实现类
     */
    <T> T getMapper(Class<?> mapperClass) throws Exception;

}

DefaultSqlSession类:SqlSession的实现类

package org.apache.ibatis.session;

import org.apache.ibatis.configuration.Configuration;
import org.apache.ibatis.configuration.MappedStatement;
import org.apache.ibatis.executor.SimpleExecutor;

import java.lang.reflect.*;
import java.util.List;
import java.util.Map;

/**
 * @Author: Juechen
 * @Date: 2024/4/22
 * @Description: SqlSession的实现类
 * @Version: 1.0
 **/
public class DefaultSqlSession implements SqlSession {

    // 封装的是配置信息
    private Configuration configuration;

    public DefaultSqlSession(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public <E> List<E> selectList(String statemenntId, Object... params) throws Exception {
        //SimpleExecutorQuery完成查询
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        MappedStatement mappedStatement = this.configuration.getMapperStatemengMap().get(statemenntId);
        List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);
        return (List<E>) list;
    }

    @Override
    public <E> E selectOne(String statemenntId, Object... params) throws Exception {
        List<Object> objects = this.selectList(statemenntId, params);
        if (objects.size() == 1) {
            return (E) objects.get(0);
        } else if (objects.size() > 1) {
            throw new RuntimeException("查询的结果不唯一!");
        } else {
            throw new RuntimeException("查询的结果为空!");
        }
    }

    @Override
    public <E> E insert(String statemenntId, Object... params) throws Exception {
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        MappedStatement mappedStatement = this.configuration.getMapperStatemengMap().get(statemenntId);
        List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);
        if (list.size()>0) {
            return (E) list.get(0);
        }else {
            return (E) "0";
        }
    }

    @Override
    public <E> E update(String statemenntId, Object... params) throws Exception {
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        MappedStatement mappedStatement = this.configuration.getMapperStatemengMap().get(statemenntId);
        List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);
        if (list.size()>0) {
            return (E) list.get(0);
        }else {
            return (E) "0";
        }
    }

    @Override
    public <E> E delete(String statemenntId, Object... params) throws Exception {
        SimpleExecutor simpleExecutor = new SimpleExecutor();
        MappedStatement mappedStatement = this.configuration.getMapperStatemengMap().get(statemenntId);
        List<Object> list = simpleExecutor.query(configuration, mappedStatement, params);
        if (list.size()>0) {
            return (E) list.get(0);
        }else {
            return (E) "0";
        }
    }

    /**
     * 使用JDK动态代理给UserMapper接口生成一个代理类对象
     *
     * @param mapperClass 字节码
     * @param <T>
     * @return
     * @throws Exception
     */
    @Override
    public <T> T getMapper(Class<?> mapperClass) throws Exception {
        //使用JDK动态代理技术为Mapper接口层生成代理类对象,其实就是实现类,并返回
        // 直接new一个接口,匿名内部类的写法
        Object instance = Proxy.newProxyInstance(mapperClass.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // <select id="list" ... >
                // 接口中的方法名字 "list"
                String methodName = method.getName();
                // UserMapper接口的全类名 "com.bruce.mapper.UserMapper"
                String className = method.getDeclaringClass().getName();
                // 拼接SQL的唯一标识
                String statementId = className + "." + methodName;
                // 获取方法被调用之后的返回值类型
                Type genericReturnType = method.getGenericReturnType();

                Map<String, MappedStatement> mapperStatemengMap = configuration.getMapperStatemengMap();
                MappedStatement mappedStatement = mapperStatemengMap.get(statementId);

                // 根据标签判断SQL类型
                if ("insert".equals(mappedStatement.getSqlType())) {
                    return insert(statementId, args);
                } else if ("update".equals(mappedStatement.getSqlType())) {
                    return update(statementId, args);
                } else if ("delete".equals(mappedStatement.getSqlType())) {
                    return delete(statementId, args);
                }

                //判断是否进行泛型类型的参数化(返回结果是泛型的话,查询集合)
                if (genericReturnType instanceof ParameterizedType) {
                    List<Object> objects = selectList(statementId, args);
                    return objects;
                } else {
                    // 查询结果不是泛型
                    return selectOne(statementId, args);
                }

            }
        });
        return (T) instance;
    }
}

SqlSessionFactory接口:

package org.apache.ibatis.session;

/**
 * @Author: Juechen
 * @Date: 2024/4/22
 * @Description: SqlSessionFactory
 * @Version: 1.0
 **/
public interface SqlSessionFactory {

    // 获取sqlsession
    SqlSession openSession();

}

DefaultSqlSessionFactory类:SqlSessionFactory的实现类

package org.apache.ibatis.session;

import org.apache.ibatis.configuration.Configuration;

/**
 * @Author: Juechen
 * @Date: 2024/4/22
 * @Description: SqlSessionFactory 的实现类
 * @Version: 1.0
 **/
public class DefaultSqlSessionFactory implements SqlSessionFactory{

    private Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public SqlSession openSession() {

        return new DefaultSqlSession(configuration);
    }
}

SqlSessionFactoryBuilder类:生成SqlSessionFactory对象

package org.apache.ibatis.session;

import org.apache.ibatis.configuration.Configuration;
import org.apache.ibatis.configuration.XmlConfigBuilder;
import org.dom4j.DocumentException;

import java.beans.PropertyVetoException;
import java.io.InputStream;

/**
 * @Author: Juechen
 * @Date: 2024/4/22
 * @Description: 生成SqlSessionFactory对象
 * @Version: 1.0
 **/
public class SqlSessionFactoryBuilder {

    public SqlSessionFactory build(InputStream inputStream) throws PropertyVetoException, DocumentException {
        // 获取configuration对象
        Configuration configuration = new Configuration();
        XmlConfigBuilder xmlConfigBuilder = new XmlConfigBuilder(configuration);
        xmlConfigBuilder.parseMyBatisConfig(inputStream);
        // 创建SqlSessionFactory
        DefaultSqlSessionFactory sessionFactory = new DefaultSqlSessionFactory(configuration);
        return sessionFactory;
    }
}


UserMapper.xml文件:

<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="com.bruce.mapper.UserMapper">

    <!--查询
        parameterType="" 输入参数类型
        resultType="" 返回参数类型
    -->
    <select id="list" resultType="com.bruce.pojo.User">
        select * from user
    </select>

    <select id="findById" resultType="com.bruce.pojo.User" parameterType="com.bruce.pojo.User">
        select * from user where id = #{id}
    </select>

    <insert id="insert" resultType="java.lang.Integer" parameterType="com.bruce.pojo.User">
        insert into user values (null,#{username})
    </insert>

    <update id="updateUser" resultType="java.lang.Integer" parameterType="com.bruce.pojo.User">
        update user set username=#{username} where id=#{id}
    </update>

    <delete id="delete" resultType="java.lang.Integer" parameterType="com.bruce.pojo.User">
        delete from user where id=#{id}
    </delete>


</mapper>

mybatis-config.xml文件:在这里填入你的数据库信息 !

<?xml version="1.0" encoding="UTF-8" ?>
<configuration>

    <!--1.数据库配置信息-->
    <dataSource>
        <property name="driverClass" value="com.mysql.cj.jdbc.Driver"></property>
        <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/hand-write-mybatis"></property>
        <property name="username" value="xxxxxx"></property>
        <property name="password" value="xxxxxx"></property>
    </dataSource>

    <!--2.存放mapper.xml的全路径-->
    <mapper resource="mappers/UserMapper.xml"></mapper>
</configuration>

test

com.bruce.dao包:

TestUserDao、MyInvercationHandler类:回顾动态代理:测试生成一个代理对象

package com.bruce.dao;

import com.bruce.pojo.User;
import org.junit.Test;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;

/**
 * @Author: Juechen
 * @Date: 2024/4/22
 * @Description: 回顾动态代理:测试生成一个代理对象
 * @Version: 1.0
 **/
public class TestUserDao {

    @Test
    public void testUserDao() {

        Class c = UserDao.class;
        MyInvercationHandler myInvercationHandler = new MyInvercationHandler();
        // 生成一个代理对象
        UserDao userDao = (UserDao) Proxy.newProxyInstance(c.getClassLoader(), new Class[]{c}, myInvercationHandler);
        int i = userDao.add(new User(111, "小黑"));
        System.out.println(i > 0 ? "新增成功" : "新增失败");
    }
}

/**
 * 方法拦截器
 */
class MyInvercationHandler implements InvocationHandler {

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 调用目标方法的add
        if (method.getName().equals("add")) {
            System.out.println("执行方法的参数:" + args);
            Object o = args[0];
            System.out.println(o);
            return 1;
        } else
            return null;
    }
}

UserDao接口: 测试动态代理,同上

package com.bruce.dao;

import com.bruce.pojo.User;

public interface UserDao {

    Integer add(User user);
}

TestJdbc类:测试JDBC

package com.bruce.test;

import com.bruce.pojo.User;
import org.junit.Test;

import java.sql.*;
import java.util.ArrayList;

/**
 * @Author: Juechen
 * @Date: 2024/4/21
 * @Description: 回顾JDBC
 * @Version: 1.0
 **/

/*1.JDBC问题分析
    - 数据库连接创建、释放频繁造成系统资源浪费,从而影响性能
    - sql语句存在硬编码,造成代码不易维护
    - 使用preparedStatement向占有位符号传参数存在硬编码问题
    - 对结果解析存在硬编码(查询列名),sgl变化导致解析代码变化

  2.问题解决方案
    - 数据库频繁创建连接以及释放资源:连接池
    - sql语句及参数存在硬编码:配置文件
    - 手动解析封装返回结果集:反射、内省
    - 这也就解释了前面提的问题,为什么要有 mybatis 框架的存在?这是因为为了解决JDBC 操作存在的这些问题
*/
public class TestJdbc {

    @Test
    public void testQuery() {

        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        ArrayList<User> users = new ArrayList<User>();
        try {
            // 注册JDBC驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            // 获取连接对象
            // 这里填入自己的数据库信息
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/hand-write-mybatis", "XXXX", "XXXX");
            // 获取preparedStatement
            preparedStatement = connection.prepareStatement("select * from user");
            // 执行SQL到ResultSet
            resultSet = preparedStatement.executeQuery();
            // 遍历数据
            while (resultSet.next()) {
                int id = resultSet.getInt("id");
                String userName = resultSet.getString("userName");
                User user = new User(id, userName);
                users.add(user);
            }

        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            try {
                if (resultSet != null) {
                    resultSet.close();
                }
                if (preparedStatement != null) {
                    preparedStatement.close();
                }
                if (connection != null) {
                    connection.close();
                }
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
        System.out.println("查询到的数据是:" + users);
    }

    @Test
    public void testInsert() {

        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            // 注册JDBC驱动
            Class.forName("com.mysql.cj.jdbc.Driver");
            // 获取连接对象
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/hand-write-mybatis", "root", "385962");
            // 获取preparedStatement
            preparedStatement = connection.prepareStatement("insert into user values (null,?)");
            preparedStatement.setObject(1,"大明");
            // 执行SQL到ResultSet
            int count = preparedStatement.executeUpdate();
            System.out.println(count > 0 ? "新增成功" : "新增失败");

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

TestMybatis类:项目中最重要的测试MyBatis类

package com.bruce.test;

import com.bruce.mapper.UserMapper;
import com.bruce.pojo.User;
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 org.junit.Before;
import org.junit.Test;

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

/**
 * @Author: Juechen
 * @Date: 2024/4/21
 * @Description: mybatis
 * @Version: 1.0
 **/
public class TestMybatis {
    UserMapper userMapper;

    /**
     * 在单元测试之前执行
     */
    @Before
    public void init() {
        try {
            // 从xml中构建SqlSessionFactory
            String resource = "mybatis-config.xml";
            // 加载MyBatis的配置文件,返回字节流
            InputStream inputStream = Resources.getResourceAsStream(resource);
            SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            SqlSession sqlSession = sqlSessionFactory.openSession();
            //框架底层使用JDK动态代理给接口生成实现类对象
            userMapper = sqlSession.getMapper(UserMapper.class);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }

    @Test
    public void testList() {
        List<User> users = userMapper.list();
        for (User user : users) {
            System.out.println(user);
        }
    }

    @Test
    public void testFindById() {
        User u = new User();
        u.setId(5);
        User user = userMapper.findById(u);
        System.out.println(user);
    }

    @Test
    public void testInsert() {
        User user = new User();
        user.setUsername("小黑");
        Integer insert = userMapper.insert(user);
        System.out.println(insert > 0 ? "新增成功" : "新增失败");
    }

    @Test
    public void testUpdate() {
        User user = new User();
        user.setId(6);
        user.setUsername("大黑");
        Integer update = userMapper.updateUser(user);
        System.out.println(update > 0 ? "更新成功" : "更新失败");
    }

    @Test
    public void testDelete() {
        User user = new User();
        user.setId(2);
        Integer delete = userMapper.delete(user);
        System.out.println(delete > 0 ? "删除成功" : "删除失败");
    }
}

TestXmlConfigBuilder类:测试XmlConfigBuilder

package com.bruce.test;

import org.apache.ibatis.configuration.Configuration;
import org.apache.ibatis.configuration.XmlConfigBuilder;
import org.apache.ibatis.io.Resources;
import org.dom4j.DocumentException;
import org.junit.Test;

import java.beans.PropertyVetoException;
import java.io.InputStream;

/**
 * @Author: Juechen
 * @Date: 2024/4/22
 * @Description: 测试XmlConfigBuilder
 * @Version: 1.0
 **/
public class TestXmlConfigBuilder {

    @Test
    public void test1(){

        try {
            Configuration configuration = new Configuration();
            XmlConfigBuilder xmlConfigBuilder = new XmlConfigBuilder(configuration);
            InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
            xmlConfigBuilder.parseMyBatisConfig(inputStream);
            System.out.println("mybatis-config.xml解析完毕!");
            System.out.println(configuration);
        } catch (DocumentException e) {
            throw new RuntimeException(e);
        } catch (PropertyVetoException e) {
            throw new RuntimeException(e);
        }
    }
}

TestBook类:测试Dom4j解析book.xml文件

package com.bruce.test;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.junit.Test;

import java.util.List;

/**
 * @Author: Juechen
 * @Date: 2024/4/21
 * @Description: 测试Dom4j解析book.xml文件
 * @Version: 1.0
 **/
public class TestBook {

    @Test
    public void test1() {
        try {
            // 创建一个解析器
            SAXReader saxReader = new SAXReader();
            //获取一个文档对象
            Document document = saxReader.read("D:\\Java\\JAVA2022\\Handwriting-MyBatis\\src\\test\\resources\\book.xml");
            //获取XML文件的根节点 <books></books>
            Element rootElement = document.getRootElement();
            System.out.println("根节点的名字是:" + rootElement.getName());

            // 获取子节点的集合
            List<Element> elements = rootElement.elements();
            System.out.println("子节点的个数为:" + elements.size());

            System.out.println("-------------------------------------");

            // 遍历子节点的集合
            for (Element element : elements) {
                // 节点属性的对象
                //这两行代码完全可以用一行搞定!
//                Attribute attribute = element.attribute("id");
//                String value = attribute.getValue();
                String value = element.attributeValue("id");
                System.out.println("id = " + value);
                List<Element> children = element.elements();
                for (Element child : children) {
                    // 标签的名字
                    String tagname = child.getName();
                    // 标签内部的值
                    String text = child.getText();
                    System.out.println(tagname + "=" + text);

                }
                System.out.println("-------------------------------------");
            }


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

    }
}


book.xml文件:

<?xml version="1.0" encoding="UTF-8" ?>
<books>
    <book id="b1">
        <name>三国演义</name>
        <price>59.8</price>
        <publish>人民教育出版社</publish>
    </book>
    <book id="b2">
        <name>水浒传</name>
        <price>100.5</price>
        <publish>机械工业出版社</publish>
    </book>
    <book id="b3">
        <name>Java编程思想</name>
        <price>88.8</price>
        <publish>机械工业出版社</publish>
    </book>
</books>
  • 22
    点赞
  • 15
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值