MyBatis学习基础知识小结

一、MyBatis安装:
https://github.com/mybatis/mybatis-3/releases
下载mybatis-3.4.6.zip,解压将mybatis-3.4.6.jar包放到WEB-INF/lib目录下

二、编写核心配置文件Configuration.xml:
下载Source code(zip)并解压
将mybatis-3-mybatis-3.4.6/src/test/java/org/apache/ibatis/submitted/complex_property/目录下的
Configuration.xml模板文件拷贝到项目com.maple.config包中,修改如下:
    <property name="driver" value="com.mysql.jdbc.Driver"/>
    <property name="url" value="jdbc:mysql://127.0.0.1:3306/demo"/>
    <property name="username" value="root"/>
    <property name="password" value="root"/>
    注释掉<settings><typeAliases>标签

三、MyBatis向Dao层提供的重要对象:SqlSession,
SqlSession的作用如下:
1、向SQL语句传入参数
2、执行SQL语句
3、获取执行SQL语句的结果
4、事务的控制

如何获得SqlSession,分三步:
1、通过核心配置文件获取数据库连接相关信息
2、通过配置信息构建SqlSessionFactory对象
3、通过SqlSessionFactory打开数据库会话(SqlSession:与根数据库交互的会话)

获得SqlSession的实现方法:
建立com.maple.db.DBAccess类【用于得到SqlSession】:
    public SqlSession getSqlSession() throws IOException {
        // 通过配置文件获取数据库连接信息
        Reader reader = Resources.getResourceAsReader("com/maple/config/Configuration.xml");
        // 通过配置信息构建一个SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
        // 通过SqlSessionFactory打开一个数据库会话
        SqlSession sqlSession = sqlSessionFactory.openSession();
        return sqlSession;
    }

总结:如此一来,dao层的工作就被简化,不再借助Connection,PrepareStatement和ResultSet等手动方式完成
转而通过sqlSession对象读配置文件的方式操作数据库

四、用MyBatis实现数据库查询操作

1、新建Message.xml子配置文件
下载Source code(zip)并解压
将mybatis-3-mybatis-3.4.6/src/test/java/org/apache/ibatis/submitted/complex_property/目录下的
User.xml模板文件拷贝到项目com.maple.config.sqlxml包中,命名为Message.xml,并修改如下:
<mapper namespace="Message">
  <resultMap type="com.maple.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="com.maple.bean.Message" resultMap="MessageResult">
    select id,command,description,content from message where 1=1
    <if test="command!=null and !&quot;&quot;.equals(command.trim())">and command=#{command}</if>
    <if test="description!=null and !&quot;&quot;.equals(description.trim())">and description like '%' #{description} '%'</if>
  </select>
</mapper>
说明:
a、mapper标签的namespace名空间是为了保证与其它配置文件命名重名不冲突,而且必须填写
b、select标签的id属性必须唯一,供SqlSession对象使用,
parameterType属性表示传递的参数类型(必须带包名,lang包除外),配置文件中只能接收一个参数
resultMap属性用于指定存放查询结果的resultMap标签的id值
c、resultMap标签用于接收select查找的结果,其type属性用于指定结果集的存储类型(必须带包名,lang包除外)
d、resultMap标签下的子标签有两种:<id>与<result>分别用于指定结果集中的主键和其它列,子标签的属性含义都相同
e、子标签中的column属性表示在数据库中查询到的列名,jdbcType属性表示该列在数据库中的类型
property属性表示该列对应存储在结果集中(一般是JavaBean)的属性名
f、查询语句中可包含查询条件:可使用<if>或<foreach>标签,并且支持OGNL表达式(不支持EL表达式)
<if test="">sql expression</if>
<foreach collection="" index="" item="" separator="">sql expression</foreach>
g、具体OGNL取值方法可参考图例,test属性中支持调用java方法
h、注意遇到双引号要用&quot;代替,遇到&&要用&amp;&amp;代替或用and代替,原来sql子表达式中的问号要用#{}写法替换
i、原子表达式前面的空格可忽略,配置文件会自动帮忙完成

2、在核心配置文件中加入子配置文件的映射关系
  <mappers>
    <mapper resource="com/maple/config/sqlxml/Message.xml"/>
  </mappers>
说明:子配置文件的路径从src目录开始

3、在dao层用SqlSession对象实现数据库查询操作  

public List<Message> queryMessageList(String command,String description){
    SqlSession sqlSession = null;
    List<Message> messageList = null;
    // org.apache.ibatis.logging.LogFactory.useLog4JLogging();
    try {
        sqlSession = new DBAccess().getSqlSession(); // 打开与数据库交互的会话
        Message msg = new Message();
        msg.setCommand(command);
        msg.setDescription(description);
        // 通过sqlSession执行SQL语句
        messageList = sqlSession.selectList("Message.queryMessageList",msg);
    } catch (IOException e) {
        e.printStackTrace();
    }finally {
        sqlSession.close();
    }
    return messageList;
}

五、使用Log4j调试动态Sql
1、下载log4j-1.2.17版本的包并拷贝到WEB-INF/lib目录下
2、在src根目录下添加log4j.properties属性配置文件,内容如下:
log4j.rootLogger=DEBUG, stdout
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d [%-5p] %t: [%c] %m%n
log4j.logger.org.apache=INFO
说明:
a、其中rootLogger表示根目录下的日志,也可记录其它包下的日志,如org.apache包
b、DEBUG表示日志输出级别,共4级,分别是debug,info,warn,error(由低到高)
c、stdout表示日志输出位置,可指定输出的布局和参数
%d产生日志的时间,
%t是产生日志所处的线程名称,
%-5p输出日志的级别,将占5位字符,不足5位用空格填补,-指的是在右边补齐,
%c你输出日志的包以及类的全名,
%m是你附加的信息

六、用MyBatis实现数据库单条删除操作

1、修改Message.xml文件,添加如下代码:
  <delete id="deleteOneMessage" parameterType="int">
      delete from message where id=#{_parameter}
  </delete>
 
2、修改dao层代码:
    public void deleteOneMessage(int id) {
        SqlSession sqlSession = null;
        try {
            sqlSession = new DBAccess().getSqlSession(); // 打开与数据库交互的会话
            sqlSession.delete("Message.deleteOneMessage",id); // 通过sqlSession执行SQL语句
            sqlSession.commit();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            sqlSession.close();
        }
    }
    
3、修改Maintain.service层代码:
    public void deleteOneMessage(String id) {
        if(id!=null && !"".equals(id.trim())) {
            MessageDao messageDao = new MessageDao();
            messageDao.deleteOneMessage(Integer.valueOf(id));
        }
    }

4、修改DeleteOneServlet层代码:
    String id = request.getParameter("msgId");
    new MaintainService().deleteOneMessage(id); // 异步到此为止
    // 向页面直接跳转(不考虑查询条件)
    request.getRequestDispatcher("/List.action").forward(request, response);
    // 向页面跳转(考虑查询条件)
    String command = request.getParameter("command");
    String description = request.getParameter("description");
    request.setAttribute("messageList", new ListService().queryMessageList(command,description));
    request.getRequestDispatcher("/WEB-INF/jsp/back/list.jsp").forward(request, response);

5、修改jsp代码:
    <c:forEach items="${messageList }" var="message" varStatus="status">
        <tr <c:if test="${status.index%2!=0 }">style="background-color:#ecf6ee;"</c:if>>
            <td><input name="selectId" type="checkbox" value="${message.id }" /></td>
            <td>${status.index + 1 }</td>
            <td>${message.command }</td>
            <td>${message.description }</td>
            <td>
                <a href="#">修改</a>&nbsp;&nbsp;&nbsp;
                <a href="javascript:void(0);" data-id="${message.id }" data-basepath="<%=basePath %>" class="delOne">POST删除(异步)</a>
                <a href="${basePath }DeleteOne.action?id=${message.id }">GET删除(同步)</a>
            </td>
        </tr>
    </c:forEach>
    
6、修改js代码:
    $(".delOne").on('click',function(e){
        $("#msgId").val($(this).data("id"));
        // 异步方式
        $.ajax({
            url:"DeleteOne.action",
            type:"post",
            data:$("#mainForm").serialize(),
            success:function(){
                console.log('删除成功');
                $(this).closest('tr').remove();
            }.bind(this),
            error:function(err){
                console.log(err);
            }
        });
        // 非异步方式
        $("#mainForm").attr('action',"<%=basePath %>DeleteOne.action");
        $("#mainForm").submit();
    });

7、说明:jdbc的方式做数据维护操作时,默认:conn.setAutoCommit(true); 而sqlSession方式下需要手动:sqlSession.commit();
    
七、用MyBatis实现数据库批量删除操作

1、修改Message.xml文件,添加如下代码:
  <delete id="deleteBatchMessage" parameterType="java.util.List">
      delete from message where id in(<foreach collection="list" index="i" item="item" separator=",">#{item}</foreach>)
  </delete>

2、修改dao层代码:
    public void deleteBatchMessage(List<Integer> ids) {
        SqlSession sqlSession = null;
        try {
            sqlSession = new DBAccess().getSqlSession(); // 打开与数据库交互的会话
            sqlSession.delete("Message.deleteBatchMessage",ids); // 通过sqlSession执行SQL语句
            sqlSession.commit();
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            sqlSession.close();
        }
    }
    
3、修改Maintain.service层代码:
    public void deleteBatchMessage(String[] ids) {
        MessageDao messageDao = new MessageDao();
        List<Integer> idList = new ArrayList<Integer>();
        for(String id:ids) {
            idList.add(Integer.valueOf(id));
        }
        messageDao.deleteBatchMessage(idList);
    }

4、修改DeleteBatchServlet层代码:
    String[] ids = request.getParameterValues("selectId");
    new MaintainService().deleteBatchMessage(ids);
    
    String command = request.getParameter("command");
    String description = request.getParameter("description");
    request.setAttribute("messageList", new ListService().queryMessageList(command,description));
    request.getRequestDispatcher("/WEB-INF/jsp/back/list.jsp").forward(request, response);

5、修改jsp代码:
    <c:forEach items="${messageList }" var="message" varStatus="status">
        <tr <c:if test="${status.index%2!=0 }">style="background-color:#ecf6ee;"</c:if>>
            <td><input name="selectId" type="checkbox" value="${message.id }" /></td>
            <td>${status.index + 1 }</td>
            <td>${message.command }</td>
            <td>${message.description }</td>
            <td>
                <a href="#">修改</a>&nbsp;&nbsp;&nbsp;
                <a href="javascript:void(0);" data-id="${message.id }" data-basepath="<%=basePath %>" class="delOne">POST删除(异步)</a>
                <a href="${basePath }DeleteOne.action?id=${message.id }">GET删除(同步)</a>
            </td>
        </tr>
    </c:forEach>
    
6、修改js代码:

$("#delBatch").on('click',function(e){
    e.preventDefault();
    $("#mainForm").attr("action",$(this).data("basepath")+"DeleteBatch.action");
    $("#mainForm").submit();
});


八、自动回复功能实现
1、异步请求:发送提交命令字段
    $.ajax({
        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}
    });

2、servlet层接受并处理异步请求
    protected void doGet(HttpServletRequest req, HttpServletResponse resp)
            throws ServletException, IOException {
        PrintWriter out = resp.getWriter();
        QueryService queryService = new QueryService();
        out.write(queryService.queryByCommand(req.getParameter("content")));
        out.flush();
        out.close();
    }

3、调用service层处理具体业务
    public String queryByCommandName(String command) {
        MessageDao messageDao = new MessageDao();
        List<Message> messageList;
        if(Iconst.HELP_COMMAND.equals(command)) {
            messageList = messageDao.queryMessageList(null, null);
            StringBuilder result = new StringBuilder();
            for(int i = 0; i < messageList.size(); i++) {
                if(i != 0) {
                    result.append("<br/>");
                }
                result.append("回复[" + messageList.get(i).getCommand() + "]可以查看" + messageList.get(i).getDescription());
            }
            return result.toString();
        }
        messageList = messageDao.queryMessageList(command,null);
        if(messageList.size()>0) {
            return messageList.get(0).getContent();
        }
        return Iconst.NO_MATCHING_CONTENT;
    }
    
4、调用dao层处理具体实现查询(直接调用第四步第3条即可)


九、实现一对多关系的配置

1、新建主表command和子表command_content,并建立相应的实体bean类
        其中主表包含子表的集合字段

2、编写数据库xml配置文件
        
a、新建Command.xml配置文件
<mapper namespace="Command">
  <resultMap type="com.imooc.bean.Command" id="Command">
    <id column="C_ID" jdbcType="INTEGER" property="id"/>
    <result column="NAME" jdbcType="VARCHAR" property="name"/>
    <result column="DESCRIPTION" jdbcType="VARCHAR" property="description"/>
    <collection resultMap="CommandContent.Content" property="contentList"/>
  </resultMap>
  <select id="queryCommandList" parameterType="com.imooc.bean.Command" resultMap="Command">
    select a.ID C_ID,a.NAME,a.DESCRIPTION,b.ID,b.CONTENT,b.COMMAND_ID
    from COMMAND a left join COMMAND_CONTENT b
    on a.ID=b.COMMAND_ID
    <where>
        <if test="name != null and !&quot;&quot;.equals(name.trim())">
            and a.NAME=#{name}
        </if>
        <if test="description != null and !&quot;&quot;.equals(description.trim())">
            and a.DESCRIPTION like '%' #{description} '%'
        </if>
    </where>
  </select>
</mapper>

b、新建CommandContent.xml配置文件  
<mapper namespace="CommandContent">
  <resultMap type="com.imooc.bean.CommandContent" id="Content">
    <id column="ID" jdbcType="INTEGER" property="id"/>
    <result column="COMMAND_ID" jdbcType="VARCHAR" property="commandId"/>
    <result column="CONTENT" jdbcType="VARCHAR" property="content"/>
  </resultMap>  
</mapper>

c、总配置文件导入上述两个xml配置文件
    <mapper resource="com/imooc/config/sqlxml/Command.xml"/>
    <mapper resource="com/imooc/config/sqlxml/CommandContent.xml"/>
    
3、调用CommandDao层实现数据查询操作
    public List<Command> queryCommandList(String name,String description) {
        DBAccess dbAccess = new DBAccess();
        List<Command> commandList = new ArrayList<Command>();
        SqlSession sqlSession = null;
        try {
            sqlSession = dbAccess.getSqlSession();
            Command command = new Command();
            command.setName(name);
            command.setDescription(description);
            // 通过sqlSession执行SQL语句
            commandList = sqlSession.selectList("Command.queryCommandList", command);
        } catch (IOException e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        } finally {
            if(sqlSession != null) {
                sqlSession.close();
            }
        }
        return commandList;
    }
    
4、调用QueryService层处理业务操作
    public String queryByCommand(String name) {
        CommandDao commandDao = new CommandDao();
        List<Command> commandList;
        if(Iconst.HELP_COMMAND.equals(name)) {
            commandList = commandDao.queryCommandList(null, null);
            StringBuilder result = new StringBuilder();
            for(int i = 0; i < commandList.size(); i++) {
                if(i != 0) {
                    result.append("<br/>");
                }
                result.append("回复[" + commandList.get(i).getName() + "]可以查看" + commandList.get(i).getDescription());
            }
            return result.toString();
        }
        commandList = commandDao.queryCommandList(name, null);
        if(commandList.size() > 0) {
            List<CommandContent> commandContentList = commandList.get(0).getContentList();
            int ram = new Random().nextInt(commandContentList.size());
            return commandContentList.get(ram).getContent();
        }
        return Iconst.NO_MATCHING_CONTENT;
    }

十、常用标签
1、定义SQL语句
<insert id="" parameterType="">
<delete id="" parameterType="">
<update id="">
<select id="" parameterType="" resultMap="">

2、配置java对象属性与查询结果集中列名(不一定是表字段名)对应关系
<resultMap type="" id="">
    <id column="" jdbcType="" property="">
    <result column="" jdbcType="" property="">
    <collection resultMap="" property="">
    <association resultMap="" property="">
</resultMap>

3、格式化标签
<where>:自动判断条件,并去除可能多余的where和and|or关键字
<set>:自动判断更新语句,并去除可能多余的set以及结尾可能多余的逗号
<trim>:可代替<where>或<set>标签
    <trim prefix="where" prefixOverrides="and|or">
        ...
    </trim>
    <trim prefix="set" suffixOverrides=",">
        ...
    </trim>

4、定义常量<sql>和引用常量<include>标签
<sql id="msgColumns">ID,COMMAND,DESCRIPTION,CONTENT</sql>
select <include refid="msgColumns"/> from MESSAGE

3、控制动态SQL拼接
<if test=""><if>
<foreach collection="" index="" item=""></foreach>
<choose>
    <when test="">...</when>
    <when test="">...</when>
    <otherwise></otherwise>
</choose>

5、配置关联关系:
<collection>:查询主表中结果集中有关联子表的多条数据(一对多)
<collection resultMap="CommandContent.Content" property="contentList"/>
<association>:查询子表中结果集中有关联到主表中的一条数据
<association resultMap="Command.Command" property="command"></association>

十一、常见问题

1、容易混淆的概念:resultMap与resultType
resultMap与resultType都是用来表示结果集中的列与java对象中的属性之间的一种关系,MyBatis帮我们处理结果集,将结果集放到java对象中。
当使用resultMap属性时,还需要单独配置resultMap标签做映射处理
当使用resultMap属性时,还具有通过typeHandler属性来做类型转换的能力(常见于日期或布尔类型)
当使用resultType属性时,就不再需要单独配置resultMap标签了,但要求结果集中的列名与实体类的属性名相同(大小写不敏感)。
当使用resultType属性时,还可以设置为java.util.Map,这时key就是结果集中的列名,value就是结果集中的值(此时列名大小写敏感)

2、容易混淆的概念:parameterType与parameterMap
parameterType指向一个java类型,表示参数类型,与ognl表达式和#{}直接相关
parameterMap需要指向一个用<parameterMap>标签配置的映射关系的id, 用来表示参数中的属性与数据库中的列对应的关系(官方不推荐)

3、#{}与${}与ognl表达式
#{}在sql中会被预编译解析为?,
${}在sql中会被直接解析为原始str,所以还必须加单引号,常用于排序表头字段order by ${}
当parameterType参数类型为String或基本数据类型时,mybatis取值可写为#{任意名},而ognl就必须写成#{_parameter},但为了保持风格统一,mybatis取值也建议写成#{_parameter}
当parameterType参数类型为自定义数据类型Message时,取值只能写为#{属性名}

4、获取自増主键值

  <insert id="insertCommandContent" useGeneratedKeys="true" parameterType="com.imooc.bean.Command">
      insert into COMMAND(name,description) values(#{name},#{description})
  </insert>
上述语句如果想取到插入时的自增主键值,就需要设置useGeneratedKeys="true",由于Command类中有id属性,但是在传入xml中的Command对象里面,其它属性值来自于页面,而id是出于自増的原因,其实是没有值的,因此这时就需要用到另外一个属性keyProperty="id",Mybatis会帮你取新増数据的主键,然后你用keyProperty属性告诉Mybatis将主键存到参数对象中的哪一个属性中(这里属性是id)。这样一来在java代码中,Command对象在传入xml中的时候,id属性是没有值的,等到sqlSession调用配置文件中的sql执行完了以后,id属性就有值了,并且是新増数据的主键值。

5、找不到namespace.id的异常
当总配置表中未加子配置表的映射时,或者调用时命名空间单词拼错,都会抛出“Mapped Statements collection does not contain value for Message.queryMessageList”

6、sql语法排查
利用Log4j将sql语句复制到sql控制台执行一遍,其中?号要替换为查询条件,并加上单引号

7、不要过度使用${}
将mybatis中的sql语句回归到java代码中的方式,叫注解sql,但需要往配置文件中通过判断循环标签去实现动态sql是非常麻烦的

8、乱码问题
a、项目文件本身的编码,通过文件右键属性设为utf-8
b、jsp页面内的编码:
<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
c、jsp页面传值到servlet后用于转换的编码:
req.setCharacterEncoding("utf-8");
d、用get方式提交中文的话,tomcat里面也要配置中文编码
e、总配置文件中:
<property name="url" value="jdbc:mysql://127.0.0.1:3306/demo?useUnicode=true&amp;characterEncoding=utf8"/>
f、mysql安装过程中有个配置编码选项选择 Character Set:utf8
g、建数据库时的字符集编码和排序规则编码
h、建表时的字符集编码和排序规则编码


猜你喜欢

转载自blog.51cto.com/maplebb/2318531
今日推荐