概要
接上一篇MyBatis 学习笔记(一)MyBatis的简介与使用以及与其他ORM框架的比较,今天我们接着来学习MyBatis的一些常用特性,包括别名,类型处理器,动态SQL
如何使用MyBatis
在本小节,我将通过一个例子介绍MyBatis 中一些常用特性的运用,包括类型处理器,动态SQL等等。
别名
MyBatis 中有个比较好用的特性就是别名,这是为了减少在配置文件中配置实体类的全限定名的冗余。运用如下:
首先在MyBatis的配置文件中配置别名:
<!--别名处理--> <typeAliases> <typeAlias type="com.jay.chapter2.entity.Student" alias="Student"/> </typeAliases>
然后,在需要使用该实体类的映射文件中进行添加即可。
<resultMap id="studentResult" type="Student"> //省略其他无关配置 </resultMap>
类型处理器的运用
在实际开发中,我们经常要对枚举类进行处理,例如,人的性别分为男,女,我们数据库中可能存的是0,1; 但是页面显示的话需要显示男,女,所以,我们在使用MyBatis时查询结果时就要通过转换器进行转换。
MyBatis 内置了很多类型处理器(typeHandlers),详细可以参考MyBatis官方文档,对枚举类的处理的是通过EnumTypeHandler和EnumOrdinalTypeHandler两个处理器来处理了,
但是其只能处理简单的枚举,例如:
public enum SexEnum { MAN, FEMALE, UNKNOWN; }
对于复杂的枚举类型,则不能处理。例如:
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进行SQL插值操作,设置的值是enum.name() ,即enum的toString() ,存储的枚举值的名称,而getNullableResult 方法返回的是Enum.valueOf(type, s)。而EnumOrdinalTypeHandler转换器也只能处理Int,String 类型。故我们需要自定义转换器来处理。分析MyBatis 源码我们可以得知,各个转换器都是继承BaseTypeHandler 基类的。为了实现代码的通用性,首先我们实现了一个枚举基类,然后定义一个通用的转换器。
枚举基类:
public interface BaseEnum<E extends Enum<?>, T> { /** * 真正与数据库进行映射的值 * @return */ T getKey(); /** * 显示的信息 * @return */ String getValue(); }
在枚举记录中我们定义了两个通用的获取key和value的方法,接着我们定义 一个枚举类SexEnum来实现枚举基类
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); } }
接下来我们再来看看通用的转换器类。
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; //获取实现枚举基类所有的枚举类 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,存入数据库中 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); } /* * 根据枚举类的key,获取其对应的枚举 * @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映射文件中进行必要的配置。
<result property="sexEnum" column="sex" typeHandler="com.jay.chapter2.Handler.GeneralEnumHandler"/>
最后我们来编写一个测试类来处理一下
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("------>返回结果"+student.toString()); } }
测试结果如下:
动态SQL的使用
MyBatis的强大特性之一便是它的动态SQL,主要是处理 根据不同条件拼接SQL语句,例如拼接时添加必要的空格,去掉列表中的最后一列的逗号,MyBatis的动态SQL 元素是基于OGNL的表达式。
详细的运用可以参考MyBatis官方文档
下面以一个例子作为一个示范。自此处我们有一张Student表,测试数据如下:
首先,我们在mybatis-cfg.xml配置好映射文件
<!-- 加载映射文件--> <mappers> <mapper resource="chapter2/xml/Student3Mapper.xml"/> </mappers>
然后,我们编写映射文件,将运用相关的动态SQL表达式。
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> <!--运用where,if元素进行条件拼接--> <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> <!--运用foreach元素进行实现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> <!--运用choose,when,otherwise元素实现多条件分支--> <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> <!--运用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>
该动态SQL对应的DAO 接口如下:
public interface Student3Mapper { /** * 查询学生 * @param id * @param name * @return */ List<Student> selectStudent(@Param("id") Integer id, @Param("name") String name); /** * 批量查询学生 * @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); /** * 更新学生记录 * @param student * @return */ boolean updateStudent(Student student); }
接口类和其对应的映射文件编写完成之后,接着我们来编写测试类进行测试下。
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返回结果"+students.toString()); List<Student> students1 = mapper.selectStudent(null, "平平"); System.out.println("----》只传name返回结果"+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查询返回结果" + 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查询返回结果" + students.toString()); List<Student> students1 = mapper.selectByNameOrAge(null, 1); System.out.println("----->selectByNameOrAge通过age查询返回的结果" + 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更新结果" + result); } }
测试结果如下:
参考文献
源代码
https://github.com/XWxiaowei/MyBatisLearn/tree/master/mybatisDemo