preface
In our JavaWeb development process, there will be more or less just a few lines of SQL service methods or HTTP requests, always repeatedly write Controller layer, service layer, dao layer. So, I made a bold attempt to encapsulate a public method for this kind of method. Instead of writing Java code, I could write a few lines of SQL statements to generate various interfaces and methods.
Table design
id | description | SQL | creator | creattime | updatetime |
---|---|---|---|---|---|
notices | Get notification list | select * from notices where reciever ={userNo} | admin | The 2018-07-06 14:07:48 | The 2018-07-06 14:07:53 |
We select
id
Is the method name of the URL requestdescription
To describesql
Is a specific SQL statement- founder
- Create time and modify time
The first edition
The url request
Tentative method name at http://a.com/common/sqls/
Note: The method name is the ID in the table.
There is no limit to how many entries can be entered. The controller layer will be passed to the service layer. Of course, if you want to limit, you can also make a whitelist list of entries.
The Controller layer:
@RequestMapping(value = "/sql/{id}")
public ResultObject getRules(@PathVariable(value = "id") String id) {
ResultObject resultObject = new ResultObject();
Map<String, Object> params=getRequestParams();
validateParams(params, "token");
User user = loginService.findByToken(params.get("token").toString());
params.put("userNo",user.getUserNo());
List<Map<String,Object>> mapList=commonService.querySql(id,params);
resultObject.setData(mapList);
return resultObject;
}
Copy the code
The Controller layer converts all variables to a paramsMap, verifies the user token, and obtains the user No through the token. If there is No token or the token cannot obtain the user No, an exception is thrown. Get user No, put the value of user No into map, and finally pass the method name (ID) and map to the service layer.
CommonService:
/** * find the specific SQL statement * @param id * @return sql
*/
public String getSqlById(String id) {
returncommonDao.getSqlById(id); } /** * returns the general SQL execution result * @param id * @param params * @return list
*/
public List<Map<String, Object>> querySql(String id,Map<String, Object> params) {
String sql=getSqlById(id);
for (Map.Entry<String, Object> stringObjectEntry : params.entrySet()) {
sql=sql.replace("{"+stringObjectEntry.getKey()+"}"."'"+(String)stringObjectEntry.getValue()+"'");
}
return commonDao.querySql(sql);
}
Copy the code
In the first version, we replaced the SQL obtained from the method ID by iterating through all the parameters in the map, and then executed.
Interface:
public interface CommonDao {
String getSqlById(String id);
List<Map<String,Object>> querySql(String sql);
}
Copy the code
Mapper:
Mybatis version
<? xml version="1.0" encoding="UTF-8"? > <! DOCTYPE mapper PUBLIC"- / / mybatis.org//DTD Mapper / 3.0 / EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="*.basic.dao.CommonDao" >
<select id="getSqlById" resultType="string">
SELECT sql from m_sql WHERE id =#{value}
</select>
<select id="querySql" resultType="map">
${value}
</select>
</mapper>
Copy the code
GetRequestParams:
public Map<String, Object> getRequestParams() {
HttpServletRequest request = ((ServletRequestAttributes)RequestContextHolder.getRequestAttributes()).getRequest();
try {
request.setCharacterEncoding("UTF-8");
} catch (UnsupportedEncodingException var5) {
var5.printStackTrace();
}
Map<String, Object> params = new HashMap();
Enumeration names = request.getParameterNames();
while(names.hasMoreElements()) {
String name = (String)names.nextElement();
params.put(name, request.getParameter(name));
}
return params;
}
Copy the code
Well here, our first edition came out, let’s try the effect first!
Internal calls
Call commonService.querySql(ID,params) return (id,params).
The new request
When we want to add an interface to get users’ friends, we just add one to the database
id | description | SQL | creator | creattime | updatetime |
---|---|---|---|---|---|
friends | Get a list of friends | select * from freindss where userNo ={userNo} | admin | The 2018-07-06 14:07:48 | The 2018-07-06 14:07:53 |
call
http://localhost:8080/*/common/sql/friends?token=cc4771aebb444d6c928a61ba5fe1153e
Reference:
{” data “: [{” id” : “1”, “name” : “* *”}], “code” : 200, the “message” : “success”}
Such an interface to get friends would be fine. Of course, the actual requirement SQL may be complicated, but this will not affect our project execution.
The second edition
Although the first version is ok, there is obviously a fatal bug that can be injected, so we need to fix the injection problem in this version.
Thinking a
Filtering parameters:
public List<Map<String, Object>> querySql(String id,Map<String, Object> params) {
String sql=getSqlById(id);
if (params.entrySet().size()>5) {
throw new CommonException("Too many parameters, please delete some.");
}
for (Map.Entry<String, Object> stringObjectEntry : params.entrySet()) {
if (checksql((String)stringObjectEntry.getValue())) {
throw new CommonException("Unsafe request!");
}
sql=sql.replace("{"+stringObjectEntry.getKey()+"}"."'"+(String)stringObjectEntry.getValue()+"'");
}
if (checksqlSecond(sql)) {
throw new CommonException("SQL parameter is invalid! Cannot contain update, delete, etc.");
}
return commonDao.querySql(sql);
}
private boolean checksql(String sql) {
if (sql.length()>50) {
return true;
}
if(! sql.equals(transactSQLInjection(sql))) {return true;
}
if (sqlValidate(sql)) {
return true;
}
return false;
}
private boolean checksqlSecond(String sql) {
String temp_sql=sql.toLowerCase();
if (temp_sql.contains("delete")||temp_sql.contains("update")||temp_sql.contains("truncate")||temp_sql.contains("drop")) {
return true;
}
return false;
}
private String transactSQLInjection(String str)
{
return str.replaceAll(". * ([';] + + | (-)). *"."");
}
private static boolean sqlValidate(String str) {
str = str.toLowerCase();
String badStr = "'|and|exec|execute|insert|select|delete|update|count|drop|*|%|chr|mid|master|truncate|" +
"char|declare|sitename|net user|xp_cmdshell|; |or|-|+|,|like'|and|exec|execute|insert|create|drop|" +
"table|from|grant|use|group_concat|column_name|" +
"information_schema.columns|table_schema|union|where|select|delete|update|order|by|count|*|" +
"chr|mid|master|truncate|char|declare|or|; |-|--|+|,|like|//|/|%|#"; String[] badStrs = badstr.split ("\ \ |");
for (int i = 0; i < badStrs.length; i++) {
if (str.indexOf(badStrs[i]) >= 0) {
return true; }}return false;
}
Copy the code
We use re to filter sensitive characters. In order to prevent too many input parameters from affecting our re matching, we limit 5 input parameters and limit the value of each parameter not to exceed 50.
This is done immediately to all kinds of abuse, good water code, why not precompile? So let’s keep exploring
Idea 2
Precompile parameters:
String patt = "\ \ {+? }";
String querySql=sql.replaceAll(patt,"?");
Pattern r = Pattern.compile(patt);
Matcher m = r.matcher(sql);
List<String> list= new ArrayList<String>();
while(m.find()){
list.add(m.group());
}
try {
PreparedStatement preparedStatement = conn.prepareStatement(querySql);
for (int i = 0; i < list.size(); i++) {
preparedStatement.setString(i+1,params.get(list.get(i).substring(1,list.get(i).length()-1));
}
preparedStatement.executeUpdate(sql_update);
}catch(Exception e){
//e.printStackTrace();
logger.error(e.message());
}
Copy the code
We start with the regular substitution
select * from notices where reciever={userNo} and isRead={isRead}
Copy the code
Replace with
select * from notices where reciever=? and isRead=?
Copy the code
Add {userNo} and {isRead} to the list, and finally pass the userNo and isRead values of the list elements into preparedStatement.
There are also other solutions, such as using mybatis SQL constructor; Use other SQL precompilation frameworks, etc.
The third version:
Now that our security problem is solved, let’s append some public methods, such as the map containing the user ID, but not the user name, and we need to display the user name. If SQL associations are used, associations make SQL more complex. Here, we encapsulate some common methods, such as user ID to name and group ID to groupName.
Table transformation
Add two fields: input method and output method
id | description | SQL | inmethod | outmethod | creator |
---|---|---|---|---|---|
notices | Get notification list | select * from notices where reciever =#{userNo} | usertoken2id | userid2name,groupid2name | admin |
Here we support comma-separated methods, identifying methods and appending them to params as follows:
String[] inMethodsplit = inMethod.split(",");
for (String s : inMethodsplit) {
switch (s){
case "usertoken2id":
params.add("userName",usertoken2id(params.get("userid")));
break;
case "xxxx":
params.add("xx",xxmethod(params.get("userxx")));
break; }}Copy the code
Switch can maintain your own company’s internal common methods to reduce SQL writing.
As for the reference, I think we all understand, so I won’t introduce it here.
conclusion
At this point, a framework for SQL programming is written, write a section of SQL, write a few public methods (optional), you can complete an HTTP interface or ordinary Java method, is not very convenient, there is no impulse to try.
This paper is a new attempt on development, but also a exploration of SQL programming. (Actually, I don’t know if I can call it SQL-oriented programming, so I’m just going to call it that.) If you have any new ideas and ideas, welcome to leave a message.
Keep an eye on me!