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

来源:互联网 发布:日式风格女装 淘宝 编辑:程序博客网 时间:2024/06/02 05:31

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 blocke.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>

@ListServlet
<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>
@QueryService.java
<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

1.数据源托管给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);



===========================================================================================================================
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


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

@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、4SELECT * FROM table LIMIT 5,5;//5、6、7、8、9SELECT * 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>


@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;@Overridepublic 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语句的IDString 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  被拦截的人)@Overridepublic Object plugin(Object target) {System.out.println(this.test);return Plugin.wrap(target, this);//this 是这个类的实例,}@Overridepublic 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 typesObject跟这些标记符代表的java类型有啥区别呢? Object是所有类的根类,任何类的对象都可以设置给该Object引用变量,使用的时候可能需要类型强制转换,但是用使用了泛型T、E等这些标识符后,在实际用之前类型就已经确定了,不需要再进行类型强制转换。Ps2:若通过执行plugin()方法,返回本身(不需要代理的对象),则不会执行intercept()方法(因为没有获取代理权);若返回代理对象,则会执行intercept()方法。Ps3:如果不清楚导入哪个包,可以查看返回类型并点击该类型就可知道是哪个包。


0 0
原创粉丝点击