小言_互联网的博客

[由0开始手写Mybatis] 四、优化自定义持久层框架实现自动映射

385人阅读  评论(0)

优化自定义持久层框架实现自动映射

首先我们最先知道Mybatis是一个半自动的持久层框架 既然是半自动 那么他可以做到自动的映射到我们的Mapper 我们只需要手动编写sql就可以实现我们想要的对数据库的相应操作

分析现有问题

前几章我们已经写好一个可以对数据库操作的持久层框架了
那么这个框架究竟好用与否 以及他是否 存在问题呢
我们应当从使用端的角度去分析这个问题

分析使用端调用时的问题

使用端调用时

//加载测试文件
    @Test
    public void test() throws DocumentException, PropertyVetoException, SQLException, IllegalAccessException, IntrospectionException, InstantiationException, ClassNotFoundException, InvocationTargetException, NoSuchFieldException {
         InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().builder(resourceAsSteam);
        SqlSession sqlSession = sqlSessionFactory.openSession();

        User user = new User();
        user.setId(1);
        user.setUsername("lucy");
        User user2 = (User) sqlSession.selectList("user.selectOne",user);
        System.out.println(user2);
    }

存在问题很多(这是直接测试调用)

第一 每次执行都要写出加载配置的重复代码
第二 存在硬编码问题

作出预期设想并提出问题解决方案

我们想要的就是在mapper.xml下编写完成sql之后 直接就可以调用
而不是要写这么多的代码
使用端只需要写一下mapper中少量配置 以及完整的sql语句就可以完成对数据库的操作
那么我们想要的结果就是使用端可以直接调用
并且不要每次都要写重复的配置代码
以及可以自动配置而不存在硬编码问题

那么现在我们就要分析一下现有的mapper.xml中的配置了
由前三章 我们知道 我们处理sql的唯一标识就是namespace.id来组成的 statementId
但是我们想要的结果就是调用mapper就可以直接执行相关代码 而不是每次都通过配置文件中的sqlMapConfig.xml中的mapper路径 再去解析mapper.xml 获取内部的sql和相关信息
修改前(封装mapper 再调用):

<mapper namespace="user">
    <!--sql的唯一标识 : namespace.id来组成 :  statementId-->
    <select id="selectOne" resultType="com.mrsoon.pojo.User"  paramterType="com.mrsoon.pojo.User">
        select * from user where id = #{id} and username = #{username}
    </select>
</mapper>

这里拿selectOne举例 (其他方法一样)

public interface IUserDao {

    //根据条件进行用户查询
    public User selectOne(User user) throws Exception;

}

根据开闭原则 我们需要建立他的实现类 一样存在重复和硬编码

public class IUserDaoImpl implements IUserDao{

    @Override
    public User selectOne(User user) throws Exception{
  InputStream resourceAsSteam = Resources.getResourceAsSteam("sqlMapConfig.xml");
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().builder(resourceAsSteam);
        SqlSession sqlSession = sqlSessionFactory.openSession();
        User user2 = (User) sqlSession.selectList("user.selectOne",user);
        return user2;
	};

}

要知道唯一标识就是namespace.id来组成的 statementId 现在我们每次执行sql 获取这个都要手动写出这个来确定我们执行哪个 这种硬编码问题 我们可不可以用一个规范 在封装的mapper中 namespace的名字与mapper名字相同 id与mapper中方法名相同 那么我们在调用的时候 就可以直接知道statementId 在调用的时候我们用动态代理去解析statementId 再执行jdbc相关代码 我们可以把CRUD全部封装到这里
第二点 重复代码问题 我们一样可以约定好sqlMapConfig.xml文件的位置和名称 这样我们自动去获取解析就解决了

实现自动映射

SqlSession中写出getMapper


public interface SqlSession {
    //根据条件查询单个
    public <T> T selectOne(String statementId,Object... params) throws SQLException, IllegalAccessException, IntrospectionException, InstantiationException, ClassNotFoundException, InvocationTargetException, NoSuchFieldException;


    <T> T getMapper(Class<T> mapperClass);

}

这是SqlSession的实现类DefaultSqlSession
利用jdk动态代理去执行query.selectOne(只是举例selectOne 本质上可以把CRUD全部封装到这里)

@Override
    public <T> T getMapper(Class<T> mapperClass) {
        //使用Jdk动态代理来为Dao接口生成代理对象
        Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                // 底层都还是去执行JDBC代码 来调用selectOne
                // 准备参数 1:statmentid :sql语句的唯一标识:namespace.id= 接口全限定名.方法名
                String methodName = method.getName();
                String className = method.getDeclaringClass().getName();
                String statementId = className + "." + methodName;
                // 准备参数2:params:args
                // 获取被调用方法的返回值类型
                Type genericReturnType = method.getGenericReturnType();
                return selectOne(statementId, args);
            }
        });
        return (T) proxyInstance;
    }

调用

 IUserDao userDao = sqlSession.getMapper(IUserDao.class);
        List<User> all = userDao.selectOne();

本质上我们的手写小型Mybatis就完成了 Mybatis还有很多功能 例如动态标签 我们也可以完善 就是在解析sql的时候对标签多一层转化和判断 Mybatis还有缓存问题 一级缓存 二级缓存 Mybatis还有很多很多功能 我们只是实现了最基本的持久层 接下来我们还是会分析Mybatis的源码来体会他的一些思想 从而让我们写出更好的代码


转载:https://blog.csdn.net/weixin_42844278/article/details/105710028
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场