Mybatis 原理分析
Posted whteway
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Mybatis 原理分析相关的知识,希望对你有一定的参考价值。
对于入门程序的流程分析
使用过程
读配置文件
读取配置文件时绝对路径和相对路径(web工程部署后没有src路径)都有一定问题,实际开发中一般有两种方法
- 使用类加载器,它只能读取类路径的配置文件
- 使用SerbletContext对象的getRealPath()
创建SqlSessionFactory工厂,使用了建造者模式(Builder Pattern)
使用工厂生产SqlSession对象,使用了工厂模式(Factory Pattern)
使用SqlSession创建Dao接口的代理对象,使用了代理模式(Proxy Pattern)
使用代理对象执行方法
释放资源
底层调用jdbc的流程,即自定义dao中selectList()方法的执行流程,也是代理对象增强的逻辑
- 注册驱动,获取Connection对象(需要数据库信息)
- 通过SqlMapConfig.xml的数据库信息,解析xml文件用到的是dom4j技术
- 获取预处理对象PreparedStatement(需要sql语句)
- 通过SqlMapConfig.xml中的mapper标签定位UserDao.xml,映射配置文件中有sql语句
- 执行查询,得到结果集ResultSet
- 遍历结果集用于封装
- 根据UserDao.xml中的resultType反射获得User对象,User对象的属性名和表中列名一致,可以一一封装进user对象中,再把user对象封装进list中
- 所以,要想让selectList()执行,需要提供两个信息,连接信息和映射信息,映射信息包括sql语句和封装结果的实体类的全限定类名,所以映射信息可以用map存储
创建代理对象流程
根据dao接口的字节码创建dao的代理对象
public <T> T getMapper(Class<T> daoInterfaceClass){ /* 类加载器,使用和被代理对象相同的类加载器 接口数组,和被代理对象实现相同的接口 增强逻辑,自己提供,此处是一个InvocationHandler接口,需要写一个该接口的实现类,在其中调用selectList()方法 */ Proxy.newProxyInstance(类加载器, 接口数组,增强逻辑); }
原理分析:模拟Mybatis各组件实现入门程序
分析用到的类
public void test(){ InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml"); SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(in); SqlSession session = factory.openSession(); UserDao userDao = session.getMapper(UserDao.class); List<User> users = userDao.findAll(); for(User user: users) System.out.println(user); session.close(); in.close(); }
解决读取配置信息的问题
读取xml文件具体实现可以使用dom4j和xpath技术,坐标名dom4j和jaxen
//模拟Mybatis的配置类 public class Configuration{ private String driver; private String url; private String username; private String password; /* mappers中的数据结构形如 ------------------------------------------------------------------------- key | value -----------------------------------|------------------------------------- "com.whteway.dao.UserDao.findAll()"| queryString:"select * from user" | resultType:"com.whteway.domain.User" -----------------------------------|------------------------------------- "com.whteway.dao.StuDao.findAll()" | queryString:"select * from stu" | resultType:"com.whteway.domain.Stu" -----------------------------------|------------------------------------- "package.daoInterface.method()" | queryString: SQL语句 | resultType: 实体类的全限定类名 */ private Map<String Mapper> mappers; //to generate getters and setters exclude setMappers; public void setMappers(Map<String, Mapper> mappers){ this.mappers.putAll(mappers); //追加而不是覆盖 } }
//用于封装映射配置文件的信息,SQL语句和封装实体类的全限定类名 //SqlMapConfig.xml的mappers标签中的每一个mapper标签对应一个Mapper对象, //也就是说第一个映射配置文件对应一个Mapper对象 public class Mapper{ private String queryString; private String resultType; //to generate getters and setters; }
//读取xml文件的工具类 public class XMLConfigBuilder{ //读取SqlMapConfig.xml文件并调用方法读取映射配置文件或加注解的类,将配置信息存入Configuration中并返回 public static Configuration loadConfiguration(InputStream is){ Configuration config = new Configuration(); //1.读取SqlMapConfig文件,将数据库连接信息存入config /*2.根据SqlMapConfig文件中的mappers,属性循环读取每一个映射配置文件 如果用的是resource属性 mappers = loadMapperResource(resource.value); 如果用的是class属性 mappers = loadMapperAnnotation(class.value); 使用追加的方式将mappers存入config.mappers中 config.setMappers(mappers); */ return config; } //读取映射配置文件 public static Map<String, Mapper> laodMapperResource(String daoClassPath) throws Exception{ Map<String, Mapper> mappers = new HashMap<String, Mapper>(); /* 1 循环读取每一个映射配置文件,如UserDao.xml 2 读取mappers标签的namespace 3 循环mappers标签中的每一个子标签(对应一个方法) 4 读取id,组成namespace.id形式的字符串,作为key 5 读取sql语句存入Mapper中的queryString 6 读取resultType存入Mapper中的resultType 7 将Mapper作为value,与key组成键值对,存入mappers中 */ return mappers; } //读取加注解的类 public static Map<String, Mapper> laodMapperAnnotation(String daoClassPath) throws Exception{ Map<String, Mapper> mappers = new HashMap<String, Mapper>(); /* 1.得到dao接口的字节码对象 2.循环每一个方法(对应一个Mapper) 3. 判断是否有select注解,如果有 4. 获取各种数据封装到一个Mapper中 包名,类名,@Select的value值,返回类型的泛型的实际类型,方法名 5. 把Mapper存入mappers中 */ return mappers; } }
Resources
//将文件名转换为输入流 public class Resources{ //根据传入的参数,返回一个输入流 //1.Resources.class 获取当前类的字节码 //2.getClassLoader() 获取这个字节码的类加载器 //3.getResourceAsStream(filePath) 根据类加载器读取文件配置 public static InputStream getResourceAsStream(String filePath){ return Resources.class.getClassLoader().getResourceAsStream(filePath); } }
SqlSessionFactoryBuilder 读取配置文件将配置信息存入配置类中的代码在此类中调用
public class SqlSessionFactoryBuilder{ //根据参数的字节输入流来构建一个SqlSessionFactory工厂 public SqlSessionFactory build(InputStream config){ Configuration cfg = XmlConfigBuilder.loadConfiguration(config); return new DefaultSqlSessionFactory(config); } }
SqlSessionFactory
//接口 public interface SqlSessionFactory{ public SqlSession openSession(); }
//实现类 public class DefaultSqlSessionFactory implements SqlSessionFactory{ private Configuration cfg; //实例化此类必须传入配置信息 public DefaultSqlSessionFactory(Configuration cfg){ this.cfg = cfg; } //创建一个新的操作数据库的对象 @Override public SqlSession openSession(){ return new DeaultSqlSession(cfg); } }
SqlSession jdbc代码调用全在此类和此类的代理对象中
//接口,和数据库交互的核心类 public interface SqlSession{ //创建代理对象,参数是dao接口的字节码 public <T> T getMapper(Class<T> daoInterfaceClass); //释放资源 public void close(); }
//实现类 public class DefaultSqlSession implements SqlSession{ private Configuration cfg; private Connection conn; //实例化此类必须传入配置信息 public DefaultSqlSession(Configuration cfg){ this.cfg = cfg; conn = DataSourceUtil.getConnection(cfg); //使用传统JDBC方式获取连接,创建数据源,建议封装到一个工具类中,源码中叫executor try{ Class.forName(cfg.getDriver()); conn = DriverManager.getConnection(cfg.getUrl(), cfg.getUserName(), cfg.getPassword()) }catch(Exception e){ throw new RuntimeException(e); } } @Override public <T> T getMapper(Class<T> daoInterfaceClass){ return Proxy.newProxyInstance(daoInterfaceClass.getClassLoader()), new Class[]{daoInterfaceClass}, new MapperProxy(cfg.getMappers())); } //关闭Connection public void close(){ if(conn != null){ try{ conn.close(); }catch(Exception e){ e.printStackTrace(); } } } }
//作为创建代理对象时的第三个参数,用于对方法进行增强,此处的增强只需要调用selectList()方法 public class MapperProxy implements InvocationHandler{ //接收参数 private Map<String, Mappers> mappers; private Connection conn; public MapperProxy(Map<String, Mapper> mappers, Connection conn){ this.mappers = mappers; this.conn = conn; } //定义增强逻辑 @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable{ //获取方法名 String methodName = method().getName(); //获取方法所在的类的名称 String className = method.getDeclaringClass().getName(); //组合key String key = className + "." + methodName(); //获取mappers中的mapper对象 Mapper mapper = mappers.get(key); //判断参数是否合法 if(mapper == null) throw new IllegalArgumentException("传入参数有误"); //使用传统JDBC方式执行SQL语句并封装到实体类列表中返回,建议作为selectList()方法封装到工具类中 selectList(); return null; } }
session.selectList()源码分析
//UserDaoImpl.java
public List<User> findAll(){
session.selectList("com.whteway.dao.UserDao.findAll");
}
//DefaultSqlSession.java
public <E> List<E> selectList(String statement){
this.selectList(statement, null);
}
public <E> List<E> selectList(String statement, Object parameter){
this.selectList(statement, parameter, RowBounds.DEFAULT);
}
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds){
try{
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.query(ms, wrapColeection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e){
} finally {
ErrorContext.instance().reset();
}
}
//CachingExecutor.java
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException{
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException{
...
return delegate.<E> query(ms, parameterObect, rowBounds, resultHandler, key, boundSql);
}
//BaseExecutor.java...
//经过一系列的调用,会走到PreparedStatementHandler类中的一个query方法
//PreparedStatementHandler.java
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException{
//出现JDBC代码
PreparedStatement ps = (PreparedStatement)statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
//DefaultResultSetHandler.java
public List<Object> handleResultSets(Statement stmt) throws SQLException{
//封装结果集的代码
}
以上是关于Mybatis 原理分析的主要内容,如果未能解决你的问题,请参考以下文章
《深入理解mybatis原理1》 MyBatis的架构设计以及实例分析