MyBatis简介及插件简单应用

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);
		}
	}
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

意向天开

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值