通过自动回复机器人学Mybatis--基础版

java.jpg

第一章 案例简介

设计类似微信公众号的自动回复功能

准备工作
JSP JSTL EL JS/JQUERY
Servlet JavaBean
JDBC MySQL


第二章 实战第一部--黎明前的黑暗

2.1 黎明前的黑暗

案例分析

基本功能 :接受发送指令 根据指令自动回复对应的内容

模块划分 :

  • 回复内容维护
  • 对话功能
  • 回复内容列表
  • 回复内容删除

2.2 案例演示

2.3 页面跳转

2.4 连接数据库

2.5 数据展示


第三章 实战第二部--Mybatis来袭

3.1 Mybatis的下载并搭建核心架构

https://github.com/mybatis/mybatis-3/releases下载Mybatis jar包及源码包

**演示代码中的配置文件的详细路径 : **
src/test/java/org/apache/ibatis/submitted/complex_property/Configuration.xml

复制到项目的src/com/qezhhnjy/config/Configuration.xml

Mybatis的相关知识

Dao的需求

  1. 对象能与数据库交互
  2. 能执行SQL语句

Mybatis中提供给Dao层的处理对象为SqlSession

SqlSession的作用 :

  1. 向SQL语句传入参数
  2. 执行SQL语句
  3. 获取执行SQL 语句的结果
  4. 事务的控制

如何获得SqlSession :
1. 通过配置文件获取数据库连接相关信息
com.qezhhnjy.config.Configuration.xml

<configuration>
<!--  <settings>
        <setting name="useGeneratedKeys" value="false"/>
        <setting name="useColumnLabel" value="true"/>
      </settings>

      <typeAliases>
        <typeAlias alias="UserAlias" type="org.apache.ibatis.submitted.complex_property.User"/>
      </typeAliases>-->

  <environments default="development">
    <environment id="development">
      <transactionManager type="JDBC">
        <property name="" value=""/>
      </transactionManager>
      <dataSource type="UNPOOLED">
        <property name="driver" value="com.mysql.jdbc.Driver"/>
        <property name="url" value="jdbc:mysql://localhost:3306/micro_message?useSSL=false"/>
        <property name="username" value="root"/>
        <property name="password" value="root" />
<!--property标签对应本机数据库连接相关信息-->
      </dataSource>
    </environment>
  </environments>

  <mappers>
    <mapper resource="com/qezhhnjy/config/sqlxml/Message.xml"/>
<!--mapper标签为配置好的各个实体类与数据表映射xml文件位置-->
  </mappers>
</configuration>

2. 通过配置信息构建SqlSessionFactory
com.qezhhnjy.db.DBAccess.getSession();

public class DBAccess {
    public SqlSession getSqlSession() throws IOException {
        //通过配置文件获取数据库连接信息
        Reader reader = Resources.getResourceAsReader("com/qezhhnjy/config/Configuration.xml");
        //通过配置信息构建一个SqlSessionFactory
        SqlSessionFactory ssf = new SqlSessionFactoryBuilder().build(reader);
        return ssf.openSession();
    }
}

3. 通过SqlSessionFactory打开数据库会话
com.qezhhnjy.dao.MessageListDao.mybatisQuery(...);

    public static List<Message> mybatisQuery(String command,String description){
        DBAccess dbAccess = new DBAccess();
        List<Message> messageList = new ArrayList<>();
        SqlSession sqlSession = null;
        try {
            sqlSession = dbAccess.getSqlSession();
            messageList = sqlSession.selectList("Message.find");
            //Message对应message.xml中mapper的属性namespace
            //find对应message.xml中select标签的id
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(sqlSession !=null)sqlSession.close();
        }
        return messageList;
    }

3.2 SQL基本配置与执行

src/test/java/org/apache/ibatis/submitted/complex_property/User.xml
将该文件复制到项目的
src/com/qezhhnjy/config/sqlxml/Message.xml

<mapper namespace="Message">
  <!--由于项目中所有select标签的id都不能重名.
  为了加以区分, 使用namespace属性来设置,不同namespace下的id可以相同.
  类似java中包的定义及使用-->

  <resultMap type="UserAlias" id="UserResult">
    <id column="id" jdbcType="INTEGER" property="id"/>//主键
    <result column="username" jdbcType="VARCHAR" property="username"/>
<!--column对应数据表列名  property对应实体类属性名  jdbcType对应java.sql.Types类中的常量名-->
    <result column="password" jdbcType="VARCHAR" property="password.encrypted"/>
    <result column="administrator" jdbcType="BOOLEAN" property="administrator"/>
  </resultMap>

  <select id="find" parameterType="long" resultMap="UserResult">
    SELECT * FROM user WHERE id = #{id:INTEGER}
  </select>
<!--原文档中有多个功能各异的select查询标签-->
</mapper>

Message类对应的全查询语句和实体类映射

  <resultMap type="com.qezhhnjy.bean.Message" id="MessageResult">
    <id column="id" jdbcType="VARCHAR" 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="find" resultMap="MessageResult">
    SELECT id,command,description,content FROM MESSAGE WHERE 1=1 
  </select>

第四章 实战第三部

4.1 动态SQL拼接

OGNL是一个强大的表达式语言
OGNL表达式语言详解
http://blog.csdn.net/yu102655/article/details/52179695
http://blog.csdn.net/hello_xusir/article/details/53423399

Paste_Image.png

Paste_Image.png

配置SQL拼接实现字段查询和模糊查询

1. 将用户输入的查询内容封装并传递给mybatis
com.qezhhnjy.dao.MessageListDao.mybatisQuery(...);

...
Message temp = new Message();
temp.setCommand(command);
temp.setDescription(description);
 try {
      sqlSession = dbAccess.getSqlSession();
      messageList = sqlSession.selectList("Message.find",temp);
...
//只截取了与全查询不同的部分. 通过封装对应属性的输入值到实体类, 然后传递给mybatis,之后配置Message.xml来映射对应属性到sql语句中.

2. 完善配置Message.xml
Message.xml

  <select id="find" parameterType="com.qezhhnjy.bean.Message" resultMap="MessageResult">
      //parameterType属性即表示传递进来的对象类型
      SELECT id,command,description,content FROM MESSAGE WHERE 1=1
      <if test="command != null && !"".equals(command.trim())">
           AND command = #{command}
      </if>
      <!--if标签test属性为OGNL语言, 可以识别java属性及方法,但是""和&(&)要进行转义,&&也可以使用and 关键字代替.
      if标签内容为衔接前面的sql语句, 为mybatis来识别,且不必刻意空格来避免错误.-->
      <if test="description != null and !"".equals(description.trim())">
           AND description LIKE '%' #{description} '%'
      </if>
      <!--模糊查询的写法容易出现错误,%前后要加上'',并且#{...}前后仍然要注意空格-->
  </select>

4.2 应用log4j调试动态SQL

在官网下载的mybatis文件目录下有log4j的jar包
Mybatis 3.4.4/mybatis-3.4.4/lib/log4j-1.2.17.jar
log4j的配置文件: log4j.properties(mybatis源码包)
Mybatis 3.4.4/mybatis-3-mybatis-3.4.4/src/test/java/log4j.properties

配置文件默认加载到src/根目录下.

### Global logging configuration
log4j.rootLogger=ERROR, stdout
//配置日志输出级别和位置,级别(debug<info<warn<error),如果设置为INFO,则会输出info,warn,error的信息,如此.
### Uncomment for MyBatis logging
log4j.logger.org.apache=INFO
### 将org.apache包的日志输出级别单独设置为INFO,也可以再单独设置输出位置.

### Console output...
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d %5p [%t] - %m%n
### 格式化输出, %d表示当前时间, %p(priority)输出优先级,%t表示当前线程,  %m表示具体信息, %n换行.

在mybatis的org.apache.ibatis.logging.jdbc包中,有jdbc各大类对象对应的日志输出类.
org.apache.ibatis.logging.jdbc.CollectionLogger类的源码

public final class ConnectionLogger extends BaseJdbcLogger implements InvocationHandler {
    private Connection connection;

    private ConnectionLogger(Connection conn, Log statementLog, int queryStack) {
        super(statementLog, queryStack);
        this.connection = conn;
    }

    public Object invoke(Object proxy, Method method, Object[] params) throws Throwable {
        try {
            if(Object.class.equals(method.getDeclaringClass())) {
                return method.invoke(this, params);
            } else {
                PreparedStatement stmt;
                if("prepareStatement".equals(method.getName())) {
                    if(this.isDebugEnabled()) {
                        this.debug(" Preparing: " + this.removeBreakingWhitespace((String)params[0]), true);
                    }

                    stmt = (PreparedStatement)method.invoke(this.connection, params);
                    stmt = PreparedStatementLogger.newInstance(stmt, this.statementLog, this.queryStack);
                    return stmt;
                } else if("prepareCall".equals(method.getName())) {
                    if(this.isDebugEnabled()) {
                        this.debug(" Preparing: " + this.removeBreakingWhitespace((String)params[0]), true);
                    }

                    stmt = (PreparedStatement)method.invoke(this.connection, params);
                    stmt = PreparedStatementLogger.newInstance(stmt, this.statementLog, this.queryStack);
                    return stmt;
                } else if("createStatement".equals(method.getName())) {
                    Statement stmt = (Statement)method.invoke(this.connection, params);
                    stmt = StatementLogger.newInstance(stmt, this.statementLog, this.queryStack);
                    return stmt;
                } else {
                    return method.invoke(this.connection, params);
                }
            }
        } catch (Throwable var5) {
            throw ExceptionUtil.unwrapThrowable(var5);
        }
    }

    public static Connection newInstance(Connection conn, Log statementLog, int queryStack) {
        InvocationHandler handler = new ConnectionLogger(conn, statementLog, queryStack);
        ClassLoader cl = Connection.class.getClassLoader();
        return (Connection)Proxy.newProxyInstance(cl, new Class[]{Connection.class}, handler);
    }

    public Connection getConnection() {
        return this.connection;
    }
}

调试阶段设置为DEBUG级别, 可以输出最多的信息

4.3 实现单条信息删除

1. 配置Message.xml

    <delete id="deleteOne" parameterType="int" >
        DELETE FROM message WHERE id = #{_parameter}
<!--String及基本类型用_parameter代替, 类似OGNL-->
    </delete>

**2. 设计Dao层方法 **MessageListDao.mybatisDeleteOne(int);

    public static void mybatisDeleteOne(int id){
        DBAccess dbAccess = new DBAccess();
        SqlSession sqlSession = null;
        try {
            sqlSession = dbAccess.getSqlSession();
            sqlSession.delete("Message.deleteOne", id);
            sqlSession.commit();
            //mybatis对数据库增删改需要手动提交
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            if(sqlSession !=null)sqlSession.close();
        }
    }

3. Message维护类Service中的逻辑方法MessageMaintainService.deletOne(String);

    public static void deleteOne(String id) {
//service层中主要处理类似的参数逻辑问题
        if (id != null &&!"".equals(id.trim())) {
           MessageListDao.mybatisDeleteOne(Integer.parseInt(id));
        }
    }

4. 设计Servlet类servlet.DeleteOneServlet

@WebServlet(name = "DeleteOneServlet",value = "/deleteone.action")
public class DeleteOneServlet extends HttpServlet {
    private static final long serialVersionUID = -1163248074005443694L;

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request, response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //设置编码
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        String id = request.getParameter("id");
        System.out.println(id);
        //数据库查询
        //servlet中尽量不要设计对数据为null及类型错误==的判定,在service中进行.
        MessageMaintainService.deleteOne(id);
        //跳转
        System.out.println("删除成功-----");
        request.getRequestDispatcher("/list.action").forward(request, response);
    //跳转不能直接跳转到list.jsp页面.
    //因为跳转的request并没有添加messageList属性,jsp中逻辑语言会出现空指针.
    //要重新跳转到ListServlet重新查询数据再跳转到实际页面.
    }
}

5. 编写list.jsp中删除链接的href

<a href="${basePath}deleteone.action?id=<%=message.getId()%>">删除</a>

比较简单的方法是通过get方法传值.但这种方法容易泄漏信息, 且只能传递少量信息.
在类似查询指定内容或模糊内容数据后进行单条删除时, 由于无法传递查询输入框中的值. 每次删除后会刷新为空并显示全部条数据,影响用户体验.

这时可以使用js设置为post方法传递信息.

<a href="javascript:(function(id) {
      document.getElementById('deleteOneId').value = id;
      var form=document.getElementById('mainForm');
     form.action='${pageContext.request.contextPath}/deleteOne.action';
    form.submit(); }) ('<%=message2.getId()%>')" >删除</a>
  <!--第二个括号里面的jsp代码要加上' '.这里由于代码的输出是数字, 不加也不会出错.但其他时候则会导致出错, 且没有报错, 很难被发现!!-->

同时在table循环输出message以外设置一个隐藏标签:

<input type="hidden" name="messageId" id="deleteOneId" value="" />

4.4 实现信息批量删除

1. 配置Message.xml

    <delete id="deleteBatch" parameterType="java.util.List" >
        DELETE FROM message WHERE ID in(
        <foreach  collection="list" item="item" separator=",">
            #{item}
        </foreach>
<!--foreach实现将接收到的list对象遍历出来, 并且用separator内的符号进行分隔-->
        )
    </delete>

2. 设计Dao层方法 MessageListDao.mybatisDeleteBatch(List idList);
3. Message维护类Service中的逻辑方法MessageMaintainService.deletBatch(String[] ids);
4. 设计Servlet类servlet.DeleteBatchServlet

@WebServlet(name = "DeleteBatchServlet",value = "/deleteBatch.action")
public class DeleteBatchServlet extends HttpServlet {
    private static final long serialVersionUID = -6881762699937663920L;

    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        doGet(request,response);
    }

    protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
        //设置编码
        request.setCharacterEncoding("utf-8");
        response.setContentType("text/html;charset=utf-8");
        String[] ids = request.getParameterValues("choose");
        MessageMaintainService.deleteBatch(ids);
        System.out.println("批量删除成功");
        response.sendRedirect("/list.action");
        //这里使用的是请求重定向到list页. 不会传递req,resp对象.查询删除后会跳转到全部数据页面.
//        request.getRequestDispatcher("/list.action").forward(request, response);
    }
}

5. list.jsp页面checkbox传值及全选全不选

<input type="checkbox" name="choose" value="<%=message1.getId()%>"  />
<!--删除按钮-->
<a class="btn03" href="javascript:(function() {var form=document.getElementById('mainForm');
form.action='deleteBatch.action';form.submit();})()">删 除</a>
<!--全选checkbox-->
<input type="checkbox" id="all" onclick="(function() {
    var chooses=document.getElementsByName('choose');                                  
    if(document.getElementById('all').checked) for(var i=0;i<chooses.length;i++) chooses[i].checked=true;
    else for(var j=0;j<chooses.length;j++) chooses[j].checked=false;})()" />

4.5 实现自动回复功能

模仿微信对话页面html
WEB-INF/jsp/front/talk.jsp

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<%@ page language="java" contentType="text/html;charset=UTF-8" %>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%>
<html xmlns="http://www.w3.org/1999/xhtml">
    <head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
    <title>微信公众号</title>
    <!--讨论区滚动条begin-->
    <link rel="stylesheet" type="text/css" href="<%= basePath %>resources/css/jscrollpane1.css" />
    <script src="<%= basePath %>resources/js/common/jquery-1.8.0.min.js" type="text/javascript"></script>
    <!-- the mousewheel plugin -->
    <script type="text/javascript" src="<%= basePath %>resources/js/common/jquery.mousewheel.js"></script>
    <!-- the jScrollPane script -->
    <script type="text/javascript" src="<%= basePath %>resources/js/common/jquery.jscrollpane.min.js"></script>
    <script type="text/javascript" src="<%= basePath %>resources/js/common/scroll-startstop.events.jquery.js"></script>
    <!--讨论区滚动条end-->
    <script type="text/javascript" src="<%= basePath %>resources/js/front/talk.js"></script>
    </head>
    <body>
        <input type="hidden" value="<%= basePath %>" id="basePath"/>
        <br/>
        <div class="talk">
            <div class="talk_title"><span>正在与公众号对话</span></div>
            <div class="talk_record">
                <div id="jp-container" class="jp-container">
                    
                </div>
            </div>
            
            <div class="talk_word">
                 
                <input class="add_face" id="facial" type="button" title="添加表情" value="" />
                <input id="content" class="messages emotion"   />
                <input class="talk_send" onclick="send();" type="button" title="发送" value="发送" />
            </div>
        </div>
        <div style="text-align:center;margin:50px 0; font:normal 14px/24px 'MicroSoft YaHei';"></div>
    </body>
</html>

talk.jsp对应的数据处理js方法
resources/js/front/talk.js

/**
 * 页面加载
 */
$(function(){
    render();
    var content = "客官,来啦,坐吧!<br/>回复[查看]收取更多精彩内容。";
    content += "<br/>回复[帮助]可以查看所有可用的指令。";
    // 添加公众号的开场白
    appendDialog("talk_recordbox","公众号",content);
    render();
});

/**
 * 发送消息
 * @param basePath
 */
function send() {
    var content = $("#content").val();
    /**
     * content = ""/null/undefined/数字0 , 则判定为false.
     */
    if(!content) {
        alert("请输入内容!");
        return;
    }
    $.ajax({
        //      <input type="hidden" value="<%= basePath %>" id="basePath"/>
        //通过这个标签取值, 减少参数的传递.跳转到指定Servlet
        url : $("#basePath").val() + "AutoReplyServlet.action",
        type : "POST",
        dataType : "text",
        timeout : 10000,
        success : function (data) {
            appendDialog("talk_recordboxme","My账号",content);
            appendDialog("talk_recordbox","公众号",data);
            //清空文本框内容
            $("#content").val("");
            render();
        },
        data : {"content":content}
    });
}

/**
 * 渲染方法,加载滚动条
 */
function render() {
    // the element we want to apply the jScrollPane
    var $el= $('#jp-container').jScrollPane({
        verticalGutter  : -16
    }),
    // the extension functions and options  
    extensionPlugin     = {
        extPluginOpts   : {
            // speed for the fadeOut animation
            mouseLeaveFadeSpeed : 500,
            // scrollbar fades out after hovertimeout_t milliseconds
            hovertimeout_t      : 1000,
            // if set to false, the scrollbar will be shown on mouseenter and hidden on mouseleave
            // if set to true, the same will happen, but the scrollbar will be also hidden on mouseenter after "hovertimeout_t" ms
            // also, it will be shown when we start to scroll and hidden when stopping
            useTimeout          : true,
            // the extension only applies for devices with width > deviceWidth
            deviceWidth         : 980
        },
        hovertimeout    : null, // timeout to hide the scrollbar
        isScrollbarHover: false,// true if the mouse is over the scrollbar
        elementtimeout  : null, // avoids showing the scrollbar when moving from inside the element to outside, passing over the scrollbar
        isScrolling     : false,// true if scrolling
        addHoverFunc    : function() {
            // run only if the window has a width bigger than deviceWidth
            if( $(window).width() <= this.extPluginOpts.deviceWidth ) return false;
            var instance        = this;
            // functions to show / hide the scrollbar
            $.fn.jspmouseenter  = $.fn.show;
            $.fn.jspmouseleave  = $.fn.fadeOut;
            // hide the jScrollPane vertical bar
            var $vBar           = this.getContentPane().siblings('.jspVerticalBar').hide();
            /*
             * mouseenter / mouseleave events on the main element
             * also scrollstart / scrollstop - @James Padolsey : http://james.padolsey.com/javascript/special-scroll-events-for-jquery/
             */
            $el.bind('mouseenter.jsp',function() {
                
                // show the scrollbar
                $vBar.stop( true, true ).jspmouseenter();
                
                if( !instance.extPluginOpts.useTimeout ) return false;
                
                // hide the scrollbar after hovertimeout_t ms
                clearTimeout( instance.hovertimeout );
                instance.hovertimeout   = setTimeout(function() {
                    // if scrolling at the moment don't hide it
                    if( !instance.isScrolling )
                        $vBar.stop( true, true ).jspmouseleave( instance.extPluginOpts.mouseLeaveFadeSpeed || 0 );
                }, instance.extPluginOpts.hovertimeout_t );
            }).bind('mouseleave.jsp',function() {
                // hide the scrollbar
                if( !instance.extPluginOpts.useTimeout )
                    $vBar.stop( true, true ).jspmouseleave( instance.extPluginOpts.mouseLeaveFadeSpeed || 0 );
                else {
                clearTimeout( instance.elementtimeout );
                if( !instance.isScrolling )
                        $vBar.stop( true, true ).jspmouseleave( instance.extPluginOpts.mouseLeaveFadeSpeed || 0 );
                }
            });
            if( this.extPluginOpts.useTimeout ) {
                $el.bind('scrollstart.jsp', function() {
                    // when scrolling show the scrollbar
                    clearTimeout( instance.hovertimeout );
                    instance.isScrolling    = true;
                    $vBar.stop( true, true ).jspmouseenter();
                }).bind('scrollstop.jsp', function() {
                    // when stop scrolling hide the scrollbar (if not hovering it at the moment)
                    clearTimeout( instance.hovertimeout );
                    instance.isScrolling    = false;
                    instance.hovertimeout   = setTimeout(function() {
                        if( !instance.isScrollbarHover )
                                $vBar.stop( true, true ).jspmouseleave( instance.extPluginOpts.mouseLeaveFadeSpeed || 0 );
                        }, instance.extPluginOpts.hovertimeout_t );
                });
                // wrap the scrollbar
                // we need this to be able to add the mouseenter / mouseleave events to the scrollbar
                var $vBarWrapper    = $('<div/>').css({
                    position    : 'absolute',
                    left        : $vBar.css('left'),
                    top         : $vBar.css('top'),
                    right       : $vBar.css('right'),
                    bottom      : $vBar.css('bottom'),
                    width       : $vBar.width(),
                    height      : $vBar.height()
                }).bind('mouseenter.jsp',function() {
                    clearTimeout( instance.hovertimeout );
                    clearTimeout( instance.elementtimeout );
                    instance.isScrollbarHover   = true;
                    // show the scrollbar after 100 ms.
                    // avoids showing the scrollbar when moving from inside the element to outside, passing over the scrollbar                              
                    instance.elementtimeout = setTimeout(function() {
                        $vBar.stop( true, true ).jspmouseenter();
                    }, 100 );   
                }).bind('mouseleave.jsp',function() {
                    // hide the scrollbar after hovertimeout_t
                    clearTimeout( instance.hovertimeout );
                    instance.isScrollbarHover   = false;
                    instance.hovertimeout = setTimeout(function() {
                    // if scrolling at the moment don't hide it
                    if( !instance.isScrolling )
                            $vBar.stop( true, true ).jspmouseleave( instance.extPluginOpts.mouseLeaveFadeSpeed || 0 );
                    }, instance.extPluginOpts.hovertimeout_t );
                });
                $vBar.wrap( $vBarWrapper );
            }
        }
    },
    // the jScrollPane instance
    jspapi = $el.data('jsp');
    // extend the jScollPane by merging 
    $.extend( true, jspapi, extensionPlugin );
    jspapi.addHoverFunc();
}

/**
 * 向聊天记录中添加聊天内容
 * @param myClass 添内容的样式
 * @param name 发送消息的账号名称
 * @param content 发送的内容
 */
function appendDialog(myClass,name,content) {
    var div = "";
    div += "<div class='" + myClass + "'>";
    div += "<div class='user'>![](" + $("#basePath").val() + "resources/images/thumbs/" + myClass + ".jpg)" + name + "</div>";
    div += "<div class='talk_recordtextbg'> </div>";
    div += "<div class='talk_recordtext'>";
    div += "<h3>" + content + "</h3>";
    div += "<span class='talk_time'>" + getCurrentDate() + "</span>";
    div += "</div>";
    div += "</div>";
    $('#jp-container').children().eq(0).children().eq(0).append(div);
}

/**
 * 获取当前系统时间
 * @returns {String} 当前系统时间
 */
function getCurrentDate() {
    var date = new Date();
    return date.getFullYear() + "-" + (date.getMonth() + 1) + "-" + date.getDate() + " " + (date.getHours() < 10 ? "0" + date.getHours() : date.getHours()) + ":" + (date.getMinutes() < 10 ? "0" + date.getMinutes() : date.getMinutes());
}

Service层queryBycommand(String);方法
com/qezhhnjy/service/MessageQueryService.java

    public static String queryByCommand(String command) {
        List<Message> messageList;
        //设置输入'帮助'返回全部数据
        //Iconst.HELP_COMMAND为自定义帮助字符串常量
        if (Iconst.HELP_COMMAND.equals(command)){
            messageList = MessageListDao.mybatisQuery(null, null);
            StringBuilder sb = new StringBuilder();
            for (Message message : messageList) {
                sb.append("回复[").append(message.getCommand()).append("],").append("查看[").append(message.getDescription()).append("]").append("<br/>");
            }
            return sb.toString();
        }
        messageList = MessageListDao.mybatisQuery(command, null);
        if (messageList.size() > 0) return messageList.get(0).getContent();
//Iconst.NO_MATCHING_CONTENT为自定义无匹配的返回字符串常量.
        return Iconst.NO_MATCHING_CONTENT;
    }

第五章 实战第四部

5.1 一对多关系的配置 Ⅰ

如果回复"段子", 而机器人返回的始终是一个笑话,就很无聊了.
所以需要实现一个指令能够随机回复多条内容中的一条的功能.

重新设计数据表

command 表 : ID /commandname/description
content表: ID/content/command_ID(froeign key)

5.2 一对多关系的配置 Ⅱ

Content.xml

<mapper namespace="Content">
    <resultMap type="com.qezhhnjy.bean.Content" id="ContentResult">
        <id column="id" jdbcType="VARCHAR" property="id"/>
        <result column="cid" jdbcType="VARCHAR" property="cid"/>
        <result column="content" jdbcType="VARCHAR" property="content"/>
    </resultMap>
</mapper>

Command.xml

<mapper namespace="Command">
    <resultMap type="com.qezhhnjy.bean.Command" id="CommandResult">
        <id column="c_id" jdbcType="VARCHAR" property="id"/>
         <!--这里的列名对应下面select标签中的结果. 不包括前面的a./b.==,可以使用别名.-->
        <result column="command" jdbcType="VARCHAR" property="command"/>
        <result column="description" jdbcType="VARCHAR" property="description"/>
        <collection property="contents" resultMap="Content.ContentResult" />
        <!--contents为Command实体类中的content集合的属性名-->
    </resultMap>

    <select id="query" parameterType="com.qezhhnjy.bean.Command" resultMap="CommandResult" >
        SELECT a.id c_id,a.command,a.description,b.id,b.content
        FROM command a LEFT JOIN content b ON a.id=b.cid WHERE 1=1
        <if test="command != null && !"".equals(command.trim())">
            AND command = #{command}
        </if>
        <if test="description != null and !"".equals(description.trim())">
            AND description LIKE '%' #{description} '%'
        </if>
    </select>
</mapper>

最后在Configuration.xml中添加这两个实体类的xml文件映射.

5.3 一对多关系的配置 Ⅲ

5.4 常用标签

<where></where>标签
Message.xml

      SELECT id,command,description,content FROM MESSAGE WHERE 1=1
      <if test="command != null && !"".equals(command.trim())">
           AND command = #{command}
      </if>
      <if test="description != null and !"".equals(description.trim())">
           AND description LIKE '%' #{description} '%'
      </if>

等效于

      SELECT id,command,description,content FROM message
      <where>
          <if test="command != null and !"".equals(command.trim())">
              AND command = #{command}
          </if>
          <if test="description !=null and !"".equals(description.trim())">
              AND description LIKE '%' #{description} '%'
          </if>
      </where>

where标签可以在没有可选项时去除WHERE关键字. 在有可选项时去除第一个条件前面的and或者or.

<sql></sql>标签(与select标签同一级)

     SELECT <include refid="columns" /> FROM message
     <where>
         <if test="command != null and !"".equals(command.trim())">
             AND command = #{command}
         </if>
         <if test="description !=null and !"".equals(description.trim())">
             AND description LIKE '%' #{description} '%'
         </if>
     </where>
</select>
   <sql id="columns">id,command,description,content</sql>

<set></set>标签

<update id="">
UPDATE message
      <set>
          <if  test="command != null and !"".equals(command.trim())">command=#{command},
          </if>
          <if  test="description != null and !"".equals(description.trim())">description=#{description},
          </if>
      </set>
</update>

<set>标签可以不必考虑<if>中内容的','的取舍问题.

<trim></trim>标签与where/set同级

      <trim prefix="" suffix="" prefixOverrides="and/or" suffixOverrides=""></trim>

表示如果标签里有内容输出, 则 分别加上prefix/suffix指定的前缀和后缀.
prefixOverrides表示输出内容最前面有指定内容则切除. 上面就表示有and或者or时切掉and或者or.
suffixOverrides同理.

<choose></choose>标签

<choose>
    <when test=""></when>
    <when test=""></when>
    <when test=""></when>
    <otherwise></otherwise>
</choose>

类似java中的if...else及switch语句.

<association></association>标签(与collection相反)
用于在子表中映射父表

功能 标签名称
定义SQL语句 insert
delete
update
select
配置java对象属性与查询结果集中列名对应关系 resultMap
控制动态SQL拼接 foreach
if
choose
格式化输出 where
set
trim
配置关联关系 collection
association
定义常量 sql
引用常量 include

第六章 战斗总结

6.1 容易混淆的概念

reslutMap resultType

resultType指直接设定结果集映射为某个工具类或实体类.
为实体类时列名与类中属性名需一一对应,忽略大小写.
为工具类, 如java.util.Map时.以列名为Key, 取值为value. 大小写敏感.

parameterMap parameterType

parameterMap用法与resultMap类似. 不推荐使用deprecated.

#{} ${}

#{}有预编译效果,在console中会以?显示,
${}没有预编译效果, 在console中会以其值显示
${段子} 显示为: WHERE command=段子 .这个sql语句片段是错误的.段子前后没有单引号.正确的用法应该是'${段子}'.
${}的用法主要在于order by ×××时. 如果用预编译则会以?替代.没有效果 .在表头onclick事件按照该列进行排列时, 通过传递该列列名然后利用${×××}编译到sql语句中进行动态排序.

#{} ognl

如果parameterType为String及8中基本数据类型. #{}可以写成#{_parameter}.也可以#{}或者中间可以随便写别的. 都不会影响.此时,这个值是确定的. 而ognl表达式则依然只能写_parameter.
但依然不推荐省略或者乱写.

6.2 常见问题解析

1. 获取自增主键值
在对command进行插入时, 由于command.id为mysql自增. 不需要进行设置. 那么在插入command后, 应该怎样获取到这个自增的id,作为content的外键添加command包含的contents.

<!--插入一对多单方时设置实体类获取数据库中单方自增生成的主键来设置多方的外键
    useGeneratedKeys为true表示主键为mysql自增.keyProperty表示mybatis获取的主键值添加到实体类的该属性-->
<insert id="insert" parameterType="com.qezhhnjy.bean.Command"
            useGeneratedKeys="true" keyProperty="id" >
    INSERT INTO command(command,description) VALUES(#{command},#{description})
</insert>

2. 找不到namespace.id的异常效果
需要在Configuration.xml中映射各个实体类的xml文件. 否则运行就会出现这个异常.

3. sql语句编写错误

4. 不要过度使用${}

5. 乱码

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容