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<Message> queryMessageList(Map<String,Object> parameter) {
DBAccess dbAccess = new DBAccess();
List<Message> messageList = new ArrayList<Message>();
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<Message> queryMessageList(Map<String,Object> parameter);
/**
* 根据查询条件查询消息列表的条数
*/
public int count(Message message);
/**
* 根据查询条件分页查询消息列表
*/
public List<Message> queryMessageListByPage(Map<String,Object> parameter);
}
@Message.xml
<pre name="code" class="java"><mapper namespace="com.imooc.dao.IMessage">
<resultMap type="com.imooc.bean.Message" id="MessageResult">
<id column="ID" jdbcType="INTEGER" property="id"/>
<result column="COMMAND" jdbcType="VARCHAR" property="command"/>
<result column="DESCRIPTION" jdbcType="VARCHAR" property="description"/>
<result column="CONTENT" jdbcType="VARCHAR" property="content"/>
</resultMap>
<select id="queryMessageList" parameterType="java.util.Map" resultMap="MessageResult">
select <include refid="columns"/> from MESSAGE
<where><!-- <span style="font-family: Arial, Helvetica, sans-serif;">test="message.command 当是Map型时,此处是key值.类的属性值</span> -->
<if test="message.command != null and !"".equals(message.command.trim())">
and COMMAND=#{message.command}
</if>
<if test="message.description != null and !"".equals(message.description.trim())">
and DESCRIPTION like '%' #{message.description} '%'
</if>
</where>
order by ID limit #{page.dbIndex},#{page.dbNumber}
</select>
1.数据源托管给spring来管理,DB层将消失
@ListServlet
@QueryService.java<span style="font-weight: normal;"><span style="font-weight: normal;"> 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); }</span></span>
<span style="font-weight: normal;">/** * 根据查询条件分页查询消息列表 */ public class QueryService { public List<Message> 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<String,Object> parameter = new HashMap<String, Object>(); parameter.put("message", message); parameter.put("page", page); // 分页查询并返回结果 return messageDao.queryMessageList(parameter);
=============================================================================mybatis遇到spring
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);
===========================================================================================================================
解决了三个问题:IMessage imessage=sqlSession.getMapper(IMessage.class);//获取到的就是代理实例 messageList =imessage.queryMessageList(parameter);//代理实例执行接口方法时,就会触发调用处理程序,也就是第三个参数对象的invoke()方法,MapperProxy是实现了InvocationHandler接口的 MapperProxyFactory.newInstance(MapperProxy<T> 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
通过加载配置信息加载一个代理工厂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----更新配置文件********
<plugins>
<plugin interceptor="com.imooc.interceptor.PageInterceptor">
<property name="test" value="abc"/>
</plugin>
</plugins>
/**
* 分页拦截器
* @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:如果不清楚导入哪个包,可以查看返回类型并点击该类型就可知道是哪个包。