JeeSite 4.0 简化MyBatis持久层开发_JAVA_编程开发_程序员俱乐部

中国优秀的程序员网站程序员频道CXYCLUB技术地图
热搜:
更多>>
 
您所在的位置: 程序员俱乐部 > 编程开发 > JAVA > JeeSite 4.0 简化MyBatis持久层开发

JeeSite 4.0 简化MyBatis持久层开发

 2017/8/6 21:31:23  thinkgem  程序员俱乐部  我要评论(0)
  • 摘要:引言更好的阅读体验点这里:https://my.oschina.net/thinkgem/blog/1503611在做这一方面研究的时候,本人参考了很多资料和框架,如MyBatis-Mapper、MyBatis-Plus等等,这些都做的很不错,本来想集成一个,尝试了下还是有多处地方不顺手,不易扩展,不能解决我的本意,既能使用方便又不能失灵活,所以决定自己试着完成一套Dao层架构,精简开发。在此之前我先考虑API的写法,通俗易懂,大众思维。持久层实体类采用@Table注解配置
  • 标签:JEE 开发

?

引言

更好的阅读体验点这里: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:

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

class="hljs less">@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;
    function">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),

    ;
}
上一篇: Topshelf结合Quartz.NET实现服务端定时调度任务 下一篇: 没有下一篇了!
发表评论
用户名: 匿名