1. 程式人生 > >Spring Boot2(十一):Mybatis使用總結(自增長、多條件、批量操作、多表查詢等等)

Spring Boot2(十一):Mybatis使用總結(自增長、多條件、批量操作、多表查詢等等)

一、前言

上次用Mybatis還是2017年做專案的時候,已經很久過去了。中途再沒有用過Mybatis。導致現在學習SpringBoot過程中遇到一些Mybatis的問題,以此做出總結(XML極簡模式)。當然只是實用方面的總結,具體就不深究♂了。這裡只總結怎麼用!!!

(這次直接跳到十一,是因為中間是RabbitMQ 詳解,大家看微笑哥的就夠了)

二、關於Mybatis

1、什麼是Mybatis

(1)Mybatis是一個半ORM(物件關係對映)框架,它內部封裝了JDBC,開發時只需要關注SQL語句本身,不需要花費精力去處理載入驅動、建立連線、建立statement等繁雜的過程。程式設計師直接編寫原生態sql,可以嚴格控制sql執行效能,靈活度高。

(2)MyBatis 可以使用 XML 或註解來配置和對映原生資訊,將 POJO對映成資料庫中的記錄,避免了幾乎所有的 JDBC 程式碼和手動設定引數以及獲取結果集。

(3)通過xml 檔案或註解的方式將要執行的各種 statement 配置起來,並通過java物件和 statement中sql的動態引數進行對映生成最終執行的sql語句,最後由mybatis框架執行sql並將結果對映為java物件並返回。(從執行sql到返回result的過程)。

2、Mybaits的優點

(1)基於SQL語句程式設計,相當靈活,不會對應用程式或者資料庫的現有設計造成任何影響,SQL寫在XML裡,解除sql與程式程式碼的耦合,便於統一管理;提供XML標籤,支援編寫動態SQL語句,並可重用。

(2)與JDBC相比,減少了50%以上的程式碼量,消除了JDBC大量冗餘的程式碼,不需要手動開關連線;

(3)很好的與各種資料庫相容(因為MyBatis使用JDBC來連線資料庫,所以只要JDBC支援的資料庫MyBatis都支援)。

(4)能夠與Spring很好的整合;

(5)提供對映標籤,支援物件與資料庫的ORM欄位關係對映;提供物件關係對映標籤,支援物件關係元件維護。

3、MyBatis框架的缺點

(1)SQL語句的編寫工作量較大,尤其當欄位多、關聯表多時,對開發人員編寫SQL語句的功底有一定要求。

(2)SQL語句依賴於資料庫,導致資料庫移植性差,不能隨意更換資料庫。

4、MyBatis框架適用場合

(1)MyBatis專注於SQL本身,是一個足夠靈活的DAO層解決方案。

(2)對效能的要求很高,或者需求變化較多的專案,如網際網路專案,MyBatis將是不錯的選擇。

5、MyBatis與Hibernate有哪些不同

(1)Mybatis和hibernate不同,它不完全是一個ORM框架,因為MyBatis需要程式設計師自己編寫Sql語句。

(2)Mybatis直接編寫原生態sql,可以嚴格控制sql執行效能,靈活度高,非常適合對關係資料模型要求不高的軟體開發,因為這類軟體需求變化頻繁,一但需求變化要求迅速輸出成果。但是靈活的前提是mybatis無法做到資料庫無關性,如果需要實現支援多種資料庫的軟體,則需要自定義多套sql對映檔案,工作量大。

(3)Hibernate物件/關係對映能力強,資料庫無關性好,對於關係模型要求高的軟體,如果用hibernate開發可以節省很多程式碼,提高效率。


三、使用總結

以下的用法例項建議將原始碼clone到本地執行,全部使用的是XMl極簡模式

因為我沒有貼出完整的程式碼,只貼出關鍵處理的部分

所有測試都已經通過Postman傳送請求測試。

不過我建議各位看官可以用下IDEA的外掛:Restfultookit,非常好用的,根據controller定義的url地址快捷生成請求報文,可以直接測試。對於測試報文來說這個外掛簡直無敵!強烈推薦(已經安裝的當我沒說)


1、Java,JDBC與MySQL資料型別對照資料型別關係表

任何MySQL資料型別都可以轉換為Java資料型別。

如果選擇的Java數值資料型別的精度或容量低於要轉換為的MySQL資料型別,則可能會出現舍入,溢位或精度損失。

下表列出了始終保證有效的轉換。 第一列列出了一種或多種MySQL資料型別,第二列列出了可以轉換MySQL型別的一種或多種Java型別。

These MySQL Data Types Can always be converted to these Java types
CHAR, VARCHAR, BLOB, TEXT, ENUM, and SET java.lang.String, java.io.InputStream, java.io.Reader, java.sql.Blob, java.sql.Clob
FLOAT, REAL, DOUBLE PRECISION, NUMERIC, DECIMAL, TINYINT, SMALLINT, MEDIUMINT, INTEGER, BIGINT java.lang.String, java.lang.Short, java.lang.Integer, java.lang.Long, java.lang.Double, java.math.BigDecimal
DATE, TIME, DATETIME, TIMESTAMP java.lang.String, java.sql.Date, java.sql.Timestamp

ResultSet.getObject()方法使用MySQL和Java型別之間的型別轉換,遵循適當的JDBC規範。 ResultSetMetaData.GetColumnTypeName()和ResultSetMetaData.GetColumnClassName()返回的值如下表所示。 有關JDBC型別的更多資訊,請參閱java.sql.Types類的參考。

MySQL Type Name Return value of GetColumnTypeName Return value of GetColumnClassName
BIT(1) BIT java.lang.Boolean
BIT( > 1) BIT byte[]
TINYINT TINYINT java.lang.Boolean if the configuration property tinyInt1isBit is set to true (the default) and the storage size is 1, or java.lang.Integer if not.
BOOL, BOOLEAN TINYINT See TINYINT, above as these are aliases for TINYINT(1), currently.
SMALLINT[(M)] [UNSIGNED] SMALLINT [UNSIGNED] java.lang.Integer (regardless of whether it is UNSIGNED or not)
MEDIUMINT[(M)] [UNSIGNED] MEDIUMINT [UNSIGNED] java.lang.Integer (regardless of whether it is UNSIGNED or not)
INT,INTEGER[(M)] [UNSIGNED] INTEGER [UNSIGNED] java.lang.Integer, if UNSIGNED java.lang.Long
BIGINT[(M)] [UNSIGNED] BIGINT [UNSIGNED] java.lang.Long, if UNSIGNED java.math.BigInteger
FLOAT[(M,D)] FLOAT java.lang.Float
DOUBLE[(M,B)] DOUBLE java.lang.Double
DECIMAL[(M[,D])] DECIMAL java.math.BigDecimal
DATE DATE java.sql.Date
DATETIME DATETIME java.sql.Timestamp
TIMESTAMP[(M)] TIMESTAMP java.sql.Timestamp
TIME TIME java.sql.Time
YEAR[(2|4)] YEAR If yearIsDateType configuration property is set to false, then the returned object type is java.sql.Short. If set to true (the default), then the returned object is of type java.sql.Date with the date set to January 1st, at midnight.
CHAR(M) CHAR java.lang.String (unless the character set for the column is BINARY, then byte[] is returned.
VARCHAR(M) [BINARY] VARCHAR java.lang.String (unless the character set for the column is BINARY, then byte[] is returned.
BINARY(M) BINARY byte[]
VARBINARY(M) VARBINARY byte[]
TINYBLOB TINYBLOB byte[]
TINYTEXT VARCHAR java.lang.String
BLOB BLOB byte[]
TEXT VARCHAR java.lang.String
MEDIUMBLOB MEDIUMBLOB byte[]
MEDIUMTEXT VARCHAR java.lang.String
LONGBLOB LONGBLOB byte[]
LONGTEXT VARCHAR java.lang.String
ENUM('value1','value2',...) CHAR java.lang.String
SET('value1','value2',...) CHAR java.lang.String

參考:6.5 Java, JDBC, and MySQL Types

2、當實體類中的屬性名和表中的欄位名不一樣,怎麼辦

其一:定義欄位別名,使之與實體類屬性名一致。

<!-- 查詢使用者資訊列表1 -->
<select id="queryUserList1" resultType="com.niaobulashi.entity.SysUser">
   SELECT
        u.user_id, u.username userNameStr, u.password, u.salt, u.email,
        u.mobile, u.status, u.dept_id, u.create_time
    FROM
        sys_user u
    where 1=1
</select>

其二:通過resultMap對映欄位名和實體類屬性名保持一致

<resultMap id="sysUserInfoMap" type="com.niaobulashi.entity.SysUser">
    <!-- 使用者Id屬性來對映主鍵欄位 userId-->
    <id property="id" column="userId"/>
    <!-- 用result屬性來對映非主鍵欄位,property為實體類屬性名,column為資料表中的屬性-->
    <result property="userNameStr" column="username"/>
</resultMap>

<!--使用者Vo-->
<sql id="selectSysUserVo">
    SELECT
        u.user_id, u.username, u.password, u.salt, 
        u.email, u.mobile, u.status, u.dept_id, u.create_time
    FROM
        sys_user u
</sql>

<!-- 查詢使用者資訊列表2 -->
<select id="queryUserList2" resultMap="sysUserInfoMap">
    <include refid="selectSysUserVo"/>
    where 1=1
</select>

推薦使用第二種。

2、獲取Mybatis自增長主鍵

思路:useGeneratedKeys="true" keyProperty="id"

<!-- 獲取自動生成的(主)鍵值 -->
<insert id="insertSysTest" parameterType="com.niaobulashi.model.SysTest"
        useGeneratedKeys="true" keyProperty="id">
    INSERT INTO sys_test(name, age, nick_name) VALUES (#{name},#{age},#{nickName})
</insert>

獲取自增長主鍵

/**
 * 獲取自增長主鍵ID
 * @param sysTest
 * @throws Exception
 */
@RequestMapping(value = "/add", method = RequestMethod.POST)
private void addSysTest(@RequestBody SysTest sysTest) throws Exception {
    try {
        SysTest sysTestParam = new SysTest();
        // 將傳入引數Copy到新申明的物件中,這樣才能從sysTestParam中獲取到自增長主鍵
        BeanUtils.copyProperties(sysTest, sysTestParam);
        this.sysTestService.insertSysTest(sysTestParam);
        log.info("獲取自增長主鍵為:" + sysTestParam.getId());
    } catch (Exception e) {
        e.printStackTrace();
        throw new Exception();
    }
}

3、模糊查詢

使用%"#{value}"%"方法會引起SQL注入

推薦使用:CONCAT('%',#{value},'%')

<!--使用者Vo-->
<sql id="selectSysUserVo">
    SELECT
        u.user_id, u.username, u.password, u.salt, 
        u.email, u.mobile, u.status, u.dept_id, u.create_time
    FROM
        sys_user u
</sql>

<!-- 查詢使用者資訊列表2 -->
<select id="queryUserListByName" parameterType="String" resultMap="sysUserInfoMap">
    <include refid="selectSysUserVo"/>
    where 1=1
    and u.username like concat('%',#{userName},'%')
</select>

4、多條件查詢

1、使用@Param

List<SysUser> queryUserByNameAndEmail(@Param("userName") String userName, @Param("email") String email);
<!--使用使用者名稱和郵箱查詢使用者資訊-->
<select id="queryUserByNameAndEmail" resultMap="sysUserInfoMap">
    <include refid="selectSysUserVo"/>
    <where>
        <if test="userName != null and userName != ''">
            AND u.username like concat('%',#{userName},'%')
        </if>
        <if test="email != null and email != ''">
            AND u.email like concat('%',#{email},'%')
        </if>
    </where>
</select>

2、使用JavaBean

這裡給了一些常見的查詢條件:日期、金額。

List<SysUser> queryUserByUser(SysUser sysUser);
<select id="queryUserByUser" parameterType="com.niaobulashi.model.SysUser" resultMap="sysUserInfoMap">
    <include refid="selectSysUserVo"/>
    <where>
        1=1
        <if test="userNameStr != null and userNameStr != ''">
            AND u.username like concat('%', #{userNameStr}, '%')
        </if>
        <if test="email != null and email != ''">
            AND u.email like concat('%', #{email}, '%')
        </if>
        <if test="mobile != null and mobile != ''">
            AND u.mobile like concat('%', #{mobile}, '%')
        </if>
        <if test="createDateStart != null and createDateStart != ''">/*開始時間檢索*/
            AND date_format(u.create_time, '%y%m%d') <![CDATA[ >= ]]> date_format(#{createDateStart}, '%y%m%d')
        </if>
        <if test="createDateEnd != null and createDateEnd != ''">/*結束時間檢索*/
            AND date_format(u.create_time, '%y%m%d') <![CDATA[ <= ]]> date_format(#{createDateEnd}, '%y%m%d')
        </if>
        <if test="amtFrom != null and amtFrom != ''">/*起始金額*/
            AND u.amt <![CDATA[ >= ]]> #{amtFrom}
        </if>
        <if test="amtTo != null and amtTo != ''">/*截至金額*/
            AND u.amt <![CDATA[ <= ]]> #{amtTo}
        </if>
    </where>
</select>

5、批量刪除foreach

xml部分

<delete id="deleteSysTestByIds" parameterType="String">
    delete from sys_test where id in
    <foreach collection="array" item="id" open="(" separator="," close=")">
        #{id}
    </foreach>
</delete>

其中foreach包含屬性講解:

  • open:整個迴圈內容開頭的字串。
  • close:整個迴圈內容結尾的字串。
  • separator:每次迴圈的分隔符。
  • item:從迭代物件中取出的每一個值。
  • index:如果引數為集合或者陣列,該值為當前索引值,如果引數為Map型別時,該值為Map的key。
  • collection:要迭代迴圈的屬性名。

dao部分

int deleteSysTestByIds(String[] ids);

service層

@Transactional(rollbackFor = Exception.class)
@Override
public int deleteDictDataByIds(String ids) throws Exception{
    try {
        return sysTestDao.deleteSysTestByIds(ids.split(","));
    } catch (Exception e) {
        e.printStackTrace();
        throw new Exception();
    }
}

controller

@RequestMapping(value = "/deleteIds", method = RequestMethod.POST)
public int deleteIds(String ids) throws Exception {
    try {
        return sysTestService.deleteDictDataByIds(ids);
    } catch (Exception e) {
        e.printStackTrace();
        throw new Exception();
    }
}

請求URL:http://localhost:8081/test/deleteIds

請求報文:

ids : 1,2

6、多表查詢association和collection

多表查詢,多表肯定首先我們先要弄清楚兩個關鍵字:

association: 一對一關聯(has one);collection:一對多關聯(has many)

的各個屬性的含義:

association和collection
property:對映資料庫列的欄位或屬性。
colum:資料庫的列名或者列標籤別名。
javaTyp:完整java類名或別名。
jdbcType:支援的JDBC型別列表列出的JDBC型別。這個屬性只在insert,update或delete的時候針對允許空的列有用。
resultMap:一個可以對映聯合巢狀結果集到一個適合的物件檢視上的ResultMap。這是一個替代的方式去呼叫另一個select語句。

這樣說起來可能不好理解,我舉個栗子

涉及到這三張表,我粗略的畫了一下:

- 使用者表 部門表 角色表
表名 sys_user sys_dept sys_role
與使用者表關係 - 一對一(一個使用者只屬於一個部門) 一對多(一個使用者可以有多個角色)

於是使用者表關聯部門表,我們用association

使用者表關聯角色表,我們用collection

當然了,能用得這麼蛋疼關鍵字的前提條件是,你要查詢關聯的欄位,如果你只是關聯不查它,那就不需要用這玩意。。

辣麼,我結合這兩個多表查詢的關鍵字association、collection舉個栗子。

1、使用者表實體類

@Data
public class SysUser implements Serializable {
    private static final long serialVersionUID = 1L;
    /** 使用者ID */
    private Long userId;
    /** 使用者名稱 */
    private String userNameStr;
    /** 密碼 */
    private String password;
    /** 鹽 */
    private String salt;
    /** 郵箱 */
    private String email;
    /** 手機號 */
    private String mobile;
    /** 狀態  0:禁用   1:正常 */
    private Integer status;
    /** 部門Id */
    private Long deptId;
    /** 建立時間 */
    private Date createTime;
    /****************關聯部分**************
    /** 部門 */
    private SysDept dept;
    /** 角色集合 */
    private List<SysRole> roles;
}

2、部門表實體類

@Data
public class SysDept implements Serializable {
    /** 部門ID */
    private Long deptId;
    /** 部門名稱 */
    private String deptName;
}

3、角色表實體類

@Data
public class SysRole implements Serializable {
    /** 角色ID */
    private Long roleId;
    /** 角色名稱 */
    private String roleName;
}

4、Mapper、Service部分(略)

List<SysUser> queryUserRoleDept(SysUser user);

5、XML部分

<!--檢視使用者部門和角色資訊-->
<select id="queryUserRoleDept" parameterType="com.niaobulashi.model.SysUser" resultMap="UserResult">
    select u.user_id, u.username, u.dept_id, d.dept_name, r.role_id, r.role_name
    from sys_user u
    LEFT JOIN sys_dept d on d.dept_id = u.dept_id
    LEFT JOIN sys_user_role ur on ur.user_id = u.user_id
    LEFT JOIN sys_role r on r.role_id = ur.role_id
    WHERE 1=1
    <if test="userId != null and userId != ''">
        AND u.user_id = #{userId}
    </if>
</select>

UserResult部分

<!--使用者表-->
<resultMap type="com.niaobulashi.model.SysUser" id="UserResult">
    <id property="userId" column="user_id"/>
    <result property="userNameStr" column="username"/>
    <result property="password" column="login_name"/>
    <result property="salt" column="password"/>
    <result property="email" column="email"/>
    <result property="mobile" column="mobile"/>
    <result property="status" column="status"/>
    <result property="deptId" column="dept_id"/>
    <result property="createTime" column="create_time"/>
    <association property="dept" column="dept_id" javaType="com.niaobulashi.model.SysDept" resultMap="DeptResult"/>
    <collection property="roles" javaType="java.util.List" resultMap="RoleResult"/>
</resultMap>

<!--部門表-->
<resultMap id="DeptResult" type="com.niaobulashi.model.SysDept">
    <id property="deptId" column="dept_id"/>
    <result property="deptName" column="dept_name"/>
</resultMap>

<!--角色表-->
<resultMap id="RoleResult" type="com.niaobulashi.model.SysRole">
    <id property="roleId" column="role_id"/>
    <result property="roleName" column="role_name"/>
</resultMap>

6、Controller部分

@RequestMapping(value = "/queryUserRoleDept", method = RequestMethod.POST)
private List<SysUser> queryUserRoleDept(@RequestBody SysUser sysUser) {
    List<SysUser> userList = sysUserService.queryUserRoleDept(sysUser);
    return userList;
}

7、測試部分

請求結果:

7、分頁外掛

使用分頁外掛PageHelper Spring Boot Starter,引入maven依賴:PageHelper Spring Boot Starter1.2.12

application.yml配置

# PageHelper分頁外掛
pagehelper:
  helperDialect: mysql
  reasonable: true
  supportMethodsArguments: true
  params: count=countSql

controller

@RequestMapping(value = "/queryUserByPage", method = RequestMethod.GET)
private PageInfo queryUserByPage(Integer currentPage, Integer pageSize) {
    PageHelper.startPage(currentPage, pageSize);
    List<SysUser> userList = sysUserService.queryUserRoleDept(new SysUser());
    PageInfo info=new PageInfo(userList);
    return info;
}

目前暫時寫到這裡,本篇會持續補充

To be contin