mybatis源码解析准备工作

Mybatis源码解析准备工作

tips:mybatis

一、搭建Mybatis源码环境

1、环境要求:

JDK1.8、Maven、Eclipse或者IntelliJ IDEA

2、获取源码

  • Mybatis源码:

https://github.com/mybatis/mybatis-3

  • Mybatis和Spring整合源码:

https://github.com/mybatis/spring

  • Mybatis父类源码:

https://github.com/mybatis/parent

3、导入源码到IDE中

  • File => New => Project… => Empty Project => Next => 命名 => Finish

  • File => New => Module from Existing Sources… => 分别选择三个源码和一个测试代码,分别导入

4、HSQLDB简介

  • Mybatis内嵌HSQLDB的内存模式作为单元测试的数据库,因此不用安装Mysql等数据库。
  • 内存模式用户名和密码可以随便指定,但是如果是需要持久化在本地,则需要指定用户名和密码。
  • 单元测试不用在意,可以随便指定。默认用户名是sa,密码为空。
  • ScriptRunner用来执行(XXX.sql)脚本,SqlRunner用来执行sql语句(增删改查语句)。

5、源码解析入口:

public class Example01 {
    private Connection conn = null;
    @Before
    public void initData() {
        try {
            //Example02 example02 = new Example02();//調用方法的時候回提示,引入依賴,但是調用common裡面的文件的時候卻不用。
            // 加载HSQLDB驱动
            Class.forName("org.hsqldb.jdbcDriver");
            // 获取Connection对象,用户名和密码可以随便指定
            conn = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis",
                    "satestlalla", "");
            // 使用Mybatis的ScriptRunner工具类执行数据库脚本
            ScriptRunner scriptRunner = new ScriptRunner(conn);
            scriptRunner.setLogWriter(null);
            //文件都放在mybatis-common module的resource下面
            scriptRunner.runScript(Resources.getResourceAsReader("create-table.sql"));
            scriptRunner.runScript(Resources.getResourceAsReader("init-data.sql"));
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    @Test
    public void testHsqldbQuery() {
        // SqlRunner是Mybatis封装的操作数据库的工具类
        SqlRunner sqlRunner = new SqlRunner(conn);
        try {
            //调用SqlRunner类的selectAll()方法查询数据
            List<Map<String, Object>> results = sqlRunner.selectAll("select * from user");
            results.forEach(System.out::println);//jdk8之后新特性
            sqlRunner.closeConnection();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }
}

二、JDBC规范

1、jdbc规范文档

  • JDBC4.2规范文档下载地址:https://download.oracle.com/otndocs/jcp/jdbc-4_2-mrel2-spec/index.html

2、操作数据库步骤

  • 建立与数据连接
  • 执行SQL语句
  • 检索SQL执行结果
  • 关闭连接

3、建立与数据库连接

三种方式:

  • DriverManager——JDBC1.0提供

    需要显示加载驱动类:

    Class.forName("org.hsqldb.jdbcDriver");

  • DataSource——JDBC2.0提供(建议使用,配置更加灵活)

    • DataSource:最基本类
    • DataSourceFactory:工厂类
    • ConnectionPoolDataSource:支持缓存和复用Connection对象
    • XADataSource:支持分布式事务

源码如下:

//第一种方式
// 加载驱动 到JDBC4之后不需要直接声明驱动,可以在META-INF.services目录下面创建一个跟驱动同名的文件即可,参考chapter02.SPIExample
/*原理如下
ServiceLoader<Driver> drivers = ServiceLoader.load(java.sql.Driver.class);
        for (Driver driver : drivers ) {
            System.out.println(driver.getClass().getName());
        }
*/
Class.forName("org.hsqldb.jdbcDriver");
// 获取Connection对象
Connection connection = DriverManager.getConnection("jdbc:hsqldb:mem:mybatis",
                    "sa", "");

//第二种方式
// 创建DataSource实例
DataSource dataSource = new UnpooledDataSource("org.hsqldb.jdbcDriver","jdbc:hsqldb:mem:mybatis", "sa", "");
// 获取Connection对象
Connection connection = dataSource.getConnection();

//第三种方式
// 创建DataSource实例
DataSourceFactory dsf = new UnpooledDataSourceFactory();
Properties properties = new Properties();
InputStream configStream = Thread.currentThread().getContextClassLoader().getResourceAsStream("database.properties");
properties.load(configStream);
dsf.setProperties(properties);
DataSource dataSource = dsf.getDataSource();
// 获取Connection对象
Connection connection = dataSource.getConnection();

//插入操作
Statement statement = connection.createStatement();
statement.addBatch("insert into  " +
                    "user(create_time, name, password, phone, nick_name) " +
                    "values('2010-10-24 10:20:30', 'User1', 'test', '18700001111', 'User1');");
            statement.addBatch("insert into " +
                    "user (create_time, name, password, phone, nick_name) " +
                    "values('2010-10-24 10:20:30', 'User2', 'test', '18700002222', 'User2');");
            statement.executeBatch();

//PreparedStatement可配置参数方式操作数据库
PreparedStatement stmt = connection.prepareStatement("insert into  " +
                    "user(create_time, name, password, phone, nick_name) " +
                    "values(?,?,?,?,?);");
            stmt.setString(1,"2010-10-24 10:20:30");
            stmt.setString(2,"User1");
            stmt.setString(3,"test");
            stmt.setString(4,"18700001111");
            stmt.setString(5,"User1");
            ParameterMetaData pmd = stmt.getParameterMetaData();
            for(int i = 1; i <= pmd.getParameterCount(); i++) {
                String typeName = pmd.getParameterTypeName(i);
                String className = pmd.getParameterClassName(i);
                System.out.println("第" + i + "个参数," + "typeName:" + typeName + ", className:" + className);
            }
            stmt.execute();

//自增长主键按,可以获取数据库里面自增长的当前值
Statement stmt = conn.createStatement();
            String sql = "insert into user(create_time, name, password, phone, nick_name) " +
                    "values('2010-10-24 10:20:30','User1','test','18700001111','User1');";
            ResultSet genKeys = stmt.getGeneratedKeys();
            stmt.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);
            genKeys = stmt.getGeneratedKeys();
            if(genKeys.next()) {
                System.out.println("自增长主键:" + genKeys.getInt(1));
            }

//数据库配置信息
DatabaseMetaData dmd = conn.getMetaData();
            System.out.println("数据库URL:" + dmd.getURL());
            System.out.println("数据库用户名:" + dmd.getUserName());
            System.out.println("数据库产品名:" + dmd.getDatabaseProductName());
            System.out.println("数据库产品版本:" + dmd.getDatabaseProductVersion());
            System.out.println("驱动主版本:" + dmd.getDriverMajorVersion());
            System.out.println("驱动副版本:" + dmd.getDriverMinorVersion());
            System.out.println("数据库供应商用于schema的首选术语:" + dmd.getSchemaTerm());
            System.out.println("数据库供应商用于catalog的首选术语:" + dmd.getCatalogTerm());
            System.out.println("数据库供应商用于procedure的首选术语:" + dmd.getProcedureTerm());
            System.out.println("null值是否高排序:" + dmd.nullsAreSortedHigh());
            System.out.println("null值是否低排序:" + dmd.nullsAreSortedLow());
            System.out.println("数据库是否将表存储在本地文件中:" + dmd.usesLocalFiles());
            System.out.println("数据库是否为每个表使用一个文件:" + dmd.usesLocalFilePerTable());
            System.out.println("数据库SQL关键字:" + dmd.getSQLKeywords());

//保存节点savepoint,可以回滚使用。
String sql1 = "insert into user(create_time, name, password, phone, nick_name) " +
                    "values('2010-10-24 10:20:30','User1','test','18700001111','User1')";
            String sql2 = "insert into user(create_time, name, password, phone, nick_name) " +
                    "values('2010-10-24 10:20:30','User2','test','18700001111','User2')";
            conn.setAutoCommit(false);
            Statement stmt = conn.createStatement();
            stmt.executeUpdate(sql1);
            // 创建保存点
            Savepoint savepoint = conn.setSavepoint("SP1");
            stmt.executeUpdate(sql2);
            // 回滚到保存点
            conn.rollback(savepoint);
            conn.commit();

//后续打印统一代码
			ResultSet rs  = conn.createStatement().executeQuery("select * from user ");
			//上面是下面的简单缩写
			//Statement statement = connection.createStatement();
            //ResultSet resultSet = statement.executeQuery("select * from user");
            // 遍历ResultSet
            ResultSetMetaData metaData = resultSet.getMetaData();
            int columCount = metaData.getColumnCount();
            while (resultSet.next()) {
                for (int i = 1; i <= columCount; i++) {
                    String columName = metaData.getColumnName(i);
                    String columVal = resultSet.getString(columName);
                    System.out.println(columName + ":" + columVal);
                }
                System.out.println("--------------------------------------");
            }
            // 关闭连接
            IOUtils.closeQuietly(statement);
            IOUtils.closeQuietly(connection);

三、Mybatis常用工具

1、使用SQL类生成语句:

传统通过字符串拼接,需要注意空格等细节,非常麻烦,通过SQL封装类可以解决这个问题。

两种实现方式:

  • new SQL().SELECT().SELECT().FROM()....

  • new SQL(){{SELECT();SELECT();FROM();....}}

具体代码如下:

String insertSql = new SQL().
            INSERT_INTO("PERSON").
            VALUES("ID, FIRST_NAME", "#{id}, #{firstName}").
            VALUES("LAST_NAME", "#{lastName}").toString();
/*结果是:
INSERT INTO PERSON
 (ID, FIRST_NAME, LAST_NAME)
VALUES (#{id}, #{firstName}, #{lastName})
*/
String sql_area = new SQL()
            {
                {
                    SELECT("P.ID, P.USERNAME, P.PASSWORD");
                    SELECT("P.FIRST_NAME, P.LAST_NAME");
                    FROM("PERSON P");
                    WHERE("P.ID = #{id}");
                    WHERE("P.FIRST_NAME = ${first_name}");
                }
            }.toString();
/*结果是:
SELECT P.ID, P.USERNAME, P.PASSWORD, P.FIRST_NAME, P.LAST_NAME
FROM PERSON P
WHERE (P.ID = #{id} AND P.FIRST_NAME = ${first_name})
*/

2、使用ScriptRunner执行脚本

源码逻辑:

  • 调用setAuoCommit()方法,根据autoCommit属性的值设置事务是否自动提交
  • 判断sendFullScript属性值,如果为true,则调用executeFullScript()方法一次性读取文件内容,然后调用Statement对象的execute()方法一次性执行所有SQL语句。
  • 如果sendFullScript属性值为false,则调用executeLineByLine()方法逐行读取SQL,以分好作为一条SQL语句结束的标志,逐行执行SQL语句。

3、使用SqlRunner执行脚本

  • 类方法汇总:

  • closeConnection:关闭Connection连接。

  • selectOne、selectAll:查找一条记录(返回一条),查找所有记录(返回多条)。

  • insert、update、delete:插入、更新、删除操作

  • run:执行一条sql语句,最好是DDL

  • 源码逻辑:

  • 调用Connection对象的prepareStatement()方法获取PreparedStatement对象后,调用setParameters()方法为SQL中的参数占位符赋值,同时要判断每个参数的类型

  • 调用PreparedStatement的executeQuery()方法执行查询操作

  • 调用getResult()方法,将ResultSet对象转换为List对象,其中List的每各Map对象对应一条记录。

四、涉及设计模式

  1. Builder模式:例如SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder;

  2. 工厂模式:例如SqlSessionFactory、ObjectFactory、MapperProxyFactory;

  3. 单例模式:例如ErrorContext和LogFactory;

  4. 代理模式:Mybatis实现的核心,比如MapperProxy、ConnectionLogger,用的jdk的动态代理;还有executor.loader包使用了cglib或者javassist达到延迟加载的效果;

  5. 组合模式:例如SqlNode和各个子类ChooseSqlNode等;

  6. 模板方法模式:例如BaseExecutor和SimpleExecutor,还有BaseTypeHandler和所有的子类例如IntegerTypeHandler;

  7. 适配器模式:例如Log的Mybatis接口和它对jdbc、log4j等各种日志框架的适配实现;

  8. 装饰者模式:例如Cache包中的cache.decorators子包中等各个装饰者的实现;

  9. 迭代器模式:例如迭代器模式PropertyTokenizer;

五、源码逻辑

XML解析 =》 获取Configuration =》 包含DateSource数据源、statementId和MappedStatement对应的Map变量 =》 MappedStatement包含sqlSource、参数和返回值类型等XML解析信息 =》 sqlSouce接口实现多种类,包含了SqlNode接口的类 (XML解析原始的sql,只有执行的时候才会知道具体sql,因为有判断和具体属性值)和获取BoundSql方法(执行时候原始sql经过解析的结果,只是封装在一个类中而已)=》 Sqlnode实现类就是一个列表,列表中元素是树形结构,存储sql标签下面的文本和标签循环。同时SqlSouce实现类有一个BoundSql属性值,Sqlsouce是获取的XML解析之后的,boundSql是执行时候获取的XML解析结果再解析的。=》根据解析结果执行sql
在这里插入图片描述

发布了10 篇原创文章 · 获赞 2 · 访问量 1924

猜你喜欢

转载自blog.csdn.net/yuhao22/article/details/105619630