优化自定义持久层框架实现自动映射
首先我们最先知道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