概要

今天我们接着来学习MyBatis的一些常用特性,包括别名,类型处理器,动态SQL

如何使用MyBatis

在本小节,我将通过一个例子介绍MyBatis 中一些常用特性的运用,包括类型处理器,动态SQL等等。

别名

MyBatis 中有个比较好用的特性就是别名,这是为了减少在配置文件中配置实体类的全限定名的冗余。运用如下:

首先在MyBatis的配置文件中配置别名:

1    <!--别名处理-->
2 <typeAliases>
3 <typeAlias type="com.jay.chapter2.entity.Student" alias="Student"/>
4 </typeAliases>

然后,在需要使用该实体类的映射文件中进行添加即可。

1    <resultMap id="studentResult" type="Student">
2 //省略其他无关配置
3 </resultMap>

类型处理器的运用

在实际开发中,我们经常要对枚举类进行处理,例如,人的性别分为男,女,我们数据库中可能存的是0,1;但是页面显示的话需要显示男,女,所以,我们在使用MyBatis时查询结果时就要通过转换器进行转换。

MyBatis 内置了很多类型处理器(typeHandlers),详细可以参考MyBatis官方文档,对枚举类的处理的是通过EnumTypeHandler和EnumOrdinalTypeHandler两个处理器来处理了,

MyBatis常用特性运用_ide在这里插入图片描述

但是其只能处理简单的枚举,例如:

1public enum SexEnum {
2 MAN,
3 FEMALE,
4 UNKNOWN;
5}

对于复杂的枚举类型,则不能处理。例如:

1 MAN("0", "男")

我们来查看源码分析下原因,我们以EnumTypeHandler为例来说明下。

 1public class EnumTypeHandler<E extends Enum<E>> extends BaseTypeHandler<E> {
2
3 private final Class<E> type;
4
5 public EnumTypeHandler(Class<E> type) {
6 if (type == null) {
7 throw new IllegalArgumentException("Type argument cannot be null");
8 }
9 this.type = type;
10 }
11
12 @Override
13 public void setNonNullParameter(PreparedStatement ps, int i, E parameter, JdbcType jdbcType) throws SQLException {
14 if (jdbcType == null) {
15 ps.setString(i, parameter.name());
16 } else {
17 ps.setObject(i, parameter.name(), jdbcType.TYPE_CODE);
18 }
19 }
20
21 @Override
22 public E getNullableResult(ResultSet rs, String columnName) throws SQLException {
23 String s = rs.getString(columnName);
24 return s == null ? null : Enum.valueOf(type, s);
25 }
26
27 @Override
28 public E getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
29 String s = rs.getString(columnIndex);
30 return s == null ? null : Enum.valueOf(type, s);
31 }
32
33 @Override
34 public E getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
35 String s = cs.getString(columnIndex);
36 return s == null ? null : Enum.valueOf(type, s);
37 }
38}

分析上述源码,setNonNullParameter方法包装PreparedStatement进行SQL插值操作,设置的值是enum.name() ,即enum的toString() ,存储的枚举值的名称,而getNullableResult 方法返回的是Enum.valueOf(type, s)。而EnumOrdinalTypeHandler转换器也只能处理Int,String 类型。故我们需要自定义转换器来处理。分析MyBatis 源码我们可以得知,各个转换器都是继承BaseTypeHandler 基类的。为了实现代码的通用性,首先我们实现了一个枚举基类,然后定义一个通用的转换器。

枚举基类:

 1public interface BaseEnum<E extends Enum<?>, T> {
2 /**
3 * 真正与数据库进行映射的值
4 * @return
5 */
6 T getKey();
7
8 /**
9 * 显示的信息
10 * @return
11 */
12 String getValue();
13}

在枚举记录中我们定义了两个通用的获取key和value的方法,接着我们定义 一个枚举类SexEnum来实现枚举基类

 1public enum SexEnum implements BaseEnum<SexEnum, String> {
2 MAN("0", "男"),
3 WEMAN("1", "女"),;
4 private String key;
5 private String value;
6
7 final static Map<String, SexEnum> SEX_MAP = new HashMap<>();
8
9 static {
10 for (SexEnum sexEnums : SexEnum.values()) {
11 SEX_MAP.put(sexEnums.key, sexEnums);
12 }
13 }
14
15 SexEnum(String key, String value) {
16 this.key = key;
17 this.value = value;
18 }
19 @Override
20 public String getKey() {
21 return key;
22 }
23
24 @Override
25 public String getValue() {
26 return value;
27 }
28
29 public static SexEnum getEnums(String key) {
30 return SEX_MAP.get(key);
31 }
32}

接下来我们再来看看通用的转换器类。

 1public class GeneralEnumHandler<E extends BaseEnum> extends BaseTypeHandler<E> {
2 private Class<E> type;
3 private E[] enums;
4
5 public GeneralEnumHandler(Class<E> type) {
6 if (type == null) {
7 throw new IllegalArgumentException("Type argument cannot be null");
8 }
9 this.type = type;
10 //获取实现枚举基类所有的枚举类
11 this.enums = type.getEnumConstants();
12 if (enums == null) {
13 throw new IllegalArgumentException(type.getSimpleName()
14 + "does not represent an enum type.");
15 }
16 }
17
18 @Override
19 public void setNonNullParameter(PreparedStatement preparedStatement, int i, E e, JdbcType jdbcType) throws SQLException {
20 if (jdbcType == null) {
21 preparedStatement.setObject(i, e.getKey());
22 } else {
23 //将枚举类的key,存入数据库中
24 preparedStatement.setObject(i, e.getKey(), jdbcType.TYPE_CODE);
25 }
26 }
27
28 @Override
29 public E getNullableResult(ResultSet resultSet, String s) throws SQLException {
30 if (resultSet.wasNull()) {
31 return null;
32 }
33 Object key = resultSet.getObject(s);
34 return locateEnumsStatus(key);
35 }
36
37 @Override
38 public E getNullableResult(ResultSet resultSet, int i) throws SQLException {
39 if (resultSet.wasNull()) {
40 return null;
41 }
42 Object key = resultSet.getObject(i);
43 return locateEnumsStatus(key);
44 }
45
46 @Override
47 public E getNullableResult(CallableStatement callableStatement, int i) throws SQLException {
48 if (callableStatement.wasNull()) {
49 return null;
50 }
51 Object key = callableStatement.getObject(i);
52 return locateEnumsStatus(key);
53 }
54
55 /*
56 * 根据枚举类的key,获取其对应的枚举
57 * @param key
58 * @return
59 */
60 private E locateEnumsStatus(Object key) {
61 if (key instanceof Integer) {
62 for (E anEnum : enums) {
63 if (anEnum.getKey() == key) {
64 return anEnum;
65 }
66 }
67 throw new IllegalArgumentException("未知的枚举类型:" + key + ",请核对" + type.getSimpleName());
68 }
69 if (key instanceof String) {
70 for (E anEnum : enums) {
71 if (anEnum.getKey().equals(key)) {
72 return anEnum;
73 }
74 }
75 throw new IllegalArgumentException("未知的枚举类型:" + key + ",请核对" + type.getSimpleName());
76 }
77 throw new IllegalArgumentException("未知的枚举类型:" + key + ",请核对" + type.getSimpleName());
78 }
79
80}

代码编写好之后,我们需要在所使用的Mapper映射文件中进行必要的配置。

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

最后我们来编写一个测试类来处理一下

 1public class SimpleMyBatis3Test {
2
3 private SqlSessionFactory sqlSessionFactory;
4 /**
5 *
6 */
7 @Before
8 public void setUp() {
9 String config = "chapter2/mybatis-cfg.xml";
10 try {
11 InputStream inputStream = Resources.getResourceAsStream(config);
12 sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
13 inputStream.close();
14 } catch (IOException e) {
15 e.printStackTrace();
16 }
17 }
18
19 @Test
20 public void testSelectStudent() {
21 SqlSession sqlSession = sqlSessionFactory.openSession();
22 Student2Mapper mapper = sqlSession.getMapper(Student2Mapper.class);
23 Student student = mapper.selectStudentById(1);
24 System.out.println("------>返回结果"+student.toString());
25 }
26}

测试结果如下:

MyBatis常用特性运用_ide_02在这里插入图片描述

动态SQL的使用

MyBatis的强大特性之一便是它的动态SQL,主要是处理 根据不同条件拼接SQL语句,例如拼接时添加必要的空格,去掉列表中的最后一列的逗号,MyBatis的动态SQL 元素是基于OGNL的表达式。

MyBatis常用特性运用_枚举类_03在这里插入图片描述详细的运用可以参考MyBatis官方文档

下面以一个例子作为一个示范。自此处我们有一张Student表,测试数据如下:

MyBatis常用特性运用_ide_04在这里插入图片描述

首先,我们在mybatis-cfg.xml配置好映射文件

1    <!-- 加载映射文件-->
2 <mappers>
3 <mapper resource="chapter2/xml/Student3Mapper.xml"/>
4 </mappers>

然后,我们编写映射文件,将运用相关的动态SQL表达式。

 1mapper namespace="com.jay.chapter2.mapper.Student3Mapper">
2 <resultMap id="studentResult" type="com.jay.chapter2.entity.Student">
3 <id property="id" column="id"/>
4 <result property="name" column="name"/>
5 <result property="age" column="age"/>
6 <result property="sexEnum" column="sex"
7 typeHandler="com.jay.chapter2.Handler.GeneralEnumHandler"/>
8 </resultMap>
9
10 <!--运用where,if元素进行条件拼接-->
11 <select id="selectStudent" resultMap="studentResult">
12 SELECT * FROM student
13 <where>
14 <if test="id!=null">
15 id=#{id}
16 </if>
17 <if test="name!=null">
18 AND name like CONCAT('%',#{name},'%')
19 </if>
20 </where>
21 </select>
22
23 <!--运用foreach元素进行实现in查询-->
24 <select id="selectByIds" resultMap="studentResult">
25 SELECT * FROM student
26 <where>
27 <if test="ids!=null">
28 id IN
29 <foreach collection="ids" open="(" close=")" separator="," item="id">
30 #{id}
31 </foreach>
32 </if>
33 </where>
34 </select>
35
36 <!--运用choose,when,otherwise元素实现多条件分支-->
37 <select id="selectByNameOrAge" resultMap="studentResult">
38 SELECT * FROM student
39 <where>
40 <choose>
41 <when test="name!=null">
42 AND name like CONCAT('%',#{name},'%')
43 </when>
44 <when test="age!=null">
45 AND age=#{age}
46 </when>
47 <otherwise>
48 1=1
49 </otherwise>
50 </choose>
51 </where>
52 </select>
53
54 <!--运用set元素sql拼接问题-->
55 <update id="updateStudent" parameterType="Student">
56 UPDATE student
57 <set>
58 <if test="name!=null">
59 name=#{name},
60 </if>
61 <if test="age!=null">
62 age=#{age},
63 </if>
64 </set>
65 <where>
66 id=#{id}
67 </where>
68 </update>
69</mapper>

该动态SQL对应的DAO 接口如下:

 1public interface Student3Mapper {
2
3 /**
4 * 查询学生
5 * @param id
6 * @param name
7 * @return
8 */
9 List<Student> selectStudent(@Param("id") Integer id,
10 @Param("name") String name);
11
12 /**
13 * 批量查询学生
14 * @param ids
15 * @return
16 */
17 List<Student> selectByIds(@Param("ids") List<Integer> ids);
18
19 /**
20 * @param name
21 * @param age
22 * @return
23 */
24 List<Student> selectByNameOrAge(@Param("name") String name,
25 @Param("age") Integer age);
26
27 /**
28 * 更新学生记录
29 * @param student
30 * @return
31 */
32 boolean updateStudent(Student student);
33}

接口类和其对应的映射文件编写完成之后,接着我们来编写测试类进行测试下。

 1public class SimpleMyBatis3Test extends BaseMyBatisTest {
2
3 @Test
4 public void testSelectStudent() {
5 SqlSession session = sqlSessionFactory.openSession();
6 Student3Mapper mapper = session.getMapper(Student3Mapper.class);
7 List<Student> students = mapper.selectStudent(1, null);
8 System.out.println("----》只传id返回结果"+students.toString());
9 List<Student> students1 = mapper.selectStudent(null, "平平");
10 System.out.println("----》只传name返回结果"+students1.toString());
11 }
12
13 @Test
14 public void testSelectByIds() {
15 SqlSession session = sqlSessionFactory.openSession();
16 Student3Mapper mapper = session.getMapper(Student3Mapper.class);
17 List<Integer> ids = new ArrayList<>();
18 ids.add(1);
19 ids.add(2);
20 List<Student> students = mapper.selectByIds(ids);
21 System.out.println("---->通过ids查询返回结果" + students.toString());
22 }
23 @Test
24 public void selectByNameOrAge() {
25 SqlSession session = sqlSessionFactory.openSession();
26 Student3Mapper mapper = session.getMapper(Student3Mapper.class);
27 List<Student> students = mapper.selectByNameOrAge("美美", null);
28 System.out.println("---->selectByNameOrAge通过name查询返回结果" + students.toString());
29 List<Student> students1 = mapper.selectByNameOrAge(null, 1);
30 System.out.println("----->selectByNameOrAge通过age查询返回的结果" + students1.toString());
31
32 }
33 @Test
34 public void testUpdateStudent() {
35 SqlSession session = sqlSessionFactory.openSession();
36 Student3Mapper mapper = session.getMapper(Student3Mapper.class);
37 Student student = new Student();
38 student.setId(1);
39 student.setName("小伟");
40 student.setAge(29);
41 boolean result = mapper.updateStudent(student);
42 System.out.println("--->updateStudent更新结果" + result);
43 }
44}

测试结果如下:

MyBatis常用特性运用_映射文件_05在这里插入图片描述

参考文献

MyBatis 3官方文档

mybatis枚举自动转换(通用转换处理器实现)

源代码

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