0%

手写Mybatis框架(掌握原理)

  • 手写一个模仿MyBatis的框架GodBatis

五、手写MyBatis框架(掌握原理)

5.1 dom4j解析XML文件

  • 这一小节不是这个笔记的学习重点,知道就行。

5.1.1 解析mybatis-config.xml文件

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="mybatisDB">
<environment id="mybatisDB">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
<environment id="javawebDB">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/javaweb"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="CarMapper.xml"/>
</mappers>
</configuration>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
/**
* 使用dom4j解析XML文件
*/
public class ParseXMLByDom4jTest {
@Test
public void testParseMybatisConfigXML() throws Exception{

// 创建SAXReader对象
SAXReader saxReader = new SAXReader();
// 获取输入流
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("mybatis-config.xml");
// 读XML文件,返回document对象,document对象是文档对象,代表了整个XML文件
Document document = saxReader.read(is);

// 获取<environments>标签的default属性的值
String xPath = "/configuration/environments";
Element environmentsElt = (Element)document.selectSingleNode(xPath);
String defaultId = environmentsElt.attributeValue("default");
System.out.println(defaultId);

// 获取指定id的environment标签
Element environmentElt = (Element)document.selectSingleNode("/configuration/environments/environment[@id='" + defaultId + "']");

// 获取事务管理器类型
Element transactionManager = environmentElt.element("transactionManager");
String transactionManagerType = transactionManager.attributeValue("type");
System.out.println(transactionManagerType);

// 获取数据源类型
Element dataSource = environmentElt.element("dataSource");
String dataSourceType = dataSource.attributeValue("type");
System.out.println(dataSourceType);

// 将数据源信息封装到Map集合
Map<String,String> dataSourceMap = new HashMap<>();
dataSource.elements().forEach(propertyElt -> {
dataSourceMap.put(propertyElt.attributeValue("name"), propertyElt.attributeValue("value"));
});
dataSourceMap.forEach((k, v) -> System.out.println(k + ":" + v));

// 获取sqlmapper.xml文件的路径
Element mappersElt = (Element) document.selectSingleNode("/configuration/mappers");
mappersElt.elements().forEach(mapper -> {
System.out.println(mapper.attributeValue("resource"));
});
}
}

5.1.2 解析CarMapper.xml文件

1
2
3
4
5
6
7
8
9
10
11
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="car">
<insert id="insertCar">INSERT INTO t_car(id, car_num, brand, guide_price, produce_time, car_type) VALUES (null, #{carNum}, #{brand}, #{guidePrice}, #{produceTime}, #{carType})</insert>
<delete id="deleteCarByNum">DELETE FROM t_car WHERE car_num = #{SuiBianXie}</delete>
<update id="updateCar">UPDATE t_car SET guide_price = #{guidePrice} WHERE car_num = #{carNum}</update>
<select id="selectCarById" resultType="com.f.mybatis.bean.Car">SELECT car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType FROM t_car WHERE id = #{id}</select>
<select id="selectCar" resultType="com.f.mybatis.bean.Car">SELECT car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType FROM t_car</select>
</mapper>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
@Test
public void testParseSqlMapperXML() throws Exception{
// 创建SAXReader对象
SAXReader saxReader = new SAXReader();
// 获取输入流
InputStream is = ClassLoader.getSystemClassLoader().getResourceAsStream("CarMapper.xml");
// 读XML文件,返回document对象,document对象是文档对象,代表了整个XML文件
Document document = saxReader.read(is);

// 获取namespace
String xpath = "/mapper";
Element mapperElt = (Element) document.selectSingleNode(xpath);
String namespace = mapperElt.attributeValue("namespace");
System.out.println(namespace);

// 获取sql id
mapperElt.elements().forEach(statementElt -> {
// 标签名
String name = statementElt.getName();
System.out.println("name:" + name);
// 如果是select标签,还要获取它的resultType
if ("select".equals(name)) {
String resultType = statementElt.attributeValue("resultType");
System.out.println("resultType:" + resultType);
}
// sql id
String id = statementElt.attributeValue("id");
System.out.println("sqlId:" + id);
// sql语句
String sql = statementElt.getTextTrim();
System.out.println("sql:" + sql);
});
}

5.2 ★GodBatis

5.2.1 创建SqlSessionFactory类

  • 手写 Mybatis 框架,命名为 GodBatis

    • 第一步:IDEA中创建模块 godbatis,并引入相关依赖。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>

      <groupId>com.f</groupId>
      <artifactId>godbatis</artifactId>
      <version>1.0-SNAPSHOT</version>
      <packaging>jar</packaging>

      <dependencies>
      <!--dom4j依赖-->
      <dependency>
      <groupId>org.dom4j</groupId>
      <artifactId>dom4j</artifactId>
      <version>2.1.3</version>
      </dependency>
      <!--jaxen依赖-->
      <dependency>
      <groupId>jaxen</groupId>
      <artifactId>jaxen</artifactId>
      <version>1.2.0</version>
      </dependency>
      <!--junit依赖-->
      <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.13.2</version>
      <scope>test</scope>
      </dependency>
      <!--mysql依赖-->
      <dependency>
      <groupId>com.mysql</groupId>
      <artifactId>mysql-connector-j</artifactId>
      <version>8.2.0</version>
      </dependency>
      </dependencies>

      <properties>
      <maven.compiler.source>17</maven.compiler.source>
      <maven.compiler.target>17</maven.compiler.target>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      </properties>

      </project>
    • 第二步:创建资源工具类Resources,方便获取指向配置文件的输入流:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      package org.god.ibatis.utils;

      import java.io.InputStream;

      /**
      * GodBatis框架提供的一个工具类
      * 这个工具类专门完成“类路径”中资源的加载
      *
      * @author fzy
      * @date 2024/1/3 14:15
      */
      public class Resources {
      /**
      * 工具类的构造方法建议私有化
      * 因为工具类的方法都是静态的,不需要创建对象就能调用
      * 为了避免 new 对象,所以构造方法私有化
      */
      private Resources() {
      }

      /**
      * 从类路径中加载资源
      * @param resource 放在类路径中的资源文件
      * @return 指向资源文件的一个输入流
      */
      public static InputStream getResourceAsStream(String resource) {
      return ClassLoader.getSystemClassLoader().getResourceAsStream(resource);
      }
      }
    • 第三步:定义SqlSessionFactoryBuilder类:

      • 提供一个无参数构造方法,再提供一个 build 方法,该 build 方法要返回 SqlSessionFactory 对象:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        package org.god.ibatis.core;

        import java.io.InputStream;

        /**
        * SqlSessionFactory构建器对象
        * 通过SqlSessionFactoryBuilder的build方法来解析godbatis-config.xml文件,
        * 然后创建SqlSessionFactory对象
        *
        * @author fzy
        * @date 2024/1/3 14:21
        */
        public class SqlSessionFactoryBuilder {
        public SqlSessionFactoryBuilder() {
        }

        /**
        * 解析godbatis-config.xml文件,来构建SqlSessionFactory对象
        * @param is 指向godbatis-config.xml文件的一个输入流
        * @return SqlSessionFactory对象
        */
        public SqlSessionFactory build(InputStream is) {
        // 解析配置文件,创建数据源对象
        // 解析配置文件,创建事务管理器对象
        // 解析配置文件,获取所有的SQL映射对象
        // 将以上信息封装到SqlSessionFactory对象中
        // 返回
        return null;
        }
        }
    • 第四步:在创建 SqlSessionFactory 类前,分析 SqlSessionFactory 类中有哪些属性

      • 事务管理器:Transaction

      • 数据源:DataSource

      • SQL映射对象集合:Map<String, MappedStatement>

        • MappedStatement 类中有什么属性?
          • sql 语句
          • 要封装的结果集类型 resultType
    • 第五步:创建 MappedStatement 类,该类中有 sqlresultType 属性。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      package org.god.ibatis.core;

      /**
      * 普通的Java类,封装了一个SQL标签
      * 一个MappedStatement对象对应一个SQL标签
      * 一个SQL标签中的所有信息封装到MappedStatement对象当中
      *
      * @author fzy
      * @date 2024/1/3 14:48
      */
      public class MappedStatement {
      /**
      * sql语句
      */
      private String sql;
      /**
      * 要封装的结果集类型
      * 当为 insert、delete、update语句时,该值为null
      * 只有当sql语句是select语句的时候,resultType才有值
      */
      private String resultType;

      public MappedStatement() {
      }

      public MappedStatement(String sql, String resultType) {
      this.sql = sql;
      this.resultType = resultType;
      }

      public String getSql() {
      return sql;
      }

      public void setSql(String sql) {
      this.sql = sql;
      }

      public String getResultType() {
      return resultType;
      }

      public void setResultType(String resultType) {
      this.resultType = resultType;
      }

      @Override
      public String toString() {
      return "MappedStatement{" +
      "sql='" + sql + '\'' +
      ", resultType='" + resultType + '\'' +
      '}';
      }
      }
    • 第六步:实现事务管理器 Transaction ,因为事务管理器需要能根据配置文件灵活切换(JDBCMANAGED),所以事务管理器最好是定义一个接口,然后每一个具体的事务管理器都实现这个接口。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      package org.god.ibatis.core;

      /**
      * 事务管理器接口
      * 所有的事务管理器都应该遵循该规范
      * JDBC事务管理器,MANAGED事务管理器都应该实现这个接口
      * Transaction事务管理器,提供管理事务的方法
      * @author fzy
      * @date 2024/1/3 15:16
      */
      public interface Transaction {
      /**
      * 提交事务
      */
      void commit();
      /**
      * 回滚事务
      */
      void rollback();
      /**
      * 关闭事务
      */
      void close();

      // 其他所需方法,根据后面的分析再添加
      }
      • 因为在提交事务、回滚事务、关闭事务时,都需要用到连接对象 Connection,而 Connection 对象是由数据源 DataSource 提供的,所以接下来我们先实现数据源。
    • 第七步:分析发现,Transaction 中的方法需要频繁使用 Connection 对象,而 Connection 对象是由数据源提供的,所以直接在 Transaction 中定义数据源属性。

      SqlSessionFactory 就可以从 Transaction 来得到数据源对象,而不需要自己另外定义数据源属性了。因此,在 SqlSessionFactory 中删除 DataSource 属性。

    • 第八步:实现数据源 DataSource,因为数据源也需要能够根据配置文件灵活切换(UNPOOLEDPOOLEDJNDI),所以数据源也是定义一个接口,然后每一个具体的数据源都实现这个接口。

      因为所有的数据源都要实现 JDK 自带的规范:javax.sql.DataSource,所以这个 DataSource 接口就不用我们自己写了。

    • 第九步:创建 UnpooledDataSourcePooledDataSourceJNDIDataSource 实现 javax.sql.DataSource 接口,在这里只是实现了 UnpooledDataSource 的具体编写:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      package org.god.ibatis.core;

      import java.io.PrintWriter;
      import java.sql.Connection;
      import java.sql.DriverManager;
      import java.sql.SQLException;
      import java.sql.SQLFeatureNotSupportedException;
      import java.util.logging.Logger;

      /**
      * 数据源的实现类:UNPOOLED
      * 不使用连接池,每一次都新建Connection对象
      *
      * @author fzy
      * @date 2024/1/3 15:37
      */
      public class UnpooledDataSource implements javax.sql.DataSource {
      private String driver;
      private String url;
      private String username;
      private String password;

      /**
      * 创建一个数据源对象。
      *
      * @param driver
      * @param url
      * @param username
      * @param password
      */
      public UnpooledDataSource(String driver, String url, String username, String password) {
      // 直接注册驱动
      try {
      Class.forName(driver);
      } catch (ClassNotFoundException e) {
      e.printStackTrace();
      }
      this.driver = driver;
      this.url = url;
      this.username = username;
      this.password = password;
      }

      @Override
      public Connection getConnection() throws SQLException {
      return DriverManager.getConnection(url, username, password);
      }

      ...其余的实现方法...
      }
    • 第十步:有了数据源后,就可以继续实现事务管理器了。创建 JDBCTransactionManagedTransaction 实现 Transaction 接口,在这里只是实现了 JDBCTransaction 的具体编写:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      package org.god.ibatis.core;

      import javax.sql.DataSource;
      import java.sql.Connection;
      import java.sql.SQLException;

      /**
      * JDBC事务管理器
      *
      * @author fzy
      * @date 2024/1/3 15:20
      */
      public class JDBCTransaction implements Transaction {
      /**
      * 数据源属性
      */
      private DataSource dataSource;

      /**
      * 自动提交标志
      * true表示自动提交
      * false表示不自动提交
      */
      private boolean autoCommit;

      /**
      * 连接对象
      */
      private Connection connection;

      @Override
      public Connection getConnection() {
      return connection;
      }

      /**
      * 创建事务管理器对象
      *
      * @param dataSource
      * @param autoCommit
      */
      public JDBCTransaction(DataSource dataSource, boolean autoCommit) {
      this.dataSource = dataSource;
      this.autoCommit = autoCommit;
      }

      @Override
      public void commit() {
      try {
      connection.commit();
      } catch (SQLException e) {
      e.printStackTrace();
      }
      }

      @Override
      public void rollback() {
      try {
      connection.rollback();
      } catch (SQLException e) {
      e.printStackTrace();
      }
      }

      @Override
      public void close() {
      try {
      connection.close();
      } catch (SQLException e) {
      e.printStackTrace();
      }
      }

      @Override
      public void openConnection() {
      if (connection == null) {
      try {
      connection = dataSource.getConnection();
      connection.setAutoCommit(autoCommit);
      } catch (SQLException e) {
      e.printStackTrace();
      }
      }
      }
      }
      • 为了保证在提交事务、回滚事务、关闭事务时,使用的是同一个 Connection 对象,所以在事务管理器中定义 Connection 属性,并添加方法 openConnection()

        而在之后执行 sql 语句时,也需要用到 Connection 对象,所以添加方法 getConnection()

        因此补充 Transaction 接口的方法:

        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        package org.god.ibatis.core;

        import java.sql.Connection;

        /**
        * 事务管理器接口
        * 所有的事务管理器都应该遵循该规范
        * JDBC事务管理器,MANAGED事务管理器都应该实现这个接口
        * Transaction事务管理器,提供管理事务的方法
        *
        * @author fzy
        * @date 2024/1/3 15:16
        */
        public interface Transaction {
        /**
        * 提交事务
        */
        void commit();

        /**
        * 回滚事务
        */
        void rollback();

        /**
        * 关闭事务
        */
        void close();

        /**
        * 开启数据库连接
        */
        void openConnection();

        /**
        * 获取数据库连接对象
        */
        Connection getConnection();
        }

      到这里,我们就已经实现了 DataSourceTransactionMappedStatement,接下来就可以创建 SqlSessionFactory 类了。

    • 第十一步:创建 SqlSessionFactory 类:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      package org.god.ibatis.core;

      import java.util.Map;

      /**
      * SqlSessionFactory对象:
      * 一个数据库对应一个SqlSessionFactory对象
      * 通过SqlSessionFactory对象可以获取SqlSession对象(开启会话)
      * 一个SqlSessionFactory对象可以开启多个SqlSession
      *
      * @author fzy
      * @date 2024/1/3 14:24
      */
      public class SqlSessionFactory {
      // 思考:SqlSessionFactory应该定义哪些属性?
      // 1.事务管理器属性
      // 2.数据源属性
      // 3.存放sql语句的Map集合,key是sqlId,value是对应的sql标签信息对象

      /**
      * 事务管理器属性
      * 事务管理器需要能够灵活切换
      * SqlSessionFactory类中的事务管理器应该是面向接口编程的
      */
      private Transaction transaction;

      /**
      * 数据源属性(可以由transaction获得,不需要在这里定义)
      */

      /**
      * 存放sql语句的Map集合
      * key是sqlId
      * value是对应的sql标签信息对象
      */
      private Map<String, MappedStatement> mappedStatements;

      public SqlSessionFactory() {
      }

      public SqlSessionFactory(Transaction transaction, Map<String, MappedStatement> mappedStatements) {
      this.transaction = transaction;
      this.mappedStatements = mappedStatements;
      }

      public Transaction getTransaction() {
      return transaction;
      }

      public void setTransaction(Transaction transaction) {
      this.transaction = transaction;
      }

      public Map<String, MappedStatement> getMappedStatements() {
      return mappedStatements;
      }

      public void setMappedStatements(Map<String, MappedStatement> mappedStatements) {
      this.mappedStatements = mappedStatements;
      }
      }
    • 第十二步:为了创建 SqlSessionFactory 对象,需要传入 Transaction 对象和 Map<String, MappedStatement> 表,而创建 Transaction 对象前还需要先创建 DataSource 对象。这些对象从哪来呢?

      • 当然是根据配置文件的信息来创建。
    • 第十三步:根据 godbatis-config.xml 文件来创建 SqlSessionFactory 对象(用 dom4j 技术解析 XML 文件)。完善 SqlSessionFactoryBuilderbuild 方法:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      42
      43
      44
      45
      46
      47
      48
      49
      50
      51
      52
      53
      54
      55
      56
      57
      58
      59
      60
      61
      62
      63
      64
      65
      66
      67
      68
      69
      70
      71
      72
      73
      74
      75
      76
      77
      78
      79
      80
      81
      82
      83
      84
      85
      86
      87
      88
      89
      90
      91
      92
      93
      94
      95
      96
      97
      98
      99
      100
      101
      102
      103
      104
      105
      106
      107
      108
      109
      110
      111
      112
      113
      114
      115
      116
      117
      118
      119
      120
      121
      122
      123
      124
      125
      126
      127
      128
      129
      130
      131
      132
      133
      134
      135
      136
      137
      138
      139
      140
      141
      142
      143
      144
      145
      146
      147
      148
      149
      150
      package org.god.ibatis.core;

      import org.dom4j.Document;
      import org.dom4j.Element;
      import org.dom4j.io.SAXReader;
      import org.god.ibatis.utils.Resources;

      import javax.sql.DataSource;
      import java.io.InputStream;
      import java.util.ArrayList;
      import java.util.HashMap;
      import java.util.List;
      import java.util.Map;

      /**
      * SqlSessionFactory构建器对象
      * 通过SqlSessionFactoryBuilder的build方法来解析godbatis-config.xml文件,
      * 然后创建SqlSessionFactory对象
      *
      * @author fzy
      * @date 2024/1/3 14:21
      */
      public class SqlSessionFactoryBuilder {
      public SqlSessionFactoryBuilder() {
      }

      /**
      * 解析godbatis-config.xml文件,来构建SqlSessionFactory对象
      *
      * @param is 指向godbatis-config.xml文件的一个输入流
      * @return SqlSessionFactory对象
      */
      public SqlSessionFactory build(InputStream is) {
      SqlSessionFactory factory = null;
      try {
      SAXReader saxReader = new SAXReader();
      Document document = saxReader.read(is);
      Element environments = (Element) document.selectSingleNode("/configuration/environments");
      String defaultId = environments.attributeValue("default");
      Element environment = (Element) document.selectSingleNode("/configuration/environments/environment[@id='" + defaultId + "']");
      Element transactionElt = environment.element("transactionManager");
      Element dataSourceElt = environment.element("dataSource");
      // 用于存储SqlMapper.xml文件的路径(SqlMapper.xml文件可能不止一个)
      List<String> sqlMapperXMLPaths = new ArrayList<>();
      Element mappersElt = (Element) document.selectSingleNode("/configuration/mappers");
      List<Element> mapperElts = mappersElt.elements("mapper");
      for (Element mapperElt : mapperElts) {
      String resourcePath = mapperElt.attributeValue("resource");
      sqlMapperXMLPaths.add(resourcePath);
      }
      // 解析配置文件,创建数据源对象
      DataSource dataSource = getDataSource(dataSourceElt);
      // 解析配置文件,创建事务管理器对象
      Transaction transaction = getTransaction(transactionElt, dataSource);
      // 解析配置文件,获取所有的SQL映射对象
      Map<String, MappedStatement> mappedStatements = getMappedStatements(sqlMapperXMLPaths);
      // 将以上信息封装到SqlSessionFactory对象中
      factory = new SqlSessionFactory(transaction, mappedStatements);
      } catch (Exception e) {
      e.printStackTrace();
      }
      return factory;
      }

      /**
      * 解析所有的SqlMapper.xml文件,然后构建Map集合
      *
      * @param sqlMapperXMLPaths
      * @return
      */
      private Map<String, MappedStatement> getMappedStatements(List<String> sqlMapperXMLPaths) {
      Map<String, MappedStatement> mappedStatements = new HashMap<>();
      for (int i = 0; i < sqlMapperXMLPaths.size(); i++) {
      String sqlMapperXMLPath = sqlMapperXMLPaths.get(i);
      try {
      SAXReader saxReader = new SAXReader();
      Document document = saxReader.read(Resources.getResourceAsStream(sqlMapperXMLPath));
      Element mapperElt = (Element) document.selectSingleNode("/mapper");
      String namespace = mapperElt.attributeValue("namespace");
      List<Element> sqlElts = mapperElt.elements();
      for (Element sqlElt : sqlElts) {
      String id = sqlElt.attributeValue("id");
      String sqlId = namespace + "." + id; //得到sql语句的sqlId
      String resultType = sqlElt.attributeValue("resultType");
      String sql = sqlElt.getTextTrim();
      MappedStatement mappedStatement = new MappedStatement(sql, resultType); //得到MappedStatement对象
      mappedStatements.put(sqlId, mappedStatement);
      }
      } catch (Exception e) {
      e.printStackTrace();
      }
      }
      return mappedStatements;
      }

      /**
      * 获取事务管理器对象
      *
      * @param transactionElt 事务管理器标签元素
      * @param dataSource 数据源对象
      * @return 事务管理器对象
      */
      private Transaction getTransaction(Element transactionElt, DataSource dataSource) {
      Transaction transaction = null;
      String type = transactionElt.attributeValue("type").trim().toUpperCase();
      switch (type) {
      case Const.JDBC_TRANSACTION:
      transaction = new JDBCTransaction(dataSource, false); //默认开启事务,将来需要手动提交
      break;
      case Const.MANAGED_TRANSACTION:
      transaction = new ManagedTransaction();
      break;
      default:
      }
      return transaction;
      }

      /**
      * 获取数据源对象
      *
      * @param dataSourceElt 数据源标签元素
      * @return 数据源对象
      */
      private DataSource getDataSource(Element dataSourceElt) {
      // 获取dataSourceElt的所有property
      Map<String, String> map = new HashMap<>();
      List<Element> propertyElts = dataSourceElt.elements("property");
      for (Element propertyElt : propertyElts) {
      String name = propertyElt.attributeValue("name");
      String value = propertyElt.attributeValue("value");
      map.put(name, value);
      }

      DataSource dataSource = null;
      String type = dataSourceElt.attributeValue("type").trim().toUpperCase();
      switch (type) {
      case Const.UNPOOLED_DATASOURCE:
      dataSource = new UnpooledDataSource(map.get("driver"), map.get("url"), map.get("username"), map.get("password"));
      break;
      case Const.POOLED_DATASOURCE:
      dataSource = new PooledDataSource();
      break;
      case Const.JNDI_DATASOURCE:
      dataSource = new JNDIDataSource();
      break;
      default:
      }
      return dataSource;
      }
      }
  • 到这里,我们就创建好了 SqlSessionFactory 类,并且能根据配置文件创建 SqlSessionFactory 对象。

    • SqlSessionFactory 类中包含有:Transaction 对象和 Map<String, MappedStatement> 表。

      • 而在 Transaction 对象中,包含有:DataSource 对象。

      这些对象都通过 dom4j 技术解析 XML 配置文件来创建:

      • DataSource 对象和 Transaction 对象通过解析 godbatis-config.xml 文件来创建。
      • MappedStatement 对象通过解析 SqlMapper.xml 文件来创建。

5.2.2 创建SqlSession类

  • 在有了 SqlSessionFactory 类后,我们接着来创建 SqlSession 类。

    • 第十四步:首先要明确,SqlSession 是用来执行 sql 语句的,既然要执行 sql 语句,那就需要:

      • Connection 对象
      • 想要执行的 sql 语句,即需要 Map<String, MappedStatement> 表。

      Connection 对象和 Map<String, MappedStatement> 表都可以通过 SqlSessionFactory 获得:

      • Connection 对象由 DataSource 直接创建,DataSourceTransaction 里,TransactionSqlSessionFactory 里。
      • Map<String, MappedStatement>表在 SqlSessionFactory 里。

      所以我们在创建 SqlSession 对象的时候,传入 SqlSessionFactory 对象即可。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      /**
      * 获取sql会话对象
      *
      * @return
      */
      public SqlSession openSession() {
      // 开启会话的前提是开启连接
      transaction.openConnection();
      // 创建SqlSession对象
      SqlSession sqlSession = new SqlSession(this); //传入SqlSessionFactory对象
      return sqlSession;
      }
    • 第十五步:为了方便起见,我们只实现 SqlSessioninsert 方法和 selectOne 方法。我们的目的只是为了搞懂原理,没必要全部实现。

      • insert 方法:insert 方法的难点在于:

        1. 你不知道有多少个 ?
        2. 你不知道该将 pojo 对象中的哪个属性赋值给哪个 ?
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        /**
        * 执行insert语句,向数据库表中插入记录
        *
        * @param sqlId sql语句的Id
        * @param pojo 插入的数据
        * @return
        */
        public int insert(String sqlId, Object pojo) {
        int count = 0;
        try {
        // JDBC代码,执行insert语句,完成插入操作
        Connection connection = factory.getTransaction().getConnection();
        // 配置文件中的sql语句:INSERT INTO t_user VALUES (#{id}, #{name}, #{age})
        String godbatisSql = factory.getMappedStatements().get(sqlId).getSql();
        // 需要将其转换为:INSERT INTO t_user VALUES (?, ?, ?)
        String sql = godbatisSql.replaceAll("#\\{[0-9A-Za-z_]*}", "?");
        PreparedStatement ps = connection.prepareStatement(sql);

        // 给 ? 占位符传值
        // 难点:
        // 1.你不知道有多少个?
        // 2.你不知道该将pojo对象中的哪个属性赋值给哪个?
        int fromIndex = 0;
        int questionMarkIndex = 1; // ? 的下标
        while (true) {
        int jingIndex = godbatisSql.indexOf("#", fromIndex);
        int rightBraceIndex = godbatisSql.indexOf("}", fromIndex);
        if (jingIndex < 0 || rightBraceIndex < 0) {
        break;
        }
        String propertyName = godbatisSql.substring(jingIndex + 2, rightBraceIndex).trim();
        String getMethodName = "get" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);
        Method declaredMethod = pojo.getClass().getDeclaredMethod(getMethodName);
        Object getMethodValue = declaredMethod.invoke(pojo);
        ps.setString(questionMarkIndex, getMethodValue.toString());
        fromIndex = rightBraceIndex + 1;
        questionMarkIndex++;
        }

        count = ps.executeUpdate();
        } catch (Exception e) {
        e.printStackTrace();
        }
        return count;
        }
      • selectOne 方法:

        • selectOne 方法的难点在于:给返回结果的哪个属性赋哪个值。
        1
        2
        3
        4
        5
        6
        7
        8
        9
        10
        11
        12
        13
        14
        15
        16
        17
        18
        19
        20
        21
        22
        23
        24
        25
        26
        27
        28
        29
        30
        31
        32
        33
        34
        35
        36
        37
        38
        39
        40
        41
        42
        43
        44
        45
        46
        /**
        * 执行查询语句,返回一个对象。该方法只适合返回一条记录的sql语句
        *
        * @param sqlId
        * @param param
        * @return
        */
        public Object selectOne(String sqlId, Object param) {
        Object result = null;
        try {
        Connection connection = factory.getTransaction().getConnection();
        Map<String, MappedStatement> mappedStatements = factory.getMappedStatements();
        // 配置文件中的sql语句:SELECT * FROM t_user WHERE id = #{id}
        String godbatisSql = mappedStatements.get(sqlId).getSql();
        // 将其转换为:SELECT * FROM t_user WHERE id = ?
        String sql = godbatisSql.replaceAll("#\\{[0-9A-Za-z_]*}", "?");
        // 配置文件中的resultType:org.god.ibatis.pojo.User
        String resultType = mappedStatements.get(sqlId).getResultType();
        // 得到相应的Class对象
        Class<?> resultTypeClass = Class.forName(resultType);
        PreparedStatement ps = connection.prepareStatement(sql);
        // 给占位符传值(假设占位符只有一个,这里简化了)
        ps.setString(1, param.toString());
        ResultSet rs = ps.executeQuery();
        // 从结果集中取数据,封装java对象
        if (rs.next()) {
        result = resultTypeClass.newInstance();
        // 给result中的属性赋值
        // 难点:给 result 的哪个属性赋哪个值
        ResultSetMetaData rsmd = rs.getMetaData(); // 获取查询结果元数据,其中就有查询结果的列名
        int columnCount = rsmd.getColumnCount();
        for (int i = 1; i <= columnCount; i++) {
        String propertyName = rsmd.getColumnName(i); // 下标要从1开始
        // 拼接方法名
        String setMethodName = "set" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);
        // 获取set方法
        Method setMethod = resultTypeClass.getDeclaredMethod(setMethodName, String.class);
        // 调用set方法,给result中的属性赋值
        setMethod.invoke(result, rs.getString(propertyName));
        }
        }
        } catch (Exception e) {
        e.printStackTrace();
        }
        return result;
        }

      可以看到,反射机制在这里发挥了重要的作用。

5.2.3 打包GodBatis

  • 我们自己写好的框架,可以打包成 jar 包发布,这样别人就可以用我们写的框架了。

    • 第十六步:使用 mavenGodBatis 打包成 jar 包:

      ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Java-SSM-notebook/img/Mybatis/打包godbatis.png)

      install 以后,jar 包就已经在本地仓库里了:

      ![](../../../../../Running Noob/计算机/Typora笔记/笔记-git仓库/Java-SSM-notebook/img/Mybatis/godbatis-jar包.png)

    • 第十七步:有了 godbatisjar 包以后,就可以像使用其他 jar 包一样,使用 godbatis 中的方法了。

      下面的代码都是在 godbatis-test 模块中。

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      <?xml version="1.0" encoding="UTF-8"?>
      <project xmlns="http://maven.apache.org/POM/4.0.0"
      xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
      xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
      <modelVersion>4.0.0</modelVersion>

      <groupId>com.f</groupId>
      <artifactId>godbatis-test</artifactId>
      <version>1.0-SNAPSHOT</version>
      <packaging>jar</packaging>

      <dependencies>
      <!--godbatis依赖-->
      <dependency>
      <groupId>com.f</groupId>
      <artifactId>godbatis</artifactId>
      <version>1.0</version>
      </dependency>
      <!--junit依赖-->
      <dependency>
      <groupId>junit</groupId>
      <artifactId>junit</artifactId>
      <version>4.13.2</version>
      <scope>test</scope>
      </dependency>
      </dependencies>

      <properties>
      <maven.compiler.source>17</maven.compiler.source>
      <maven.compiler.target>17</maven.compiler.target>
      <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
      </properties>

      </project>
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      24
      25
      26
      27
      28
      29
      30
      31
      32
      33
      34
      35
      36
      37
      38
      39
      40
      41
      package com.f.mybatis.test;

      import com.f.mybatis.pojo.Food;
      // 引入godbatis中的类
      import org.god.ibatis.core.SqlSession;
      import org.god.ibatis.core.SqlSessionFactory;
      import org.god.ibatis.core.SqlSessionFactoryBuilder;
      import org.god.ibatis.utils.Resources;
      import org.junit.Test;

      /**
      * @author fzy
      * @date 2024/1/5 17:10
      */
      public class GodBatisTest {
      @Test
      public void testGodBatisInsert() {
      SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
      SqlSessionFactory factory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("godbatis-config.xml"));
      SqlSession sqlSession = factory.openSession();
      Food food = new Food();
      food.setId("1");
      food.setName("rice");
      food.setColor("white");
      int count = sqlSession.insert("food.insertFood", food);
      System.out.println(count);
      sqlSession.commit();
      sqlSession.close();
      }

      @Test
      public void testGodBatisSelectOne() {
      SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
      SqlSessionFactory factory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("godbatis-config.xml"));
      SqlSession sqlSession = factory.openSession();
      Object obj = sqlSession.selectOne("food.selectById", "1");
      System.out.println(obj);
      sqlSession.commit();
      sqlSession.close();
      }
      }
---------------The End---------------