1. 程式人生 > >MyBatis學習基礎知識小結

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、建表時的字符集編碼和排序規則編碼