Mybaits-接口编程---------19

Mybaits-接口编程---------19


	使用原始的方法开发dao时,在具体的实现类中,调用sqlSession.selectList()时需要传递两个参数,根据第一个参数在配置文件中找具体的sql语句,这里需要注意两个风险,①编码中的namespace 与配置文件中的namespace 一致,②编码中引用的id与配置文件中的id也要一致,手写就会有很大的风险,㈢就是第二个参数的问题,selectList方法中传入的参数是object,而配置文件中是一个pojo对象,那么在编写代码的时候,传递的参数是其他的类型,编译器也不会提示错误,只有程序运行起来的时候,才会出错,④还有返回值,编码中是通过泛型来约束list的,至于list中是什么泛型,编译器都是可以通过的,在配置文件中的返回类型使用resultMap来约束的。
规避以上四个风险的手段,mybatis称之为接口是编程,需要遵循以下规范:
1、	类的映射:使用接口类的全限定名作为配置文件的namespace,完成类与配置文件的对应关系。
2、	方法名:接口方法名与配置文件中将要执行的sql语句的id一样,这样就完成了方法调用的映射。
3、 参数类型:接口方法的输入参数类型和配置文件中sql的parameterType的类型相同
4、	返回值类型:接口方法的返回值类型和配置文件的resultMap的类型相同
具体实现方法:①将调用的接口类传给sqlSession.getMapper(IMessage.class)方法,返回类型就是这个传入参数的类型IMessage,但是这个时候就不在是接口,而是一个具体的实现,调用接口的方法,就可以得到与映射文件一一对应的参数类型
@MessageDao.java
	/**
	 * 根据查询条件查询消息列表
	 */
	public List queryMessageList(Map parameter) {
		DBAccess dbAccess = new DBAccess();
		List messageList = new ArrayList();
		SqlSession sqlSession = null;
		try {
			sqlSession = dbAccess.getSqlSession();
			// 通过sqlSession执行SQL语句
			IMessage imessage = sqlSession.getMapper(IMessage.class);
			messageList = imessage.queryMessageList(parameter);
		} catch (Exception e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} finally {
			if(sqlSession != null) {
				sqlSession.close();
			}
		}
		return messageList;
	}
@IMessage.java
package com.imooc.dao;


import java.util.List;
import java.util.Map;


import com.imooc.bean.Message;


/**
 * 与Message配置文件相对应的接口
 */
public interface IMessage {
	/**
	 * 根据查询条件查询消息列表
	 */
	public List queryMessageList(Map parameter);
	
	/**
	 * 根据查询条件查询消息列表的条数
	 */
	public int count(Message message);
	
	/**
	 * 根据查询条件分页查询消息列表
	 */
	public List queryMessageListByPage(Map parameter);
}
@Message.xml


  
    
    
    
    
  

  
 
    
 
    

@ListServlet
 
    
	protected void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws ServletException, IOException {
		// 设置编码
		req.setCharacterEncoding("UTF-8");
		// 接受页面的值
		String command = req.getParameter("command");
		String description = req.getParameter("description");
		String currentPage = req.getParameter("currentPage");
		// 创建分页对象
		Page page = new Page();
		//正则表达式--类型
		Pattern pattern = Pattern.compile("[0-9]{1,9}");
		//匹配正则表达式
		if(currentPage == null ||  !pattern.matcher(currentPage).matches()) {
			page.setCurrentPage(1);
		} else {
			page.setCurrentPage(Integer.valueOf(currentPage));
		}
		QueryService listService = new QueryService();
		// 查询消息列表并传给页面
		req.setAttribute("messageList", listService.queryMessageListByPage(command, description,page));
		// 向页面传值
		req.setAttribute("command", command);
		req.setAttribute("description", description);
		req.setAttribute("page", page);
		// 向页面跳转
		req.getRequestDispatcher("/WEB-INF/jsp/back/list.jsp").forward(req, resp);
	}
@QueryService.java
 
    
/**
	 * 根据查询条件分页查询消息列表
	 */
public class QueryService {
public List queryMessageList(String command,String description,Page page) {
// 组织消息对象
Message message = new Message();
message.setCommand(command);
message.setDescription(description);
MessageDao messageDao = new MessageDao();
// 根据条件查询条数
int totalNumber = messageDao.count(message);
// 组织分页查询参数
page.setTotalNumber(totalNumber);
Map parameter = new HashMap();
parameter.put("message", message);
parameter.put("page", page);
// 分页查询并返回结果
return messageDao.queryMessageList(parameter);
=============================================================================
mybatis遇到spring1.数据源托管给spring来管理,DB层将消失
2.sqlsession将会托管给spring,组织对象的代码移交给service层,接口式编程将统一由spring来实现,整个dao层将会由之前的接口来替换(小三上位),整个dao层就剩下接口文件和配置文件

Mapper 动态代理:

 
     
/*动态代理,接口没有实现类.Mybatis为接口提供实现类,即用Proxy.newProxyInstance()创建代理实例,返回类型为Object,利用泛型强制转换*/
IMessage imessage = sqlSession.getMapper(IMessage.class);
/*代理实例调用接口方法时,并不会执行,而是触发 MapperProxy.invoke(),其中包含sqlSession.selectList(namespace.id,parameter)*/
/*至于为什么会包含,因为接口方法与(加载Mybatis的)配置信息对应得上,即 接口名.方法=namespace.id*/
messageList = imessage.queryMessageList(message);


Mybaits-接口编程---------19_第1张图片
===========================================================================================================================
 
    
IMessage imessage=sqlSession.getMapper(IMessage.class);//获取到的就是代理实例
messageList =imessage.queryMessageList(parameter);//代理实例执行接口方法时,就会触发调用处理程序,也就是第三个参数对象的invoke()方法,MapperProxy是实现了InvocationHandler接口的
MapperProxyFactory.newInstance(MapperProxy mapperProxy){
--return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader()//通过接口获取类加载器,new Class[]{mapperInterface}//代理类实现的接口数组,mapperProxy//调用代理实例的处理程序)
--}
解决了三个问题:
①为什么只定义了一个接口,没有具体实现的情况下,接口方法可以被调用,因为动态代理。
②为什么sqlSession.getMapper(.class)可以根据传入的参数,返回一个对应的类型,因为泛型。
③Mybatis加载文件时,利用namespace加载了一个class,然后把这个class与代码中传入接口的class进行匹配,方法执行所需要的信息就是来自于已经匹配成功的配置文件中,当结果与配置文件对应上后,调用接口的方法执行sql语句。

mybatis接口式编程原理


加载配置信息……
通过加载配置信息加载一个代理工厂Map:
这个Map存放的是接口Class与对应的代理工厂
通过接口的Class从代理工厂Map取出对应的代理工厂
通过代理工厂实例化一个代理类
用这个代理类生成一个代理实例返回出去
====代理实例:myInterface====
通过接口与method获取对应的配置文件中的信息:
接口名称.方法名==namespace.id
通过配置文件中的信息获取SQL语句的类型
根据SQL语句类型调用sqlSession对应的增删改查方法
当SQL语句类型是查询时
根据返回值的类型是List、Map、Object
分别调用selectList、selectMap、selectOne方法
3


====================================================================================================

 
   

@SQL语句里的limit使用方法

SELECT * FROM table  LIMIT [offset,] rows | rows OFFSET offset
	在我们使用查询语句的时候,经常要返回前几条或者中间某几行数据,这个时候怎么办呢?不用担心,mysql已经为我们提供了上面这样一个功能。  LIMIT 子句可以被用于强制 SELECT 语句返回指定的记录数。LIMIT 接受一个或两个数字参数。参数必须是一个整数常量。如果给定两个参数,第一个参数指定第一个返回记录行的偏移量,第二个参数指定返回记录行的最大数目。初始记录行的偏移量是 0(而不是 1): 为了与 PostgreSQL 兼容,MySQL 也支持句法: LIMIT # OFFSET #。
mysql> SELECT * FROM table LIMIT 5,10; //检索记录行6-15 
   //为了检索从某一个偏移量到记录集的结束所有的记录行,可以指定第二个参数为 -1:
mysql> SELECT * FROM table LIMIT 95,-1; // 检索记录行 96-last.
   //如果只给定一个参数,它表示返回最大的记录行数目: 
   mysql> SELECT * FROM table LIMIT 5;     //检索前 5 个记录行
   //换句话说,LIMIT n 等价于 LIMIT 0,n。
案例:
SELECT * FROM table LIMIT 0,5;//0、1、2、3、4
SELECT * FROM table LIMIT 5,5;//5、6、7、8、9
SELECT * FROM table LIMIT 10,5;//10、11、12、13、14
====================================================================================
 
   

拦截器--关键代码:

拦截器实现分页

@configuration.xml----更新配置文件********
 
  	
  		
  	
  


@PageInterceptor.java
/**
 * 分页拦截器
 * @Intercepts({@Signature(type=要拦截的class,method="要拦截的方法",args={参数})})
 */
@Intercepts({@Signature(type=StatementHandler.class,method="prepare",args={Connection.class})})
public class PageInterceptor implements Interceptor {
	
	private String test;
	
	@Override
	public Object intercept(Invocation invocation) throws Throwable {
		StatementHandler statementHandler = (StatementHandler)invocation.getTarget();
		//通过反射调用BaseStatementHandler类中的protect属性:mappedstatement(其中包含ID,ResultMap等属性)
		//BaseStatementHandler与statementHandler不同包也不是子类
		//MetaObject类帮我们实现了这样反射的功能。
		//这时的metaObject等同于statementHandler,只是metaObject多了一个方便访问属性的方法:metaObject.getValue("delegate.属性名");
		MetaObject metaObject = MetaObject.forObject(statementHandler, SystemMetaObject.DEFAULT_OBJECT_FACTORY, SystemMetaObject.DEFAULT_OBJECT_WRAPPER_FACTORY);
		MappedStatement mappedStatement = (MappedStatement)metaObject.getValue("delegate.mappedStatement");
		// 配置文件中SQL语句的ID
		String id = mappedStatement.getId();
		//正则表达式查找ID中包含的字段(.+ByPage$)
		if(id.matches(".+ByPage$")) {
			BoundSql boundSql = statementHandler.getBoundSql();
			// 原始的SQL语句
			String sql = boundSql.getSql();
			// 查询总条数的SQL语句
			String countSql = "select count(*) from (" + sql + ")a";
			Connection connection = (Connection)invocation.getArgs()[0];
			PreparedStatement countStatement = connection.prepareStatement(countSql);
			//通过metaObject.getValue("delegate.属性名");获得parameterHandler(sql的参数--?相关的配置)
			ParameterHandler parameterHandler = (ParameterHandler)metaObject.getValue("delegate.parameterHandler");
			//将sql语句中的?号换成参数
			parameterHandler.setParameters(countStatement);
			ResultSet rs = countStatement.executeQuery();
			
			Map parameter = (Map)boundSql.getParameterObject();
			Page page = (Page)parameter.get("page");
			if(rs.next()) {
				//为page赋值
				page.setTotalNumber(rs.getInt(1));
			}
			// 改造后带分页查询的SQL语句
			String pageSql = sql + " limit " + page.getDbIndex() + "," + page.getDbNumber();
			//将修改好的sql语句,替换掉原先的SQL语句
			metaObject.setValue("delegate.boundSql.sql", pageSql);
		}
		//交回主权
		return invocation.proceed();
	}
	
//plugin(Object  被拦截的人)
	@Override
	public Object plugin(Object target) {
		System.out.println(this.test);
		return Plugin.wrap(target, this);//this 是这个类的实例,
	}

	@Override
	public void setProperties(Properties properties) {
		this.test = properties.getProperty("test");
		// TODO Auto-generated method stub
		
	}

}


===================================================================================================================
Ps1:

Java泛型中的标记符含义:

E - Element (在集合中使用,因为集合中存放的是元素) T - Type(Java 类) K - Key(键) V - Value(值) N - Number(数值类型) ? - 表示不确定的java类型 S、U、V - 2nd、3rd、4th types Object跟这些标记符代表的java类型有啥区别呢? Object是所有类的根类,任何类的对象都可以设置给该Object引用变量,使用的时候可能需要类型强制转换,但是用使用了泛型T、E等这些标识符后,在实际用之前类型就已经确定了,不需要再进行类型强制转换。 Ps2:若通过执行plugin()方法,返回本身(不需要代理的对象),则不会执行intercept()方法(因为没有获取代理权);若返回代理对象,则会执行intercept()方法。 Ps3:如果不清楚导入哪个包,可以查看返回类型并点击该类型就可知道是哪个包。

你可能感兴趣的:(Mybaits-接口编程---------19)