前言
Mybatis在我所见过的持久化框架里真心是一个异类,因为它是sql-centric的,而不是基于对象和表映射的。我会在本文中讲一下Mybatis几个重要的技巧,与本文的上一篇文章Hibernate做个对比。
Mybatis配置
在ApplicationContext上加上如下配置:
XML
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource" /> <property name="mapperLocations" value="classpath*:mapper/*.xml" /> <property name="configLocation" value="classpath:mybatis-config.xml"/> </bean> <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate"> <constructor-arg index="0" ref="sqlSessionFactory" /> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="sbeat.dao" /> </bean>
然后在mybatis-config.xml中做进一步的配置。
XML
<configuration> <typeAliases> <package name="sbeat.model" /> </typeAliases> </configuration>
Mybatis 分页
Mybatis不支持分页,所以我采用了PageHelper这个插件,首先在你的Mybatis配置文件里加上一下配置
XML
<plugins> <!-- com.github.pagehelper为PageHelper类所在包名 --> <plugin interceptor="com.github.pagehelper.PageHelper"> <property name="dialect" value="mysql" /> <!-- 该参数默认为false --> <!-- 设置为true时,会将RowBounds第一个参数offset当成pageNum页码使用 --> <!-- 和startPage中的pageNum效果一样 --> <!-- <property name="offsetAsPageNum" value="false" /> --> <!-- 该参数默认为false --> <!-- 设置为true时,使用RowBounds分页会进行count查询 --> <!-- <property name="rowBoundsWithCount" value="false" /> --> <!-- 设置为true时,如果pageSize=0或者RowBounds.limit = 0就会查询出全部的结果 --> <!-- (相当于没有执行分页查询,但是返回结果仍然是Page类型) --> <property name="pageSizeZero" value="true" /> <!-- 3.3.0版本可用 - 分页参数合理化,默认false禁用 --> <!-- 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages会查询最后一页 --> <!-- 禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据 --> <property name="reasonable" value="false" /> <!-- 3.5.0版本可用 - 为了支持startPage(Object params)方法 --> <!-- 增加了一个`params`参数来配置参数映射,用于从Map或ServletRequest中取值 --> <!-- 可以配置pageNum,pageSize,count,pageSizeZero,reasonable,不配置映射的用默认值 --> <!-- 不理解该含义的前提下,不要随便复制该配置 --> <!-- <property name="params" value="pageNum=start;pageSize=limit;" /> --> </plugin> </plugins>
然后在需要分页的查询之前,加上下面一句话:
java
PageHelper.startPage(pageNum,PAGE_SIZE);
Mybatis 中的sql标签
由于Mybatis是通过文本替换组装生成SQL语句的,所以不难发现它的插入和更新同样是静态的,对象里是null的插入也是null。你数据库的默认值不起作用而是得到null,那怎么解决这个问题呢?
XML
<sql id="userColumn"> <trim suffixOverrides=","> <if test="id!=null">id,</if> <if test="phone!=null">phone,</if> <if test="email!=null">email,</if> <if test="photo!=null">photo,</if> </trim> </sql> <sql id="userValue"> <trim suffixOverrides=","> <if test="id!=null">#{id},</if> <if test="phone!=null">#{phone},</if> <if test="email!=null">#{email},</if> <if test="photo!=null">#{photo},</if> </trim> </sql> <insert id="insert" parameterType="User" useGeneratedKeys="true" keyProperty="id"> insert into user (<include refid="userColumn" />) values (<include refid="userValue"></include>) </insert>
通过使用include和sql标签我们解决了这个问题。
Mybatis typeHandler
有时候我们常常想将Collection或者其他对象直接以Json字符串的形式存在数据库里而不是再开一张表,虽然普遍的观点是不赞同这种做法,但这种需求却是实际存在的。怎么才能在DAO中就将字符串和对象的转换做掉而不用交给上层显式地转换呢?
采用自定义的Typehandler就可以,下面给出一个例子
java
@MappedJdbcTypes(JdbcType.VARCHAR) public class JSONHandler implements TypeHandler<Object> { /** * json数据和类名的分隔符号 * */ private static final char SPLIT = '/'; /** * json 转换成对象 * */ private Object jsonToObject(String json) throws RuntimeException{ if (json == null) { return null; } int index = json.lastIndexOf(SPLIT); if (index < 0) { return null; } String key = json.substring(index + 1, json.length()); json = json.substring(0, index); Class<?> cls = null; try { cls = Class.forName(key); } catch (ClassNotFoundException e) { throw new RuntimeException("序列化成json时找不到指定的类", e); } Object ob = JSON.parseObject(json, cls); return ob; } public void setParameter(PreparedStatement ps, int i, Object parameter, JdbcType jdbcType) throws SQLException { if(parameter == null){ ps.setString(i, null); return; } String json = JSON.toJSONString(parameter); json = json + SPLIT + parameter.getClass().getName(); ps.setString(i, json); } public Object getResult(ResultSet rs, String columnName) throws SQLException { String json = rs.getString(columnName); return jsonToObject(json); } public Object getResult(ResultSet rs, int columnIndex) throws SQLException { String json=rs.getString(columnIndex); return jsonToObject(json); } public Object getResult(CallableStatement cs, int columnIndex) throws SQLException { String json = cs.getString(columnIndex); return jsonToObject(json); } }
首先设定处理的JDBCType,显然是变长字符,然后实现给定的接口,最后在Mybatis的配置文件中加上这么一句。
XML
<typeHandlers> <typeHandler handler="sbeat.util.helper.JSONHandler" javaType="java.util.List"/> <typeHandler handler="sbeat.util.helper.JSONHandler" javaType="java.util.Map"/> </typeHandlers>
将Map和List均交由该handler处理,但本人实测,这个好像并没有什么卵用,有用的是在Mapper.xml文件中显式指定,如下所示。
XML
<insert> insert (tags) values (#{tags,typeHandler=sbeat.util.helper.JSONHandler}) </insert> <resultMap type="Employee" id="employeeResult"> <result property="tags" column="tags" javaType="java.util.List" typeHandler="sbeat.util.helper.JSONHandler"/> </resultMap>
Mybatis 外键查询
外键查询需要使用resultMap的association标签,如下所示
XML
<resultMap type="Message" id="msgResultMap"> <id property="id" column="id" /> <result property="created" column="created" /> <result property="title" column="title" /> <result property="content" column="content" /> <result property="has_read" column="has_read" /> <result property="msgType" column="msgType" /> <result property="receiverId" column="receiverId" /> <association property="sender" javaType="User"> <id property="id" column="userId"/> <result property="name" column="name"/> <result property="phone" column="phone"/> <result property="passwd" column="passwd"/> <result property="photo" column="photo"/> <result property="email" column="email"/> <result property="userType" column="userType"/> </association> </resultMap>
Mybatis 多参数
在接口定义中使用@Params注解,并在XML中不定义paramType,如下所示
java
public List<Feedback> findByTradeId(@Param("tradeId") Long tradeId,@Param("ownerType") UserType ownerType);
XML
<select id="findByTradeId" resultType="Feedback"> select * from feedback where tradeId=#{tradeId} <if test="ownerType!=null">AND ownerType=#{ownerType}</if> ORDER BY created DESC </select>
Mybatis 逗号问题
逗号的不注意往往是使用Mybatis中出现最多的失误,可以通过使用where和set一级trim标签来尽量避免,如下所示:
XML
<trim suffixOverrides=","> <if test="id!=null">id,</if> <if test="phone!=null">phone,</if> </trim> <set> <if test="phone!=null">phone=#{phone},</if> <if test="name!=null">name=#{name},</if> </set> <where> <if test="id!=null">id=#{id}</if> <choose> <when test="logic_delete!=null">AND logic_delete=#{logic_delete}</when> <otherwise> AND logic_delete=false </otherwise> </choose> <if test="name!=null"> AND name=#{name}</if> </where>