MyException - 我的异常网
当前位置:我的异常网» 软件架构设计 » JeeSite 4.0 简单化MyBatis持久层开发

JeeSite 4.0 简单化MyBatis持久层开发

www.MyException.Cn  网友分享于:2013-08-11  浏览:0次
JeeSite 4.0 简化MyBatis持久层开发

 

引言

更好的阅读体验点这里:https://my.oschina.net/thinkgem/blog/1503611

在做这一方面研究的时候,本人参考了很多资料和框架,如MyBatis-Mapper、MyBatis-Plus等等,这些都做的很不错,本来想集成一个,尝试了下还是有多处地方不顺手,不易扩展,不能解决我的本意,既能使用方便又不能失灵活,所以决定自己试着完成一套Dao层架构,精简开发。

在此之前我先考虑API的写法,通俗易懂,大众思维。持久层实体类采用@Table注解配置,自动生成增删改通用SQL,不需要在mapper.xml里写重复又费时的SQL,遇见复杂的情况下支持扩展。而报表统计分析的情况下又能支持mybatis原生写法,在写sql的时候,又能调用之前实体配置的一些参数。从而减少开发和后期维护成本。

众多的持久层框架@Column注解定义都是分布到get或属性上,或者干脆直接使用属性作为字段名,这在JeeSite是不推荐的,JeeSite的实体不仅仅是物理实体,它是与Model实体结合的一个产物,视乎记得JFinal作者也说过这一点,也是推荐的一个做法。总合考虑,将@Column所有定义到类头,而不是分布到各个属性或方法上,主要是有以下三点原因:

  1. 可一览熟知该实体类对应的物理表结构是什么样,引领开发者思维从物理表结构到对象的映射转换,都是基于物理表结构的,@Column中的name指定物理字段名,而不是指定类上的属性名,也是这个原因;
  2. 自动化生成的SQL和查询条件,是有序的,可方便核查定义,优化查询;
  3. 方便@JoinTable关联表和其它扩展信息的设置,如果分布到类的属性上就需要来回滚动屏幕查找,不利于管理字段列。

下面举例说明,最后附上API:

以定义员工实体举例,配置如下:(注意代码上的注释)

@Table(name="${_prefix}sys_employee", alias="a", columns={
        @Column(includeEntity=BaseEntity.class),        // 支持Include
        @Column(includeEntity=DataEntity.class),        // 支持Include,如:自动导入status、create_by、create_date等字段
        @Column(name="emp_code", label="员工编码", isPK=true),  // 支持设置主键PK字段,调用get方法时自动加入主键唯一条件
        @Column(name="emp_name", label="名称", queryType=QueryType.LIKE),  // 支持设置查询字段类型,如LIKE自动在查询值前后加 % 符号。
        @Column(name="emp_name_en", label="英文名", queryType=QueryType.LIKE),
        @Column(name="emp_no", label="工号"),   // 字段名到Java属性名的转换,采用驼峰命名法规则自动进行转换
        // 驼峰命名法转换不了的,支持设置特殊对象属性,如mapper.xml的sql中 a.office_code AS "office.officeCode" 的写法
        @Column(name="office_code", attrName="office.officeCode", label="机构编码"),  
        @Column(name="office_name", attrName="office.officeName", label="机构名称", queryType=QueryType.LIKE),
        @Column(name="company_code", attrName="company.companyCode", label="公司编码"),
        @Column(name="company_name", attrName="company.companyName", label="公司名称", queryType=QueryType.LIKE),
        @Column(name="sex", label="性别"),
        @Column(name="birthday", label="生日"),
        @Column(name="photo", label="员工照片", isQuery=false), // 支持设置非查询字段,添加查询条件时忽略该字段
        @Column(name="email", label="电子邮件"),
        @Column(name="mobile", label="手机号码"),
        @Column(name="phone", label="办公电话"),
        @Column(name="fax", label="传真号码"),
        @Column(name="qq", label="QQ号"),
        @Column(name="weixin", label="微信号"),
        @Column(name="stations", label="岗位"),
    },
    // 支持联合查询,如左右连接查询,支持设置查询自定义关联表的返回字段列
    joinTable={
        @JoinTable(type=Type.LEFT_JOIN, entity=Office.class, alias="o", 
            on="o.office_code = a.office_name",
            columns={@Column(includeEntity=Office.class)}),
        @JoinTable(type=Type.LEFT_JOIN, entity=Company.class, alias="c", 
            on="c.company_code = a.company_name",
            columns={@Column(includeEntity=Company.class)}),
    },
    // 支持扩展Column、Form、Where等,主要用于该注解实现不了的复杂情况,扩展SQL写法,这里设置的是sqlMap的key
    extWhereKeys="dsfOffice, dsfCompany"
    // 自动设置默认排序
    orderBy="a.update_date DESC"
)
public classEmployeeextendsDataEntity<Employee> {
    private static final long serialVersionUID = 1L;
    private String empCode;        // 员工编码
    private String empName;        // 名称
    private String empNameEn;    // 英文名
    private String empNo;        // 工号
    private Office office;        // 机构编码
    private Company company;    // 公司编码
    private String sex;            // 性别
    private Date birthday;        // 生日
    private String photo;        // 员工照片
    private String email;        // 电子邮件
    private String mobile;        // 手机号码
    private String phone;        // 办公电话
    private String fax;            // 传真号码
    private String qq;            // QQ号
    private String weixin;        // 微信号
    private String stations;    // 岗位

    /// 省略  get  set 方法

}

请仔细看上面的代码和注释,其以上之外,还支持是否为插入字段,是否为更新字段等等。

再举一个例子,扩展上面介绍的Employee表,与用户表联合查询单独定义实体,用户员工实体:

@Table(name="${_prefix}sys_user", alias="a", columns={
        @Column(includeEntity=User.class),
    }, joinTable={
        @JoinTable(type=Type.JOIN, entity=Employee.class, alias="e",
            on="e.emp_code = a.ref_code AND a.user_type=#{USER_TYPE_EMPLOYEE}",
            columns={@Column(includeEntity=Employee.class)}),
        @JoinTable(type=Type.LEFT_JOIN, entity=Office.class, alias="o", 
            on="o.office_code = a.office_name", attrName="employee.office",
            columns={@Column(includeEntity=Office.class)}),
        @JoinTable(type=Type.LEFT_JOIN, entity=Company.class, alias="c", 
            on="c.company_code = a.company_name", attrName="employee.company",
            columns={@Column(includeEntity=Company.class)}),
    }, extWhereKeys="dsfOffice, dsfCompany", orderBy="a.update_date DESC"
)
public classEmpUserextendsUser{
    private static final long serialVersionUID = 1L;
    publicEmpUser(){
        this(null);
    }
    publicEmpUser(String id){
        super(id);
    }
    @Valid
    public Employee getEmployee(){
        Employee employee = (Employee)super.getRefObj();
        if (employee == null){
            employee = new Employee();
        }
        return employee;
    }
    publicvoidsetEmployee(Employee employee){
        super.setRefObj(employee);
    }
}

注解配置完成了,下面看看如何使用

如何使用

贴了这么多配置代码,下面介绍下用法。

你的Dao只需要继承CrudDao即可享受便捷体验,是不是特Easy,如下:

/**
 * 员工管理DAO接口
 * @author ThinkGem
 */
@MyBatisDao(entity = Employee.class)
public interfaceEmployeeDaoextendsCrudDao<Employee> {

}

EmployeeDao继承CrudDao后,里面的方法你都可以调用,如下方法:

/**
 * DAO实现增删改接口
 * @author ThinkGem
 */
public interfaceCrudDao<T> extendsQueryDao<T> {

    /**
     * 插入数据
     */
    publicintinsert(T entity);

    /**
     * 批量插入数据
     */
    publicintinsertBatch(List<T> entityList);

    /**
     * 更新数据  By PK
     */
    publicintupdate(T entity);

    /**
     * 更新数据  By Entity
     */
    publicintupdateByEntity(T entity, T whereEntity);

    /**
     * 更新状态数据  By PK
     */
    publicintupdateStatus(T entity);

    /**
     * 更新状态数据  By Entity
     */
    publicintupdateStatusByEntity(T entity, T whereEntity);

    /**
     * 删除数据  By PK(如果有status字段,则为逻辑删除,更新status字段为1,否则物理删除)
     */
    publicintdelete(T whereEntity);

    /**
     * 删除数据  By Entity(如果有status字段,则为逻辑删除,更新status字段为1,否则物理删除)
     */
    publicintdeleteByEntity(T whereEntity);

    /**
     * 获取单条数据
     * @param entity
     * @return entity
     */
    public T get(T entity);

    /**
     * 获取单条数据
     * @param entity
     * @return entity
     */
    public T getByEntity(T entity);

    /**
     * 查询数据列表,如果需要分页,请设置分页对象,如:entity.setPage(new Page<T>(pageNo, pageSize));
     * @param entity
     * @return
     */
    public List<T> findList(T entity);

    /**
     * 查询数据总数
     * @param entity
     * @return
     */
    publiclongfindCount(T entity);

}

调用举例:

// 查询一条,更新
Employee employee = new Employee();
employee.setEmpCode('E001');
employee = employeeDao.get(employee);
employee.setMobile('18666666666');
employeeDao.update(employee);

// 列表查询、统计
Employee employee = new Employee();
employee.setEmpName('小王');
employee.setPage(new Page(1, 20)); // 分页查询
List<Employee> list = employeeDao.findList(employee);
Long count = employeeDao.findCount(employee);

// 批量插入
employeeDao.insertBatch(list);

是不是有种事半功倍的感觉,小小的配置,可以实现几乎可以完成原来需要写代码的80%时间。

也许你会觉着配置复杂,难以理解,只要你用上了相信你就会爱不释手。

还有一个惊喜,这些配置也可以通过代码生成工具快速生成,喜欢不喜欢。

嗯!基本增删改查,批量操作,按实体属性查询,按实体属性更新,以及统计都有了 ↓↓↓ 可是 ↓↓↓

可是

这么多还是还不够,比如,我们想实现,日期范围查询怎么办?某个实体属性,实现双重查询(如那么既能eq又能like)怎么办?想实现or、is null,括号查询怎么办?这些都么关系,已经替你考虑了,如下:


////////// 日期范围查询,gte,lte ////////////////

public Date getCreateDate_gte(){
    return sqlMap.getWhere().getValue("create_date", QueryType.GTE);
}

publicvoidsetCreateDate_gte(Date createDate){
    createDate = DateUtils.getOfDayFirst(createDate); // 将日期的时间改为0点0分0秒
    sqlMap.getWhere().and("create_date", QueryType.GTE, createDate);
}

public Date getCreateDate_lte(){
    return sqlMap.getWhere().getValue("create_date", QueryType.LTE);
}

publicvoidsetCreateDate_lte(Date createDate){
    createDate = DateUtils.getOfDayLast(createDate); // 将日期的时间改为23点59分59秒
    sqlMap.getWhere().and("create_date", QueryType.LTE, createDate);
}

////////// 双重字段查询,支持eq,支持like ////////////////

public String getTableName(){
    return StringUtils.lowerCase(tableName);
}

publicvoidsetTableName(String tableName){
    this.tableName = tableName;
}

public String getTableName_like(){
    return sqlMap.getWhere().getValue("table_name", QueryType.LIKE);
}

publicvoidsetTableName_like(String tableName){
    sqlMap.getWhere().and("table_name", QueryType.LIKE, tableName);
}

////////// 实现 or、is null,括号 ////////////////

public String getParentTableName_isNull(){
    return this.getParentTableName();
}

publicvoidsetParentTableName_isNull(String parentTableName){
    if (StringUtils.isBlank(parentTableName)){
        sqlMap.getWhere().andBracket("parent_table_name", QueryType.IS_NULL, null, 2)
            .or("parent_table_name", QueryType.EQ_FORCE, "", 3).endBracket();
        this.setParentTableName(null);
    }else{
        this.setParentTableName(parentTableName);
    }
}

还有一种情况,如所有的配置都配置好了,我只需要在sql返回值里加一个简单的统计数,多返回一列,你可以这样写:

// 实体类定义
@Table(name="${_prefix}gen_table", alias="a", columns={
        // @Column 。。。此处省略 。。。
    },
    // 扩展Column里指定一个Key名字,类里并定义一个需要返回的属性和get set
    extColumnKeys="extColumn"
)
public classGenTableextendsDataEntity<GenTable>{
    private Long childNum;                // 子表个数
    publicLonggetChildNum(){
        return childNum;
    }
    publicvoidsetChildNum(Long childNum){
        this.childNum = childNum;
    }
}

// Service 里,通过sqlMap设置你刚定义的Key即可,如下
publicPage<GenTable> findPage(Page<GenTable> page, GenTable genTable){
    // 添加扩展列,查询子表个数
    String extColumn = "(SELECT count(1) FROM "+MapperHelper.getTableName(genTable)
        +" WHERE parent_table_name=a.table_name) AS \"childNum\"";
    genTable.getSqlMap().add("extColumn", extColumn);
    return super.findPage(page, genTable);
}

如果以上耐得不到你的满足,怎么办,那你可以写Mapper.xml了,比如EmployeeDao.xml一些通用的字段、条件,你就不需要在xml再写一遍了,你只需要补充SQL即可:

<selectid="findExtList"resultType="Employee">
    SELECT ${sqlMap.column.toSql('a')}
    FROM ${_prefix}sys_employee a
    <where>
        ${sqlMap.where.toSql('a')}</where>
    ORDER BY ${sqlMap.order.toSql('a')}</select>

这样的Dao,你满意吗?编码原来如此简单,提高了效率,又不损失灵活,是不是很有趣呢。

附:API

@Table

/**
 * 指定实体的物理表属性 
 * @author ThinkGem
 */
public @interface Table {

    /**
     * 物理表名
     */
    Stringname()default"";

    /**
     * 当前表别名
     */
    Stringalias()default"a";

    /**
     * 表列定义
     */
    Column[] columns();

    /**
     * 查询,关联表
     */
    JoinTable[] joinTable() default {};

    /**
     * 指定排序
     */
    StringorderBy()default"";

    /**
     * 表说明
     */
    Stringcomment()default"";

    /**
     * 扩展ColumnSQL,在这里指定sqlMap的key。<br>
     * 例如:\@Table(extColumnKeys="dataScopeColumn");<br>
     * Service里设置:sqlMap.put("extColumn", "column_name AS \"columnName\"");<br>
     * 在执行查询的时候,该语句放到自动会加到Where最后并执行。<br>
     * <b>注意:</b>如果设置,必须后台代码中设置,否则可能造成sql注入漏洞<br>
     */
    StringextColumnKeys()default"";

    /**
     * 扩展FromSQL,在这里指定sqlMap的key。<br>
     * 例如:\@Table(extFromKeys="dataScopeFrom");<br>
     * Service里设置:sqlMap.put("dataScopeFrom", "JOIN table_name t on t.pk=a.pk");<br>
     * 在执行查询的时候,该语句放到自动会加到Where最后并执行。<br>
     * <b>注意:</b>如果设置,必须后台代码中设置,否则可能造成sql注入漏洞<br>
     */
    StringextFromKeys()default"";

    /**
     * 扩展WhereSQL,在这里指定sqlMap的key。<br>
     * 例如:\@Table(extWhereKeys="dataScopeWhere");<br>
     * Service里设置:sqlMap.put("dataScopeWhere", "AND column_name='value'");<br>
     * 在执行查询的时候,该语句放到自动会加到Where最后并执行。<br>
     * <b>注意:</b>如果设置,必须后台代码中设置,否则可能造成sql注入漏洞<br>
     */
    StringextWhereKeys()default"";

}

@Column

/**
 * 定义物理表列属性(不继承父类注解)
 * @author ThinkGem
 */
public @interface Column {

    /**
     * 字段名(例如:config_key)
     */
    String name()default"";

    /**
     * 属性名,若不指定,则根据name()字段名进行驼峰命名法转换(例如:config_key 转换为   configKey)
     */
    String attrName()default"";

    /**
     * 属性名,定义的类型
     */
    Class<?> type() default Class.class;

    /**
     * 标签名
     */
    String label()default"";

    /**
     * 字段备注
     */
    String comment()default"";

    /**
     * 是否主键(update、delete时的条件)
     */
    booleanisPK()defaultfalse;

    /**
     * 是否插入字段
     */
    booleanisInsert()defaulttrue;

    /**
     * 是否更新字段
     */
    booleanisUpdate()defaulttrue;

    /**
     * 是否是查询字段
     */
    booleanisQuery()defaulttrue;

    /**
     * 查询类型
     */
    QueryType queryType()default QueryType.EQ;

    /**
     * 包含嵌入一个实体
     */
    Class<?> includeEntity() default Class.class;
}

@JoinTable

/**
 * 指定实体的物理表的关联表属性 
 * @author ThinkGem
 */
public @interface JoinTable {

    /**
     * 连接类型
     */
    Type type()default Type.JOIN;
    public enum Type{
        JOIN("JOIN"), // INNER JOIN
        LEFT_JOIN("LEFT JOIN"),
        RIGHT_JOIN("RIGHT JOIN");
        private final String value;
        Type(String value) { this.value = value; }
        public String value(){ return this.value; }
    }

    /**
     * 连接的表,指定实体Class
     */
    Class<?> entity();

    /**
     * 当前表别名
     */
    String alias();

    /**
     * 连接表条件
     */
    String on();

    /**
     * 对应主表中对应的属性名,若不指定,则根据entity()进行首字母小写得到属性名(例如:Config 转换为   config)
     */
    String attrName()default "";

    /**
     * 连接表,返回的列,若不指定,则读取entity()的所有列。
     */
    Column[] columns() default{};

}

QueryType

/**
 * 查询类型
 * @author ThinkGem
 */
public enumQueryType{

    EQ("="),
    NE("!="),
    GT(">"),
    GTE(">="),
    LT("<"),
    LTE("<="),
    IN("IN"),
    NOT_IN("NOT IN"),
    LIKE("LIKE", "%", "%"),
    LEFT_LIKE("LIKE", "%", ""),
    RIGHT_LIKE("LIKE", "", "%"),
    IS_NULL("IS NULL"),
    IS_NOT_NULL("IS NOT NULL"),

    // 强制条件,不管值是不是空字符串都加载这个查询条件
    EQ_FORCE("=", true),
    NE_FORCE("!=", true),

    ;
}
 

文章评论

旅行,写作,编程
旅行,写作,编程
为什么程序员都是夜猫子
为什么程序员都是夜猫子
中美印日四国程序员比较
中美印日四国程序员比较
程序员必看的十大电影
程序员必看的十大电影
十大编程算法助程序员走上高手之路
十大编程算法助程序员走上高手之路
老美怎么看待阿里赴美上市
老美怎么看待阿里赴美上市
科技史上最臭名昭著的13大罪犯
科技史上最臭名昭著的13大罪犯
程序员眼里IE浏览器是什么样的
程序员眼里IE浏览器是什么样的
2013年美国开发者薪资调查报告
2013年美国开发者薪资调查报告
 程序员的样子
程序员的样子
每天工作4小时的程序员
每天工作4小时的程序员
不懂技术不要对懂技术的人说这很容易实现
不懂技术不要对懂技术的人说这很容易实现
我的丈夫是个程序员
我的丈夫是个程序员
为啥Android手机总会越用越慢?
为啥Android手机总会越用越慢?
5款最佳正则表达式编辑调试器
5款最佳正则表达式编辑调试器
要嫁就嫁程序猿—钱多话少死的早
要嫁就嫁程序猿—钱多话少死的早
看13位CEO、创始人和高管如何提高工作效率
看13位CEO、创始人和高管如何提高工作效率
程序员最害怕的5件事 你中招了吗?
程序员最害怕的5件事 你中招了吗?
如何成为一名黑客
如何成为一名黑客
程序员周末都喜欢做什么?
程序员周末都喜欢做什么?
什么才是优秀的用户界面设计
什么才是优秀的用户界面设计
那些争议最大的编程观点
那些争议最大的编程观点
总结2014中国互联网十大段子
总结2014中国互联网十大段子
程序员都该阅读的书
程序员都该阅读的书
聊聊HTTPS和SSL/TLS协议
聊聊HTTPS和SSL/TLS协议
60个开发者不容错过的免费资源库
60个开发者不容错过的免费资源库
程序员应该关注的一些事儿
程序员应该关注的一些事儿
“肮脏的”IT工作排行榜
“肮脏的”IT工作排行榜
Google伦敦新总部 犹如星级庄园
Google伦敦新总部 犹如星级庄园
鲜为人知的编程真相
鲜为人知的编程真相
10个调试和排错的小建议
10个调试和排错的小建议
我跳槽是因为他们的显示器更大
我跳槽是因为他们的显示器更大
我是如何打败拖延症的
我是如何打败拖延症的
初级 vs 高级开发者 哪个性价比更高?
初级 vs 高级开发者 哪个性价比更高?
2013年中国软件开发者薪资调查报告
2013年中国软件开发者薪资调查报告
那些性感的让人尖叫的程序员
那些性感的让人尖叫的程序员
一个程序员的时间管理
一个程序员的时间管理
Web开发人员为什么越来越懒了?
Web开发人员为什么越来越懒了?
亲爱的项目经理,我恨你
亲爱的项目经理,我恨你
Java程序员必看电影
Java程序员必看电影
写给自己也写给你 自己到底该何去何从
写给自己也写给你 自己到底该何去何从
团队中“技术大拿”并非越多越好
团队中“技术大拿”并非越多越好
做程序猿的老婆应该注意的一些事情
做程序猿的老婆应该注意的一些事情
编程语言是女人
编程语言是女人
“懒”出效率是程序员的美德
“懒”出效率是程序员的美德
老程序员的下场
老程序员的下场
程序员的鄙视链
程序员的鄙视链
代码女神横空出世
代码女神横空出世
当下全球最炙手可热的八位少年创业者
当下全球最炙手可热的八位少年创业者
软件开发程序错误异常ExceptionCopyright © 2009-2015 MyException 版权所有