MyBatis核心机制

实现MyBatis核心机制环境搭建

1.核心框架示意图

img

2.模块搭建

1.创建maven项目

CleanShot 2024-08-08 at 15.29.52@2x

2.引入依赖
<?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>
    <parent>
        <groupId>com.sunxiansheng</groupId>
        <artifactId>core_mechanisms</artifactId>
        <version>1.0-SNAPSHOT</version>
    </parent>

    <artifactId>sun-mybatis</artifactId>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <!-- 解析xml -->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.6.1</version>
        </dependency>
        <!-- mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.22</version>
        </dependency>
        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.30</version>
        </dependency>
        <!-- junit -->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
        </dependency>
    </dependencies>

</project>
3.连接数据库
4.数据库表设计
CREATE DATABASE `sun_mybatis`;
USE `sun_mybatis`;

CREATE TABLE `monster`
(
    `id`       INT          NOT NULL AUTO_INCREMENT,
    `age`      INT          NOT NULL,
    `birthday` DATE DEFAULT NULL,
    `email`    VARCHAR(255) NOT NULL,
    `gender`   TINYINT      NOT NULL,
    `name`     VARCHAR(255) NOT NULL,
    `salary`   DOUBLE       NOT NULL,
    PRIMARY KEY (`id`)
) CHARSET = utf8;

INSERT INTO `monster` (`age`, `birthday`, `email`, `gender`, `name`, `salary`)
VALUES (25, '1998-01-15', 'example@example.com', 1, 'John Doe', 50000.00);

3.设计图

img

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

1.目录

CleanShot 2024-08-08 at 16.57.51@2x

2.sun-config.xml 配置文件

<?xml version="1.0" encoding="utf-8" ?>
<!-- 配置数据库连接信息 -->
<database>
    <!-- 驱动 -->
    <property name="driverClassName" value="com.mysql.cj.jdbc.Driver"/>
    <!-- 连接地址 -->
    <property name="url" value="jdbc:mysql://bj--grp-.sql.tencentcdb.com:24169/sun_mybatis?useSSL=false&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>    <!-- 用户名 -->
    <!-- 用户名 -->
    <property name="username" value=""/>
    <!-- 密码 -->
    <property name="password" value=""/>
</database>

3.SunConfiguration.java 读取配置文件获取数据库连接

package com.sunxiansheng.mybatis.sqlsession;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.InputStream;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.List;

/**
 * Description: 读取xml配置文件
 *
 * @Author sun
 * @Create 2024/8/8 16:25
 * @Version 1.0
 */
public class SunConfiguration {

    private static final Logger log = LoggerFactory.getLogger(SunConfiguration.class);
    private static ClassLoader loader = ClassLoader.getSystemClassLoader();

    /**
     * 读取xml文件
     *
     * @param resource 这个必须是类路径下的文件路径,不用带第一个斜杠,sun-config.xml就可以
     * @return
     */
    public Connection build(String resource) {
        InputStream resourceAsStream = loader.getResourceAsStream(resource);
        // 使用dom4j解析配置文件
        SAXReader saxReader = new SAXReader();
        try {
            // 获取文档
            Document document = saxReader.read(resourceAsStream);
            // 根元素
            Element rootElement = document.getRootElement();
            // 根元素下的所有property元素
            List<Element> property = rootElement.elements("property");
            // 初始化连接信息
            String driverClassName = null;
            String url = null;
            String username = null;
            String password = null;
            // 遍历property元素获取连接信息
            for (Element element : property) {
                String name = element.attributeValue("name");
                String value = element.attributeValue("value");
                switch (name) {
                    case "driverClassName":
                        driverClassName = value;
                        log.info("加载驱动类:{}", value);
                        break;
                    case "url":
                        url = value;
                        log.info("加载url:{}", value);
                        break;
                    case "username":
                        username = value;
                        log.info("加载用户名:{}", value);
                        break;
                    case "password":
                        password = value;
                        log.info("加载密码:{}", value);
                        break;
                    default:
                        throw new RuntimeException("未知的property属性名");
                }
            }
            // 类加载驱动类
            Class.forName(driverClassName);
            return DriverManager.getConnection(url, username, password);
        } catch (DocumentException | ClassNotFoundException | SQLException e) {
            log.error("解析xml配置文件失败", e);
            throw new RuntimeException(e);
        }
    }

}

4.SunConfigurationTest.java 测试

package com.sunxiansheng.mybatis.sqlsession;

import org.junit.Test;

import java.sql.Connection;

/**
 * Description: 读取xml配置文件
 *
 * @Author sun
 * @Create 2024/8/8 16:25
 * @Version 1.0
 */
public class SunConfigurationTest {

    @Test
    public void build() {
        SunConfiguration sunConfiguration = new SunConfiguration();
        Connection build = sunConfiguration.build("sun-config.xml");
        System.out.println("build = " + build);
    }

}

使用SunExecutor来执行SQL

1.目录

CleanShot 2024-08-09 at 13.02.47@2x

2.Monster.java

package com.sunxiansheng.mybatis.entity;

import lombok.Data;

import java.io.Serializable;
import java.util.Date;

@Data
public class Monster implements Serializable {

    private Integer id;

    private Integer age;

    private Date birthday;

    private String email;

    private Byte gender;

    private String name;

    private Double salary;

    private static final long serialVersionUID = 1L;
}

3.Executor.java

package com.sunxiansheng.mybatis.sqlsession;

/**
 * Description: 执行器接口
 *
 * @Author sun
 * @Create 2024/8/9 12:37
 * @Version 1.0
 */
public interface Executor {

    public <T> T query(String sql, Object parameter);

}

4.SunExecutor.java 具体的执行器

package com.sunxiansheng.mybatis.sqlsession;

import com.sunxiansheng.mybatis.entity.Monster;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;

/**
 * Description: 执行器
 *
 * @Author sun
 * @Create 2024/8/9 12:39
 * @Version 1.0
 */
public class SunExecutor implements Executor{

    public static final SunConfiguration configuration = new SunConfiguration();

    private static final Logger log = LoggerFactory.getLogger(SunExecutor.class);

    @Override
    public <T> T query(String sql, Object parameter) {
        // 获取DB连接
        Connection connection = configuration.build("sun-config.xml");
        log.info("获取连接:{}", connection);
        ResultSet resultSet = null;
        PreparedStatement preparedStatement = null;
        // 实体类
        Monster monster = new Monster();
        try {
            // sql预处理
            preparedStatement = connection.prepareStatement(sql);
            // 填参数
            preparedStatement.setString(1, parameter.toString());
            // 执行查询
            resultSet = preparedStatement.executeQuery();
            // 将结果封装到实体类中(写死了)
            while (resultSet.next()) {
                monster.setId(resultSet.getInt("id"));
                monster.setAge(resultSet.getInt("age"));
                monster.setBirthday(resultSet.getDate("birthday"));
                monster.setEmail(resultSet.getString("email"));
                monster.setGender(resultSet.getByte("gender"));
                monster.setName(resultSet.getString("name"));
                monster.setSalary(resultSet.getDouble("salary"));
            }
            log.info("查询结果:{}", monster);
        } 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 (Exception e) {
                throw new RuntimeException(e);
            }
        }
        // 这里的T是由类型推断来指定的,也就是使用什么类型接受,就返回什么类型
        return (T) monster;
    }

}

5.SunConfigurationTest.java 测试

@Test
public void executor() {
    SunExecutor sunExecutor = new SunExecutor();
    // 这里是使用的类型推断自动转换类型
    Monster monster = sunExecutor.query("select * from monster where id = ?", 1);
}

将SqlSession封装到执行器

1.目录

CleanShot 2024-08-09 at 13.17.02@2x

2.SunSqlSession.java

package com.sunxiansheng.mybatis.sqlsession;

/**
 * Description: SqlSession
 *
 * @Author sun
 * @Create 2024/8/9 13:08
 * @Version 1.0
 */
public class SunSqlSession {

    // 执行器(操作db)
    private Executor executor = new SunExecutor();
    // 配置(用于获取连接)
    private SunConfiguration configuration = new SunConfiguration();

    public <T> T selectOne(String statement, Object parameter) {
        return executor.query(statement, parameter);
    }
}


3.测试

@Test
public void selectOne() {
    SunSqlSession sunSqlSession = new SunSqlSession();
    Monster monster = sunSqlSession.selectOne("select * from monster where id = ?", 1);
    System.out.println("monster = " + monster);
}

开发MapperBean和Function

1.目录

CleanShot 2024-08-09 at 13.39.36@2x

2.MonsterMapper.java

package com.sunxiansheng.mapper;

import com.sunxiansheng.entity.Monster;

/**
 * Description: MonsterMapper
 *
 * @Author sun
 * @Create 2024/8/9 13:21
 * @Version 1.0
 */
public interface MonsterMapper {

    public Monster getMonsterById(Integer id);

}

3.MonsterMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="com.sunxiansheng.mapper.MonsterMapper">
    <select id="getMonsterById" resultType="com.sunxiansheng.entity.Monster">
        select * from monster where id = ?
    </select>
</mapper>

4.Function.java 记录对应的mapper.xml的方法信息

package com.sunxiansheng.mybatis.config;

import lombok.Data;

/**
 * Description: 记录对应的mapper.xml的方法信息
 *
 * @Author sun
 * @Create 2024/8/9 13:31
 * @Version 1.0
 */
@Data
public class Function {

    /**
     * sql类型
     */
    private String sqlType;

    /**
     * 方法名
     */
    private String funcName;

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

    /**
     * 返回类型
     */
    private Object resultType;

}

5.MapperBean.java 将mapper接口信息进行封装

package com.sunxiansheng.mybatis.config;

import java.util.List;

/**
 * Description: 将mapper接口信息进行封装
 *
 * @Author sun
 * @Create 2024/8/9 13:35
 * @Version 1.0
 */
public class MapperBean {

    /**
     * 接口全路径
     */
    private String interfaceName;

    /**
     * 方法列表
     */
    private List<Function> functions;

}

读取xml文件解析MapperBean

1.目录

CleanShot 2024-08-09 at 14.03.00@2x

2.Function.java 加链式调用注解

CleanShot 2024-08-09 at 14.03.31@2x

3.MapperBean.java 加Data注解

CleanShot 2024-08-09 at 14.03.51@2x

4. SunConfiguration.java 增加解析MapperBean的方法

/**
 * 读取mapper.xml文件,构建MapperBean
 * @param path
 * @return
 */
public MapperBean readMapper(String path) {
    // 要返回的结果
    MapperBean mapperBean = new MapperBean();
    // 使用类加载器读取配置文件
    ClassLoader loader = ClassLoader.getSystemClassLoader();
    InputStream resourceAsStream = loader.getResourceAsStream(path);
    // 解析xml文件
    SAXReader saxReader = new SAXReader();
    try {
        // 获取文档
        Document document = saxReader.read(resourceAsStream);
        // 获取根元素
        Element rootElement = document.getRootElement();
        // 获取根元素的属性:namespace
        String namespace = rootElement.attributeValue("namespace");
        // 获取子元素
        List<Element> elements = rootElement.elements();
        // 遍历子元素
        List<Function> functions = elements.stream().map(
                element -> {
                    Function function = new Function();
                    // 获取sql类型
                    String sqlType = element.getName();
                    // 获取方法名
                    String funcName = element.attributeValue("id");
                    // 获取返回类型
                    String resultType = element.attributeValue("resultType");
                    // 获取sql语句
                    String sql = element.getTextTrim();
                    // 封装到Function对象中
                    function.setSqlType(sqlType)
                            .setFuncName(funcName)
                            .setResultType(resultType)
                            .setSql(sql);
                    return function;
                }
        ).collect(Collectors.toList());
        // 构建MapperBean
        mapperBean.setInterfaceName(namespace);
        mapperBean.setFunctions(functions);
    } catch (DocumentException e) {
        throw new RuntimeException(e);
    }
    // 输出mapperBean的日志
    log.info("mapperBean:{}", mapperBean);
    return mapperBean;
}

5.SunConfigurationTest.java 测试

    @Test
    public void readMapper() {
        SunConfiguration sunConfiguration = new SunConfiguration();
        sunConfiguration.readMapper("MonsterMapper.xml");
    }

CleanShot 2024-08-09 at 14.05.29@2x

动态代理Mapper方法

1.目录

CleanShot 2024-08-09 at 14.58.13@2x

2.MapperProxyFactory.java 代理工厂,可以获取接口的代理对象

package com.sunxiansheng.mybatis.sqlsession;

import java.lang.reflect.Proxy;

/**
 * Description: 代理工厂
 *
 * @Author sun
 * @Create 2024/8/9 14:14
 * @Version 1.0
 */
public class MapperProxyFactory {

    public static <T> T getMapperProxy(Class<T> clazz, SunSqlSession sunSqlSession, SunConfiguration sunConfiguration) {
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new SunMapperProxy(sunSqlSession, clazz, sunConfiguration));
    }

}

3.SunMapperProxy.java 代理逻辑

package com.sunxiansheng.mybatis.sqlsession;

import com.sunxiansheng.mybatis.config.Function;
import com.sunxiansheng.mybatis.config.MapperBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.List;

/**
 * Description: 代理Mapper接口
 *
 * @Author sun
 * @Create 2024/8/9 14:08
 * @Version 1.0
 */
public class SunMapperProxy implements InvocationHandler {

    private static final Logger log = LoggerFactory.getLogger(SunMapperProxy.class);
    /**
     * 用于连接数据库和执行sql
     */
    private SunSqlSession sunSqlSession;

    /**
     * mapper文件名
     */
    private String mapperFile;

    /**
     * 配置类(可以根据mapper文件名来获取MapperBean)
     */
    private SunConfiguration sunConfiguration;

    public SunMapperProxy(SunSqlSession sunSqlSession, Class<?> mapperFile, SunConfiguration sunConfiguration) {
        this.sunSqlSession = sunSqlSession;
        this.mapperFile = mapperFile.getSimpleName() + ".xml";
        this.sunConfiguration = sunConfiguration;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        // 获取方法名
        String methodName = method.getName();
        // 获取MapperBean
        MapperBean readMapper = sunConfiguration.readMapper(mapperFile);
        // 获取Function列表,过滤出需要执行的Function
        List<Function> functions = readMapper.getFunctions();
        Function targetFunction = null;
        for (Function function : functions) {
            if (methodName.equals(function.getFuncName())) {
                targetFunction = function;
                break;
            }
        }
        // 获取Function的信息
        String sqlType = targetFunction.getSqlType();
        String sql = targetFunction.getSql();
        // 将sql的第一个问号使用参数替换,使用字符串处理
        // 根据sqlType执行sql
        if (sqlType.equals("select")) {
            Object selected = sunSqlSession.selectOne(sql, args[0]);
            log.info("invoke查询结果:{}", selected);
            return selected;
        }
        return null;
    }
}

4.测试

@Test
public void getResult() {
    MonsterMapper mapperProxy = MapperProxyFactory.getMapperProxy(MonsterMapper.class, new SunSqlSession(), new SunConfiguration());
    Monster monsterById = mapperProxy.getMonsterById(1);
}
  • 3
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

S-X-S

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

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

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

打赏作者

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

抵扣说明:

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

余额充值