Tubida JDBC持久层框架使用

/ Java / 没有评论 / 2262浏览

框架定义

  1. 核心思想
  1. 自动生成
  1. Dao层基本业务逻辑
  1. 任务计划
  1. 性能监控

核心思想

SQL语句缓存和执行

定义缓存对象

public class UserDaoImpl extends BaseDaoImpl
{
    @Override
    protected void InitSql() {
        SqlTableData table = this.RegisterSql("System_User", UserModel.class);
	// 添加SQL语句在这里
    }
    // Todo : 当前对象其他方法在这里
}

其中 table 是缓存对象,可以在其中添加SQL语句用于执行;
System_User 为对应的数据库表名;
UserModel.class 为对应的实体类,第一个字段为主键;
在注册后,会自动根据实体创建对应的Create 、 Update 、Remove 、Load等SQL语句.

UserModel实体对应代码:

public class UserModel 
{
    /**
     * 员工编号
     */
    private String UserID;
    /**
     * 景区编号
     */
    private String ScenicID;
    /**
     * 员工名称
     */
    private String UserName;
    /**
     * 工号
     */
    private String UserCode;
    /**
     * 性别
     */
    private int UserSex;
    /**
     * 密码
     */
    private String UserPassword;
}

添加缓存SQL语句

//查询当前景区员工是否存在,用于登陆
table.AddSql("loginUserQuery_SQL",
	"SELECT * FROM SYSTEM_USER WHERE ScenicID=? AND (UserName=? OR UserCode=?) AND UserPassword=?",
	"scenic_id", "user", "user", "pwd"
);

添加了名为 loginUserQuery_SQL 的SQL语句;
该语句有4各参数,对应SQL语句中的 ,分别对应实体中的字段名称 ;
"scenic_id", "user", "user", "pwd" 为对象中的字段,SQL语句执行时会自动获取对象字段填充到SQL语句中。

获取缓存的SQL语句

添加了SQL语句后,可以通过名称来获取已经添加的SQL语句,并可以通过SQL语句来Copy生成新的SQL语句,用于执行。

 SqlData sql = this.getSql("loginUserQuery_SQL");
 List<UserModel> model = this.QueryData(UserModel.class ,  sql , inputObject );

inputObject 可以为 Map或者Object对象,其对应JSON格式为:

{
 "scenic_id" : 1 ,
 "user" : "yanzuoguang" ,
 "pwd" : "123456"
}

执行缓存的SQL语句

执行缓存的SQL语句有很多方法:

public UserModel getLoginUser(UserLoginRequest request) {
	request.setPwd(StringHelper.GetMD5(request.getPwd()));
	return this.QueryFirst(UserModel.class, "loginUserQuery_SQL", request);
}

UserLoginRequest 实体的代码:

@Data
public class UserLoginRequest
{
    /**
     * 景区编号
     */
    private String scenic_id;
    /**
     * 用户名
     */
    private String user;

    /**
     * 密码
     */
    private String pwd;
}

目前支持的方法有,具体执行效果后续解释:

执行非缓存的SQL语句(不建议,重复利用性差)

 public UserModel getLoginUser(UserLoginRequest request) {
    request.setPwd(StringHelper.GetMD5(request.getPwd()));
    List<UserModel> list = this.getDb().Query(UserModel.class,
        "SELECT * FROM SYSTEM_USER WHERE ScenicID=? 
            AND (UserName=? OR UserCode=?) AND UserPassword=?",
        request.getScenic_ID() , request.getUser() ,
        request.getUser() , request.getPwd();
    );
    return list.size() > 0 ? list.get(0) : null;
}

下面为缓存执行和非缓存执行优缺点:

名称缓存执行非缓存执行
效率稍慢
维护只需要维护实体和SQL语句需要维护实体、SQL语句、以及对应关系的参数顺序
代码长度稍短稍长
新手适应性不适应适应
扩展性更容易去进行扩展功能,如进行SQL语句语法检测不容易扩展
自动性可以根据规范、特点去自动生成SQL语句,放入缓存,用于执行不能自动生成SQL语句
使用建议使用不建议使用
适用仅仅适用于框架的相关代码可以任意组合,适用于任何功能,假如在框架不足时,可以用于自定义

代码片段的替换

名词解释

究竟什么叫代码片段呢,什么叫输入参数,什么叫条件.

public List<UserModel> getUserList(UserModel req){
    String sql = "SELECT * FROM System_User WHERE IsRemove = 0 ";
    List<Object> para = new ArrayList<Object>();
    if(!StringHelper.IsEmpty(req.getUserName())){
        sql += " AND UserName = ? ";
        para.Add(req.getUserName());
    }
    return this.getDb().Query(UserModel.class , sql , para.toArray());
}

其中 userName 是输入参数 ; " AND UserName = ? " 是条件 ; 条件{WHERE}的代码片段;

在我们看来,上面的这种写法很正常,但是假如条件达到20个呢? 那么你会发现代码里面全部都是 if 条件,代码的可读性是非常差.

我们会自动在SQL语句之后增加代码片段,用用SqlData执行SQL语句时,如:

SELECT * FROM System_User WHERE IsRemove = 0 {WHERE}{GROUP}{ORDER}{LIMIT}

当默认代码片段存在时,则不增加.

参数输入

参见如下SQL语句:

//查询当前景区员工是否存在,用于登陆
table.AddSql("loginUserQuery_SQL",
	"SELECT * FROM SYSTEM_USER WHERE ScenicID=? AND (UserName=? OR UserCode=?) AND UserPassword=?",
	"scenic_id", "user", "user", "pwd"
);

其中, "scenic_id", "user", "user", "pwd" 对应SQL语句中的四个 ? 号,用这种方式时我们的训序不能出错,并且所有的参数是必需输入的。

条件处理

条件分为必需输入的条件和非必需输入条件,那么我们这种情况该这么处理呢?参见上面的参数,可以改成如下SQL语句:

SqlData sql = table.AddSql("loginUserQuery_SQL",
	"SELECT * FROM SYSTEM_USER WHERE IsRemove = 0"
);
sql.Add("scenic_id", " AND ScenicID=?");
sql.Add("user", " AND (UserName=? OR UserCode=?) ");
sql.Add("pwd", " AND UserPassword=?");

这个表示,假如存在 scenic_id 时,则增加条件 " AND ScenicID=?"{WHERE}代码片段中。

假如要把其中的条件改为必需输入,则:

sql.Add("pwd", " AND UserPassword=?").auto = false ;

代码片段处理

如下代码为我们常见的拼接SQL语句的方式:

public List<UserModel> getUserList(UserModel req){
    String sql = "SELECT a.* FROM System_User AS a ";
    String sqlWhere = " WHERE a.IsRemove = 0 ";
    
    List<Object> para = new ArrayList<Object>();
    
    if(!StringHelper.IsEmpty(req.getUserName())){
        sqlWhere += " AND a.UserName = ? ";
        para.Add(req.getUserName());
    }
    if(!StringHelper.IsEmpty(req.getRoleName())){
        sql += " INNER JOIN System_Role AS b ON a.RoleID = b.RoleID "
        sqlWhere += " AND b.RoleName = ? ";
        para.Add(req.getRoleName());
    }
    return this.getDb().Query(UserModel.class , sql + sqlWhere , para.toArray());
}

可以看到,我们已经需要用两个字符串去拼接了,假如状态更多呢,难道我们程序里面要存在更多的IF条件吗? 哦,那我宁愿自杀;不过你不用自杀了,因为我已经解决了这个难题,我真是一个伟大的人,挽救了好多程序员的生命。 具体解决方式如下:

SQL语句定义:

SqlData sql = table.AddSql("query_SQL","SELECT a.* FROM System_User AS a {INNER} WHERE a.IsRemove = 0 ");
sql.Add("userName" , " AND a.UserName = ? ");
// 等于 sql.AddInputCode("userName" , "{WHERE}" , " AND a.UserName = ? ");
sql.Add("roleName" , " AND b.RoleName = ? ",
        "{INNER}" , " INNER JOIN System_Role AS b ON a.RoleID = b.RoleID "
    );
    

方法调用编写:

public List<UserModel> getUserList(UserModel req){
    return this.Query(UserModel.class , "query_SQL" , req );
}

"query_SQL" 为定义的名称,UserModel.class 指明结果的类型 ,req 为请求的参数.

可以看到的是,后一种方式代码可读性远远的好于前面一种代码拼接的方式,这样代码的后续维护性也会更好。

常规代码片段

常规代码片段指的是不论如何,这些代码片段都要增加上去,和前台任何参数输入无关。 如:分页

protected <T extends Object> List<T> QueryPageData(Class<T> cls, PageSize pageSize, String sqlName, Object model) {
    SqlData from = this.getSql(sqlName);

    // 对SQL语句进行分页处理
    SqlData to = from.Copy();
    to.Sql = from.Sql;
    to.AddCode("{LIMIT}", " LIMIT " + pageSize.getPageStart() + "," + pageSize.getPage_size());

    return QueryData(cls, to, model);
}

AddCode,添加常规代码片段,无任何输入参数,直接增加到指定位置,最主要是因为分页不支持 limit ?,?

高级应用

数组参数处理

列表参数处理


实体生成SQL语句

实现

实体类:

代码定义:

生成的语句

标记删除、版本号



任务计划

实现原理

增加数据

a. 在数据调用Create时,监控所有的数据创建事件

b. 判断是否属于 log_ 、report_ 开头的表,假如是开头的表则忽略

c. 将Create的表名、主键编号写入到 Log_TablePlanFromTemp 中

d. 执行实时数据计划,并写入到 Log_TablePlanTo表中

报表线程准备时:(单独一个线程)

a. 查询 Log_TablePlanFromTemp 表

b. 将 Log_TablePlanFromTemp 写入到 Log_TablePlanFrom中

c. 将 Log_TablePlanFromTemp 数据删除

b. 将 查询到的结果与离线报表类型关联,并将结果同时写入到 Log_TablePlanToTemp

报表线程执行时:(单独一个线程,会根据数据多少调用多线程处理)

a. 查询 Log_TablePlanToTemp、关联离线数据表表类型(表列表)中数据

b. 根据任务计划进行执行特定任务(类继承自Plan)

c. 执行失败,修改 Log_TablePlanToTemp 执行状态

d. 执行成功, 将执行结果存入 Log_TablePlanTo 中,并删除 Log_TablePlanToTemp 对应数据

报表线程启动时:(单独一个线程)

a. 关联查询 Log_TablePlanFrom、Log_TablePlanTo、所有执行类型的前10000条插入到 Log_TablePlanToTemp

b. 判断操作的数据的数量,当数量等于0时,则退出执行线程

传入表对象

需要代码片段名称定义以Table开头,如{Table}、{Table.A}、{TableB}等. 传入参数JSON:

{
	tables:[
		{ "TableFrom" : "net_used" , "PlanTo" : "net_used"  },
		{ "TableFrom" : "order_used" , "PlanTo" : "order_used"  }
	]
}

SQL语句源码定义:

SqlData sqlList = sql.AddSql(QueryList, 
"SELECT c.PlanToID,a.TableFrom,a.TableID,b.PlanTo,IFNULL(c.HandleStatus,0) AS HandleStatus " +
"FROM Log_TablePlanFrom AS a " +
"INNER JOIN Log_TablePlanType AS b ON a.TableFrom = b.TableFrom " +
"LEFT JOIN Log_TablePlanTo AS c ON a.TableID = c.TableID  " +
"    AND a.TableFrom = c.TableFrom AND b.PlanTo = c.PlanTo  "  +
"{Table} " +
"WHERE IFNULL(c.HandleStatus,0) IN ( 0 , 2 )");

sqlList.AddInputCode("tables",
                "{Table}", " INNER JOIN (?) t ON b.TableFrom=t.TableFrom AND b.PlanTo = t.PlanTo ");

最终SQL语句:

SELECT 
	c.PlanToID,a.TableFrom,a.TableID,b.PlanTo,IFNULL(c.HandleStatus,0) AS HandleStatus 
FROM Log_TablePlanFrom AS a 
INNER JOIN Log_TablePlanType AS b ON a.TableFrom = b.TableFrom 
LEFT JOIN Log_TablePlanTo AS c 
	ON a.TableID = c.TableID AND a.TableFrom = c.TableFrom AND b.PlanTo = c.PlanTo  
INNER JOIN ( 
	SELECT 'net_used'AS PlanTo,'net_used'AS TableFrom 
	UNION ALL  
	SELECT 'order_used'AS PlanTo,'order_used'AS TableFrom 
) t ON b.TableFrom=t.TableFrom AND b.PlanTo = t.PlanTo  
WHERE IFNULL(c.HandleStatus,0) IN ( 0 , 2 ) LIMIT 0,30000