MyBatis學習總結(三)——多表關聯查詢與動態SQL


在上一章中我們學習了《MyBatis學習總結(二)——MyBatis核心配置文件與輸入輸出映射》,這一章主要是介紹一對一關聯查詢、一對多關聯查詢與動態SQL等內容。

一、多表關聯查詢

表與表之間有三種常見的關聯關系,分別是一對一,一對多與多對多關系,MyBatis直接提供一對一與一對多的關聯關系,可能通過間接的方式實現一對多關聯。

1.1、一對一關系

1.1.1、執行環境

假定一個員工(emp)擁有一個登錄用戶(user),員工與用戶表之間是一對一關系:

 用戶表:

員工表:

SQL:

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for `user`
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
 `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '編號',
 `username` varchar(64) NOT NULL COMMENT '用戶名',
 `password` varchar(64) NOT NULL COMMENT '密碼',
 PRIMARY KEY (`id`),
 UNIQUE KEY `users_username_uindex` (`username`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COMMENT='用戶表';

-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES ('1', 'tom', '123456');
INSERT INTO `user` VALUES ('2', 'rose', '888888');
INSERT INTO `user` VALUES ('3', 'mark', 'qwerty');
INSERT INTO `user` VALUES ('4', 'jack', 'qaz123');
INSERT INTO `user` VALUES ('5', 'mali', 'uio890');

SET FOREIGN_KEY_CHECKS=0;

-- ----------------------------
-- Table structure for `emp`
-- ----------------------------
DROP TABLE IF EXISTS `emp`;
CREATE TABLE `emp` (
 `id` int(11) NOT NULL AUTO_INCREMENT COMMENT '編號',
 `user_id` int(11) DEFAULT NULL COMMENT '用戶編號',
 `realname` varchar(32) NOT NULL COMMENT '姓名',
 `email` varchar(64) DEFAULT NULL COMMENT '郵箱',
 PRIMARY KEY (`id`),
 KEY `emp_user_id` (`user_id`),
 CONSTRAINT `emp_user_id` FOREIGN KEY (`user_id`) REFERENCES `user` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=7 DEFAULT CHARSET=utf8 COMMENT='員工表';

-- ----------------------------
-- Records of emp
-- ----------------------------
INSERT INTO `emp` VALUES ('1', '1', '湯姆', 'tom@gmail.com');
INSERT INTO `emp` VALUES ('2', '2', '梅貴', 'rose@163.com');
INSERT INTO `emp` VALUES ('3', '3', '馬克', 'mark@sina.com');
INSERT INTO `emp` VALUES ('4', '4', '岳翰', 'jack@gmail.com');
INSERT INTO `emp` VALUES ('5', '5', '馬麗', 'mali@sina.com');

關系:

1.1.2、關聯查詢(1次查詢)

實體:

  用戶:

package com.zhangguo.mybatis03.entities;

/**用戶POJO*/
public class User {
 private int id;
 private String username;
 private String password;

 public int getId() {
 return id;
 }

 public void setId(int id) {
 this.id = id;
 }

 public String getUsername() {
 return username;
 }

 public void setUsername(String username) {
 this.username = username;
 }

 public String getPassword() {
 return password;
 }

 public void setPassword(String password) {
 this.password = password;
 }
}

  員工:

package com.zhangguo.mybatis03.entities;

/**員工POJO*/
public class Emp {
 private int id;
 /**用戶編號*/
 private int user_id;
 private String realname;
 private String email;

 /**用戶對象*/ private User user; public int getId() {
 return id;
 }

 public void setId(int id) {
 this.id = id;
 }

 public int getUser_id() {
 return user_id;
 }

 public void setUser_id(int user_id) {
 this.user_id = user_id;
 }

 public String getRealname() {
 return realname;
 }

 public void setRealname(String realname) {
 this.realname = realname;
 }

 public String getEmail() {
 return email;
 }

 public void setEmail(String email) {
 this.email = email;
 }

 public User getUser() {
 return user;
 }

 public Emp setUser(User user) {
 this.user = user;
 return this;
 }
}

接口:

package com.zhangguo.mybatis03.dao;

import com.zhangguo.mybatis03.entities.Emp;

/**員工數據訪口*/
public interface EmpMapper {

 /**獲得員工通過員工編號*/
 Emp getEmpById_1(int id);

}

映射:

<?xml version="1.0" encoding="UTF-8" ?>

<!DOCTYPE mapper
 PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
 "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.zhangguo.mybatis03.dao.EmpMapper">

 <!--一對一查詢,方法1,通過內聯接-->
 <select id="getEmpById_1" resultMap="empMap_1" parameterType="int">
 SELECT
 emp.id,
 emp.user_id,
 emp.realname,
 emp.email,
 `user`.username,
 `user`.`password`
 FROM
 emp
 INNER JOIN `user` ON emp.user_id = `user`.id where emp.id=#{id}
 </select>

 <!--員工關聯查詢結果映射-->
 <resultMap id="empMap_1" type="Emp">
 <id property="id" column="id"></id>
 <result property="user_id" column="user_id"></result>
 <result property="realname" column="realname"></result>
 <result property="email" column="email"></result>
 <!--映射關系,指定屬性與屬性的類型-->
 <association property="user" javaType="User">
 <id property="id" column="user_id"></id>
 <result property="username" column="username"></result>
 <result property="password" column="password"></result>
 </association>
 </resultMap>

</mapper>

測試:

package com.zhangguo.mybatis03.dao;

import com.zhangguo.mybatis03.entities.Emp;
import org.junit.Assert;
import org.junit.Test;
import org.junit.Before;
import org.junit.After;

/**
 * EmpDao Tester.
 *
 * @author <Authors name>
 * @version 1.0
 * @since <pre>09/30/2018</pre>
 */
public class EmpDaoTest {
 EmpMapper empDao;
 @Before
 public void before() throws Exception {
 empDao=new EmpDao();
 }

 @After
 public void after() throws Exception {
 }

 /**
 * Method: getEmpById_1(int id)
 * 獲得員工通過員工編號
 */
 @Test
 public void testGetEmpById_1() throws Exception {
 Emp entity=empDao.getEmpById_1(1);
 System.out.println(entity);
 Assert.assertNotNull(entity);
 }


} 

結果:

1.1.3、嵌套查詢(2次查詢)

實體:同上

接口:

 /**獲得員工通過員工編號,多次查詢*/
 Emp getEmpById_2(int id);

映射:

 <!--一對一查詢,方法2,通過多次查詢(嵌套查詢)-->
 <select id="getEmpById_2" resultMap="empMap_2">
 SELECT
 emp.id,
 emp.user_id,
 emp.realname,
 emp.email
 FROM
 emp where id=#{id}
 </select>

 <!--員工多次查詢結果映射-->
 <resultMap id="empMap_2" type="Emp">
 <id property="id" column="id"></id>
 <result property="user_id" column="user_id"></result>
 <result property="realname" column="realname"></result>
 <result property="email" column="email"></result>
 <!--通過外鍵user_id再次發起查詢,調用selectUserById獲得User對象-->
 <association property="user" column="user_id" select="selectUserById"></association>
 </resultMap>

 <!--根據用戶編號獲得用戶對象-->
 <select id="selectUserById" resultType="User">
 SELECT
 `user`.id,
 `user`.username,
 `user`.`password`
 FROM
 `user` where id=#{id}
 </select>

測試:

 /**
 * Method: getEmpById_2(int id)
 * 獲得員工通過員工編號,一對一方法二
 */
 @Test
 public void testGetEmpById_2() throws Exception {
 Emp entity=empDao.getEmpById_2(2);
 System.out.println(entity);
 Assert.assertNotNull(entity);
 }

結果:

 

MyBatis中使用association標簽來解決一對一的關聯查詢,association標簽可用的屬性如下:

  • property:對象屬性的名稱
  • javaType:對象屬性的類型
  • column:所對應的外鍵字段名稱
  • select:使用另一個查詢封裝的結果

1.2、一對多關系

1.2.1、執行環境

一個用戶帳號可以被多個員工使用,形成一個一對多的關系,表中的數據如下:

員工表emp:

用戶表user:

1.2.2、關聯查詢(1次查詢)

實體:

  員工:

package com.zhangguo.mybatis03.entities;

/**員工POJO*/
public class Emp {
 private int id;
 /**用戶編號*/
 private int user_id;
 private String realname;
 private String email;

 /**用戶對象*/
 private User user; public int getId() {
 return id;
 }

 public void setId(int id) {
 this.id = id;
 }

 public int getUser_id() {
 return user_id;
 }

 public void setUser_id(int user_id) {
 this.user_id = user_id;
 }

 public String getRealname() {
 return realname;
 }

 public void setRealname(String realname) {
 this.realname = realname;
 }

 public String getEmail() {
 return email;
 }

 public void setEmail(String email) {
 this.email = email;
 }

 public User getUser() {
 return user;
 }

 public Emp setUser(User user) {
 this.user = user;
 return this;
 }

 @Override
 public String toString() {
 return "Emp{" +
 "id=" + id +
 ", user_id=" + user_id +
 ", realname='" + realname + '\'' +
 ", email='" + email + '\'' +
 ", user=" + user +
 '}';
 }
}

  用戶:

package com.zhangguo.mybatis03.entities;

import java.util.List;

/**用戶POJO*/
public class User {
 private int id;
 private String username;
 private String password;

 /**員工集合,一個用戶對象對應多個員工對象*/ private List<Emp> emps; public int getId() {
 return id;
 }

 public void setId(int id) {
 this.id = id;
 }

 public String getUsername() {
 return username;
 }

 public void setUsername(String username) {
 this.username = username;
 }

 public String getPassword() {
 return password;
 }

 public void setPassword(String password) {
 this.password = password;
 }

 public List<Emp> getEmps() {
 return emps;
 }

 public User setEmps(List<Emp> emps) {
 this.emps = emps;
 return this;
 }

 @Override
 public String toString() {
 return "User{" +
 "id=" + id +
 ", username='" + username + '\'' +
 ", password='" + password + '\'' +
 ", emps=" + emps +
 '}';
 }
}

接口:

 /**獲得用戶通過用戶編號,1對多級聯查詢*/
 User getUserById_1(int id);

映射:

 <!--一對多查詢,方法1,通過內聯接-->
 <select id="getUserById_1" resultMap="userMap_1" parameterType="int">
 SELECT
 emp.id,
 emp.user_id,
 emp.realname,
 emp.email,
 `user`.username,
 `user`.`password`
 FROM
 emp
 INNER JOIN `user` ON emp.user_id = `user`.id
 where `user`.id=#{id}
 </select>

 <resultMap id="userMap_1" type="User">
 <id property="id" column="user_id"></id>
 <result property="username" column="username"></result>
 <result property="password" column="password"></result>
<!--將emps對象映射成一個集合,emps是user類型中的屬性,ofType用於指定集合中存放的對象類型-->
 <collection property="emps" ofType="Emp">
 <id property="id" column="id"></id>
 <result property="user_id" column="user_id"></result>
 <result property="realname" column="realname"></result>
 <result property="email" column="email"></result>
 </collection>
 </resultMap>

 

測試:

 /**
 * Method: getUserById_1(int id)
 * 獲得用戶過用戶編號,級聯查詢
 */
 @Test
 public void testGetUserById_1() throws Exception {
 User entity=empDao.getUserById_1(2);
 System.out.println(entity);
 Assert.assertNotNull(entity);
 }

結果:

上面的示例中會發現User對象中包含多個Emp對象,此時的Emp對象中又引用了User對象,但值是空的,如果想設置值可以繼續用1對1的辦法賦值:

映射:

 <resultMap id="userMap_1" type="User">
 <id property="id" column="user_id"></id>
 <result property="username" column="username"></result>
 <result property="password" column="password"></result>
 <!--將emps對象映射成一個集合,emps是user類型中的屬性,ofType用於指定集合中存放的對象類型-->
 <collection property="emps" ofType="Emp">
 <id property="id" column="id"></id>
 <result property="user_id" column="user_id"></result>
 <result property="realname" column="realname"></result>
 <result property="email" column="email"></result>
 <!--映射關系,指定屬性與屬性的類型-->
 <association property="user" javaType="User">
 <id property="id" column="user_id"></id>
 <result property="username" column="username"></result>
 <result property="password" column="password"></result>
 </association>
 </collection>
 </resultMap>

 

結果:

1.1.3、嵌套查詢(多次查詢)

實體:同上

接口:

 /**獲得用戶通過用戶編號,1對多嵌套查詢*/
 User getUserById_2(int id);

映射:

 <!--一對多查詢,方法2,通過嵌套查詢多次-->
 <select id="getUserById_2" resultMap="userMap_2" parameterType="int">
 SELECT
 `user`.id,
 `user`.username,
 `user`.`password`
 FROM
 `user` where id=#{id}
 </select>

 <resultMap id="userMap_2" type="User">
 <id property="id" column="user_id"></id>
 <result property="username" column="username"></result>
 <result property="password" column="password"></result>
 <!--將emps對象映射成一個集合,emps是user類型中的屬性,ofType用於指定集合中存放的對象類型-->
 <!--select用於指定再次查詢的SQL編號,column用於指定參數列-->
 <collection property="emps" ofType="Emp" column="id" select="selectEmpById"></collection>
 </resultMap>

 <!--根據員工編號獲得員工對象-->
 <select id="selectEmpById" resultType="Emp">
 SELECT
 emp.id,
 emp.user_id,
 emp.realname,
 emp.email
 FROM
 emp where user_id=#{id}
 </select>

測試:

 /**
 * Method: getUserById_2(int id)
 * 獲得用戶過用戶編號,嵌套查詢
 */
 @Test
 public void testGetUserById_2() throws Exception {
 User entity=empDao.getUserById_2(5);
 System.out.println(entity);
 Assert.assertNotNull(entity);
 }

結果:

 

MyBatis中使用collection標簽來解決一對多的關聯查詢,ofType屬性指定集合中元素的對象類型。

二、動態SQL

2.0、MySQL環境與前置要求

數據與SQL環境如下:

前置要求:

2.1、什么是動態SQL

MyBatis的動態SQL是基於OGNL的表達式的。它對SQL語句進行靈活的操作,通過表達式判斷來實現對SQL的靈活拼接、組裝。

 mybatis核心對sql語句進行靈活操作,通過表達式進行判斷,對sql進行靈活拼接、組裝。

 主要通過以下標簽:if,where,choose(when,otherwise),trim,set,foreach。

2.2、if條件判斷

根據 name和 sex 來查詢數據。如果name為空,那么將只根據sex來查詢;反之只根據name來查詢

首先不使用 動態SQL 來書寫

接口:

    /**
     * 根據學生姓名和性別獲得學生集合
     */
    List<Student> selectStudentsByNameAndSex(@Param("name") String name,@Param("sex") String sex);

映射:

    <select id="selectStudentsByNameAndSex" resultType="student">
        SELECT id,name,sex from student where name=#{name} and sex=#{sex};
    </select>

測試:

    /**
     * Method: selectStudentsByNameAndSex
     */
    @Test
    public void testSelectStudentsByNameAndSex() throws Exception {
        List<Student> students=dao.selectStudentsByNameAndSex("rose",null);
        System.out.println(students);
        Assert.assertNotNull(students);
    }

結果:

 

上面的查詢語句,我們發現如果 #{sex} 為空,那么查詢結果也是空,如何解決這個問題呢?使用 if 來判斷

    <select id="selectStudentsByNameAndSex" resultType="student">

        SELECT id,name,sex from student where 1=1
        <!--如果test為真會輸出中間的內容-->
        <if test="name!=null and name!=''">
            and name=#{name}
        </if>

        <if test="sex!=null and sex!=''">
            and sex=#{sex}
        </if>

    </select>

結果:

參考:

<!-- 2 if(判斷參數) - 將實體類不為空的屬性作為where條件 --> 
<select id="getStudentList_if" resultMap="resultMap_studentEntity" parameterType="liming.student.manager.data.model.StudentEntity"> 
 SELECT ST.STUDENT_ID, 
 ST.STUDENT_NAME, 
 ST.STUDENT_SEX, 
 ST.STUDENT_BIRTHDAY, 
 ST.STUDENT_PHOTO, 
 ST.CLASS_ID, 
 ST.PLACE_ID 
 FROM STUDENT_TBL ST 
 WHERE 
 <if test="studentName !=null "> 
 ST.STUDENT_NAME LIKE CONCAT(CONCAT('%', #{studentName, jdbcType=VARCHAR}),'%') 
 </if> 
 <if test="studentSex != null and studentSex != '' "> 
 AND ST.STUDENT_SEX = #{studentSex, jdbcType=INTEGER} 
 </if> 
 <if test="studentBirthday != null "> 
 AND ST.STUDENT_BIRTHDAY = #{studentBirthday, jdbcType=DATE} 
 </if> 
 <if test="classId != null and classId!= '' "> 
 AND ST.CLASS_ID = #{classId, jdbcType=VARCHAR} 
 </if> 
 <if test="classEntity != null and classEntity.classId !=null and classEntity.classId !=' ' "> 
 AND ST.CLASS_ID = #{classEntity.classId, jdbcType=VARCHAR} 
 </if> 
 <if test="placeId != null and placeId != '' "> 
 AND ST.PLACE_ID = #{placeId, jdbcType=VARCHAR} 
 </if> 
 <if test="placeEntity != null and placeEntity.placeId != null and placeEntity.placeId != '' "> 
 AND ST.PLACE_ID = #{placeEntity.placeId, jdbcType=VARCHAR} 
 </if> 
 <if test="studentId != null and studentId != '' "> 
 AND ST.STUDENT_ID = #{studentId, jdbcType=VARCHAR} 
 </if> 
</select> 

 雖然1=1這種方法結合if可以解決我們的需求,但是1=1明顯是冗余的,通過where可以解決。

2.3、where條件

where 元素知道只有在一個以上的if條件有值的情況下才去插入“WHERE”子句,若最后的內容是“AND”或“OR”開頭的,where 元素也知道如何將他們去除。

修改后的映射:

    <select id="selectStudentsByNameAndSex" resultType="student">

        SELECT id,name,sex from student

        <!--1、如果兩個if只要有一個有輸出就會在sql中添加 where-->
        <where>
            <if test="name!=null and name!=''">
                <!--2、如果where后以and或or開始則會刪除and或or-->
                and name like concat(concat('%',#{name}),'%');
            </if>

            <if test="sex!=null and sex!=''">
                and sex=#{sex}
            </if>
        </where>

    </select>

 

測試:

    /**
     * Method: selectStudentsByNameAndSex
     */
    @Test
    public void testSelectStudentsByNameAndSex() throws Exception {
        List<Student> students=dao.selectStudentsByNameAndSex("a",null);
        System.out.println(students);
        Assert.assertNotNull(students);
    }

 

 結果:

 

這個“where”標簽會知道如果它包含的標簽中有返回值的話,它就插入一個‘where’。此外,如果標簽返回的內容是以AND 或OR 開頭的,則它會剔除掉。

where標記的作用類似於動態sql中的set標記,他的作用主要是用來簡化sql語句中where條件判斷的書寫的,如下所示:

  <select id="selectByParams" parameterType="map" resultType="user">
    select * from user
    <where>
      <if test="id != null ">id=#{id}</if>
      <if test="name != null and name.length()>0" >and name=#{name}</if>
      <if test="gender != null and gender.length()>0">and gender = #{gender}</if>
    </where>
  </select>   

在上述SQL中加入ID的值為null的話,那么打印出來的SQL為:select * from user where name="xx" and gender="xx"

where 標記會自動將其后第一個條件的and或者是or給忽略掉

2.4、if+set設置值

當update語句中沒有使用if標簽時,如果有一個參數為null,都會導致錯誤。

當在update語句中使用if標簽時,如果前面的if沒有執行,則或導致逗號多余錯誤。使用set標簽可以將動態的配置SET 關鍵字,和剔除追加到條件末尾的任何不相關的逗號。如果set包含的內容為空的話則會出錯。

使用if+set標簽修改后,如果某項為null則不進行更新,而是保持數據庫原值。

如果通過if判斷表面可以解決問題,如下所示:

    <update id="updateStudent" parameterType="student">

        update student set  
        
        <if test="name!=null and name.lenght()>0">
            name=#{name} ,
        </if>

        <if test="sex!=null and sex.lenght()>0">
            sex=#{sex}
        </if>
        
        where id=#{id}
        
    </update>

這樣做也會有問題,就是當sex為空時的sql就變成了 update student set name=#{name} , where id=#{id},這明顯是錯誤的。

同理,上面的對於查詢 SQL 語句包含 where 關鍵字,如果在進行更新操作的時候,含有 set 關鍵詞,我們怎么處理呢?

接口:

    /**
     * 更新學生
     */
    int updateStudent(Student entity);

 

映射:

    <update id="updateStudent" parameterType="student">
        update student
        <!--自動添加set-->
        <set>
            <!--智能處理逗號問題-->
            <if test="name!=null and name.length()>0">
                name=#{name}
            </if>

            <if test="sex!=null and sex.length()>0">
                sex=#{sex}
            </if>
        </set>
        where id=#{id}
    </update>

注意:某些情況下逗號必須添加,如下所示:

 

    <update id="updateStudent" parameterType="student">
        update student
        <!--自動添加set-->
        <set>
            <!--智能處理逗號問題-->
            <if test="name!=null and name.length()>0">
                name=#{name} , 
            </if>

            <if test="sex!=null and sex.length()>0">
                sex=#{sex} , 
            </if>
        </set>
        where id=#{id}
    </update>
View Code

 

結尾的逗號會被自動刪除。

 

測試:

    /**
     * Method: updateStudent
     */
    @Test
    public void testUpdateStudent() throws Exception {
        //會將實體中的每一個字段都更新,不好
//        Student entity=dao.selectStudentById(11);
//        //entity.setName("張麗美");
//        entity.setSex("girl");
//
//        Assert.assertEquals(1,dao.updateStudent(entity));

        //不需要先執行查詢
        Student student=new Student();
        student.setId(9);
        //只更新了name與sex沒有關系
        student.setName("malili");
        Assert.assertEquals(1,dao.updateStudent(student));
    }

 

結果:

這樣寫,如果第一個條件 name 為空,那么 sql 語句為:update student set sex=? where id=?

如果第一個條件不為空,那么 sql 語句為:update student u set name= ? , sex = ? where id=?

set主要解決了自動添加標簽與處理逗號的問題,另外這種更新方法比較以前的全部更新方式在開發中性能更高。

2.5、choose(when,otherwise) 開關

如果不想用到所有的查詢條件,只想選擇其中的一個,查詢條件有一個滿足即可,使用 choose 標簽可以解決此類問題,類似於 Java 的 switch 語句。

假定這里需要優先根據編號搜索,沒有時選擇name,最后考慮sex:

接口:

    /**
     * 根據學生編號、姓名和性別獲得學生集合
     */
    List<Student> selectStudentsByNameAndSex(@Param("id") int id, @Param("name") String name,@Param("sex") String sex);

 

映射:

    <select id="selectStudentsByNameAndSex" resultType="student">

        SELECT id,name,sex from student
        <where>
        <choose>
            <when test="id>0">
                id=#{id}
            </when>
            <when test="name!=null and name!=''">
                name=#{name}
            </when>
            <otherwise>
                sex=#{sex}
            </otherwise>
        </choose>
        </where>
    </select>

 

測試:

    /**
     * Method: selectStudentsByNameAndSex
     */
    @Test
    public void testSelectStudentsByNameAndSex() throws Exception {
        List<Student> students=dao.selectStudentsByNameAndSex(1,"rose","girl");
        System.out.println(students);
        Assert.assertNotNull(students);
    }

 

結果:

 

也就是說,這里我們有三個條件,id,name,sex,只能選擇一個作為查詢條件

如果 id 不為空,那么查詢語句為:select * from student where  id=?

如果 id 為空,那么看name是否為空,如果不為空,那么語句為 select * from student where  name=?;

如果name為空,那么查詢語句為 select * from student  where sex=?

2.6、trim裁剪

trim標記是一個格式化的標記,可以完成set或者是where標記的功能

①、用 trim 改寫上面第二點的 if+where 語句

if+where的辦法:

    <select id="selectStudentsByNameAndSex" resultType="student">

        SELECT id,name,sex from student

        <!--1、如果兩個if只要有一個有輸出就會在sql中添加 where-->
        <where>
            <if test="name!=null and name!=''">
                <!--2、如果where后以and或or開始則會刪除and或or-->
                and name like concat(concat('%',#{name}),'%');
            </if>

            <if test="sex!=null and sex!=''">
                and sex=#{sex}
            </if>
        </where>

    </select>

trim的辦法:

    <select id="selectStudentsByNameAndSex" resultType="student">

        SELECT id,name,sex from student

        <!--1、prefix表示將前置where,prefixOverrides將刪除打頭內容-->
        <trim prefix="where" prefixOverrides="and | or">
            <if test="name!=null and name!=''">
                and name like concat(concat('%',#{name}),'%')
            </if>

            <if test="sex!=null and sex!=''">
                and sex=#{sex}
            </if>
        </trim>

    </select>

 

測試結果:

prefix:將加上前綴      

prefixoverride:去掉第一個and或者是or

②、用 trim 改寫上面第三點的 if+set 語句

if+set的方法:

    <update id="updateStudent" parameterType="student">
        update student
        <!--自動添加set-->
        <set>
            <!--智能處理逗號問題-->
            <if test="name!=null and name.length()>0">
                name=#{name}
            </if>

            <if test="sex!=null and sex.length()>0">
                sex=#{sex}
            </if>
        </set>
        where id=#{id}
    </update>

trim的方法:

    <update id="updateStudent" parameterType="student">
        update student

           <trim prefix="set" suffixOverrides=",">
               <if test="name!=null and name.length()>0">
                   name=#{name},
               </if>

               <if test="sex!=null and sex.length()>0">
                   sex=#{sex},
               </if>
           </trim>

        where id=#{id}
    </update>

結果:

suffix:后綴  

suffixoverride:去掉最后一個逗號(也可以是其他的標記,就像是上面前綴中的and一樣) 

可以自定義添加前后綴,與之對應的屬性是prefix和suffix。同時通過prefixOverrides和suffixOverrides分別來覆蓋首尾部的內容,即忽略不必要的前后綴。就是說它可以充當where標簽,也可以充當set標簽啦~
充當where標簽:

<trim prefix = "where" prefixOverrides="and|or" >
...
</trim>

充當set標簽:

<trim prefix="set" suffixOverrides=",">
...
</trim>

例子:動態添加用戶屬性

<insert id="find" resultType="Admin">
insert into admin 
<trim prefix="(" suffix=")" suffixOverrides=",">
<if test = "aname != null and aname !='' ">
aname,
</if>
<if test = "city != null and city !='' ">
city,
</if>
<if test = "age != null and age !='' ">
age,
</if>
</trim>
<trim prefix="values(" suffix=")" suffixOverrides=",">
<if test = "aname != null and aname !='' ">
#{aname},
</if>
<if test = "city != null and city !='' ">
#{city},
</if>
<if test = "age != null and age !='' ">
#{age},
</if>
</trim>
</insert>

上面相應的語句為:insert into admin (…) values(…);。通過trim標簽用()包裹,以及自動忽略尾部的逗號。

2.7、SQL 片段

有時候可能某個 sql 語句我們用的特別多,為了增加代碼的重用性,簡化代碼,我們需要將這些代碼抽取出來,然后使用時直接調用。

比如:下面的映射文件中對於id,name,sex出現多次:

    <select id="selectStudentsByNameOrSex" resultType="student">
        SELECT id,name,sex from student where name like '%${realname}%' or sex=#{sex};
    </select>

    <select id="selectStudentsByIdOrSex" resultType="student">
        SELECT id,name,sex from student where id=#{no} or sex=#{sex};
    </select>

    <select id="selectStudentsByNameAndSex" resultType="student">

        SELECT id,name,sex from student

        <!--1、prefix表示將前置where,prefixOverrides將刪除打頭內容-->
        <trim prefix="where" prefixOverrides="and | or">
            <if test="name!=null and name!=''">
                and name like concat(concat('%',#{name}),'%')
            </if>

            <if test="sex!=null and sex!=''">
                and sex=#{sex}
            </if>
        </trim>

    </select>

 

 

加粗的內容是重復的,通過sql片段復用。

定義sql片段:

    <!--定義sql片段-->
    <sql id="col_student">
        id,name,sex
    </sql>
    

引用 sql 片段

    <select id="selectStudentsByNameOrSex" resultType="student">
        <!--引用sql片段-->
        SELECT <include refid="col_student"></include> from student where name like '%${realname}%' or sex=#{sex};
    </select>

    <select id="selectStudentsByIdOrSex" resultType="student">
        SELECT <include refid="col_student"></include> from student where id=#{no} or sex=#{sex};
    </select>

    <select id="selectStudentsByNameAndSex" resultType="student">

        SELECT <include refid="col_student"></include> from student

        <!--1、prefix表示將前置where,prefixOverrides將刪除打頭內容-->
        <trim prefix="where" prefixOverrides="and | or">
            <if test="name!=null and name!=''">
                and name like concat(concat('%',#{name}),'%')
            </if>

            <if test="sex!=null and sex!=''">
                and sex=#{sex}
            </if>
        </trim>

    </select> 

結果:

注意:①、最好基於 單表來定義 sql 片段,提高片段的可重用性

   ②、在 sql 片段中不要包括 where 

sql片段帶參數:

定義時使用參數:

    <!--定義sql片段-->
    <sql id="col_student">
        ${alias}.id,${alias}.name,${alias}.sex
    </sql>

引用時指定參數:

    <select id="selectStudentsByNameAndSex" resultType="student">

        SELECT

        <include refid="col_student">
            <property name="alias" value="s"></property>
        </include>

        from student s

        <!--1、prefix表示將前置where,prefixOverrides將刪除打頭內容-->
        <trim prefix="where" prefixOverrides="and | or">
            <if test="name!=null and name!=''">
                and name like concat(concat('%',#{name}),'%')
            </if>

            <if test="sex!=null and sex!=''">
                and sex=#{sex}
            </if>
        </trim>

    </select>

結果:

2.8、foreach循環

foreach元素的功能非常強大,它允許你指定一個集合,聲明可以在元素體內使用的集合項(item)和索引(index)變量。它也允許你指定開頭與結尾的字符串以及在迭代結果之間放置分隔符。這個元素是很智能的,因此它不會偶然地附加多余的分隔符。

注意 你可以將任何可迭代對象(如 List、Set 等)、Map 對象或者數組對象傳遞給 foreach 作為集合參數。當使用可迭代對象或者數組時,index 是當前迭代的次數,item 的值是本次迭代獲取的元素。當使用 Map 對象(或者 Map.Entry 對象的集合)時,index 是鍵,item 是值。

到此我們已經完成了涉及 XML 配置文件和 XML 映射文件的討論。下一章將詳細探討 Java API,這樣就能提高已創建的映射文件的利用效率。

foreach的主要用在構建in條件中,他可以迭代一個集合。foreach元素的屬性主要有:item,index,collection,open,separator,close。

下面對屬性進行簡單的介紹:

item:表示集合中每一個元素進行迭代時的別名。

index:指定一個名字,用於表示在迭代過程中每次迭代的位置。

open:表示以什么開始。

separator:每次迭代以什么分割。

close:以什么關閉。

collection:最重要且必須指定的有三種情況:

1.如果傳入的是單獨參數的List類型時,collection的屬性值為list。

2.如果傳入的是單獨參數的數組時,collection的屬性值為array。

3.如果傳入多個參數時,我們把多個參數放入map中,單參數也可以放入map中。map中的key就是參數名,所以collection屬性值就是傳入的List或者array對象在Map里的key。 

一、用 foreach 來改寫 select * from user where id=1 or id=2 or id=3

<select id="selectUserByListId" parameterType="com.ys.vo.UserVo" resultType="com.zhangguo.User">
 select * from user
 <where>
 <!--
 collection:指定輸入對象中的集合屬性
 item:每次遍歷生成的對象
 open:開始遍歷時的拼接字符串
 close:結束時拼接的字符串
 separator:遍歷對象之間需要拼接的字符串
 select * from user where 1=1 and (id=1 or id=2 or id=3)
 -->
 <foreach collection="ids" item="id" open="and (" close=")" separator="or">
 id=#{id}
 </foreach>
 </where>
</select>

二、我們用 foreach 來改寫 select * from user where id in (1,2,3)

<select id="selectUserByListId" parameterType="com.ys.vo.UserVo" resultType="com.zhangguo.User">
 select * from user
 <where>
 <!--
 collection:指定輸入對象中的集合屬性
 item:每次遍歷生成的對象
 open:開始遍歷時的拼接字符串
 close:結束時拼接的字符串
 separator:遍歷對象之間需要拼接的字符串
 select * from user where 1=1 and id in (1,2,3)
 -->
 <foreach collection="ids" item="id" open="and id in (" close=") " separator=",">
 #{id}
 </foreach>
 </where>
 </select> 

其實動態 sql 語句的編寫往往就是一個拼接的問題,為了保證拼接准確,我們最好首先要寫原生的 sql 語句出來,然后在通過 mybatis 動態sql 對照着改。

參考:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- namespace的名字需要跟接口的類名一致 -->
<mapper namespace="cn.bdqn.dao.UserMapper">
 <!-- 
 1、resultMap屬性:type為java實體類;id為此resultMap的標識
 2、resultMap的子元素:
 id – 一般對應到數據庫中該行的ID,設置此項可以提高Mybatis性能.
 result – 映射到JavaBean 的某個“簡單類型”屬性,String,int等.
 association – 映射到JavaBean 的某個“復雜類型”屬性,其他JavaBean類.
 collection –復雜類型集合 
 -->
 
 <!--根據roleId獲取用戶列表: 當數據庫中的字段信息與對象的屬性不一致時需要通過resultMap來映射 -->
 <!-- <resultMap type="User" id="seachUserResult">
 <result property="id" column="id"/>
 <result property="userCode" column="userCode"/>
 <result property="userName" column="userName"/>
 <result property="roleId" column="roleId"/>
 <result property="roleName" column="roleName"/>
 </resultMap>
 
 <select id="getUserListByRoleId" parameterType="Role" resultMap="seachUserResult">
 select u.*,r.roleName as roleName from user u,role r where u.roleId = r.id and u.roleId = #{id}
 </select> -->
 
 <!-- 根據roleId獲取用戶列表 association start-->
 <resultMap type="User" id="seachUserResult">
 <result property="id" column="id"/>
 <result property="userCode" column="userCode" />
 <result property="userName" column="userName" />
 <result property="roleId" column="roleId" />
 <!-- <association property="role" javaType="Role" >
 <result property="id" column="id"/>
 <result property="roleCode" column="roleCode"/>
 <result property="roleName" column="roleName"/>
 </association> -->
 <association property="role" javaType="Role" resultMap="roleMap"/>
 </resultMap>
 
 <resultMap type="Role" id="roleMap">
 <result property="id" column="id"/> 
 <result property="roleCode" column="roleCode"/> 
 <result property="roleName" column="roleName"/> 
 </resultMap>
 
 <select id="getUserListByRoleId" parameterType="Role" resultMap="seachUserResult">
 select u.*,r.roleCode as roleCode,r.roleName as roleName from user u,role r where u.roleId = r.id and u.roleId = #{id}
 </select>
 
 <!-- association end-->
 
 <!-- 獲取指定用戶的地址列表(user表-address表:1對多關系) collection start-->
 <resultMap type="User" id="userMap">
 <id property="id" column="userId"/>
 <collection property="addressList" ofType="Address">
 <id property="id" column="a_id"/>
 <result property="postCode" column="postCode"/>
 <result property="addressContent" column="addressContent"/>
 </collection>
 </resultMap>
 
 <select id="getAddressListByUserId" parameterType="User" resultMap="userMap">
 select *,a.id as a_id from user u,address a where u.id=a.userId and u.id=#{id}
 </select>
 <!-- collection end -->
 
 <resultMap type="User" id="seachUser">
 <result property="id" column="id"/>
 <result property="userCode" column="userCode"/>
 <result property="userName" column="userName"/>
 <result property="roleId" column="roleId"/>
 <result property="roleName" column="roleName"/>
 </resultMap>
 
 <!-- <select id="searchUserList" parameterType="User" resultMap="seachUser">
 select u.*,r.roleName as roleName from user u,role r where u.roleId = r.id
 and u.roleId = #{roleId}
 and u.userCode like CONCAT ('%',#{userCode},'%') //防止sql注入
 and u.userName like CONCAT ('%',#{userName},'%') 
 </select> -->
 
 <!-- 
 1、有些時候,sql語句where條件中,需要一些安全判斷,例如按性別檢索,如果傳入的參數是空的,此時查詢出的結果很可能是空的,也許我們需要參數為空時,是查出全部的信息。這是我們可以使用動態sql,增加一個判斷,當參數不符合要求的時候,我們可以不去判斷此查詢條件。
 2、mybatis 的動態sql語句是基於OGNL表達式的。可以方便的在 sql 語句中實現某些邏輯. 總體說來mybatis 動態SQL 語句主要有以下幾類: 
 if 語句 (簡單的條件判斷) 
 choose (when,otherwize) ,相當於java 語言中的 switch ,與 jstl 中的choose 很類似.
 trim (對包含的內容加上 prefix,或者 suffix 等,前綴,后綴) 
 where (主要是用來簡化sql語句中where條件判斷的,能智能的處理 and or ,不必擔心多余導致語法錯誤) 
 set (主要用於更新時) 
 foreach (在實現 mybatis in 語句查詢時特別有用) 
 -->
 
 <!-- if(判斷參數) - 將實體類不為空的屬性作為where條件 -->
 <select id="searchUserList" parameterType="User" resultMap="seachUser">
 select u.*,r.roleName as roleName from user u,role r where u.roleId = r.id
 <if test="roleId!=null">
 and u.roleId = #{roleId}
 </if>
 <if test="userCode != null">
 and u.userCode like CONCAT ('%',#{userCode},'%') 
 </if>
 <if test="userName != null">
 and u.userName like CONCAT ('%',#{userName},'%') 
 </if>
 </select>
 
 
 
 
 
 <select id="count" resultType="int">
 select count(1) from user
 </select>
 
 <insert id="add" parameterType="User">
 insert into user (userCode,userName,userPassword) 
 values (#{userCode},#{userName},#{userPassword})
 </insert>
 
 <!-- if/set(判斷參數) - 將實體類不為空的屬性更新 --> 
 <!-- <update id="update" parameterType="User">
 update user 
 <set>
 <if test="userCode != null and userCode != ''">userCode=#{userCode},</if>
 <if test="userName != null">userName=#{userName},</if>
 <if test="userPassword != null">userPassword=#{userPassword},</if>
 <if test="roleId != null">roleId=#{roleId}</if>
 </set>
 where id=#{id}
 </update> -->
 
 <!-- if/trim代替set(判斷參數) - 將實體類不為空的屬性更新 --> 
 <update id="update" parameterType="User">
 update user 
 <trim prefix="set" suffixOverrides=",">
 <if test="userCode != null and userCode != ''">userCode=#{userCode},</if>
 <if test="userName != null">userName=#{userName},</if>
 <if test="userPassword != null">userPassword=#{userPassword},</if>
 <if test="roleId != null">roleId=#{roleId}</if>
 </trim>
 where id=#{id}
 </update>
 
 <!--注意: 你可以傳遞一個List實例或者數組作為參數對象傳給MyBatis。
 當你這么做的時候,MyBatis會自動將它包裝在一個Map中,用名稱在作為鍵。
 List實例將會以“list”作為鍵,而數組實例將會以“array”作為鍵。
 配置文件中的parameterType是可以不配置的-->
 <resultMap type="User" id="userMapByDep">
 <result property="id" column="id"/>
 <result property="userCode" column="userCode"/>
 <result property="userName" column="userName"/>
 </resultMap>
 <!-- foreach(循環array參數) - 作為where中in的條件 -->
 <select id="getUserByDepId_foreach_array" resultMap="userMapByDep">
 select * from user where depId in 
 <foreach collection="array" item="depIds" open="(" separator="," close=")">
 #{depIds}
 </foreach>
 </select>
 
 <!-- foreach(循環List<String>參數) - 作為where中in的條件 -->
 <select id="getUserByDepId_foreach_list" resultMap="userMapByDep">
 select * from user where depId in 
 <foreach collection="list" item="depIdList" open="(" separator="," close=")">
 #{depIdList}
 </foreach>
 </select>
 
 
 <delete id="delete" parameterType="User">
 delete from user where id=#{id}
 </delete>
 
 <select id="getUserList" resultType="User">
 select * from user
 </select>
</mapper>
View Code

假定我們要多刪除:

接口:

    /**
     * 刪除多個學生通過編號
     */
    int deleteStudents(List<Integer> ids);

 

映射:

    <delete id="deleteStudents">
        delete from student where id in
        <foreach collection="list" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
    </delete>

 

注意collection這里只能是list,不能是ids,因為反射時獲取不到參數名稱。

測試:

 

    /**
     * Method: deleteStudents
     */
    @Test
    public void testDeleteStudents() throws Exception {
        List<Integer> ids=new ArrayList<Integer>();
        ids.add(8);
        ids.add(9);
        Assert.assertEquals(2,dao.deleteStudents(ids));
    }

 

結果:

 

2.9、bind 綁定變量

bind標簽可以使用OGNL表達式創建一個變量並將其綁定到上下文中。

bind標簽的兩個屬性都是不選項,name為綁定到上下文的變量名,value為OGNL表達式,創建一個bind標簽后,就可以在下面直接使用了。 使用bind拼接字符串不僅可以避免因更換數據庫而修改SQL,也能預防SQL注入。

      <!-- List<Employee> getEmpsTestInnerParameter(Employee employee); -->
      <select id="getEmpsTestInnerParameter" resultType="com.hand.mybatis.bean.Employee">
          <!-- bind:可以將OGNL表達式的值綁定到一個變量中,方便后來引用這個變量的值 -->
          <bind name="bindeName" value="'%'+eName+'%'"/> eName是employee中一個屬性值
          SELECT * FROM emp 
          <if test="_parameter!=null">
            where ename like #{bindeName}
          </if>
      </select>

 

三、視頻

 https://www.bilibili.com/video/av32447485/

四、示例

https://git.coding.net/zhangguo5/MyBatis03.git

五、作業

1、個人項目的數據庫設計,個人項目的靜態頁面設計(2個,一個必須是首頁,PC端)

2、重現本章示例

3、任務指導手冊所有mybatis理論題

4、根據如下ER圖創建4個表,完成1-1,1-N,M-N關系的查詢,無需界面,測試通過即可

5、完成圖書管理系統中二表關聯,顯示圖書類型

請實現一個簡易圖書管理系統(LibSystem),實現圖書管理功能,要求如下:
1、管理數據庫中所有圖書(Books),包含圖書編號(isbn)、書名(title)、作者(author)、價格(price)、出版日期(publishDate)
2、Maven多模塊+MySQL+Git+MyBatis+JUnit單元測試
3、表示層可以是AJAX或JSTL

請實現一個迷你圖書管理系統(LibSystem),實現圖書管理功能,要求如下:
1、管理數據庫中所有圖書分類(Categories),包含圖書編號(id),名稱(name)
2、管理數據庫中所有圖書(Books),包含圖書編號(isbn)、類別(categoryId,外鍵)書名(title)、作者(author)、價格(price)、出版日期(publishDate)、封面(cover)、詳細介紹(details)
3、分頁
4、多條件組件查詢(3個以上的條件)
5、上傳封面
6、富文本編輯器
View Code

 

6、使用任意的原型開發工具設計出個人項目的1-2個界面,工具:Balsamiq Mockups,Axure RP Pro 7.0。

7、將動態sql中的所有知識點全部應用於作業4

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM