1.MyBatis架构
MyBatis相比于Hibernate,Hibernate是一个完整ORM框架(Object->Relation和Relation->Object两方面),而MyBatis主要完成Relation->Object,这样让开发人员使用更简单、更方便的方式完成数据库操作功能,对于应用系统来说也是最实用的。
mybatis架构如下图
1)接口层
MyBatis提供给开发人员的一套API.主要使用SqlSession接口.通过SqlSession接口和Mapper接口.开发人员,可以通知MyBatis框架调用那一条SQL命令以及SQL命令关联参数.
获取方式代码如下
public void start() throws Exception{
InputStream is = Resources.getResourceAsStream("myBatis-config.xml");
SqlSessionFactoryBuilder builderObj = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = builderObj.build(is);
session = factory.openSession();
}
public void test(){
ObDao dao = session.getMapper(ObDao.class);
Ob ob = dao.find(1);
}
2)数据处理层
数据处理层是MyBatis框架内部实现.来完成对数据库具体操作.主要负责:
- 参数与SQL命令绑定
- SQL命令发送方式
- 查询结果类型转换
3)支撑层
支撑层用来完成MyBaits与数据库基本连接方式以及SQL命令与配置文件对应.主要负责:
- MyBatis与数据库连接方式管理
- MyBatis对事务管理方式
- SQL命令与XML配置对应
- MyBatis查询缓存管理
2.Mybatis的4大神器
1)Executor
每一个SqlSession对象都会拥有一个Executor(执行器对象);这个执行对象负责增删改查的具体操作
2)StatementHandler
负责操作Statement与数据库进行交流.在工作时
还会使用ParameterHandler进行参数配置,使用ResultHandler将查询结果与实体类对象进行绑定
此处理器是最重要的一个处理器,看一下源码种接口定义
prepare:用于具体创建一个Statement对象或则preparedStatement对象
parameterize:用于初始化Statement及对Sql中占位符进行赋值.
update:用于通知Statement将[insert,update,delete]推送到数据库
query:用于通知Statement将[select]推送到数据库并返回对应查询结果
后面讲mybatis插件将会用到这里
3)ParameterHandler
参数处理器,负责为PreparedStatement的sql语句参数动态赋值
4)ResultSetHandler
- 处理Statement执行后产生的结果集,生成结果列表
- 处理存储过程执行后的输出参数
3.MyBatis插件简单应用
- 1)MyBatis插件开发本质是JDK动态代理设计模式的封装
- 2)MyBatis插件开发作用是监听MyBatis中四大神器的行为,在四大神器运行时进行代码植入
- 3)通过源代码可以发现,四大神器对象创建几乎都会涉及到一个方法
interceptorChain.pluginAll(四大神器对象)
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
executor = (Executor) interceptorChain.pluginAll(executor);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
- 4)pluginAll方法做了什么
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
将四大神器某一个实例对象交给【Interceptor】管理返回【代理对象】
- 5)Interceptor接口作用
拦截器接口,通过实现这个接口开发人员可以对四大神器对象进行监控处理。
- 6)如何开发自己的拦截器【也就是常说插件】
第一件事: 开发一个Interceptor接口的实现类
第二件事: 需要重写Interceptor接口方法
第三件事: 在mybatis框架通过核心配置注册拦截器
第四件事: 需要通过@Intercpts指定当前拦截器监听的神器类型和行为
- 7)mybatis插件案例(分页插件实现)
/**
* 继承Interceptor,拦截4神器StatementHandler种方法prepare
*/
@Intercepts({@Signature(type=StatementHandler.class,method="prepare",args={Connection.class})})
public class PagePlugin implements Interceptor {
private static String dialect = ""; //数据库方言设置(通过属性复制供判断oracle还是mysql)
private static String pageSqlId = ""; //mapper.xml中需要拦截的ID(正则匹配)
//实现intercept方法
public Object intercept(Invocation ivk) throws Throwable {
/*RoutingStatementHandler:是一个具体实现类.在这个类中并没有对Statement对象进行具体使用.只是根据得到Executor类型,决定创建何种类型StatementHandler对象.在MyBatis工作时,使用的StatementHandler接口对象实际上就是RoutingStatementHandler对象*/
if(ivk.getTarget() instanceof RoutingStatementHandler){
//获得当前拦截的目标对象
RoutingStatementHandler statementHandler = (RoutingStatementHandler)ivk.getTarget();
//根据接收的Executor类型创建BaseStatementHandler(StatementHandler接口实现的抽象类),其中delegate可以是任一的SimpleExecutor、ReuseExecutor、BatchExecutor等执行器
BaseStatementHandler delegate = (BaseStatementHandler) ReflectHelper.getValueByFieldName(statementHandler, "delegate");
//获取MappedStatement对象,其作用是维护了一条<select|update|delete|insert>节点的封装
MappedStatement mappedStatement = (MappedStatement) ReflectHelper.getValueByFieldName(delegate, "mappedStatement");
if(mappedStatement.getId().matches(pageSqlId)){ //拦截需要分页的SQL
BoundSql boundSql = delegate.getBoundSql();
Object parameterObject = boundSql.getParameterObject();//分页SQL<select>中parameterType属性对应的实体参数,即Mapper接口中执行分页方法的参数,该参数不得为空
if(parameterObject==null){
throw new NullPointerException("parameterObject尚未实例化!");
}else{
//获取到连接(注解中定义的args={Connection.class})
Connection connection = (Connection) ivk.getArgs()[0];
String sql = boundSql.getSql();//获取到sql
String countSql = "";
//根据方言嵌入sql语句
if("oracle".equals(dialect)){
countSql = "select count(0) from (" + sql+ ") tmp_count"; //记录统计
}else if("mysql".equals(dialect)){
countSql = "select count(0) from (" + sql+ ") as tmp_count"; //记录统计 == oracle 加 as 报错(SQL command not properly ended)
}
PreparedStatement countStmt = connection.prepareStatement(countSql);
BoundSql countBS = new BoundSql(mappedStatement.getConfiguration(),countSql,boundSql.getParameterMappings(),parameterObject);
setParameters(countStmt,mappedStatement,countBS,parameterObject);
ResultSet rs = countStmt.executeQuery();
int count = 0;
if (rs.next()) {
count = rs.getInt(1);
}
rs.close();
countStmt.close();
//System.out.println(count);
Page page = null;
if(parameterObject instanceof Page){ //参数就是Page实体
page = (Page) parameterObject;
page.setEntityOrField(true);
page.setTotalResult(count);
}else{ //参数为某个实体,该实体拥有Page属性
Field pageField = ReflectHelper.getFieldByFieldName(parameterObject,"page");
if(pageField!=null){
page = (Page) ReflectHelper.getValueByFieldName(parameterObject,"page");
if(page==null)
page = new Page();
page.setEntityOrField(false);
page.setTotalResult(count);
ReflectHelper.setValueByFieldName(parameterObject,"page", page); //通过反射,对实体对象设置分页对象
}else{
throw new NoSuchFieldException(parameterObject.getClass().getName()+"不存在 page 属性!");
}
}
String pageSql = generatePageSql(sql,page);
ReflectHelper.setValueByFieldName(boundSql, "sql", pageSql); //将分页sql语句反射回BoundSql.
}
}
}
return ivk.proceed();
}
/**
* 对SQL参数(?)设值,参考org.apache.ibatis.executor.parameter.DefaultParameterHandler
* @param ps
* @param mappedStatement
* @param boundSql
* @param parameterObject
* @throws SQLException
*/
private void setParameters(PreparedStatement ps,MappedStatement mappedStatement,BoundSql boundSql,Object parameterObject) throws SQLException {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
Configuration configuration = mappedStatement.getConfiguration();
TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
MetaObject metaObject = parameterObject == null ? null: configuration.newMetaObject(parameterObject);
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
PropertyTokenizer prop = new PropertyTokenizer(propertyName);
if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (propertyName.startsWith(ForEachSqlNode.ITEM_PREFIX)&& boundSql.hasAdditionalParameter(prop.getName())) {
value = boundSql.getAdditionalParameter(prop.getName());
if (value != null) {
value = configuration.newMetaObject(value).getValue(propertyName.substring(prop.getName().length()));
}
} else {
value = metaObject == null ? null : metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
if (typeHandler == null) {
throw new ExecutorException("There was no TypeHandler found for parameter "+ propertyName + " of statement "+ mappedStatement.getId());
}
typeHandler.setParameter(ps, i + 1, value, parameterMapping.getJdbcType());
}
}
}
}
/**
* 根据数据库方言,生成特定的分页sql
* @param sql
* @param page
* @return
*/
private String generatePageSql(String sql,Page page){
if(page!=null && Tools.notEmpty(dialect)){
StringBuffer pageSql = new StringBuffer();
if("mysql".equals(dialect)){
pageSql.append(sql);
pageSql.append(" limit "+page.getCurrentResult()+","+page.getShowCount());
}else if("oracle".equals(dialect)){
pageSql.append("select * from (select tmp_tb.*,ROWNUM row_id from (");
pageSql.append(sql);
//pageSql.append(") as tmp_tb where ROWNUM<=");
pageSql.append(") tmp_tb where ROWNUM<=");
pageSql.append(page.getCurrentResult()+page.getShowCount());
pageSql.append(") where row_id>");
pageSql.append(page.getCurrentResult());
}
return pageSql.toString();
}else{
return sql;
}
}
public Object plugin(Object arg0) {
// TODO Auto-generated method stub
return Plugin.wrap(arg0, this);
}
public void setProperties(Properties p) {
dialect = p.getProperty("dialect");//通过属性获取数据库方言
if (Tools.isEmpty(dialect)) {
try {
throw new PropertyException("dialect property is not found!");
} catch (PropertyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
pageSqlId = p.getProperty("pageSqlId");//获取mapper.xml中需要拦截的ID(正则匹配)
if (Tools.isEmpty(pageSqlId)) {
try {
throw new PropertyException("pageSqlId property is not found!");
} catch (PropertyException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
}
<!--mybatis.xml中引入插件-->
<plugins>
<plugin interceptor="插件类路劲">
<property name="dialect" value="oracle"/>
<!--标识ID包含listPage的将进行分页-->
<property name="pageSqlId" value=".*listPage.*"/>
</plugin>
</plugins>
import java.lang.reflect.Field;
/**
* 反射工具类
*/
public class ReflectHelper {
/**
* 获取obj对象fieldName的Field
* @param obj
* @param fieldName
* @return
*/
public static Field getFieldByFieldName(Object obj, String fieldName) {
for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass
.getSuperclass()) {
try {
return superClass.getDeclaredField(fieldName);
} catch (NoSuchFieldException e) {
}
}
return null;
}
/**
* 获取obj对象fieldName的属性值
* @param obj
* @param fieldName
* @return
* @throws SecurityException
* @throws NoSuchFieldException
* @throws IllegalArgumentException
* @throws IllegalAccessException
*/
public static Object getValueByFieldName(Object obj, String fieldName)
throws SecurityException, NoSuchFieldException,
IllegalArgumentException, IllegalAccessException {
Field field = getFieldByFieldName(obj, fieldName);
Object value = null;
if(field!=null){
if (field.isAccessible()) {
value = field.get(obj);
} else {
field.setAccessible(true);
value = field.get(obj);
field.setAccessible(false);
}
}
return value;
}
/**
* 设置obj对象fieldName的属性值
* @param obj
* @param fieldName
* @param value
* @throws SecurityException
* @throws NoSuchFieldException
* @throws IllegalArgumentException
* @throws IllegalAccessException
*/
public static void setValueByFieldName(Object obj, String fieldName,
Object value) throws SecurityException, NoSuchFieldException,
IllegalArgumentException, IllegalAccessException {
Field field = obj.getClass().getDeclaredField(fieldName);
if (field.isAccessible()) {
field.set(obj, value);
} else {
field.setAccessible(true);
field.set(obj, value);
field.setAccessible(false);
}
}
}