插件开发简介

  • MyBatis在四大对象的创建过程中,都会有插件进行介入。插件可以利用动态代理机制一层层的包装目标对象,而实现在目标对象执行目标方法之前进行拦截的效果。
  • MyBatis 允许在已映射语句执行过程中的某一点进行拦截调用。
  • 默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:
  • Executor(update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
  • ParameterHandler(getParameterObject, setParameters)
  • ResultSetHandler(handleResultSets, handleOutputParameters)
  • StatementHandler(prepare, parameterize, batch, update, query)

插件开发步骤

– 1)、编写插件实现Interceptor接口,并使用@Intercepts注解完成插件签名

mybatis 打开日志 配置 springboot_mybatis


–2)、在全局配置文件中注册插件

mybatis 打开日志 配置 springboot_java_02

小案例

MyFirstPlugin.java

import java.util.Properties;

import org.apache.ibatis.executor.parameter.ParameterHandler;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;

/**
 * 完成插件签名:
 *		告诉MyBatis当前插件用来拦截哪个对象的哪个方法
 */
@Intercepts(
		{
			@Signature(type=StatementHandler.class,method="parameterize",args=java.sql.Statement.class)
		})
public class MyFirstPlugin implements Interceptor{

	/**
	 * intercept:拦截:
	 * 		拦截目标对象的目标方法的执行;
	 */
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		// TODO Auto-generated method stub
		System.out.println("MyFirstPlugin...intercept:"+invocation.getMethod());
		//动态的改变一下sql运行的参数:以前1号员工,实际从数据库查询3号员工
		Object target = invocation.getTarget();
		System.out.println("当前拦截到的对象:"+target);
		//拿到:StatementHandler==>ParameterHandler===>parameterObject
		//拿到target的元数据
		MetaObject metaObject = SystemMetaObject.forObject(target);
		Object value = metaObject.getValue("parameterHandler.parameterObject");
		System.out.println("sql语句用的参数是:"+value);
		//修改完sql语句要用的参数
		metaObject.setValue("parameterHandler.parameterObject", 11);
		//执行目标方法
		Object proceed = invocation.proceed();
		//返回执行后的返回值
		return proceed;
	}

	/**
	 * plugin:
	 * 		包装目标对象的:包装:为目标对象创建一个代理对象
	 */
	@Override
	public Object plugin(Object target) {
		// TODO Auto-generated method stub
		//我们可以借助Plugin的wrap方法来使用当前Interceptor包装我们目标对象
		System.out.println("MyFirstPlugin...plugin:mybatis将要包装的对象"+target);
		Object wrap = Plugin.wrap(target, this);
		//返回为当前target创建的动态代理
		return wrap;
	}

	/**
	 * setProperties:
	 * 		将插件注册时 的property属性设置进来
	 */
	@Override
	public void setProperties(Properties properties) {
		// TODO Auto-generated method stub
		System.out.println("插件配置的信息:"+properties);
	}

}

mybatis-config.xml

<!--plugins:注册插件  -->
	<plugins>
		<plugin interceptor="com.atguigu.mybatis.dao.MyFirstPlugin">
			<property name="username" value="root"/>
			<property name="password" value="123456"/>
		</plugin>
		<plugin interceptor="com.atguigu.mybatis.dao.MySecondPlugin"></plugin>
	</plugins>
/**
	 * 插件原理
	 * 在四大对象创建的时候
	 * 1、每个创建出来的对象不是直接返回的,而是
	 * 		interceptorChain.pluginAll(parameterHandler);
	 * 2、获取到所有的Interceptor(拦截器)(插件需要实现的接口);
	 * 		调用interceptor.plugin(target);返回target包装后的对象
	 * 3、插件机制,我们可以使用插件为目标对象创建一个代理对象;AOP(面向切面)
	 * 		我们的插件可以为四大对象创建出代理对象;
	 * 		代理对象就可以拦截到四大对象的每一个执行;
	 * 
		public Object pluginAll(Object target) {
		    for (Interceptor interceptor : interceptors) {
		      target = interceptor.plugin(target);
		    }
		    return target;
		  }
		
	 */
	/**
	 * 插件编写:
	 * 1、编写Interceptor的实现类
	 * 2、使用@Intercepts注解完成插件签名
	 * 3、将写好的插件注册到全局配置文件中
	 * 
	 */
	@Test
	public void testPlugin(){
		
		
	}

插件原理

  1. 按照插件注解声明,按照插件配置顺序调用插件plugin方法,生成被拦截对象的动态代理
  2. 多个插件依次生成目标对象的代理对象,层层包裹,先声明的先包裹;形成代理链
  3. 目标方法执行时依次从外到内执行插件的intercept方法。
  4. 多个插件情况下,我们往往需要在某个插件中分离出目标对象。可以借助MyBatis提供的SystemMetaObject类来进行获取最后一层的h以及target属性的值

Interceptor接口

•Intercept:拦截目标方法执行
•plugin:生成动态代理对象,可以使用MyBatis提供的Plugin类的wrap方法
•setProperties:注入插件配置时设置的属性

mybatis 打开日志 配置 springboot_java_03

日志工厂

如果一个数据库操作,出现了异常,我们需要排错。日志就是最好的助手!

曾经用:sout 、debug

现在有:日志工厂!

mybatis 打开日志 配置 springboot_java_04

  • SLF4J
  • LOG4J 【掌握】
  • LOG4J2
  • JDK_LOGGING
  • COMMONS_LOGGING
  • STDOUT_LOGGING 【掌握】
  • NO_LOGGING

在Mybatis中具体使用那个一日志实现,可以在设置中设定!

STDOUT_LOGGING标准日志输出

在mybatis核心配置文件中,配置我们的日志!

<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

Log4j

什么是Log4j?

  • Log4j是Apache的一个开源项目,通过使用Log4j,我们可以控制日志信息输送的目的地是控制台、文件、GUI组件
  • 我们也可以控制每一条日志的输出格式;
  • 通过定义每一条日志信息的级别,我们能够更加细致地控制日志的生成过程。
  • 通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。
  1. 先导入log4j的包
<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
  1. log4j.properties
#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file

#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n

#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./log/kuang.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yy-MM-dd}][%c]%m%n

#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG
  1. 配置log4j为日志的实现
<settings>
    <setting name="logImpl" value="LOG4J"/>
</settings>
  1. Log4j的使用!,直接测试运行刚才的查询
  2. mybatis 打开日志 配置 springboot_mybatis_05

  3. 简单使用
  4. 在要使用Log4j 的类中,导入包 import org.apache.log4j.Logger;
  5. 日志对象,参数为当前类的class
    static Logger logger = Logger.getLogger(UserDaoTest.class);
  6. 日志级别
logger.info("info:进入了testLog4j");
logger.debug("debug:进入了testLog4j");
logger.error("error:进入了testLog4j");

分页

使用Limit分页

语法:SELECT * from user limit startIndex,pageSize;
SELECT * from user limit 3;  #[0,n]

使用Mybatis实现分页,核心SQL

  1. 接口
//分页
List<User> getUserByLimit(Map<String,Integer> map);
  1. Mapper.xml
<!--//分页-->
<select id="getUserByLimit" parameterType="map" resultMap="UserMap">
    select * from  mybatis.user limit #{startIndex},#{pageSize}
</select>
  1. 测试
@Test
public void getUserByLimit(){
SqlSession sqlSession = MybatisUtils.getSqlSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);

HashMap<String, Integer> map = new HashMap<String, Integer>();
    map.put("startIndex",1);
    map.put("pageSize",2);

    List<User> userList =  mapper.getUserByLimit(map);
    for (User user : userList) {
    System.out.println(user);
    }

    sqlSession.close();
    }

RowBounds分页(自带的)

不再使用SQL实现分页

  1. 接口
//分页2
List<User> getUserByRowBounds();
  1. mapper.xml
<!--分页2-->
   <select id="getUserByRowBounds" resultMap="UserMap">
       select * from  mybatis.user
   </select>
  1. 测试
@Test
   public void getUserByRowBounds(){
   SqlSession sqlSession = MybatisUtils.getSqlSession();
   
   //RowBounds实现
   RowBounds rowBounds = new RowBounds(1, 2);
   
   //通过Java代码层面实现分页
   List<User> userList = sqlSession.selectList("com.kuang.dao.UserMapper.getUserByRowBounds",null,rowBounds);
   
       for (User user : userList) {
       System.out.println(user);
       }
   
       sqlSession.close();
       }

分页插件(PageHelper)

PageHelper是MyBatis中非常方便的第三方分页插件。

  • 官方文档:https://github.com/pagehelper/Mybatis-PageHelper/blob/master/README_zh.md
  • 我们可以对照官方文档的说明,快速的使用插件

使用步骤

  • 1、导入相关包pagehelper-x.x.x.jar和jsqlparser-0.9.5.jar。
  • 2、在MyBatis全局配置文件中配置分页插件。
  • mybatis 打开日志 配置 springboot_apache_06

  • 3、使用PageHelper提供的方法进行分页
  • 4、可以使用更强大的PageInfo封装返回结果

案例:

//获取第1页,10条内容,默认查询总数count
PageHelper.startPage(1, 10);
List<User> list = userMapper.selectAll();
//用PageInfo对结果进行包装
PageInfo page = new PageInfo(list);
//测试PageInfo全部属性
//PageInfo包含了非常全面的分页属性
assertEquals(1, page.getPageNum());
assertEquals(10, page.getPageSize());
assertEquals(1, page.getStartRow());
assertEquals(10, page.getEndRow());
assertEquals(183, page.getTotal());
assertEquals(19, page.getPages());
assertEquals(1, page.getFirstPage());
assertEquals(8, page.getLastPage());
assertEquals(true, page.isFirstPage());
assertEquals(false, page.isLastPage());
assertEquals(false, page.isHasPreviousPage());
assertEquals(true, page.isHasNextPage());

测试二

PageHelper.startPage(1, 4);
		 List<Book> books=bookService.queryAllBook();
		
		  PageInfo<Book> pageInfo=new PageInfo<Book>(books);
		 for (Book c : pageInfo.getList()) {
	            System.out.println(c);
	        }

注意

PageHelper.startPage一定要在查询前面

实战

/**
     * 分页查询
     */
    @RequestMapping(value="/allBook",method=RequestMethod.GET)
    public String pageList(@RequestParam(value = "page", defaultValue = "1") Integer page, Model model){
    	//获取指定页数据,大小为8
    	Integer pageSize=4;
        //分页查询
        PageHelper.startPage(page, pageSize);
        List<Book> list = bookServiceImpl.queryAllBook();
      //使用PageInfo包装数据
        PageInfo<Book> pageInfo=new PageInfo<Book>(list);
        model.addAttribute("pageInfo", pageInfo);

        return "allBook";
    }
<div class="row">
        <div class="col-md-6">
            第${pageInfo.pageNum}页,共${pageInfo.pages}页,共${pageInfo.total}条记录
        </div>
        <div class="col-md-6 offset-md-4">
            <nav aria-label="Page navigation example">
                <ul class="pagination pagination-sm">
                    <li class="page-item"><a class="page-link" href="${pageContext.request.contextPath}/allBook?page=1">首页</a></li>
                    <c:if test="${pageInfo.hasPreviousPage}">
                        <li class="page-item"><a class="page-link"
                                                 href="${pageContext.request.contextPath}/allBook?page=${pageInfo.pageNum-1}">上一页</a></li>
                    </c:if>
                    <c:forEach items="${pageInfo.navigatepageNums}" var="page">
                        <c:if test="${page==pageInfo.pageNum}">
                            <li class="page-item active"><a class="page-link" href="#">${page}</a></li>
                        </c:if>
                        <c:if test="${page!=pageInfo.pageNum}">
                            <li class="page-item"><a class="page-link"
                                                     href="${pageContext.request.contextPath}/allBook?page=${page}">${page}</a></li>
                        </c:if>
                    </c:forEach>
                    <c:if test="${pageInfo.hasNextPage}">
                        <li class="page-item"><a class="page-link"
                                                 href="${pageContext.request.contextPath}/allBook?page=${pageInfo.pageNum+1}">下一页</a></li>
                    </c:if>
                    <li class="page-item"><a class="page-link" href="${pageContext.request.contextPath}/allBook?page=${pageInfo.pages}">末页</a>
                    </li>
                </ul>
            </nav>
        </div>
    </div>
</div>

PageInfo

//当前页
    private int pageNum;
    //每页的数量
    private int pageSize;
    //当前页的数量
    private int size;
    //由于startRow和endRow不常用,这里说个具体的用法
    //可以在页面中"显示startRow到endRow 共size条数据"
 
    //当前页面第一个元素在数据库中的行号
    private int startRow;
    //当前页面最后一个元素在数据库中的行号
    private int endRow;
    //总记录数
    private long total;
    //总页数
    private int pages;
    //结果集
    private List<T> list;
 
    //第一页
    private int firstPage;
    //前一页
    private int prePage;
 
    //是否为第一页
    private boolean isFirstPage = false;
    //是否为最后一页
    private boolean isLastPage = false;
    //是否有前一页
    private boolean hasPreviousPage = false;
    //是否有下一页
    private boolean hasNextPage = false;
    //导航页码数
    private int navigatePages;
    //所有导航页号
    private int[] navigatepageNums;