手写MyBatis框架——按执行流程编写

一、框架介绍

1.目录结构

在这里插入图片描述

2.执行流程

在这里插入图片描述

3.框架分析

代码参照使用mybatis的最基本流程进行编写,即如下步骤

//1.读取配置文件
InputStream is=Resources.getResourceAsStream("mybatis.xml");
//2.构建SqlSessionFactory
SqlSessionFactory factory=new SqlSessionFactoryBuilder().build(is);
//3.获取SqlSessoin对象
SqlSession session = factory.openSession();
//4.创建Mapper接口的代理对象
AccountMapper userMapper=session.getMapper(AccountMapper.class);
//5.执行查询
Object res= accountMapper.queryAccountById(1)
bean
  • Configuartion——封装数据库连接信息和SQL语句信息
  • Environment ——封装数据库连接信息
  • MapperStatement ——SQL语句信息
core
  • Executor——执行对数据库的操作
  • MapperProxy——Mapper接口的代理类,调用SqlSession中的CRUD方法
  • SqlSession——提供创建代理类的方法,通过调用Executor类来提供CRUD方法
  • SqlSessionFactory——读取Configuartion类信息创建Executor类,并创建SqlSession类,将Executor类传入其中
  • SqlSessionFactoryBuilder——调用XmlConfiguration类解析传入的输入流,生成Configuration类,创建SqlSessionFactory对象并将Configuration类传入
pool
  • MyDataSource——数据库连接池接口类
  • MyDataSource——数据库连接池实现类,双重检测锁实现单例
utils
  • ReflectUtils——反射实现实体类的set方法,对结果集数据进行封装
  • Resources——读取配置文件
  • StringUtils——下划线和驼峰命名之间的转换
  • XmlConfigBuilder——解析配置文件
整体分析

整个程序实现了简单解析mapper.xml中的sql语句(即只是获取了了sql标签中的内容,无法对如< where >等标签进行解析并对标签内容进行处理),使用构建者模式构建SqlSessionFactory并生产Session对象,在Session对象中使用动态代理创建接口的代理实现类,代理的实现类拦截接口方法并调用Session中的查询方法,由Executor类真正进行数据库操作和封装结果集对象。

GitHub地址:点此查看源码

不足之处

目前只能进行查询操作,在sql语句中只能使用?作为占位符,无法解析#{id}和${id}的占位符类型。没有实现实体类和数据库表的关系映射,没有进行事务管理,对xml文件的解析也只是对几个标签进行了解析,满足最基本配置信息的需求。

二、详细分析

1.bean对象分析

封装数据库连接信息和SQL语句信息的对象,以下只展示对象中的属性代码

1.1 Configuartion
/**
 * 封装配置文件和mapper文件的信息
 * @author xxbb
 */
public class Configuration {
    /**
     * 封装xml配置文件中的数据库连接信息
     */
    private Environment environment;
    /**
     * 封装mapper文件的信息
     */
    private Map<String,MapperStatement> mapperStatementMap;
}

1.2 Environment
/**
 * 数据库连接信息
 * @author xxbb
 */
public class Environment {
    private String url;
    private String driver;
    private String username;
    private String password;
}
1.3 MapperStatement
/**
 * 映射mapper.xml,对其中数据进行封装
 * @author xxbb
 */
public class MapperStatement {
    /**
     * 命名空间
     */
    private String namespace;

    /**
     * sql语句的标签id
     */
    private String id;
    /**
     * 传入参数类型
     */
    private String parameterType;
    /**
     * 结果集封装参数类型
     */
    private String resultType;
    /**
     * 执行的sql语句
     */
    private String sql;
}

2.core核心业务分析
2.1 Executor

执行器,真正操作数据库的对象,其中初始化了连接池,提供对数据库操作的方法。对于查询的结果集对象,这里使用获取结果集元数据的方式得到其字段名,通过反射调用字段名对应的set方法实现将数据封装到实体类中。

/**
 * 执行器,真正进行数据库操作的对象
 *
 * @author xxbb
 */
public class Executor {
    private final DataSource dataSource;

    public Executor(Configuration configuration) {
        this.dataSource = MyDataSourceImpl.getInstance(configuration.getEnvironment());
    }

    /**
     * 查询信息
     * @param mapperStatement 封装了sql相关信息的对象
     * @param args 需要传入sql语句中的参数
     * @param <T> 泛型
     * @return 泛型集合
     */
    public <T> List<T> query(MapperStatement mapperStatement, Object[] args) {
        Connection connection = null;
        try {
            connection = dataSource.getConnection();
            PreparedStatement preparedStatement = connection.prepareStatement(mapperStatement.getSql());
            ResultSet resultSet;

            if (args != null) {
                for (int i = 0; i < args.length; i++) {
                    if (args[i] instanceof Integer) {
                        preparedStatement.setInt(i + 1, (Integer) args[i]);
                    } else if (args[i] instanceof Long) {
                        preparedStatement.setLong(i + 1, (Long) args[i]);
                    } else if (args[i] instanceof Double) {
                        preparedStatement.setDouble(i + 1, (Double) args[i]);
                    } else if (args[i] instanceof String) {
                        preparedStatement.setString(i + 1, (String) args[i]);
                    }
                }
            }

            System.out.println("执行的查询语句:" + preparedStatement.toString());
            resultSet = preparedStatement.executeQuery();

            //将查询结果转化为对象
            List<T> resultList = new ArrayList<>();
            handlerResultType(resultSet, mapperStatement.getResultType(), resultList);
            return resultList;
        } catch (SQLException throwables) {
            throw new RuntimeException(throwables.getMessage());
        } finally {
            ((MyDataSourceImpl) dataSource).returnConnection(connection);
        }
    }

    /**
     * 将结果集封装到对象中
     * @param resultSet 结果集
     * @param resultType 封装的对象类型
     * @param resultList 封装处理得到对象的集合
     * @param <T> 泛型
     */
    private <T> void handlerResultType(ResultSet resultSet, String resultType, List<T> resultList) {
        //获取返回类型的对象
        try {
            Class<?> clazz = Class.forName(resultType);
            //获取结果集元数据
            ResultSetMetaData resultSetMetaData = resultSet.getMetaData();
            //遍历结果集,封装成bean对象存入resultList中
            while (resultSet.next()) {
                Object entity = clazz.newInstance();
                for (int i = 0; i < resultSetMetaData.getColumnCount(); i++) {
                    //获取列名和值,从1开始,如果查询中该字段有别名则获取别名
                    String columnName = resultSetMetaData.getColumnLabel(i + 1);
                    Object columnValue = resultSet.getObject(i + 1);

                    //将字段值赋值给类对象
                    ReflectUtils.invokeSet(entity, columnName, columnValue);
                }
                resultList.add((T) entity);

            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

2.2 MapperProxy

基于接口的动态代理,生成mapper接口的代理对象,拦截接口方法,通过接口名+方法名获取对应的存储SQL信息的MapperStatement对象,调用SqlSession中的CRUD方法。

/**
 * mapper接口类的代理实现类
 * @author xxbb
 */
public class MapperProxy implements InvocationHandler {
    private SqlSession sqlSession;

    public MapperProxy(SqlSession sqlSession) {
        this.sqlSession = sqlSession;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        //判断返回值类型
        Class<?> clazz = method.getReturnType();

        if (Collection.class.isAssignableFrom(clazz)) {
            String statementKey = method.getDeclaringClass().getName() + "." + method.getName();
            return sqlSession.selectList(statementKey, args);
            //返回值为集合类型
        } else if (Map.class.isAssignableFrom(clazz)) {
            //返回值为Map类型
            return null;
        } else {
            //返回值为单条数据
            String statementKey = method.getDeclaringClass().getName() + "." + method.getName();
            return sqlSession.selectOne(statementKey, args);
        }
    }
}
2.3 SqlSession

提供接口的代理对象和CRUD方法,通过调用Executor类实现对数据库的操作。

/**
 * @author xxbb
 */
@SuppressWarnings("unchecked")
public class SqlSession {
    /**
     * 配置对象
     */
    private Configuration configuration;
    /**
     * 执行器对象
     */
    private Executor executor;

    public SqlSession(Configuration configuration, Executor executor) {
        this.configuration = configuration;
        this.executor = executor;
    }

    /**
     * 创捷接口的代理实现类
     * @param clazz 传入的接口字节码文件
     * @param <T> 泛型
     * @return 接口的代理实现类
     */
    public <T> T getMapper(Class<T> clazz) {
        MapperProxy mapperProxy = new MapperProxy(this);
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(), new Class[]{clazz}, mapperProxy);
    }

    public <T> T selectOne(String statementKey, Object[] args) {
        //获取查询对象
        MapperStatement mapperStatement = configuration.getMapperStatementMap().get(statementKey);
        List<T> resultList = executor.query(mapperStatement, args);
        if (resultList.size() > 1) {
            throw new RuntimeException("get result number > 1");
        } else {
            return resultList.get(0);
        }
    }

    public <T> List<T> selectList(String statementKey, Object[] args) {
        //获取查询对象
        MapperStatement mapperStatement = configuration.getMapperStatementMap().get(statementKey);
        return executor.query(mapperStatement, args);
    }

    public <T> T selectMap(Object[] args) {
        return null;
    }
}

2.4 SqlSessionFactory

提供Executor类对象,创建SqlSession对象并将Executor类对象传入

/**
 * @author xxbb
 */
public class SqlSessionFactory {
    /**
     * 封装xml配置信息的类
     */
    private Configuration configuration;


    public SqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }

    /**
     * 创建SqlSession对象
     * @return SqlSession对象
     */
    public SqlSession openSession() {
        Executor executor = new Executor(configuration);
        return new SqlSession(configuration, executor);
    }
}

2.5 SqlSessionFactoryBuilder

调用XmlConfigBuilder类对输入流进行解析,生成Configuration对象,创建SqlSessionFactory并将Configuration对象传入。

/**
 * @author xxbb
 */
public class SqlSessionFactoryBuilder {
    public SqlSessionFactory build(InputStream is){
        Configuration configuration=new XmlConfigBuilder().parse(is);
        
        return new SqlSessionFactory(configuration);
    }

}

3.pool连接池

简单的数据库连接池,通过双重检测锁保证单例,归还连接和获取连接之间通过notify()和wait()进行通信。在接口类中使用Default重载DataSource的方法,从而在连接池实现类中不需要去实现哪些不必要的方法。

3.1MyDataSource
/**
 * 数据库连接池接口
 * @author xxbb
 */
public interface MyDataSource extends DataSource {
    @Override
    default Connection getConnection() throws SQLException {
        return null;
    }

    @Override
    default Connection getConnection(String username, String password) throws SQLException {
        return null;
    }

    @Override
    default <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    default boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    @Override
    default PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    default void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    default void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    default int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    default Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
}

3.2 MyDataSourceImpl
/**
 * 连接池实现类
 * @author xxbb
 */
public class MyDataSourceImpl implements MyDataSource {
    /**
     * 数据库连接属性
     */
    private Environment environment;
    /**
     * 初始连接数量
     */
    private static int initCount = 5;
    /**
     * 最小连接数量
     */
    private static int minCount = 5;
    /**
     * 最大连接数量
     */
    private static int maxCount = 20;
    /**
     * 已创建的连接数量
     */
    private static int createdCount;
    /**
     * 连接数增长步长
     */
    private static int increasingCount = 2;
    /**
     * 存储连接的集合
     */
    LinkedList<Connection> conns = new LinkedList<>();
    /**
     * 用于获取连接和归还连接的同步锁对象
     */
    private static final Object monitor = new Object();

    /**
     * 连接池对象
     */
    private static volatile MyDataSourceImpl instance;

    private MyDataSourceImpl(Environment environment) {

        //防止反射破坏单例
        if (instance != null) {
            throw new RuntimeException("Object has been instanced!!!");
        }
        //获取配置信息
        this.environment = environment;
        //初始化连接池
        init();


    }

    public static MyDataSourceImpl getInstance(Environment environment) {
        //双重检测锁
        if (null == instance) {
            synchronized (MyDataSourceImpl.class) {
                if (null == instance) {
                    instance = new MyDataSourceImpl(environment);
                }
            }
        }
        return instance;
    }

    /**
     * 初始化连接池
     */
    private void init() {
        //循环给集合中添加初始化连接
        for (int i = 0; i < initCount; i++) {
            boolean flag = conns.add(createConnection());
            if (flag) {
                createdCount++;
            }
        }
        System.out.println("ConnectionPool1初始化------>连接池对象:" + this);
        System.out.println("ConnectionPool1初始化------>连接池可用连接数量:" + createdCount);
    }

    /**
     * 构建数据库连接对象
     *
     * @return
     */
    private Connection createConnection() {
        try {

            Class.forName(environment.getDriver());
            return DriverManager.getConnection(environment.getUrl(), environment.getUsername(), environment.getPassword());

        } catch (Exception e) {
            throw new RuntimeException("数据库连接创建失败:" + e.getMessage());
        }
    }

    /**
     * 连接自动增长
     */
    private synchronized void autoAdd() {
        //增长步长默认为2
        if (createdCount == maxCount) {
            throw new RuntimeException("连接池中连接已达最大数量,无法再次创建连接");
        }
        //临界时判断增长个数
        for (int i = 0; i < increasingCount; i++) {
            if (createdCount == maxCount) {
                break;
            }
            conns.add(createConnection());
            createdCount++;
        }

    }

    /**
     * 自动减少连接
     */
    private synchronized void autoReduce() {
        if (createdCount > minCount && conns.size() > 0) {
            //关闭池中空闲连接
            try {
                conns.removeFirst().close();
                createdCount--;
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }

    /**
     * 获取池中连接
     *
     * @return 连接
     */
    @Override
    public Connection getConnection() {
        //判断池中是否还有连接
        synchronized (monitor) {
            if (conns.size() > 0) {
                System.out.println(Thread.currentThread().getName() + "--->获取到连接:" + conns.getFirst() + "  已创建连接数量:" + createdCount + "  空闲连接数" + (conns.size() - 1));
                return conns.removeFirst();
            }
            //如果没有空连接,则调用自动增长方法
            if (createdCount < maxCount) {
                autoAdd();
                return getConnection();
            }
            //如果连接池连接数量达到上限,则等待连接归还
            System.out.println(Thread.currentThread().getName() + "--->连接池中连接已用尽,请等待连接归还");
            try {
                monitor.wait();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            return getConnection();
        }

    }


    /**
     * 归还连接
     *
     * @param conn
     */

    public void returnConnection(Connection conn) {

        synchronized (monitor) {
            System.out.println(Thread.currentThread().getName() + "--->准备归还数据库连接" + conn);
            conns.add(conn);
            monitor.notify();
            autoReduce();
        }

    }

    /**
     * 返回可用连接数量
     *
     * @return
     */
    public int getCreatedCount() {
        return createdCount;
    }
}

4.utils工具类
4.1 ReflectUtils
/**
 * @author xxbb
 */
public class ReflectUtils {
    /**
     * 通过数据库字段名反射调用其po类对应的set方法
     *
     * @param object     po类对象
     * @param columnName 字段名
     * @param value      字段的值
     */
    public static void invokeSet(Object object, String columnName, Object value) {
        Class<?> clazz = object.getClass();
        Method method = null;
        try {
            method = clazz.getDeclaredMethod("set" + StringUtils.columnNameToMethodName(columnName), value.getClass());
            method.invoke(object, value);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

4.2 Resources
/**
 * @author xxbb
 */
public class Resources {
    /**
     * 通过静态方法获取配置文件的输入流对象
     * @param xmlName 配置文件名称
     * @return 流对象
     */
    public static InputStream getResourcesAsStream(String xmlName){
        return Resources.class.getClassLoader().getResourceAsStream(xmlName);
    }

}
4.3 StringUtils
/**
 * 封装了字符串常用的操作
 *
 * @author xxbb
 */
public class StringUtils {
    /**
     * 正则表达式 用于匹配下划线
     */
    private static Pattern linePattern = Pattern.compile("_(\\w)");
    /**
     * 正则表达式 用于匹配大写字母
     */
    private static Pattern humpPattern = Pattern.compile("[A-Z]");


    /**
     * 将传入的表名去除t_字符,转化为类名
     *
     * @param str 传入字段
     * @return 取出t_的下划线转驼峰+首字母大写字段
     */
    public static String tableNameToClassName(String str) {
        return firstCharToUpperCase(lineToHump(str.substring(2)));
    }

    /**
     * 将类名转化为数据库表名
     *
     * @param str 传入类名
     * @return 数据库表名
     */
    public static String classNameToTableName(String str) {
        return "t_" + humpToLine(str);
    }

    /**
     * 将数据库字段名转化为类的命名规则,即下划线改驼峰+首字母大写,例如要获取if_freeze字段的方法,方法为getIfFreeze(),
     *
     * @param str 传入字段
     * @return 下划线改驼峰+首字母大写
     */
    public static String columnNameToMethodName(String str) {
        return firstCharToUpperCase(lineToHump(str));
    }

    /**
     * 将传入字符串的首字母大写
     *
     * @param str 传入字符串
     * @return 首字母大写的字符串
     */
    public static String firstCharToUpperCase(String str) {
        return str.toUpperCase().substring(0, 1) + str.substring(1);
    }

    /**
     * 下划线转驼峰
     *
     * @param str 待转换字符串
     * @return 驼峰风格字符串
     */
    public static String lineToHump(String str) {
        //将小写转换
        String newStr = str = str.toLowerCase();
        Matcher matcher = linePattern.matcher(newStr);
        StringBuffer sb = new StringBuffer();
        while (matcher.find()) {
            matcher.appendReplacement(sb, matcher.group(1).toUpperCase());
        }
        matcher.appendTail(sb);
        return sb.toString();
    }

    /**
     * 驼峰转下划线
     *
     * @param str 待转换字符串
     * @return 下划线风格字符串
     */
    public static String humpToLine(String str) {
        //将首字母先进行小写转换
        String newStr = str.substring(0, 1).toLowerCase() + str.substring(1);
        //比对字符串中的大写字符
        Matcher matcher = humpPattern.matcher(newStr);
        StringBuffer sb = new StringBuffer();
        //匹配替换
        while (matcher.find()) {
            matcher.appendReplacement(sb, "_" + matcher.group(0).toLowerCase());
        }
        matcher.appendTail(sb);
        return sb.toString();
    }

    public static void main(String[] args) {
        String str = columnNameToMethodName(humpToLine("ifFreeze"));
        System.out.println(str);
    }

}

4.4 XmlConfigBuilder
/**
 * 解析配置文件的工具类
 * @author xxbb
 */
public class XmlConfigBuilder {

    /**
     * 解析配置
     * @return 封装好的配置信息
     */
    public Configuration parse(InputStream config){
        try {

             //封装数据库配置和mapper映射配置信息
            Configuration configuration = new Configuration();
            //通过jsoup解析
            Document document= Jsoup.parse(config,"UTF-8","");

            //解析数据库配置文件信息
            configuration.setEnvironment(parseEnvironment(document));

            //解析mapper文件信息
            configuration.setMapperStatementMap(parseMapperStatementMap(document));
            return configuration;

        } catch (IOException e) {
            e.printStackTrace();
            throw new RuntimeException(e.getMessage());
        }finally {
            try{
                config.close();
            }catch (Exception e){
                e.printStackTrace();
            }
        }
    }

    /**
     * 解析xml中数据库配置环境信息
     * @param document xml的数据对象
     * @return 封装好的数据库配置环境对象
     */
    private Environment parseEnvironment(Document document){
        //解析数据库配置信息
        Environment environment=new Environment();
        //目前只简单解析第一个配置
        Element dataSource=document.getElementsByTag("dataSource").get(0);
        for(Element element:dataSource.getElementsByTag("property")){
            String name=element.attr("name");
            if("driver".equals(name)){
                environment.setDriver(element.val());
            }else if("url".equals(name)){
                environment.setUrl(element.val());
            }else if("username".equals(name)){
                environment.setUsername(element.val());
            }else if("password".equals(name)){
                environment.setPassword(element.val());
            }
        }
        return environment;
    }

    /**
     * 解析xml中mapper映射文件信息
     * @param document xml的数据对象
     * @return 封装好mapper映射的map
     */
    private Map<String, MapperStatement> parseMapperStatementMap(Document document){
        //存放mapper的map
        Map<String, MapperStatement> mapperStatementMap=new HashMap<>(10);
        Element mappers=document.getElementsByTag("mappers").get(0);

        for(Element element:mappers.getElementsByTag("mapper")){
            String resource=element.attr("resource");
            if(resource!=null&&!"".equals(resource)){
                //解析每一个mapper文件,将每一条sql语句的标签都封装到MapperStatement对象中
                String mapperFilePath=
                        Objects.requireNonNull(this.getClass().getClassLoader().getResource(resource)).getPath();

                try {
                    Document mapperDocument=Jsoup.parse(new File(mapperFilePath),"UTF-8");
                    //获取命名空间
                    Element namespaceElement=mapperDocument.getElementsByAttribute("namespace").get(0);
                    String namespace=namespaceElement.attr("namespace");

                    //获取CRUD标签,并将其封装到mapperStatementMap中
                        //查询
                    Elements selects=mapperDocument.getElementsByTag("select");
                    parseSql(mapperStatementMap, namespace, selects);
                        //添加
                    Elements inserts=mapperDocument.getElementsByTag("insert");
                    parseSql(mapperStatementMap, namespace, inserts);
                        //修改
                    Elements updates=mapperDocument.getElementsByTag("update");
                    parseSql(mapperStatementMap, namespace, updates);
                        //删除
                    Elements deletes=mapperDocument.getElementsByTag("delete");
                    parseSql(mapperStatementMap, namespace, deletes);

                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return mapperStatementMap;
    }

    /**
     * 将从mapper.xml中解析出每一条sql标签封装到mapperStatementMap中
     * @param mapperStatementMap 存放所有sql语句标签信息的map对象
     * @param namespace 该mapper.xml的命名空间
     * @param elements 解析出的sql标签组,分select、update、insert、delete四种
     */
    private void parseSql(Map<String, MapperStatement> mapperStatementMap, String namespace, Elements elements) {
        MapperStatement mapperStatement;
        for(Element element:elements){
            mapperStatement=new MapperStatement();
            String id=element.attr("id");
            mapperStatement.setNamespace(namespace);
            mapperStatement.setId(element.attr("id"));
            mapperStatement.setSql(element.html());
            //无论是否该标签是否存在或者是否有值,它都会返回一个字符串(可能是一个空的字符串),所以我们要对其空字符串进行判断
            if(!"".equals(element.attr("parameterType"))){
                mapperStatement.setParameterType(element.attr("parameterType"));
            }

            if(!"".equals(element.attr("resultType"))){
                mapperStatement.setResultType(element.attr("resultType"));
            }

            //封装的Map中
            String key=namespace+"."+id;
            mapperStatementMap.put(key,mapperStatement);
        }
    }

}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值