有多少人在滥用 service+serviceImpl,又有多少人在误用myBatis

被滥用的service+serviceImpl

    JAVA大概是从2003年开始流行,我也是从那时开始学习JAVA。在这十多年中,相关技术推陈出新,我切身感受到这些变化。虽然很多程序员不断追随新技术,但未必领悟到这些变化的推动因素。     最近我看到不少新开工的项目,仍然大量采用 “service+serviceImpl、dao+daoImpl” 的代码结构,说真的,我有点痛心,似乎这种做法是理所当然的,似乎这成了一个技术套路。 今天,我想说的是,这样做是不合理的、没有意义的、过时的。

从代码混战到分层分块

    由于java的流行和互联网的普及,企业网站、企业应用的开发开始从C/S转为B/S, 所以从那时起做好一个WEB应用很重要。     刚开始,也即2002~2003年,大家都是采用jsp+javabean+jdbc的方式,也不知道怎么分层,有的干脆把所有代码放在jsp中。 后来大家发现这样没法玩了,系统根本没法维护,也不安全,于是就有了MVC这样的架构模式。     MVC的理念挽救了这种局面,并且,运用该理念的模块化开发框架Struts出现了。MVC和Struts的广泛使用,使得开发WEB应用在业界达成了一个共识,那就是WEB应用基本可分为表现层、控制层、业务处理层。

    到2004年,对WEB应用进行分层、分模块已是程序员的常识。 但是,有追求有讲究的企业发现了新的问题,那就是如何使自己的应用或产品既可以跑在mysql上,也可以跑在oracle上。 这个问题对程序员来说,就是如何编写业务处理层,使之易于移植到其它数据库。

为解决移植性问题而产生的套路

    2005年以前的大多数项目都是直接在业务处理层的Service类中嵌入JDBC代码,这就使得这个Service类与数据库紧藕合,在换一种数据库的情况下,就要修改Service类中的sql。 根据软件设计的开闭原则,软件应该对修改关闭、对扩展开放。 因此,那时聪明的程序员就把这个Service类设计成一个接口,使控制层只依赖这个接口,于是就有了controller+service+serviceImpl;这样,当某天这个应用要跑在其它数据库上时,就而只需要增加一个serviceImpl类。 这就是service+serviceImpl套路产生的背景。

    在那时service+serviceImpl并非解决这个问题的唯一方案,还有部分项目,他们的团队更有想法,他们把与数据库打交道的代码从service类中提取出来,成为单独的“数据访问层”(也称为“持久层”),于是形成了这样的层次结构 controller+service+dao+daoImpl。 了不起!这样对不同的数据库,可以有对应的daoImpl。 相比前面那种方案,而扩展一个daoImpl比扩展一个serviceImpl省事多了。

数据访问层的解决方案或框架

    由于传统的JDBC代码,繁锁费事,因此不少人或团队尝试将这些代码封装起来,以使程序员不用再编写操作数据库连接、游标、数据集这样的逻辑。这样的工具通常以O/RMapping的思想作为基础,也称为O/RMapping框架。 2006年,hibernate从多种ORMapping框架中脱颖而出,很多项目中的 serviceImpl类开始采用hibernate来实现。hibernate强大,是把双刃剑,易上手但用好难、扩展性好但效率一般。(这也是我曾自研easydb的原因,代码: https://github.com/HuQingmiao/easydb)

    2010年,myBatis诞生,2012年开始流行并讯速得到广泛认可。由于myBatis本身是采用xml文件实现的,因此能极好地融入到项目中,只需要把service+dao+daoImpl 中的daoImpl类去掉,改由其mapp.xml实现即可,即service+dao+mapper.xml。 然而,还有很多人在用myBatis的项目中,采用service+serviceImpl+ dao+daoImpl+ mapper.xml, 真的是浪费青春。所以,我说很多人在误用myBatis。

myBatis的极简用法

     myBatis天生就是“依赖反转”、“依赖接口编程”的极佳范本,你无需再弄个daoImpl。我建议的简洁用法,思路如下:

一. 先把增、删、查、改的操作抽象成11个标准的接口方法,形成DAO接口的基类。

public interface BasicDao {

    public int save(BasicVo basicVo);

    public int saveBatch(List list);

    public int update(BasicVo basicVo);

    public int updateIgnoreNull(BasicVo basicVo);

    public int updateBatch(List list);

    public int delete(BasicVo basicVo);

    public int deleteBatch(List list);

    public int deleteByPK(Long id);

    public int deleteAll();

    public long count();

    public BasicVo findByPK(Long id);

    public List find(Map<String, Object> paramMap, PageBounds pageBounds);

二. 定义一个VO基类

public abstract class BasicVo implements Serializable {
    public BasicVo() {        
    }
}

三. 为每个数据库表编写Vo类、Dao类、Mapper.xml, 我以Book表为例,编写相应代码:

/**
 * Book表对应的Vo类,它继承BasicVo。 
 */
public class Book extends BasicVo {
    private static final long serialVersionUID = 1L;

    private Long bookId;
    private String title;
    private Double price;
    private java.sql.Date publishTime;
    private byte[] blobContent;
    private String textContent;

    public Long getBookId() {
        return bookId;
    }
    public void setBookId(Long bookId) {
        this.bookId = bookId;
    }

    public String getTitle() {
        return title;
    }
    public void setTitle(String title) {
        this.title = title;
    }

    // 以下省略其它get/set方法
    ...
}

/**
 * Book表对应的Dao类,它继承BasicDao。 注意:这个接口类不需要再声明方法,父接口BasicDao中的11个方法
 * 已经能满足我们90%使用场景,对于另外10%的场景,你可以在这里添加你的个性方法。  
 */
public interface BookDao extends BasicDao {
}

以下是BookMapper.xml的内容:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC  "-//mybatis.org//DTD Mapper 3.0//EN"  "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="xxx.xxx.xxx.BookDao">

    <!-- ============================= INSERT ============================= -->
    <insert id="save" useGeneratedKeys="true" keyProperty="id" >
        INSERT INTO book( book_id,title,price,publish_time,blob_content,text_content )
        VALUES ( #{bookId},#{title},#{price},#{publishTime},#{blobContent},#{textContent})
    </insert>

    <insert id="saveBatch">
        INSERT INTO book( book_id,title,price,publish_time,blob_content,text_content )
        <foreach collection="list" item="item" index="index" separator="UNION ALL">
           SELECT  #{item.bookId},#{item.title},#{item.price},#{item.publishTime},#{item.blobContent},#{item.textContent}
            FROM DUAL
        </foreach>
    </insert>

    <!-- ============================= UPDATE ============================= -->
    <update id="update">
        UPDATE book
        <set>
            title=#{title},
            price=#{price},
            publish_time=#{publishTime},
            blob_content=#{blobContent},
            text_content=#{textContent},
        </set>
        WHERE book_id=#{bookId}
    </update>

    <update id="updateIgnoreNull">
        UPDATE book
        <set>
            <if test="title!= null">title=#{title},</if>
            <if test="price!= null">price=#{price},</if>
            <if test="publishTime!= null">publish_time=#{publishTime},</if>
            <if test="blobContent!= null">blob_content=#{blobContent},</if>
            <if test="textContent!= null">text_content=#{textContent},</if>
        </set>
        WHERE book_id=#{bookId}
    </update>

    <update id="updateBatch" parameterType="java.util.List">
        <foreach collection="list" item="item" index="index"  separator=";">
            UPDATE book
            <set>
                title=#{item.title},
                price=#{item.price},
                publish_time=#{item.publishTime},
                blob_content=#{item.blobContent},
                text_content=#{item.textContent},
            </set>
            WHERE book_id=#{item.bookId}
        </foreach>
    </update>

    <!-- ============================= DELETE ============================= -->
    <delete id="delete">
        DELETE FROM book
        WHERE book_id=#{bookId}
    </delete>

    <delete id="deleteBatch">
        DELETE FROM book
        WHERE
        <foreach collection="list" item="item" index="index" open="(" separator="OR" close=")">
            (book_id=#{item.bookId} )
        </foreach>
    </delete>

    <delete id="deleteByPK">
        DELETE FROM book
        WHERE book_id=#{bookId}
    </delete>

    <delete id="deleteAll">
        DELETE FROM book
    </delete>

    <!-- ============================= SELECT ============================= -->
    <select id="count" resultType="java.lang.Long">
        SELECT COUNT(1) FROM book
    </select>

    <select id="findByPK" resultType="Book">
        SELECT * FROM book
        WHERE book_id=#{bookId}
    </select>

    <select id="find" resultType="Book">
        SELECT  *
         FROM book
        <where>
            <if test="bookId!= null">
               AND book_id = #{bookId}
            </if>
            <if test="title!= null">
               AND title like #{title}
            </if>
            <if test="minprice!= null">
               AND price >= #{minprice}
            </if>
            <if test="maxprice!= null">
                <![CDATA[  AND price < #{maxprice}  ]]>
            </if>
            <if test="publishTime!= null">
               AND publish_time = #{publishTime}
            </if>
            <if test="blobContent!= null">
               AND blob_content = #{blobContent}
            </if>
            <if test="textContent!= null">
               AND text_content = #{textContent}
            </if>
        </where>
    </select>

</mapper>

四. 现在你的service类就可以直接调各dao接口了:

        // 增加两条书目
        Book[] BookArray = new Book[size];

        BookArray[0] = new Book();
        BookArray[0].setBookId(new Long(102));
        BookArray[0].setTitle(new String("UNIX-上册"));
        BookArray[0].setPrice(new Double(40.0));

        BookArray[1] = new Book();
        BookArray[1].setBookId(new Long(103));
        BookArray[1].setTitle(new String("UNIX-中册"));
        BookArray[1].setPrice(new Double(60.0));

        bookDao.saveBatch(Arrays.asList(BookArray));

        // 查询相关书目
        HashMap<String, Object> paramMap = new HashMap<String, Object>();
        paramMap.put("title", "UNIX%");
        paramMap.put("minCost", new Float(21));

        //取第4条开始的3条记录
        PageBounds pageBounds = new PageBounds(4, 3);
        List<Book> bookList = bookDao.find(paramMap, pageBounds);

    以上代码,非常简洁、易于维护。 并且,我编写了一个代码生成器mybatis-daoj(代码:https://github.com/HuQingmiao/mybatis-daoj ),使得以上这些代码你都不用去写。你只要配置好数据库地址、指定表名,这个工具就能为你生成以上这些代码。

    另外要说明的是,虽然以上代码默认为每个表生成一个mapper.xml, 但并不是说不支持多表关联。比如,你要查出某些书及作者的相关信息,你完全可以在BookDao中增加一个接口方法:

public List findBooksAndAuthor(Map<String, Object> paramMap, PageBounds pageBounds);

然后在BookMapper.xml中增加一段:

    <select id="findBooksAndAuthor" resultType="BookAuthor">
        SELECT  a.book_id, a.title, a.price, a.publish_time, b.name, b.sex, b.birthday
         FROM book a, author b 
        <where>
            a.author_id = b.id
            <if test="title!= null">
               AND a.title like #{title}
            </if>
        </where>
    </select>

    以上代码中的“BookAuthor”为Vo类名,其实也可以继续用“Book”这个Vo,只需在其中增加'sex', 'birthday' 两个属性即可。

转载于:https://my.oschina.net/HuQingmiao/blog/636161

  • 5
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值