半天擼一個簡易版mybatis

為什麼需要持久層框架?

首先我們先看看使用原生jdbc存在的問題?

public static void main(String[] args) {
 Connection connection = null;
 PreparedStatement preparedStatement = null;
 ResultSet resultSet = null;
 try {
 // 載入資料庫驅動
 Class.forName("com.mysql.jdbc.Driver");
 // 通過驅動管理類獲取資料庫鏈接
 connection =
DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?
characterEncoding=utf-8", "root", "root");
 // 定義sql語句?表示佔位符
 String sql = "select * from user where username = ?";
 // 獲取預處理statement
 preparedStatement = connection.prepareStatement(sql);
 // 設置參數,第⼀個參數為sql語句中參數的序號(從1開始),第⼆個參數為設置的參數值
preparedStatement.setString(1, "tom");
 // 向資料庫發出sql執⾏查詢,查詢出結果集
 resultSet = preparedStatement.executeQuery();
 // 遍歷查詢結果集
 while (resultSet.next()) {
 int id = resultSet.getInt("id");
 String username = resultSet.getString("username");
 // 封裝User
 user.setId(id);
 user.setUsername(username);
 }
 System.out.println(user);
 }
 } catch (Exception e) {
 e.printStackTrace();
 } finally {
 // 釋放資源
 if (resultSet != null) {
 try {
 resultSet.close();
 } catch (SQLException e) {
 e.printStackTrace();
 }
 }
 if (preparedStatement != null) {
 try {
 preparedStatement.close();
 } catch (SQLException e) {
 e.printStackTrace();
 } }
 if (connection != null) {
 try {
 connection.close();
 } catch (SQLException e) {
 e.printStackTrace();
 }
 } }

可以看出原始jdbc存在的問題如下:

  1. 資料庫連接、創建、釋放頻繁造成資源浪,影響系統性能
  2. sql語句卸載程式碼里,不易維護,也不好復用
  3. 使用preparedStatement向佔位符傳參存在硬編碼,如果where條件變了,需要改sql
  4. 對結果集解析存在硬編碼,加欄位需要改sql並且改解析的程式碼,如果能將資料庫查出的記錄封裝成pojo對象解析會比較方便

解決方法如下:

  1. 使用資料庫連接池初始化連接資源
  2. 將sql語句抽取到xml配置文件中
  3. 使用反射等技術,自動將實體與表進行屬性與欄位的自動映射

自定義持久層框架

本質就是對jdbc的一個封裝和功能的增強,大概需要這幾個步驟

  1. 創建配置文件
    • sqlMapConfig.xml :資料庫配置資訊
    • xxMapper.xml:sql語句資訊
  2. 創建兩個bean(容器對象):存放的就是對配置文件解析出來的內容
    • Configuration(核心配置類):存放sqlMapConfig.xml解析出來的內容
    • MappedStatement(映射配置類):存放xxMapper.xml解析出來的內容
  3. 創建類SqlSessionFactoryBuilder
    • 使用dom4j解析配置文件,將內容封裝到容器對象中
    • 創建SqlSessionFactory對象,生產SqlSession
  4. 創建SqlSessionFactory介面及實現類DefaultSqlSessionFactory。通過方法openSession生產SqlSession
  5. 創建介面SqlSession介面及實現類DefaultSqlSession。定義query、update、save介面
  6. 創建Executor介面及實現類SimpleExecutor,執行具體的jdbc程式碼操作。

開發完成後,第一階段我們的dao層的實現是這樣的:

public class UserDaoImpl implements UserDao {

    @Override
    public List<User> queryAll() throws IllegalAccessException, IntrospectionException, InstantiationException, SQLException, InvocationTargetException, NoSuchFieldException, DocumentException, PropertyVetoException, ClassNotFoundException {
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();
        SqlSessionFactory builder = sqlSessionFactoryBuilder.builder("sqlMapConfig.xml");
        SqlSession sqlSession = builder.openSession();
        List<User> objects = sqlSession.queryAll("User.selectList");
        System.out.println(objects);
        return objects;
    }
}

可以看出還有兩個明顯需要改進的地方:

  • 每個dao層的實現都需要寫大段重複程式碼,userDaoImpl寫一次,ProductDaoImpl寫一次
  • 每次調用queryAll都需要傳入參數statementid

那麼可以怎麼解決呢?

  • SqlSessionFactory可以做成單例,方便調取。Dao層的實現類重複編碼可以通過動態代理實現,動態生成Dao的實現類,Dao層只寫介面就行了。
  • statementid可以通過約定名稱對應的方式替代

程式碼如下:

原dao層實現:

public class UserDaoImpl implements UserDao {

    @Override
    public List<User> queryAll() throws IllegalAccessException, IntrospectionException, InstantiationException, SQLException, InvocationTargetException, NoSuchFieldException, DocumentException, PropertyVetoException, ClassNotFoundException {
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder=new SqlSessionFactoryBuilder();
        SqlSessionFactory builder = sqlSessionFactoryBuilder.builder("sqlMapConfig.xml");
        SqlSession sqlSession = builder.openSession();
        List<User> objects = sqlSession.queryAll("User.selectList");
        System.out.println(objects);
        return objects;
    }
}

動態代理:

  public <T> T getMapper(Class mapperClass) {
        Object instance = Proxy.newProxyInstance(mapperClass.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                if (method.getName().equals("queryAll")) {
                    String className = method.getDeclaringClass().getName();
                    String statementid = className + "." + method.getName();
                    List<User> objects = queryAll(statementid);
                    return objects;
                }
                return null;
            }
        });
        return (T) instance;
    }

簡單的封裝就完成了。
總體項目地址://gitee.com/mmcLine/simple-mybatis