MyBatis 工作流程及插件開發


1. MyBatis 框架分層架構

2. MyBatis 工作流程

  1. 獲取 SqlSessionFactory 對象:
    • 解析配置文件(全局映射,Sql映射文件)的每一個信息,並保存在Configuration中,返回包含Configuration
      的DefaultSqlSession;
    • MappedStatement: 代表一個增刪改查標簽的詳細信息;
  2. 獲取 SqlSession 對象:
    • 返回一個DefaultSqlSession對象,包含Executor和Configuration;
  3. 獲取接口的代理對象(MapperProxy)
    • getMapper()使用MapperProxyFactory創建一個MapperProxy的代理對象;
    • 代理對象中包含了DefaultSqlSession(Executor);
  4. 執行增刪改查方法
    • 代理對象查詢依賴DefaultSqlSession對象中的Executor,Executor創建StatementHandler對象,
      同時,創建ParameterHandler和ResultSetHandler對象,而ParameterHandler和ResultSetHandler都依賴TypeHandler;
    • StatementHandler: 設置sql語句,預編譯,設置參數等相關工作,以及執行增刪改查方法;
    • ParameterHandler: 設置預編譯參數;
    • ResultHandler: 處理查詢結果集;
    • TypeHandler: 在設置參數和處理查詢結果時,都是依賴TypeHandler,進行數據庫類型和javaBean類型的映射;

3. MyBatis 插件開發

  1. MyBatis 允許在已映射語句執行過程中的某一點進行攔截調用.MyBatis支持對以下方法(四大對象)的攔截:
    • Executor
    • StatementHandler
    • ParameterHandler
    • ResultSetHandler
  2. 在創建四大對象的時候,並不是直接返回的,而是 interceptorChain.pluginAll(xxxHandler);
  3. pluginAll 方法就是獲取到所有的Interceptor(插件需要實現的接口),調用interceptor.plugin(target),
    最終,返回包裝后的target對象;
// pluginAll() 源碼
public Object pluginAll(Object target){
    for(Interceptor interceptor : interceptors){
        target = interceptor.plugin(target);
    }
    return target;
}

3.1 插件的編寫

  1. 步驟:
    • 編寫Interceptor的實現類;
    • 使用@Intercepts注解完成插件簽名;
    • 將寫好的插件注冊到全局配置文件中;
  2. 多個插件的執行順序
    • 創建動態代理的時候,是按照插件的配置順序,創建層層代理對象;
    • 執行目標方法的時候,按照逆序執行;

// 編寫Interceptor實現類: MyFirstPlugin.java
// @Intercepts 注解: 為當前插件指定要攔截哪個對象的哪個方法,以及方法中的參數
@Intercepts(
    {
        @Signature(type=StatementHandler.class,method="parameterize",
                   args=java.sql.Statement.class)
    }
)
public class MyFirstPlugin implements Interceptor{
    // 攔截目標對象中目標方法的執行
    public Object intercept(Invocation invocation) throws Throwable{

        // 執行目標方法
        Object proceed = invocation.proceed();

        // 返回攔截之后的目標方法
        return proceed;
    }

    // 包裝目標對象,即為目標對象創建一個代理對象
    public Object plugin(Object target){

        // 借助 Plugin 的 wrap(Object target,Interceptor interceptor); 包裝我們的目標對象
        // target: 目標對象, interceptor: 攔截器, this 表示使用當前攔截器
        Object proxy = Plugin.wrap(target,this);
        return proxy;
    }

    // 可以獲取插件注冊時,傳入的property屬性
    public void setProperties(Propreties properties){
        System.out.println("插件的配置信息:"+properties);
    }
}

// 在全局配置文件: mybatis-config.xml 中注冊插件
<plugins>   
    <!-- interceptor: 攔截器的類路徑 -->
    <plugin interceptor="cn.itcast.mybatis.dao.MyFirstPlugin">
        <property name="username" value="zhangsan"/>
    </plugin>
</plugins>

4. 使用 PageHelper 插件進行分頁

// 測試類: 查詢所有員工
public class MyBatisTest{

    @Test
    public void test() throws IOException{
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

        SqlSession openSession = sqlSessionFactory.openSession();

        try{
            EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);
            // 參數說明: 1 表示第一頁數據, 3 表示每頁3條數據
            Page<Object> page = PageHelper.startPage(1,3);
            List<Employee> emps = mapper.getEmps();
            for(Employee emp : emps){
                System.out.println(emp);
            }

            System.out.println("當前頁碼:"+page.getPageNum());
            System.out.println("總記錄數:"+page.getTotal());
			System.out.println("每頁記錄數:"+page.getPageSize());
			System.out.println("總頁碼:"+page.getPages());

        } finally{
            openSession.close();
        }
    }
}

// 在mybatis-plugin中注冊PageHelper插件
<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"/>
</plugins>

5. 批量操作

// 測試類: 批量保存員工
public class MyBatisTest{

    @Test
    public void test() throws IOException{
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

        // 可以執行批量操作的sqlSession
        SqlSession openSession = sqlSessionFactory.openSession(ExecutorType.BATCH);

        try{
            EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);

            // 向數據庫中插入10000條數據
            for(int i=0; i<10000; i++){
                mapper.addEmp(new Employee(UUID.randomUUID().toString().substring(0,5),
                                                "zhangsan@163.com","1");
            }

            openSession.commit();
        }finally{
            openSession.close();
        }
    }

6. 自定義類型處理器(TypeHandler)處理枚舉

  1. 我們可以通過自定義TypeHandler的形式來在設置參數或者取出結果集的時候,自定義參數封裝策略;
  2. 步驟:
    • 實現TypeHandler接口或者繼承BaseTypeHandler;
    • 使用@MappedTypes定義處理的java類型;
      使用@MappedJdbcTypes定義jdbcType類型;
    • 在自定義結果集標簽或者參數處理的時候,聲明使用自定義 TypeHandler 進行處理
      或者在全局配置自定義TypeHandler;
// Employee.java
public class Employee{
    private Integer id;
    private String lastName;
    private String email;
    private String gender;
    // 枚舉類型: 用戶狀態包括登錄,登出,不存在
    // 用戶默認狀態:用戶登出
    private EmpStatus empStatus=EmpStatus.LOGOUT;

    get 和 set 方法(略)
}

// EmpStatus.java
public enum EmpStatus{
    LOGIN(100,"用戶登錄"),LOGOUT(200,"用戶登出"),REMOVE(300,"用戶不存在")
}

// EmployMapper.xml
<!-- 保存客戶 -->
<insert id="addEmp" useGeneratedKeys="true" keyProperty="id">
    insert into tbl_employee(last_name,email,gender,empStatus)
    values(#{lastName},#{email},#{gender},#{empStatus})
</insert>

// mybatis-config.xml
<typeHandlers>
    <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler"
                 javaType="cn.itcast.mybatis.bean.EmpStatus"/>
</typeHandlers>

// 測試類
public class MyBatisTest{

    @Test
    public void test() throws IOException{
        SqlSessionFactory sqlSessionFactory = getSqlSessionFactory();

        SqlSession openSession = sqlSessionFactory.openSession();

        try{
            EmployeeMapper mapper = openSession.getMapper(EmployeeMapper.class);

            Employee employee = new Employee("test_enum","zhangsan@163.com","1");

            // MyBatis 在處理枚舉對象時,默認保存的是枚舉的名字: EnumTypeHandler
            //                  也可以使用枚舉的索引來保存:EnumOrdinalTypeHandler
            //
            mapper.addEmp(employee);
            System.out.println("保存成功"+employee.getId());
            openSession.commit();
        }finally{
            openSession.close();
        }

    @Test
    public void testEnumUse(){
        EmpStatus login = EmpStatus.LOGIN;
        System.out.println("枚舉的索引:"+login.ordinal());
        System.out.println("枚舉的名字:"+login.name());
    }
}


// 升級版: 數據庫保存的是 100, 200等這些自定義的狀態碼,而不是枚舉的索引或者枚舉的名字
// EmpStatus.java
public enum EmpStatus{
    LOGIN(100,"用戶登錄"),LOGOUT(200,"用戶登出"),REMOVE(300,"用戶不存在");

    private Integer code; // 狀態碼
    private String msg; // 枚舉的提示信息
    // 有參構造函數
    private EmpStatus(Integer code, String msg){
        this.code = code;
        this.msg = msg;
    }

    get 和 set 方法(略)

    // 按照狀態碼,返回枚舉對象
    public static EmpStatus getEmpStatusByCode(Integer code){
        switch(code){
            case 100:
                return LOGIN;
            case 200:
                return LOGOUT;
            case 300:
                return REMOVE;
            default:
                return LOGOUT;
        }
    }
}

// 自定義枚舉處理器
public class MyEnumEmpStatusTypeHandler implements TypeHandler<EmpStatus>{

    //定義當前數據如何保存到數據庫中
    public void setParameter(PreparedStatement ps, int i, EmpStatus parameter,
                            JdbcType jdbcType) throws SQLException{
        ps.setString(i,parameter.getCode().toString());                   
    }

    //從數據庫中獲取到枚舉狀態碼,返回一個枚舉對象
    public EmpStatus getResult(ResultSet rs, String columnName) throws SQLException{

        int code = rs.getInt(columnName);
        EmpStatus status = EmpStatus.getEmpStatusByCode(code);

        return status;
    }

    public EmpStatus getResult(ResultSet rs, String columnIndex) throws SQLException{

        int code = rs.getInt(columnIndex);
        EmpStatus status = EmpStatus.getEmpStatusByCode(code);

        return status;
    }

    public EmpStatus getResult(CallableStatement cs, String columnIndex) throws SQLException{

        int code = cs.getInt(columnIndex);
        EmpStatus status = EmpStatus.getEmpStatusByCode(code);

        return status;
    }
}

// mybatis-config.xml 中配置自定義枚舉處理器
<typeHandlers>
    <typeHandler handler="cn.itcast.mybatis.typehandler.MyEnumEmpStatusTypeHandler"
                 javaType="cn.itcast.mybatis.bean.EmpStatus"/>
</typeHandlers>

// 測試類
public class MyBatisTest{

    @Test
    public void testEnumUse(){
        EmStatus login = EmpStatus.LOGIN;
        System.out.println("枚舉的索引:"+login.ordinal());
        System.out.println("枚舉的名字:"+login.name());
        System.out.println("枚舉的狀態碼:"+login.getCode());
        System.out.println("枚舉的提示信息:"+login.getMsg());
    }

    @Test
    public void testEnum() throws IOException{
        同上;
    }
}

參考資料


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM