1. MyBatis 框架分層架構

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

3. MyBatis 插件開發
- MyBatis 允許在已映射語句執行過程中的某一點進行攔截調用.MyBatis支持對以下方法(四大對象)的攔截:
- Executor
- StatementHandler
- ParameterHandler
- ResultSetHandler
- 在創建四大對象的時候,並不是直接返回的,而是
interceptorChain.pluginAll(xxxHandler); - pluginAll 方法就是獲取到所有的Interceptor(插件需要實現的接口),調用
interceptor.plugin(target),
最終,返回包裝后的target對象;
// pluginAll() 源碼
public Object pluginAll(Object target){
for(Interceptor interceptor : interceptors){
target = interceptor.plugin(target);
}
return target;
}
3.1 插件的編寫
- 步驟:
- 編寫Interceptor的實現類;
- 使用
@Intercepts注解完成插件簽名; - 將寫好的插件注冊到全局配置文件中;
- 多個插件的執行順序
- 創建動態代理的時候,是按照插件的配置順序,創建層層代理對象;
- 執行目標方法的時候,按照逆序執行;

// 編寫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)處理枚舉
- 我們可以通過自定義TypeHandler的形式來在設置參數或者取出結果集的時候,自定義參數封裝策略;
- 步驟:
- 實現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{
同上;
}
}
參考資料
