手写MyBatis框架

手写Mybatis框架更有利于了解框架底层,做到知其然知其所以然。

原生JDBC

/**
 * 原生jdbc写法
 *
 * @author lipangpang
 * @date 2022/3/13
 * @description
 */
public class JdbcDemo {
    public static void main(String[] args) {
        MallUser user = new MallUser();
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            Class.forName("com.mysql.jdbc.Driver");
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai", "root", "123456");
            String sql = "select * from mall_user where name = ?";
            preparedStatement = connection.prepareStatement(sql);
            preparedStatement.setString(1, "张三");
            resultSet = preparedStatement.executeQuery();
            while (resultSet.next()) {
                Long id = resultSet.getLong("id");
                String username = resultSet.getString("name");
                user.setId(id);
                user.setName(username);
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException sqlException) {
                    sqlException.printStackTrace();
                }
            }
            if (preparedStatement != null) {
                try {
                    preparedStatement.close();
                } catch (SQLException sqlException) {
                    sqlException.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException sqlException) {
                    sqlException.printStackTrace();
                }
            }
        }
        System.out.println(user);
    }
}

原生JDBC写法的弊端有:

  1. 数据库配置信息及sql语句硬编码。
  2. 重复创建和关闭数据库连接。

框架写法

此处文件的命名参考MyBatis源码命名风格,有利于后续查看源码。

工程结构如下:

image-20220326165856027

表结构如下:

image-20220326165915654

框架大致流程如下:

  1. 创建mapper.xml文件用来存储sql查询语句,创建sqlMapConfig.xml来存储数据库配置信息和mapper.xml文件所在路径。

    sqlMapConfig.xml
    <configuration>
        <dataSource>
            <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
            <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai"></property>
            <property name="username" value="root"></property>
            <property name="password" value="123456"></property>
        </dataSource>
    
        <mapper resource="MallUserMapper.xml"></mapper>
        <mapper resource="MallRoleMapper.xml"></mapper>
    </configuration>
    
    MallUserMapper.xml
    <mapper namespace="org.example.dao.IMallUserDao">
        <select id="queryAll" parameterType="org.example.entity.MallUser" resultType="org.example.entity.MallUser">
            select * from mall_user
        </select>
        <select id="selectOne" parameterType="org.example.entity.MallUser" resultType="org.example.entity.MallUser">
            select * from mall_user where id = #{id} and name = #{name}
        </select>
        <insert id="insertOne" parameterType="org.example.entity.MallUser" resultType="int">
            insert into mall_user(name) values(#{name})
        </insert>
    </mapper>
    
    MallRoleMapper.xml
    <mapper namespace="org.example.dao.IMallRoleDao">
        <select id="queryAll" parameterType="org.example.entity.MallRole" resultType="org.example.entity.MallRole">
            select * from mall_role
        </select>
        <select id="selectOne" parameterType="org.example.entity.MallRole" resultType="org.example.entity.MallRole">
            select * from mall_role where id = #{id} and name = #{name}
        </select>
        <insert id="insertOne" parameterType="org.example.entity.MallRole" resultType="int">
            insert into mall_role(name) values(#{name})
        </insert>
    </mapper>
    
  2. 按照面向对象编程思想,将第一步解析出来的内容分别封装到MappedStatement和Configuration实体类中。

    /**
     * 配置文件对象
     *
     * @author lipangpang
     * @date 2022/3/13
     * @description
     */
    @Data
    public class Configuration {
        private DataSource dataSource;
        //key:命名空间+id
        private Map<String, MappedStatement> mappedStatementMap = new HashMap<>();
    }
    
    /**
     * mapper配置文件对象
     *
     * @author lipangpang
     * @date 2022/3/13
     * @description
     */
    @Data
    public class MappedStatement {
        /**
         * 标识
         */
        private String id;
        /**
         * 返回结果类型
         */
        private String resultType;
        /**
         * 参数查询类型
         */
        private String parameterType;
        /**
         * sql语句
         */
        private String sql;
    
    }
    
  3. 通过SqlSessionFactoryBuilder构建者创建SqlSessionFactory。

    /**
     * SqlSessionFactory构建者
     *
     * @author lipangpang
     * @date 2022/3/13
     * @description
     */
    public class SqlSessionFactoryBuilder {
        /**
         * 构建
         *
         * @param inputStream 文件配置流
         * @return
         * @throws PropertyVetoException
         * @throws DocumentException
         */
        public SqlSessionFactory build(InputStream inputStream) throws PropertyVetoException, DocumentException {
            //解析配置文件,封装到到Configuration
            Configuration configuration = new XMLConfigBuilder().parseConfig(inputStream);
            //创建sqlSessionFactory对象
            SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
            return sqlSessionFactory;
        }
    }
    
  4. 通过sqlSessionFactory工厂创建SqlSession。

    /**
     * 默认SqlSessionFactory工厂
     *
     * @author lipangpang
     * @date 2022/3/13
     * @description
     */
    public class DefaultSqlSessionFactory implements SqlSessionFactory {
        private Configuration configuration;
    
        public DefaultSqlSessionFactory(Configuration configuration) {
            this.configuration = configuration;
        }
    
        @Override
        public SqlSession openSession() {
            return new DefaultSqlSession(this.configuration);
        }
    }
    
  5. 通过SqlSession执行sql语句(此处包含动态代理的执行方法)。

    /**
     * 默认SqlSession
     *
     * @author lipangpang
     * @date 2022/3/13
     * @description
     */
    public class DefaultSqlSession implements SqlSession {
        private Configuration configuration;
    
        public DefaultSqlSession(Configuration configuration) {
            this.configuration = configuration;
        }
    
        @Override
        public <E> List<E> selectList(String statementId, Object... params) throws Exception {
            Executor executor = new SimpleExecutor();
            MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
            List<Object> objects = executor.query(configuration, mappedStatement, params);
            return (List<E>) objects;
        }
    
        @Override
        public <T> T selectOne(String statementId, Object... params) throws Exception {
            List<Object> objects = this.selectList(statementId, params);
            if (objects != null && objects.size() == 1) {
                return (T) objects.get(0);
            } else {
                throw new RuntimeException("返回结果为空或返回结果过多");
            }
        }
    
        @Override
        public Integer insertOne(String statementId, Object... params) throws Exception {
            Executor executor = new SimpleExecutor();
            MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
            return executor.save(configuration, mappedStatement, params);
        }
    
        @Override
        public <T> T getMapper(Class<?> mapperClass) {
            //通过jdk动态代理获取对象
            Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new
                    InvocationHandler() {
                        @Override
                        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                            //方法名(这也是为什么xml中id要和接口方法名一致)
                            String methodName = method.getName();
                            //类全路径名(这也是为什么xml中namespace要和接口全路径一致)
                            String className = method.getDeclaringClass().getName();
                            String statementId = className + "." + methodName;
                            Type genericReturnType = method.getGenericReturnType();
                            if (genericReturnType instanceof ParameterizedType) {
                                return selectList(statementId, args);
                            }
                            if (genericReturnType.getTypeName().contains("Integer")) {
                                return insertOne(statementId, args);
                            }
                            return selectOne(statementId, args);
                        }
                    });
            return (T) proxyInstance;
        }
    }
    

测试

测试代码:

/**
 * MyBatis测试类
 *
 * @author lipangpang
 * @date 2022/3/13
 * @description
 */
public class MybatisTest {
    public static void main(String[] args) throws Exception {
        //读取配置文件为流
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        //将配置文件封装成对象并创建SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        //创建SqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        MallUser user = new MallUser();
        user.setId(1L);
        user.setName("张三");
        MallUser userNew = new MallUser();
        userNew.setName("赵六");
        //查询单个对象
        MallUser mallUserDb = sqlSession.selectOne("org.example.dao.IMallUserDao.selectOne", user);
        System.out.println("查询单个对象:" + mallUserDb);
        //新增单个对象
        Integer insertSuccessNumber = sqlSession.insertOne("org.example.dao.IMallUserDao.insertOne", userNew);
        System.out.println("新增单个对象:" + insertSuccessNumber);
        //查询所有对象
        List<MallUser> mallUsers = sqlSession.selectList("org.example.dao.IMallUserDao.queryAll");
        System.out.println("查询所有对象:" + mallUsers);
        IMallUserDao mallUserDao = sqlSession.getMapper(IMallUserDao.class);
        //通过代理查询所有对象
        List<MallUser> mallUsersByProxy = mallUserDao.queryAll();
        System.out.println("通过代理查询所有对象:" + mallUsersByProxy);
    }
}

返回结果:

查询单个对象:MallUser(id=1, name=张三)
新增单个对象:1
查询所有对象:[MallUser(id=1, name=张三), MallUser(id=2, name=李四), MallUser(id=3, name=王五), MallUser(id=4, name=赵六)]
通过代理查询所有对象:[MallUser(id=1, name=张三), MallUser(id=2, name=李四), MallUser(id=3, name=王五), MallUser(id=4, name=赵六)]

源码地址

https://gitee.com/lishihao_pm/demo-parent/tree/master/demo-mybatis

  • 0
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
手写MyBatis框架可以遵循以下步骤: 1. 创建一个配置类,用于加载数据库配置信息和Mapper接口配置信息。 2. 创建一个SqlSession类,用于管理数据库连接、执行SQL语句和返回结果。 3. 创建一个Mapper接口,定义数据库操作方法。 4. 创建一个MapperProxy类,实现动态代理,将Mapper接口的方法调用转发给SqlSession执行对应的SQL语句。 5. 创建一个MapperRegistry类,用于管理Mapper接口和对应的MapperProxy对象。 6. 创建一个DefaultSqlSessionFactory类,用于创建SqlSession对象。 7. 创建一个SqlSessionFactoryBuilder类,用于读取配置信息并创建DefaultSqlSessionFactory对象。 8. 创建一个Configuration类,用于保存MyBatis的全局配置信息。 9. 创建一个Executor类,用于执行SQL语句。 10. 创建一个StatementHandler类,用于封装和执行JDBC操作。 11. 创建一个ResultSetHandler类,用于处理查询结果集。 12. 创建一个TypeHandler类,用于处理Java类型和数据库类型之间的转换。 13. 创建一个XMLMapperBuilder类,用于解析Mapper配置文件,并将解析结果注册到Configuration中。 14. 创建一个XMLConfigBuilder类,用于解析MyBatis配置文件,并将解析结果保存到Configuration中。 15. 在主程序中,使用SqlSessionFactoryBuilder读取配置信息创建SqlSessionFactory,并通过SqlSessionFactory创建SqlSession对象,最后使用SqlSession获取Mapper接口的代理对象,从而实现对数据库的操作。

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值