1. 程式人生 > 其它 >手寫MyBatis框架

手寫MyBatis框架

原生JDBC

框架寫法

測試

原始碼地址


手寫Mybatis框架更有利於瞭解框架底層,做到知其然知其所以然。

原生JDBC

/**
* 原生jdbc寫法
*
* @author lipangpang
* @date 2022/3/13
* @description
*/
public class JdbcDemo {
  public static void main(String[] args) {
      MallUser user = new MallUser();
      Connection connection = null;
      PreparedStatement preparedStatement = null;
      ResultSet resultSet = null;
      try {
          Class.forName("com.mysql.jdbc.Driver");
          connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=UTF-8&serverTimezone=Asia/Shanghai", "root", "123456");
          String sql = "select * from mall_user where name = ?";
          preparedStatement = connection.prepareStatement(sql);
          preparedStatement.setString(1, "張三");
          resultSet = preparedStatement.executeQuery();
          while (resultSet.next()) {
              Long id = resultSet.getLong("id");
              String username = resultSet.getString("name");
              user.setId(id);
              user.setName(username);
          }
      } catch (Exception e) {
          e.printStackTrace();
      } finally {
          if (resultSet != null) {
              try {
                  resultSet.close();
              } catch (SQLException sqlException) {
                  sqlException.printStackTrace();
              }
          }
          if (preparedStatement != null) {
              try {
                  preparedStatement.close();
              } catch (SQLException sqlException) {
                  sqlException.printStackTrace();
              }
          }
          if (connection != null) {
              try {
                  connection.close();
              } catch (SQLException sqlException) {
                  sqlException.printStackTrace();
              }
          }
      }
      System.out.println(user);
  }
}

原生JDBC寫法的弊端有:

  1. 資料庫配置資訊及sql語句硬編碼。

  2. 重複建立和關閉資料庫連線。

框架寫法

此處檔案的命名參考MyBatis原始碼命名風格,有利於後續檢視原始碼。

工程結構如下:

表結構如下:

框架大致流程如下:

  1. 建立mapper.xml檔案用來儲存sql查詢語句,建立sqlMapConfig.xml來儲存資料庫配置資訊和mapper.xml檔案所在路徑。

    sqlMapConfig.xml
    <configuration>
      <dataSource>
          <property name="driverClass" value="com.mysql.jdbc.Driver"></property>
          <property name="jdbcUrl" value="jdbc:mysql://localhost:3306/test?useUnicode=true&amp;characterEncoding=UTF-8&amp;serverTimezone=Asia/Shanghai"></property>
          <property name="username" value="root"></property>
          <property name="password" value="123456"></property>
      </dataSource>

      <mapper resource="MallUserMapper.xml"></mapper>
      <mapper resource="MallRoleMapper.xml"></mapper>
    </configuration>

    MallUserMapper.xml
    <mapper namespace="org.example.dao.IMallUserDao">
      <select id="queryAll" parameterType="org.example.entity.MallUser" resultType="org.example.entity.MallUser">
          select * from mall_user
      </select>
      <select id="selectOne" parameterType="org.example.entity.MallUser" resultType="org.example.entity.MallUser">
          select * from mall_user where id = #{id} and name = #{name}
      </select>
      <insert id="insertOne" parameterType="org.example.entity.MallUser" resultType="int">
          insert into mall_user(name) values(#{name})
      </insert>
    </mapper>

    MallRoleMapper.xml
    <mapper namespace="org.example.dao.IMallRoleDao">
      <select id="queryAll" parameterType="org.example.entity.MallRole" resultType="org.example.entity.MallRole">
          select * from mall_role
      </select>
      <select id="selectOne" parameterType="org.example.entity.MallRole" resultType="org.example.entity.MallRole">
          select * from mall_role where id = #{id} and name = #{name}
      </select>
      <insert id="insertOne" parameterType="org.example.entity.MallRole" resultType="int">
          insert into mall_role(name) values(#{name})
      </insert>
    </mapper>
  2. 按照面向物件程式設計思想,將第一步解析出來的內容分別封裝到MappedStatement和Configuration實體類中。

    /**
    * 配置檔案物件
    *
    * @author lipangpang
    * @date 2022/3/13
    * @description
    */
    @Data
    public class Configuration {
      private DataSource dataSource;
      //key:名稱空間+id
      private Map<String, MappedStatement> mappedStatementMap = new HashMap<>();
    }

    /**
    * mapper配置檔案物件
    *
    * @author lipangpang
    * @date 2022/3/13
    * @description
    */
    @Data
    public class MappedStatement {
      /**
        * 標識
        */
      private String id;
      /**
        * 返回結果型別
        */
      private String resultType;
      /**
        * 引數查詢型別
        */
      private String parameterType;
      /**
        * sql語句
        */
      private String sql;

    }
  3. 通過SqlSessionFactoryBuilder構建者建立SqlSessionFactory。

    /**
    * SqlSessionFactory構建者
    *
    * @author lipangpang
    * @date 2022/3/13
    * @description
    */
    public class SqlSessionFactoryBuilder {
      /**
        * 構建
        *
        * @param inputStream 檔案配置流
        * @return
        * @throws PropertyVetoException
        * @throws DocumentException
        */
      public SqlSessionFactory build(InputStream inputStream) throws PropertyVetoException, DocumentException {
          //解析配置檔案,封裝到到Configuration
          Configuration configuration = new XMLConfigBuilder().parseConfig(inputStream);
          //建立sqlSessionFactory物件
          SqlSessionFactory sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
          return sqlSessionFactory;
      }
    }
  4. 通過sqlSessionFactory工廠建立SqlSession。

    /**
    * 預設SqlSessionFactory工廠
    *
    * @author lipangpang
    * @date 2022/3/13
    * @description
    */
    public class DefaultSqlSessionFactory implements SqlSessionFactory {
      private Configuration configuration;

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

      @Override
      public SqlSession openSession() {
          return new DefaultSqlSession(this.configuration);
      }
    }
  5. 通過SqlSession執行sql語句(此處包含動態代理的執行方法)。

    /**
    * 預設SqlSession
    *
    * @author lipangpang
    * @date 2022/3/13
    * @description
    */
    public class DefaultSqlSession implements SqlSession {
      private Configuration configuration;

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

      @Override
      public <E> List<E> selectList(String statementId, Object... params) throws Exception {
          Executor executor = new SimpleExecutor();
          MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
          List<Object> objects = executor.query(configuration, mappedStatement, params);
          return (List<E>) objects;
      }

      @Override
      public <T> T selectOne(String statementId, Object... params) throws Exception {
          List<Object> objects = this.selectList(statementId, params);
          if (objects != null && objects.size() == 1) {
              return (T) objects.get(0);
          } else {
              throw new RuntimeException("返回結果為空或返回結果過多");
          }
      }

      @Override
      public Integer insertOne(String statementId, Object... params) throws Exception {
          Executor executor = new SimpleExecutor();
          MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
          return executor.save(configuration, mappedStatement, params);
      }

      @Override
      public <T> T getMapper(Class<?> mapperClass) {
          //通過jdk動態代理獲取物件
          Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new
                  InvocationHandler() {
                      @Override
                      public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                          //方法名(這也是為什麼xml中id要和介面方法名一致)
                          String methodName = method.getName();
                          //類全路徑名(這也是為什麼xml中namespace要和介面全路徑一致)
                          String className = method.getDeclaringClass().getName();
                          String statementId = className + "." + methodName;
                          Type genericReturnType = method.getGenericReturnType();
                          if (genericReturnType instanceof ParameterizedType) {
                              return selectList(statementId, args);
                          }
                          if (genericReturnType.getTypeName().contains("Integer")) {
                              return insertOne(statementId, args);
                          }
                          return selectOne(statementId, args);
                      }
                  });
          return (T) proxyInstance;
      }
    }

測試

測試程式碼:

/**
* MyBatis測試類
*
* @author lipangpang
* @date 2022/3/13
* @description
*/
public class MybatisTest {
  public static void main(String[] args) throws Exception {
      //讀取配置檔案為流
      InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
      //將配置檔案封裝成物件並建立SqlSessionFactory
      SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
      //建立SqlSession
      SqlSession sqlSession = sqlSessionFactory.openSession();
      MallUser user = new MallUser();
      user.setId(1L);
      user.setName("張三");
      MallUser userNew = new MallUser();
      userNew.setName("趙六");
      //查詢單個物件
      MallUser mallUserDb = sqlSession.selectOne("org.example.dao.IMallUserDao.selectOne", user);
      System.out.println("查詢單個物件:" + mallUserDb);
      //新增單個物件
      Integer insertSuccessNumber = sqlSession.insertOne("org.example.dao.IMallUserDao.insertOne", userNew);
      System.out.println("新增單個物件:" + insertSuccessNumber);
      //查詢所有物件
      List<MallUser> mallUsers = sqlSession.selectList("org.example.dao.IMallUserDao.queryAll");
      System.out.println("查詢所有物件:" + mallUsers);
      IMallUserDao mallUserDao = sqlSession.getMapper(IMallUserDao.class);
      //通過代理查詢所有物件
      List<MallUser> mallUsersByProxy = mallUserDao.queryAll();
      System.out.println("通過代理查詢所有物件:" + mallUsersByProxy);
  }
}

返回結果:

查詢單個物件:MallUser(id=1, name=張三)
新增單個物件:1
查詢所有物件:[MallUser(id=1, name=張三), MallUser(id=2, name=李四), MallUser(id=3, name=王五), MallUser(id=4, name=趙六)]
通過代理查詢所有物件:[MallUser(id=1, name=張三), MallUser(id=2, name=李四), MallUser(id=3, name=王五), MallUser(id=4, name=趙六)]

原始碼地址

https://gitee.com/lishihao_pm/demo-parent/tree/master/demo-mybatis