框架定义
- 核心思想
- SQL语句缓存
- 代码片段的替换
- 数组参数处理
- 列表参数处理
- 自动生成
- 根据实体生成Create、Update、Remove、Load等SQL语句
- 生成AddGroup的SQL语句
- Dao层基本业务逻辑
- Create
- Update
- Remove
- Load
- Save , 存在主键则更新,不存在则创建
- Query
- SaveWith , 根据非主键字段进行保存.如权限明细表,根据角色编号、菜单编号进行保存
- AddGroup, 将数据添加到统计表 ,同纬度累加,不同纬度创建
- 记录创建、修改、加载、删除、登录等操作的人、设备、时间等属性到Log_Log表
- 启动创建的计划任务
- 任务计划
- 基本逻辑实现原理
- 对数据进行离线统计、实时统计等
- 通知其他系统
- 性能监控
- 记录慢SQL语句,方便对数据库性能进行优化
- SQL语句语法测试
- 逻辑检查
核心思想
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
本文由 创作,采用 知识共享署名4.0 国际许可协议进行许可。本站文章除注明转载/出处外,均为本站原创或翻译,转载前请务必署名。最后编辑时间为: 2020/05/06 13:27