MyBatis 學(xué)習(xí)筆記(二)MyBatis常用特性運(yùn)用

概要

接上一篇MyBatis 學(xué)習(xí)筆記(一)MyBatis的簡介與使用以及與其他ORM框架的比較,今天我們接著來學(xué)習(xí)MyBatis的一些常用特性,包括別名,類型處理器,動(dòng)態(tài)SQL

如何使用MyBatis

在本小節(jié),我將通過一個(gè)例子介紹MyBatis 中一些常用特性的運(yùn)用,包括類型處理器,動(dòng)態(tài)SQL等等。

別名

MyBatis 中有個(gè)比較好用的特性就是別名,這是為了減少在配置文件中配置實(shí)體類的全限定名的冗余。運(yùn)用如下:
首先在MyBatis的配置文件中配置別名:

    <!--別名處理-->
    <typeAliases>
        <typeAlias type="com.jay.chapter2.entity.Student" alias="Student"/>
    </typeAliases> 

然后,在需要使用該實(shí)體類的映射文件中進(jìn)行添加即可。

    <resultMap id="studentResult" type="Student">
    //省略其他無關(guān)配置
    </resultMap>

類型處理器的運(yùn)用

在實(shí)際開發(fā)中,我們經(jīng)常要對枚舉類進(jìn)行處理,例如,人的性別分為男,女,我們數(shù)據(jù)庫中可能存的是0,1; 但是頁面顯示的話需要顯示男,女,所以,我們在使用MyBatis時(shí)查詢結(jié)果時(shí)就要通過轉(zhuǎn)換器進(jìn)行轉(zhuǎn)換。
MyBatis 內(nèi)置了很多類型處理器(typeHandlers),詳細(xì)可以參考MyBatis官方文檔,對枚舉類的處理的是通過EnumTypeHandler和EnumOrdinalTypeHandler兩個(gè)處理器來處理了,
在這里插入圖片描述
但是其只能處理簡單的枚舉,例如:

public enum SexEnum {
    MAN,
    FEMALE,
    UNKNOWN;
} 

對于復(fù)雜的枚舉類型,則不能處理。例如:

 MAN("0", "男")

我們來查看源碼分析下原因,我們以EnumTypeHandler為例來說明下。

public class EnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {

  private final Class<E> type;

  public EnumTypeHandler(Class<E> type) {
    if (type == null) {
      throw new IllegalArgumentException("Type argument cannot be null");
    }
    this.type = type;
  }

  @Override
  public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
    if (jdbcType == null) {
      ps.setString(i, parameter.name());
    } else {
      ps.setObject(i, parameter.name(), jdbcType.TYPE_CODE); 
    }
  }

  @Override
  public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
    String s = rs.getString(columnName);
    return s == null ? null : Enum.valueOf(type, s);
  }

  @Override
  public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
    String s = rs.getString(columnIndex);
    return s == null ? null : Enum.valueOf(type, s);
  }

  @Override
  public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
    String s = cs.getString(columnIndex);
    return s == null ? null : Enum.valueOf(type, s);
  }
}

分析上述源碼,setNonNullParameter方法包裝PreparedStatement進(jìn)行SQL插值操作,設(shè)置的值是enum.name() ,即enum的toString() ,存儲(chǔ)的枚舉值的名稱,而getNullableResult 方法返回的是Enum.valueOf(type, s)。而EnumOrdinalTypeHandler轉(zhuǎn)換器也只能處理Int,String 類型。故我們需要自定義轉(zhuǎn)換器來處理。分析MyBatis 源碼我們可以得知,各個(gè)轉(zhuǎn)換器都是繼承BaseTypeHandler 基類的。為了實(shí)現(xiàn)代碼的通用性,首先我們實(shí)現(xiàn)了一個(gè)枚舉基類,然后定義一個(gè)通用的轉(zhuǎn)換器。
枚舉基類:

public interface BaseEnum<E extends Enum<?>, T> {
    /**
     * 真正與數(shù)據(jù)庫進(jìn)行映射的值
     * @return
     */
    T getKey();

    /**
     * 顯示的信息
     * @return
     */
    String getValue();
}

在枚舉記錄中我們定義了兩個(gè)通用的獲取key和value的方法,接著我們定義 一個(gè)枚舉類SexEnum來實(shí)現(xiàn)枚舉基類

public enum SexEnum implements BaseEnum<SexEnum, String> {
    MAN("0", "男"),
    WEMAN("1", "女"),;
    private String key;
    private String value;

    final static Map<String, SexEnum> SEX_MAP = new HashMap<>();

    static {
        for (SexEnum sexEnums : SexEnum.values()) {
            SEX_MAP.put(sexEnums.key, sexEnums);
        }
    }

    SexEnum(String key, String value) {
        this.key = key;
        this.value = value;
    }
    @Override
    public String getKey() {
        return key;
    }

    @Override
    public String getValue() {
        return value;
    }

    public static SexEnum getEnums(String key) {
        return SEX_MAP.get(key);
    }
}

接下來我們再來看看通用的轉(zhuǎn)換器類。

public class GeneralEnumHandler<E extends BaseEnum> extends BaseTypeHandler<E> {
    private Class<E> type;
    private E[] enums;
    
    public GeneralEnumHandler(Class<E> type) {
        if (type == null) {
            throw new IllegalArgumentException("Type argument cannot be null");
        }
        this.type = type;
        //獲取實(shí)現(xiàn)枚舉基類所有的枚舉類
        this.enums = type.getEnumConstants();
        if (enums == null) {
            throw new IllegalArgumentException(type.getSimpleName()
                    + "does not represent an enum type.");
        }
    }

    @Override
    public void setNonNullParameter(PreparedStatement preparedStatement, int i, E e, JdbcType jdbcType) throws SQLException {
        if (jdbcType == null) {
            preparedStatement.setObject(i, e.getKey());
        } else {
        	//將枚舉類的key,存入數(shù)據(jù)庫中
            preparedStatement.setObject(i, e.getKey(), jdbcType.TYPE_CODE);
        }
    }

    @Override
    public E getNullableResult(ResultSet resultSet, String s) throws SQLException {
        if (resultSet.wasNull()) {
            return null;
        }
        Object key = resultSet.getObject(s);
        return locateEnumsStatus(key);
    }

    @Override
    public E getNullableResult(ResultSet resultSet, int i) throws SQLException {
        if (resultSet.wasNull()) {
            return null;
        }
        Object key = resultSet.getObject(i);
        return locateEnumsStatus(key);
    }

    @Override
    public E getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
        if (callableStatement.wasNull()) {
            return null;
        }
        Object key = callableStatement.getObject(i);
        return locateEnumsStatus(key);
    }

    /*
     * 根據(jù)枚舉類的key,獲取其對應(yīng)的枚舉
     * @param key
     * @return
     */
    private E locateEnumsStatus(Object key) {
        if (key instanceof Integer) {
            for (E anEnum : enums) {
                if (anEnum.getKey() == key) {
                    return anEnum;
                }
            }
            throw new IllegalArgumentException("未知的枚舉類型:" + key + ",請核對" + type.getSimpleName());
        }
        if (key instanceof String) {
            for (E anEnum : enums) {
                if (anEnum.getKey().equals(key)) {
                    return anEnum;
                }
            }
            throw new IllegalArgumentException("未知的枚舉類型:" + key + ",請核對" + type.getSimpleName());
        }
        throw new IllegalArgumentException("未知的枚舉類型:" + key + ",請核對" + type.getSimpleName());
    }

}

代碼編寫好之后,我們需要在所使用的Mapper映射文件中進(jìn)行必要的配置。

 <result property="sexEnum" column="sex"
                typeHandler="com.jay.chapter2.Handler.GeneralEnumHandler"/>

最后我們來編寫一個(gè)測試類來處理一下

public class SimpleMyBatis3Test {

    private SqlSessionFactory sqlSessionFactory;
    /**
     *
     */
    @Before
    public void setUp() {
        String config = "chapter2/mybatis-cfg.xml";
        try {
            InputStream inputStream = Resources.getResourceAsStream(config);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
            inputStream.close();
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    @Test
    public void testSelectStudent() {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        Student2Mapper mapper = sqlSession.getMapper(Student2Mapper.class);
        Student student = mapper.selectStudentById(1);
        System.out.println("------>返回結(jié)果"+student.toString());
    }
}

測試結(jié)果如下:
在這里插入圖片描述

動(dòng)態(tài)SQL的使用

MyBatis的強(qiáng)大特性之一便是它的動(dòng)態(tài)SQL,主要是處理 根據(jù)不同條件拼接SQL語句,例如拼接時(shí)添加必要的空格,去掉列表中的最后一列的逗號(hào),MyBatis的動(dòng)態(tài)SQL 元素是基于OGNL的表達(dá)式。
在這里插入圖片描述詳細(xì)的運(yùn)用可以參考MyBatis官方文檔
下面以一個(gè)例子作為一個(gè)示范。自此處我們有一張Student表,測試數(shù)據(jù)如下:
在這里插入圖片描述
首先,我們在mybatis-cfg.xml配置好映射文件

    <!-- 加載映射文件-->
    <mappers>
        <mapper resource="chapter2/xml/Student3Mapper.xml"/>
    </mappers>

然后,我們編寫映射文件,將運(yùn)用相關(guān)的動(dòng)態(tài)SQL表達(dá)式。

mapper namespace="com.jay.chapter2.mapper.Student3Mapper">
    <resultMap id="studentResult" type="com.jay.chapter2.entity.Student">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="age" column="age"/>
        <result property="sexEnum" column="sex"
                typeHandler="com.jay.chapter2.Handler.GeneralEnumHandler"/>
    </resultMap>

    <!--運(yùn)用where,if元素進(jìn)行條件拼接-->
    <select id="selectStudent" resultMap="studentResult">
        SELECT * FROM student
        <where>
            <if test="id!=null">
                id=#{id}
            </if>
            <if test="name!=null">
                AND name like CONCAT('%',#{name},'%')
            </if>
        </where>
    </select>

    <!--運(yùn)用foreach元素進(jìn)行實(shí)現(xiàn)in查詢-->
    <select id="selectByIds" resultMap="studentResult">
        SELECT * FROM student
        <where>
            <if test="ids!=null">
                id IN
                <foreach collection="ids" open="(" close=")" separator="," item="id">
                    #{id}
                </foreach>
            </if>
        </where>
    </select>

    <!--運(yùn)用choose,when,otherwise元素實(shí)現(xiàn)多條件分支-->
    <select id="selectByNameOrAge" resultMap="studentResult">
        SELECT * FROM student
        <where>
            <choose>
                <when test="name!=null">
                    AND name like CONCAT('%',#{name},'%')
                </when>
                <when test="age!=null">
                    AND age=#{age}
                </when>
                <otherwise>
                    1=1
                </otherwise>
            </choose>
        </where>
    </select>

    <!--運(yùn)用set元素sql拼接問題-->
    <update id="updateStudent" parameterType="Student">
        UPDATE student
        <set>
            <if test="name!=null">
                name=#{name},
            </if>
            <if test="age!=null">
                age=#{age},
            </if>
        </set>
        <where>
            id=#{id}
        </where>
    </update>
</mapper>

該動(dòng)態(tài)SQL對應(yīng)的DAO 接口如下:

public interface Student3Mapper {

    /**
     * 查詢學(xué)生
     * @param id
     * @param name
     * @return
     */
    List<Student> selectStudent(@Param("id") Integer id,
                                @Param("name") String name);

    /**
     * 批量查詢學(xué)生
     * @param ids
     * @return
     */
    List<Student> selectByIds(@Param("ids") List<Integer> ids);

    /**
     * @param name
     * @param age
     * @return
     */
    List<Student> selectByNameOrAge(@Param("name") String name,
                                    @Param("age") Integer age);

    /**
     * 更新學(xué)生記錄
     * @param student
     * @return
     */
    boolean updateStudent(Student student);
}

接口類和其對應(yīng)的映射文件編寫完成之后,接著我們來編寫測試類進(jìn)行測試下。

public class SimpleMyBatis3Test extends BaseMyBatisTest {

    @Test
    public void testSelectStudent() {
        SqlSession session = sqlSessionFactory.openSession();
        Student3Mapper mapper = session.getMapper(Student3Mapper.class);
        List<Student> students = mapper.selectStudent(1, null);
        System.out.println("----》只傳id返回結(jié)果"+students.toString());
        List<Student> students1 = mapper.selectStudent(null, "平平");
        System.out.println("----》只傳name返回結(jié)果"+students1.toString());
    }

    @Test
    public void testSelectByIds() {
        SqlSession session = sqlSessionFactory.openSession();
        Student3Mapper mapper = session.getMapper(Student3Mapper.class);
        List<Integer> ids = new ArrayList<>();
        ids.add(1);
        ids.add(2);
        List<Student> students = mapper.selectByIds(ids);
        System.out.println("---->通過ids查詢返回結(jié)果" + students.toString());
    }
    @Test
    public void  selectByNameOrAge() {
        SqlSession session = sqlSessionFactory.openSession();
        Student3Mapper mapper = session.getMapper(Student3Mapper.class);
        List<Student> students = mapper.selectByNameOrAge("美美", null);
        System.out.println("---->selectByNameOrAge通過name查詢返回結(jié)果" + students.toString());
        List<Student> students1 = mapper.selectByNameOrAge(null, 1);
        System.out.println("----->selectByNameOrAge通過age查詢返回的結(jié)果" + students1.toString());

    }
    @Test
    public void testUpdateStudent() {
        SqlSession session = sqlSessionFactory.openSession();
        Student3Mapper mapper = session.getMapper(Student3Mapper.class);
        Student student = new Student();
        student.setId(1);
        student.setName("小偉");
        student.setAge(29);
        boolean result = mapper.updateStudent(student);
        System.out.println("--->updateStudent更新結(jié)果" + result);
    }
}

測試結(jié)果如下:
在這里插入圖片描述

參考文獻(xiàn)

MyBatis 3官方文檔
mybatis枚舉自動(dòng)轉(zhuǎn)換(通用轉(zhuǎn)換處理器實(shí)現(xiàn))

源代碼

https://github.com/XWxiaowei/MyBatisLearn/tree/master/mybatisDemo




作者:碼農(nóng)飛哥
微信公眾號(hào):碼農(nóng)飛哥