Mybatis 通用Crud-设计思路

来源:互联网 发布:文章类网站源码 编辑:程序博客网 时间:2024/05/16 23:45

原文地址:https://my.oschina.net/LittleNewbie/blog/786783

一 关于Mybatis

1.1 mybatis 的优点

1 轻量级ORM 。

2 提供了完善的缓存机制。

3 mapper.xml 原声SQL更清晰灵活,且sql便于SQL调优。

4 resultType resultMap 处理返回结果集,与pojo解耦。

1.2 mybatis的使用体验

这里只将Mybatis不便于使用之处做以说明。

1 需要为每张表写一个dao接口和mapper.xml,这对于开发者来讲就不是很友好了,假设系统有30张业务表,呵呵。

2 虽然有generator 工具,可以自动生成dao 接口和mapper.xml,是可以不用自己去写这些代码了,但是还是有2个缺点:

(1) generator只是提供了基础的增删改查功能,复杂的sql还是要自己去添加。

(2) 还是上面的问题,每张表都要有dao接口和mapper.xml。

1.3 关于物理分页

对于分页的功能,仁者见仁,智者见智吧,就我个人的观点来讲,我觉得没必要。

上面也描述了mybatis的优点之一就是mapper.xml中的原声SQL,特别是对于复杂的SQL语句,原声SQL很有魅力。

如果你分页的sql封装成如下的格式,

<!-- mysql page封装 -->select count(1) as 'totalNum',t.*  from (select  user_id , user_name from user ) t limit startRowNo,rowNum<!-- mysql 原生分页 -->select count(1) as 'totalNum', user_id , user_name from user limit startRowNo,rowNum

我觉得,还是算了吧,不如原声SQL来的实在。select * 我就不讲了,select * from (sql) 根本就没必要嘛。

如果说你希望实现一个灵活的查询方法,有分页参数就按分页查询,没有分页参数就查询全部。那么我的观点是,mybatis 提供了<if></if>标签,代码如下:

   <!-- mapper.xml -->   <select id="selectByPage" parameterType="map" resultType="hashmap">        select count(1) as 'totalNum',user_id,user_name    from user        <if test="pageParam != null" >    limit #{pageParam.startRowNo},#{pageParam.pageSize}    </if>    </select>

如果说你希望分页方法能够更灵活,希望实现一个方法对任何一张表都可以分页,代码可以这样写

   <select id="selectByPage" parameterType="map" resultType="hashmap">        select ${queryColumn}    from ${tableName}        <if test="page != null" >    limit #{page.startRowNo},#{page.pageSize}    </if>    </select>

关于分页,就这么多,仅个人观点,欢迎批评、建议或讨论。

二 Mybatis通用Crud设计

2.1 需求

基于1.2,希望提供类似于hibernate中的load、insert、delete、save方法,对于基础的数据访问层操作,系统只需要存在一处,便于统一的管理,减少不必要的代码。

基于1.1,避免使用java封装字符串sql,mapper.xml,@select,@selectProvider三选一。

2.2 设计

2.2.1 通用crud接口

希望实现的功能方法接口

/** * 通用Crud 数据访问层接口 *  * @author svili * @date 2016年11月11日 * */public interface CrudServiceInter {/** * 根据主键查询 *  * @param <T> *            pojo类 * @param clazz *            pojo类-class对象 * @param primaryValue *            主键值 * @return pojo对象 * @throws Exception */<T> T selectByPrimaryKey(Class<T> clazz, Integer primaryValue) throws Exception;/** * 插入数据 *  * @param <T> *            pojo类 * @param t *            pojo对象 * @return 数据条数 * @throws Exception */<T> int insert(T t) throws Exception;/** * 插入数据 *  * @param <T> *            pojo类 * @param t *            pojo对象 * @return 数据条数 * @throws Exception */<T> int insertSelective(T t) throws Exception;/** * 删除 * <p> * 根据主键删除 * </p> *  * @param <T> *            pojo类 * @param clazz *            pojo类-class对象 * @param primaryValue *            主键值 * @return 数据条数 */<T> int deleteByPrimaryKey(Class<T> clazz, String primaryValue);/** * 更新 * <p> * 根据主键更新 * </p> * <p> * 更新pojo的所有字段,包括空值(null值)字段 * </p> *  * @param <T> *            pojo类 * @param t *            pojo对象 * @return 数据条数 * @throws Exception */<T> int updateByPrimaryKey(T t) throws Exception;/** * 更新 * <p> * 根据主键更新 * </p> * <p> * 更新pojo的非空字段 * </p> *  * @param <T> *            pojo类 * @param t *            pojo对象 * @return 数据条数 * @throws Exception */<T> int updateByPrimaryKeySelective(T t) throws Exception;/** * 根据条件查询 *@param clazz pojo类-class对象 *@param conditionExp where 条件表达式 *@param conditionParam where 条件表达式 动态参数 *@return *@throws Exception */<T> List<T> selectByCondition(Class<T> clazz,String conditionExp,Map<String,Object> conditionParam) throws Exception;}

2.2.2 根据主键查询

1 首先,明确接口

<T> T selectByPrimaryKey(Class<T> clazz, Integer primaryValue) throws Exception;

聪明的你应该注意到了一点,主键值是整形。

关于此,要说明的是,因为我用的数据库是mysql,所以每张表我会都会设置一个int类型的主键。

如果你使用的是Oracle,主键是guid,或者说你希望主键值是String类型,亦或者你希望能够兼容String和Integer,那么你完全可以将这个接口的参数类型设置为String。

有关主键类型,文章后面会有专门的说明。

2 分析下mapper.xml中的sql

<select id="selectByPrimaryKey" parameterType="map" resultType="hashmap">    select     <foreach item="columnName" index="index" collection="queryColumn" separator="," >        #{columnName}    </foreach>    from ${tableName}    where ${primaryKey} = #{primaryValue}</select>

注意:不要使用<![CDATA[  ]]>去尝试对这段sql进行转义,会导致<foreach></foreach>标签不被解析处理。

关于xml中的<![CDATA[  ]]> ,自行百度get噢。

3 明确sql中的参数

sql中需要传递的参数有4个,分别是:tableName(表名),queryColumn(查询字段集),primaryKey(主键列名),primaryValue(主键值)。

4  接口参数与sql参数转化

(1)tableName

从Java类到数据库的表名,我们可以获取两个信息,一个是类名,第二个是JPA的@Table注解。

要注意两点,1.注解优先,2.Java的命名规则为驼峰,数据库的命名规则为下划线。如果你不按套路出牌,还是要好好想想如何去对应转换。

//获取pojo表名public static String getTableName(Class<?> clazz) {// 驼峰转下划线String tableName = StringUtil.camelToUnderline(clazz.getName());// 判断是否有Table注解if (clazz.isAnnotationPresent(Table.class)) {// 获取注解对象Table table = clazz.getAnnotation(Table.class);// 设置了name属性if (!table.name().trim().equals("")) {return table.name();}}return tableName;}

(2)queryColumn

直接获取字段名就好,注意驼峰转下划线。

//获取所有字段名public static List<String> getAllColumns(Class<?> clazz) {List<String> columns = new ArrayList<String>();Field[] fields = clazz.getDeclaredFields();for (Field field : fields) {columns.add(StringUtil.camelToUnderline(field.getName()));}return columns;}

(3)primaryKey

这只能靠JPA的@Id了。

当然你可自己定制一套标准,比如每张表都固定一个主键字段名为table_id。但是我觉得完全没必要,既然有JPA的标准,为什么要去自己费脑经呢。如果说你使用oracle的guid,没脾气,呵呵。

//获取主键字段public static Field getPrimaryFieldNotCareNull(Class<?> clazz) {Field field = FieldReflectUtil.findField(clazz, Id.class);if (field != null) {return field;} else {return null;}}//获取指定注解类型的字段public static Field findField(Class<?> clazz, Class<? extends Annotation> annotationType) {Class<?> searchType = clazz;while (!Object.class.equals(searchType) && searchType != null) {Field[] fields = searchType.getDeclaredFields();for (Field field : fields) {if (field.isAnnotationPresent(annotationType)) {return field;}}searchType = searchType.getSuperclass();}return null;}

2.2.3 其他接口

关于其他接口,其实大同小异,这里就不做说明了,只需要按照2.2.2 中的思路去查看源码就OK了。

三 源码

源码地址:https://github.com/LittleNewbie/portal

如何阅读源码:

主要4个文件 GeneralDao,GeneralMapper.xml,GeneralDaoProvider,CrudServiceImpl

GeneralDao声明了数据访问层接口;

GeneralMapper.xml和GeneralDaoProvider 配合GeneralDao。

CrudServiceImpl 实现由接口参数转化为GeneralDao参数,并对封装返回类型。

1 0