MyBatis 學(xué)習(xí)筆記(二)MyBatis常用特性運(yùn)用
概要
接上一篇MyBatis 學(xué)習(xí)筆記(一)MyBatis的簡(jiǎn)介與使用以及與其他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)常要對(duì)枚舉類進(jìn)行處理,例如,人的性別分為男,女,我們數(shù)據(jù)庫中可能存的是0,1; 但是頁面顯示的話需要顯示男,女,所以,我們?cè)谑褂肕yBatis時(shí)查詢結(jié)果時(shí)就要通過轉(zhuǎn)換器進(jìn)行轉(zhuǎn)換。
MyBatis 內(nèi)置了很多類型處理器(typeHandlers),詳細(xì)可以參考MyBatis官方文檔,對(duì)枚舉類的處理的是通過EnumTypeHandler和EnumOrdinalTypeHandler兩個(gè)處理器來處理了,
但是其只能處理簡(jiǎn)單的枚舉,例如:
public enum SexEnum {
MAN,
FEMALE,
UNKNOWN;
}
對(duì)于復(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);
}
}
接下來我們?cè)賮砜纯赐ㄓ玫霓D(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,獲取其對(duì)應(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 + ",請(qǐng)核對(duì)" + type.getSimpleName());
}
if (key instanceof String) {
for (E anEnum : enums) {
if (anEnum.getKey().equals(key)) {
return anEnum;
}
}
throw new IllegalArgumentException("未知的枚舉類型:" + key + ",請(qǐng)核對(duì)" + type.getSimpleName());
}
throw new IllegalArgumentException("未知的枚舉類型:" + key + ",請(qǐng)核對(duì)" + type.getSimpleName());
}
}
代碼編寫好之后,我們需要在所使用的Mapper映射文件中進(jìn)行必要的配置。
<result property="sexEnum" column="sex"
typeHandler="com.jay.chapter2.Handler.GeneralEnumHandler"/>
最后我們來編寫一個(gè)測(cè)試類來處理一下
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());
}
}
測(cè)試結(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表,測(cè)試數(shù)據(jù)如下:
首先,我們?cè)趍ybatis-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對(duì)應(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);
}
接口類和其對(duì)應(yīng)的映射文件編寫完成之后,接著我們來編寫測(cè)試類進(jìn)行測(cè)試下。
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);
}
}
測(cè)試結(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)飛哥