文档章节

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

林中漫步
 林中漫步
发布于 2016/03/12 08:51
字数 2190
阅读 10940
收藏 43

被滥用的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' 两个属性即可。

© 著作权归作者所有

上一篇: maven内置变量
下一篇: 架构设计的目标
林中漫步
粉丝 102
博文 55
码字总数 33266
作品 0
深圳
架构师
私信 提问
加载中

评论(24)

头发很多的程序猿
头发很多的程序猿
正在学习MyBatis的新手看到这篇文章默默注册然后留言点赞
头发很多的程序猿
头发很多的程序猿
而且我正好成为博主的第100个粉哈哈哈哈
OSC_YTfjsI
OSC_YTfjsI
增强Mybatis处理多表查询 我再也不用写VO类!
Ronaldo7
Ronaldo7
那如果碰到又需要操作很多个表的业务,是不是要重新建一个service,和对应的impl,然后将要用到的表注入到这个impl中。或者说前辈有更好的方式
Gynwn
Gynwn
看了这篇文章顿时茅塞顿开!!我们采用springmvc+mybaitsi的方式,用的就是controller+Iservice+serviceImpl+dao+sql.xml,开发时觉得非常臃肿,由于开发技能不高,觉得springmvc的框架就是这样的肯定有它的好处就没有人考虑改进,最近实在觉得这种方式很浪费精力开始寻找其他结构,我想用类的方式进行代码组织而不是接口的方式,因为controller已经相当于是接口了,请博主指教
林中漫步
林中漫步 博主

引用来自“ishuzheng”的评论

楼主知道有Spring-mybatis这个东东吗?
知道。 但我说的与spring没有关系。 为了使关注的问题更为简单,我的示例代码没有引入spring.

恕征
恕征
楼主知道有 Spring-mybatis 这个东东吗?

刚才评论提示:数据库操作异常,详细信息:Duplicate entry '296598584-993139' for key 'PRIMARY'
恕征
恕征
楼主知道有Spring-mybatis这个东东吗?
助哥的后花园
助哥的后花园

引用来自“助哥的后花园”的评论

生成的工具如果数据库字段是小写开头带下划线会有问题吧,比如u_id在VO中会生成getuId()和setuId(),首字母没有大写额,为什么

引用来自“林中漫步”的评论

这是按javabean规范生成的, 如果字段是 ab_id, 生成的方法就是 getAbId(); 如果字段是a_id, 生成的方法就是getaId(). 这取决于字段中下划线‘_'前的字符数是1个还是1个以上。
是的,之前没发现后来查了下才知道,尴尬,前辈你的那个软件我发现如果数据库里字段有下划线他会去掉下划线,这样的话就不能直接把查询结果映射到实体类中了,只能自己写个别名或者resultMap,怎么改呢?:bowtie:
林中漫步
林中漫步 博主

引用来自“助哥的后花园”的评论

生成的工具如果数据库字段是小写开头带下划线会有问题吧,比如u_id在VO中会生成getuId()和setuId(),首字母没有大写额,为什么
这是按javabean规范生成的, 如果字段是 ab_id, 生成的方法就是 getAbId(); 如果字段是a_id, 生成的方法就是getaId(). 这取决于字段中下划线‘_'前的字符数是1个还是1个以上。
助哥的后花园
助哥的后花园
生成的工具如果数据库字段是小写开头带下划线会有问题吧,比如u_id在VO中会生成getuId()和setuId(),首字母没有大写额,为什么
【沃顿商学院学习笔记】领导力与管理——01腐败Corruption

版权声明:分享或转载请获得授权并标注来源。版权合作可以联系微信:13218779868。对区块链感兴趣可以加入我们的telegram群:https://t.me/eonblock,微信号搜索:eonDAOclub ,点击‘关注‘...

乐扣老师lekkoliu
01/31
0
0
QString 乱谈(3)-Qt5与中文

两个月前,简单写过QTextCodec中的setCodecForTr等终于消失了 (Qt5) ,在Qt论坛上,不少用户都对去掉这两个函数表示特别的不了解。为什么会这样?我想多少能说明不少用户对C++中源码字符集和...

晨曦之光
2012/05/08
720
1
SQL 常用优化手段总结 - 索引的使用误区

回顾上一章索引的应用的内容,除了介绍了基本的使用索引优化 sql 语句的基本手法以外,还提到了滥用索引会引起性能恶化的问题。本章节的内容将会举例说明哪些场景的索引属于滥用,以及如何避...

给你添麻烦了
2018/01/10
0
0
spring4mvc整合mybatis3

1.pom.xml文件增加mybatis3的jar包如下,本人用的spring版本为4.1.6.RELEASE !-- 添加mybatis的核心包 --><dependency><groupId>org.mybatis</groupId><artifactId>mybatis</artifactId><ve......

阿宇_
2015/09/17
628
0
杜跃进:数据安全治理的基本思路

▲作者:杜跃进 中国网络空间安全协会副理事长,阿里巴巴集团技术副总裁 我们的世界正在进入一个奇怪的分裂状态:一方面人们为大数据时代即将在各个领域发生的革命性进步而激动难眠,一方面人...

技术小能手
2018/11/15
0
0

没有更多内容

加载失败,请刷新页面

加载更多

zk服务预启动和启动选举过程

QuorumPeerMain类public static void main(String[] args) { QuorumPeerMain main = new QuorumPeerMain(); try { main.initializeAndRun(args); } catch (Illega......

writeademo
29分钟前
5
0
深究递归和迭代的区别、联系、优缺点及实例对比

http://blog.csdn.net/laoyang360/article/details/7855860 http://www.zhihu.com/question/20278387 深究递归和迭代的区别、联系、优缺点及实例对比 1.概念区分 递归的基本概念:程序调用自身...

slagga
31分钟前
5
0
基于SOM-TL6678核心板而研发的TL6678-EasyEVM开发板硬件说明书

TL6678-EasyEVM是广州创龙基于SOM-TL6678核心板而研发的一款多核高性能DSP开发板。开发板采用核心板+底板方式,底板采用沉金无铅工艺的四层板设计,尺寸为200mm*106.65mm,它为用户提供了SOM...

Tronlong创龙
35分钟前
4
0
分别在有网和无网状态下批量安装python库

## 连网状态下,批量安装python库查看当前python环境下已安装的库包版本查看命令:pip freeze"""alabaster==0.7.10anaconda-client==1.6.3anaconda-navigator==1.6.2anaconda-projec......

KYO4321
38分钟前
3
0
fiddler、Charles-断点

一、添加Charles断点 1、用Charles抓包发起一次接口请求 2、对要打断点的接口右键,选择【Breakpoints】 二、Charles断点设置 1、点击Charles菜单-【Proxy】-【Breakpoint...】,此时会弹出【...

果树啊
38分钟前
4
0

没有更多内容

加载失败,请刷新页面

加载更多

返回顶部
顶部