增强MyBatis注解

作者: 其实我做事不过脑的 | 来源:发表于2016-04-20 02:15 被阅读13576次

    MyBatis提供了简单的Java注解,使得我们可以不配置XML格式的Mapper文件,方便的编写简单的数据库操作代码:

    public interface UserMapper {
      @Select("SELECT * FROM users WHERE id = #{userId}")
      User getUser(@Param("userId") String userId);
    }
    
    <configuration>
        <settings>
            <setting name="mapUnderscoreToCamelCase" value="true"/>
        </settings>
    </configuration>
    
    

    但是没有Dynamic SQL的注解是不完整的,故这里向大家介绍下如何通过实现LanguageDriver,优雅的在MyBatis注解中使用Dynamic SQL。

    自定义Select In注解

    @Lang(SimpleSelectInExtendedLanguageDriver.class)
    @Select("SELECT * FROM users WHERE id IN (#{userIds})")
    List<User> selectUsers(@Param("userIds") List<String> userIds);
    
    // 一次编写,受益终生
    
    public class SimpleSelectInExtendedLanguageDriver 
                            extends XMLLanguageDriver implements LanguageDriver {
        
        private final Pattern inPattern = Pattern.compile("\\(#\\{(\\w+)\\}\\)");
    
        @Override
        public SqlSource createSqlSource(Configuration configuration, 
                                            String script, Class<?> parameterType) {
            
            Matcher matcher = inPattern.matcher(script);
            if (matcher.find()) {
                script = matcher.replaceAll("(<foreach collection=\"$1\" item=\"__item\" separator=\",\" >#{__item}</foreach>)");
            }
            
            script = "<script>" + script + "</script>";
            return super.createSqlSource(configuration, script, parameterType);
        }
    }
    

    我们通过实现自己的LanguageDriver,在MyBatis编译语句前,将我们自定义的标签替换为了动态SQL语句,其等同于:

    @Select({"<script>",
             "SELECT *", 
             "FROM user",
             "WHERE id IN", 
               "<foreach item='item' index='index' collection='list'",
                 "open='(' separator=',' close=')'>",
                 "#{item}",
               "</foreach>",
             "</script>"}) 
    List<User> selectUsers(@Param("userIds") List<Intger> userIds);
    

    通过实现LanguageDriver,剥离出了冗长的动态SQL语句,简化Select In的注解代码。

    自定义Update Bean注解

    类似的,通过重写LanguageDriver,我们还能扩展出远比其它方案(e.g. XML SQL Mapper配置、在注解语句中写动态SQL)简洁的自定义操作。

    一个常用的操作是更新数据库中的一条记录。通常而言,每张表(采用下划线命名法)会有一个对应的Domain对象(采用驼峰式命名法),当我们更新一条记录时,需要为对象中的每个字段配置映射关系,会写出如下的代码:

    int updateUser(User user);
    
    <update id="updateUser">
      update users
        <set>
          <if test="username != null">username=#{username},</if>
          <if test="password != null">password=#{password},</if>
          <if test="email != null">email=#{email},</if>
          <if test="homeAddress != null">home_address=#{homeAddress}</if>
        </set>
      where id=#{userId}
    </update>
    

    冗长的代码只是把驼峰式命名的变量名映射为下划线式命名的列名,显然我们可以将这种映射规律自动化:

    @Update("UPDATE user (#{user}) WHERE id =#{userId}")
    @Lang(SimpleUpdateExtendedLanguageDriver.class)
    int updateUser(Store store);
    
    /**
     * Created by benxue on 3/1/16.
     */
    public class SimpleUpdateExtendedLanguageDriver extends XMLLanguageDriver
            implements LanguageDriver {
        private final Pattern inPattern = Pattern.compile("\\(#\\{(\\w+)\\}\\)");
    
    
        @Override
        public SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType) {
            Matcher matcher = inPattern.matcher(script);
            if (matcher.find()) {
                StringBuffer ss = new StringBuffer();
                ss.append("<set>");
    
                for (Field field : parameterType.getDeclaredFields()) {
                        String temp = "<if test=\"__field != null\">__column=#{__field},</if>";
                        ss.append(temp.replaceAll("__field", field.getName())
                                .replaceAll("__column", CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, field.getName())));
                }
                
                ss.deleteCharAt(ss.lastIndexOf(","));
                ss.append("</set>");
    
                script = matcher.replaceAll(ss.toString());
    
                script = "<script>" + script + "</script>";
            }
            return super.createSqlSource(configuration, script, parameterType);
        }
    }
    

    一个常见的情况是,Domain中的部分属性在数据库表中并不存在对应的列,我们增加一个自定义的注释并对LanguageDriver的实现稍作修改:

    public class User{
        ...
    
        @Invisible
        private UserSearchDO userSearchDO;
        
        ...
    }
    
    
    /**
     * Created by benxue on 3/10/16.
     * The field marked as Invisible will not be scanned by customized simple extended language drivers
     * of myBatis
     */
    @Target(ElementType.FIELD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface Invisible {
    }
    
    ...
    for (Field field : parameterType.getDeclaredFields()) {
        if (!field.isAnnotationPresent(Invisible.class)) {
            String temp = "<if test=\"__field != null\">__column=#{__field},</if>";
            ss.append(temp.replaceAll("__field", field.getName())
                .replaceAll("__column", CaseFormat.LOWER_CAMEL.to(CaseFormat.LOWER_UNDERSCORE, field.getName())));
        }
    }
    ...
    
    

    由此,以后就可以用一句话完成动态Update语句了。

    总结

    通过实现LanguageDriver,我们可以实现方便的自定义注解。在遵循一些约定的情况下(e.g. Domain使用驼峰命名法,数据库表使用下划线命名法),就可以和麻烦的XML配置和动态SQL编写say 88了:)

    // 清爽的数据库操作
    
    @Select("SELECT * FROM user WHERE user_id IN (#{userIds})")
    @Lang(SimpleSelectInExtendedLanguageDriver.class)
    List<User> getUsers(@Param("userIds") List<Long> userIds);
    
    @Update("UPDATE user (#{user}) WHERE user_id =#{userId}")
    @Lang(SimpleUpdateExtendedLanguageDriver.class)
    int updateUser(User user);
    
    @Insert("INSERT INTO user (#{user})")
    @Lang(SimpleInsertExtendedLanguageDriver.class)
    void insertUser(User user);
    
    

    引用

    How to use Annotations with iBatis (myBatis) for an IN query

    相关文章

      网友评论

        本文标题:增强MyBatis注解

        本文链接:https://www.haomeiwen.com/subject/dfkckttx.html