对于入门程序的流程分析
使用过程
读配置文件
读取配置文件时绝对路径和相对路径 (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{
- // 封装结果集的代码
- }
来源: http://www.bubuko.com/infodetail-3336875.html