mabatis机制实现

上一篇, 我们学习到了 mabatis 上

接下来我们学习, 手动实现MaBatis底层机制

在这里插入图片描述

实现任务阶段一

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

🥦分析 + 代码实现

●分析示意图
在这里插入图片描述创建项目

●代码实现
1.创建src/main/resouces/zzw_mybatis.xml 不一定非叫 zzw_mybatis
在这里插入图片描述

<?xml version="1.0" encoding="UTF-8" ?>
<database>
    <!--配置连接数据库的信息-->
    <property name="driverClassName" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://127.0.0.1:3306/zzw_mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
    <property name="username" value="root"/>
    <property name="password" value="zzw"/>
</database>

2.创建com.zzw.zzwmybatis.sqlsession.ZzwConfiguration.java
遍历xml指定元素 参考
数据库连接的5种方式 参考

/**
 * @author 赵志伟
 * @version 1.0
 * 读取xml文件, 建立连接
 */
public class ZzwConfiguration {

    //属性-类的加载器
    private static ClassLoader loader = ClassLoader.getSystemClassLoader();

    //读取xml文件信息, 并处理
    //Connection 是java.sql包下的
    public Connection build(String resource) {

        Connection connection = null;//java.sql

        //1.得到解析器, 解析配置文件 dom4j
        SAXReader reader = new SAXReader();
        //2.加载配置文件zzw_mybatis.xml, 获取到对应的InputStream
        InputStream inputStream = loader.getResourceAsStream(resource);
        try {
            //3.得到xml文件的文档
            Document document = reader.read(inputStream);
            //4.获取rootElement / zzw_mybatis.xml的根元素 / <database/>
            Element rootElement = document.getRootElement();
            System.out.println("root="+ rootElement.getName());//root=database
            //5.解析rootElement, 返回Connection => 单独写一个方法
            connection = evalDataSource(rootElement);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return connection;
    }

    //这个方法会解析zzw_mybatis.xml信息, 并返回Connection
    //eval: 评估/解析
    private Connection evalDataSource(Element node) {
        if (!"database".equals(node.getName())) {
            throw new RuntimeException("root 节点应该是<database/>");
        }
        //连接DB的必要参数
        String driverClassName = null;
        String url = null;
        String username = null;
        String password = null;

        //遍历node下的子节点, 获取属性值
        List<Element> properties = node.elements("property");
        for (Element property : properties) {

            String name = property.attributeValue("name");
            String value = property.attributeValue("value");

            //判断是否得到name和value
            if (name == null || value == null) {
                throw new RuntimeException("property 节点没有设置name或者value属性");
            }

            switch (name) {
                case "driverClassName":
                    driverClassName = value;
                    break;
                case "url":
                    url = value;
                    break;
                case "username":
                    username = value;
                    break;
                case "password":
                    password = value;
                    break;
                default:
                    throw new RuntimeException("属性名没有匹配到");
            }
        }
        Connection connection = null;
        try {
            Class.forName(driverClassName);
            connection = DriverManager.getConnection(url, username, password);
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
        return connection; //返回Connection
    }
}

🥦完成测试

com.zzw.test.ZzwMyBatisTest.java

在这里插入图片描述

public class ZzwMyBatisTest {
    @Test
    public void build() {
        ZzwConfiguration zzwConfiguration = new ZzwConfiguration();
        Connection connection = zzwConfiguration.build("zzw_mybatis.xml");
        System.out.println("connection--" + connection);
    }
}

实现任务阶段二

🍍编写执行器, 输入SQL语句, 完成操作

🥦分析 + 代码实现

●分析示意图
说明: 我们把对数据库的操作, 会封装到一套Executor机制中, 程序具有更好的扩展性, 结构更加清晰

下图在原生mybatis的项目中可以看到
在这里插入图片描述在这里插入图片描述

●代码实现
1.新建com.zzw.entity.Monster.java

/**
 * @author 赵志伟
 * @version 1.0
 * Monster和 monster表有映射关系
 *
 * 解读
 * @Getter 就会给所有属性 生成对应的getter方法
 * @Setter 就会给所有属性 生成对应的setter方法
 * @ToString 生成 toString...
 * @NoArgsConstructor 生成无参构造器
 * @AllArgsConstructor 生成全参构造器
 * @Data 注解 
 * 如何选择主要还是看自己的需求
 */
//@Getter
//@Setter
//@ToString
//@NoArgsConstructor
//@AllArgsConstructor
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Monster {
    private Integer id;
    private Integer age;
    private Date birthday;//java.util
    private String email;
    private Integer gender;
    private String name;
    private Double salary;
}

在这里插入图片描述

@Data注解包含了其他注解
在这里插入图片描述

2.新建com.zzw.zzwmybatis.sqlsession.Executor接口

public interface Executor {
    //泛型方法
    public <T> T query(String sql, Object parameter);
}

3.新建com.zzw.zzwmybatis.sqlsession.ZzwExecutor.java

自定义泛型方法, 参考

public class ZzwExecutor implements Executor {

    //属性
    private ZzwConfiguration zzwConfiguration =
            new ZzwConfiguration();

	//编写方法, 通过ZzwConfiguration对象, 返回连接
    private Connection getConnection() {
        Connection connection = zzwConfiguration.build("zzw_mybatis.xml");
        return connection;
    }
    
    /**
     * 根据 sql 查询结果
     * @param statement
     * @param parameter
     * @return
     * @param <T>
     */
    @Override
    public <T> T query(String sql, Object parameter) {
        //得到连接Connection
        Connection connection = getConnection();
        //查询返回的结果集
        ResultSet resultSet = null;
        PreparedStatement preparedStatement = null;

        try {
            preparedStatement = connection.prepareStatement(sql);
            //设置参数, 如果参数多, 可以使用数组处理
            preparedStatement.setString(1, parameter.toString());
            resultSet = preparedStatement.executeQuery();
            //把resultSet数据封装到对象-monster
            //说明: 这里做了简化处理
            //认为返回的结果就是一个monster记录
            //完善的写法是一套反射机制
            Monster monster = new Monster();

            //遍历结果集, 把数据封装到monster对象
            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.getInt("gender"));
                monster.setName(resultSet.getString("name"));
                monster.setSalary(resultSet.getDouble("salary"));
            }
            return (T) 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 (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    }
}

🥦完成测试

public class ZzwMyBatisTest {
    @Test
    public void query() {
        ZzwExecutor executor = new ZzwExecutor();
        Monster monster
                = executor.query("SELECT * FROM `monster` WHERE id = ?", 1);
        System.out.println("monster--" + monster);
    }
}

实现任务阶段三

🍍将SqlSession封装到执行器

🥦分析 + 代码实现

●分析示意图. 先观察原生MyBatisSqlSession接口和默认实现. 在原生mybatis项目
在这里插入图片描述在这里插入图片描述

●功能实现

●代码实现
1.创建com.zzw.zzwmybatis.sqlsession.ZzwSqlSession.java

/**
 * @author 赵志伟
 * @version 1.0
 * ZzwSqlSession: 搭建Configuration (连接) 和 Executor 之间的桥梁
 * 这里有操作DB的具体方法
 */
public class ZzwSqlSession {
    //属性
    //执行器
    private Executor executor = new ZzwExecutor();
    //配置
    private ZzwConfiguration zzwConfiguration =
            new ZzwConfiguration();

    //编写方法selectOne, 返回一条记录-对象[做了简化]
    //说明: 在原生的mybatis中, statement不是sql, 而是要执行的接口方法
    //这里我们是做了简化
    public <T> T selectOne(String statement, Object parameter) {
        return executor.query(statement, parameter);
    }

    //selectList - update - delete - insert
}

🥦完成测试

public class ZzwMyBatisTest {
    @Test
    public void selectOne() {
        ZzwSqlSession zzwSqlSession = new ZzwSqlSession();
        Monster monster =
                zzwSqlSession.selectOne("select * from `monster` where id = ?", 1);
        System.out.println("monster--" + monster);
    }
}

实现任务阶段四

🍍开发Mapper接口和Mapper.xml

🥦分析 + 代码实现

●分析示意图
在这里插入图片描述在这里插入图片描述

●代码实现
1.创建com.zzw.mapper.MonsterMapper接口

/**
 * @author 赵志伟
 * @version 1.0
 * MonsterMapper: 声明对db的crud方法
 */
public interface MonsterMapper {
    //查询方法
    public Monster getMonsterById(Integer id);
}

2.src/main/resources(类路径)下新建 MonsterMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>
<mapper namespace="com.zzw.mapper.MonsterMapper">
    <!--实现配置接口方法getMonsterById-->
    <select id="getMonsterById" resultType="com.zzw.entity.Monster">
        select * from monster where id = ?
    </select>
</mapper>

实现任务阶段五

🍍开发Mapper接口相映射的MapperBean

🥦分析 + 代码实现

●分析示意图
在这里插入图片描述

●代码实现
1.创建com.zzw.zzwmybatis.config.Function.java

/**
 * @author 赵志伟
 * @version 1.0
 * Function: 记录对应的Mapper的方法信息
 */
@Setter
@Getter
@ToString
public class Function {
    //属性
    private String sqlType;//sql类型. 比如select, insert, update, delete
    private String funcName;//方法名
    private String sql;//执行sql语句
    private Object resultType;//返回类型
    private String parameter;//参数类型
}

2.创建com.zzw.zzwmybatis.config.MapperBean.java

/**
 * @author 赵志伟
 * @version 1.0
 * MapperBean: 将Mapper信息, 进行封装
 */
@Setter
@Getter
@ToString
public class MapperBean {
	//接口的全路径-接口名
    private String interfaceName;
    
    //接口下的所有方法-集合
    private List<Function> functions;
}

实现任务阶段六

🍍在ZzwConfiguration读取XxxMapper.xml, 能够创建MapperBean对象

🥦分析 + 代码实现

●分析示意图

●代码实现
1.修改com.zzw.zzwmybatis.sqlsession.ZzwConfiguration.java, 增加方法readMapper

//读取xxxMapper.xml, 能够创建MapperBean对象
//path 就是xml的路径+文件名, 是从类的加载路径计算的
//如果: xxxMapper.xml 文件是放在resources目录下, 直接传入xml文件名即可
public MapperBean readMapper(String path) {

    MapperBean mapperBean = new MapperBean();

    //1.得到解析器 -> dom4j
    SAXReader reader = new SAXReader();
    //2.获取到xml文件对应的InputStream
    InputStream inputStream = loader.getResourceAsStream(path);
    try {
        //3.得到xml文件的文档
        Document document = reader.read(inputStream);
        //4.获取xml文档的根元素 <mapper/>
        Element rootElement = document.getRootElement();
        System.out.println("root="+ rootElement);

        //5.获取到namespace
        String namespace = rootElement.attributeValue("namespace").trim();
        //设置mapperBean的属性interfaceName
        mapperBean.setInterfaceName(namespace);

        //6.得到rootElement的迭代器-可以遍历它的子节点/子元素-生成Function
        Iterator<Element> rootIterator = rootElement.elementIterator();

        //保存接口下所有的方法信息
        List<Function> functions = new ArrayList<>();
        //遍历它的子节点/子元素-生成Function
        while (rootIterator.hasNext()) {
            //取出一个子元素-dom4j.Element
            Element element = rootIterator.next();
            /*
            <select id="getMonsterById" resultType="com.zzw.entity.Monster">
                select * from monster where id = ?
            </select>
             */
            Function function = new Function();
            String sqlType = element.getName().trim();
            String funcName = element.attributeValue("id").trim();
            String sql = element.getTextTrim();//等价于: getText().trim()
            //resultType是返回类型的全路径-即全类名
            String resultType = element.attributeValue("resultType").trim();
            //开始封装
            function.setSqlType(sqlType);
            function.setFuncName(funcName);
            function.setSql(sql);
            //这里多说一句 function-private Object resultType; 是resultType实例
            //所以我们使用反射生成一个对象, setResultType
            Object instance = Class.forName(resultType).newInstance();
            function.setResultType(instance);

            //7.将封装好的function对象放人到 list
            functions.add(function);
        }
        //while循环结束后, 将function的list设置
        mapperBean.setFunctions(functions);
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
    return mapperBean;
}

🥦完成测试

public class ZzwMyBatisTest {
    @Test
    public void readMapper() {
        ZzwConfiguration zzwConfiguration = new ZzwConfiguration();
        MapperBean mapperBean = zzwConfiguration.readMapper("MonsterMapper.xml");
        System.out.println("mapperBean--" + mapperBean);
        System.out.println("ok~~");
    }
}

实现任务阶段七

🍍实现动态代理Mapper的方法

🥦分析 + 代码实现

●分析示意图
无

前面我们有2个地方学习过动态代理. 切面编程的底层支撑是动态代理
动态代理: AOP切面编程
动态代理: 手动实现spring底层机制

●代码实现
1.新增com.zzw.zzwmybatis.sqlsession.ZzwMapperProxy.java
转String类型

/**
 * @author 赵志伟
 * @version 1.0
 * ZzwMapperProxy: 动态代理生成Mapper对象, 调用ZzwExecutor方法
 */
@SuppressWarnings({"all"})
public class ZzwMapperProxy implements InvocationHandler {
    //属性
    private ZzwSqlSession zzwSqlSession;
    private String mapperFile;
    private ZzwConfiguration zzwConfiguration;

    //构造器
    public ZzwMapperProxy(ZzwSqlSession zzwSqlSession,
                          ZzwConfiguration zzwConfiguration,
                          Class clazz) {
        this.zzwSqlSession = zzwSqlSession;
        this.zzwConfiguration = zzwConfiguration;
        this.mapperFile = clazz.getSimpleName() + ".xml";
    }

    //前面讲解spring时, 讲过动态代理知识
    //提示: 当执行Mapper接口的代理对象方法时, 会执行到invoke方法
    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {

        MapperBean mapperBean =
                zzwConfiguration.readMapper(this.mapperFile);

        //判断是否是xml文件对应的接口
        if (!method.getDeclaringClass().getName().equals(mapperBean.getInterfaceName())) {
            return null;
        }

        //取出mapperBean的functions
        List<Function> functions = mapperBean.getFunctions();
        //判断当前mapperBean解析对应MapperXML后, 是否有方法
        if (functions != null && functions.size() != 0) {

            for (Function function : functions) {
                //当前要执行的方法和function.getFuncName()一样
                //说明我们可以从当前遍历的function对象中, 取出相应的信息sql, 并执行方法
                if (method.getName().equals(function.getFuncName())) {{
                    //如果我们当前的function 要执行的sqlType是select
                    //我们就去执行selectOne
                    /**
                     * 说明:
                     * 1. 如果要执行的方法是select, 就对应执行selectOne
                     * 2. 因为我们在ZzwSqlSession就写了一个 selectOne
                     * 3. 实际上ZzwSqlSession对应不同的方法(多个方法)
                     * , 根据不同的匹配情况调用不同方法, 并且还需要进行参数解析处理, 还有比较复杂的字符串处理, 拼接sql, 处理返回类型等等工作
                     * 4. 因为我们主要是想讲解mybatis 生成mapper动态代理对象, 调用方法的机制, 所以我们做了简化
                     */
                    if ("select".equals(function.getSqlType())) {
                        return zzwSqlSession.selectOne(function.getSql(), String.valueOf(args[0]));
                    }
                }}
            }
        }
        return null;
    }
}

2.修改com.zzw.zzwmybatis.sqlsession.ZzwSqlSession, 增加方法

/**
 * 1. 返回mapper的动态代理对象
 * 2. 这里的clazz 到时传入的是 MonsterMapper.class
 * 3. 返回的就是MonsterMapper接口代理对象
 * 4. 当执行接口方法时(通过代理对象调用), 根据动态代理机制, 会执行到ZzwMapperProxy-invoke
 * @param clazz
 * @return
 * @param <T>
 */
public <T> T getMapper(Class<T> clazz) {
    //返回动态代理对象
    return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, new ZzwMapperProxy(this, zzwConfiguration, clazz));
}

🥦完成测试

1.测试com.zzw.test.ZzwMyBatisTest

public class ZzwMyBatisTest {
    @Test
    public void getMapper() {
        ZzwSqlSession zzwSqlSession = new ZzwSqlSession();
        MonsterMapper mapper = zzwSqlSession.getMapper(MonsterMapper.class);
        System.out.println("mapper运行类型=" + mapper.getClass());//mapper是一个代理对象
        Monster monster = mapper.getMonsterById(1);
        System.out.println("monster--" + monster);
    }
}

2.新建com.zzw.zzwmybatis.sqlsession.ZzwSessionFactory

/**
 * @author 赵志伟
 * @version 1.0
 * ZzwSessionFactory: 会话工厂-返回会话ZzwSqlSession
 */
public class ZzwSessionFactory {

    public static ZzwSqlSession openSession() {
        return new ZzwSqlSession();
    }
}

3.测试com.zzw.test.ZzwMyBatisTest

public class ZzwMyBatisTest {
    @Test
    public void openSession() {
        ZzwSqlSession zzwSqlSession = ZzwSessionFactory.openSession();
        MonsterMapper mapper = zzwSqlSession.getMapper(MonsterMapper.class);
        Monster monster = mapper.getMonsterById(1);
        System.out.println("monster--" + monster);
    }
}

🥦Debug原生MyBatis-DeaultSqlSession不同方法

找到mybatis项目, 在DefaultSqlSessionselectOne方法打上断点, 测试select方法

同时证明:原生的mybatis中,selectOne方法的statement参数不是sql, 而是要执行的接口方法

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

Step Into, 追到BaseExecutordoQuery方法

在这里插入图片描述

继续 Step Into

在这里插入图片描述

继续 Step Into

在这里插入图片描述

继续 Step Into

在这里插入图片描述

继续 Step Into

在这里插入图片描述

继续 Step Into

在这里插入图片描述

继续 Step Into

在这里插入图片描述

继续 Step Into, doQuery里面就是原生的Jdbc代码了

在这里插入图片描述

DefaultSqlSessioninsert方法打上断点, 测试insert方法
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

Step Into

在这里插入图片描述

继续 Step Into

在这里插入图片描述

继续 Step Into

在这里插入图片描述

继续 Step Into, doUpdate方法里面就是原生的Jdbc代码了

在这里插入图片描述

DefaultSqlSessiondelete方法打上断点, 测试delete方法
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

DefaultSqlSessionupdate方法打上断点, 测试update方法
在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述
在这里插入图片描述

在这里插入图片描述

🥦Debug执行流程

回到自己写的zzw-mybatis项目, 开始debug
在这里插入图片描述

Step Into

在这里插入图片描述

Step Into

在这里插入图片描述

Step Out

在这里插入图片描述

估值

在这里插入图片描述

Step Into

在这里插入图片描述

Step Out

在这里插入图片描述

估值, mapper是个代理对象

在这里插入图片描述

ZzwMapperProxyinvoke方法下个断点, 直接放行

在这里插入图片描述

mapperBean估值

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

Step Into

在这里插入图片描述

继续 Step Into

在这里插入图片描述

查看传进来的参数

在这里插入图片描述

拿到Jdbc的结果集

在这里插入图片描述

Step Out

在这里插入图片描述

monster进行估值

在这里插入图片描述

接下来我们学习, mybatis 下在这里插入图片描述
💐💐💐💐💐💐💐💐给个赞, 点个关注吧, 各位大佬!💐💐💐💐💐💐💐💐

💐💐💐💐💐💐💐💐祝各位2024年大吉大运💐💐💐💐💐💐💐💐💐💐
请添加图片描述

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

~ 小团子

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

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

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

打赏作者

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

抵扣说明:

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

余额充值