IDEA + SSM + Oracle
暂无
这里与我们平时的查询不太一样,可以由用户控制查询操作符。这样相当于把操作权交给用户,我们想到的就是SQL注入,那Mybatis怎么实现注入呢?
我们平时使用的基本都是 #{field}
,这样的好处是防止SQL注入,但这里我们想使用注入SQL,可以使用 ${field}
,有什么区别呢?
比较两条SQL语句:drop table ${tableName};
与 drop table #{tableName};
当那么name=sys_user
时,使用 ${tableName}
对应下来就是drop table sys_user;
,使用 #{tableName}
的就是drop table "sys_user";
只有drop table sys_user
才是符合我们的预期的想要得到的结果。
这时候应该使用${tableName}
代码
<% layout("/layouts/microHplus.html",{title:"登记表管理"}){ %>
<link rel="stylesheet" href="${ctxStatic }/fsdq/css/fsdqDjb.css" media="all"/>
<style>
.error {
z-index: 999999;
}
</style>
<div class="gray-bg">
<form id="inputForm" action="${ctx}/fsdq/djb/composeQueryList" method="get" class="form-horizontal">
<div class="row">
<div class="col-sm-12">
<div class="ibox float-e-margins">
<div class="ibox-title">
<h5>组合查询</h5>
<div class="ibox-tools">
<a class="collapse-link">
<i class="fa fa-chevron-up"></i>
</a>
</div>
</div>
<div class="ibox-content">
<input type="button" class="btn btn-w-m btn-primary" onclick="addTrCx('cxTable', -1)" value="添加">
<input type="button" class="btn btn-w-m btn-danger" onclick="delTr('cxId')" value="删除">
<input type="submit" class="btn btn-w-m btn-info" value="重置" >
<input type="submit" class="btn btn-w-m btn-success" value="查询" >
<table class="table table-bordered" id="cxTable">
<thead>
<th><input id="allCx" type="checkbox" disabled/></th>
<th>搜索字段</th>
<th>搜索条件</th>
<th>搜索内容</th>
</thead>
<tbody>
<tr>
<td><input type='checkbox' name='cxId' disabled/></td>
<td>
<!-- 必须和数据库字段一致 -->
<select class="form-control" name="fsdqFamilySelectList[0].queryField">
<option value="">请选择</option>
<option value="name">母亲名字</option>
<option value="age">年龄</option>
<option value="education">文化程度</option>
<option value="political">政治面貌</option>
<option value="reportdate">申报时间</option>
</select>
</td>
<td>
<select class="form-control" name="fsdqFamilySelectList[0].queryCondition">
<option value="">请选择</option>
<option value="" disabled>-------- 字符型 --------</option>
<option value="andLikeStr">包含 (搜索的内容中间包含所输入的查询条件值)</option>
<option value="andStr">等于 (搜索的内容等于所输入的查询条件值)</option>
<option value="andNotLikeStr">不等于 (搜索的内容不等于所输入的查询条件值)</option>
<option value="" disabled>-------- 日期型 --------</option>
<option value="andEqualDate">等于 (搜索的日期等于所输入的查询条件值)</option>
<option value="andGtDate">晚于 (搜索的日期晚于所输入的查询条件值)</option>
<option value="andGtEqualDate">晚于或等于 (搜索的日期晚于或等于所输入的查询条件值)</option>
<option value="andLtDate">早于 (搜索的日期早于所输入的查询条件值)</option>
<option value="andLtEqualDate">早于或等于 (搜索的日期早于或等于所输入的查询条件值)</option>
<option value="" disabled>-------- 数字型 --------</option>
<option value="andInteger">等于 (搜索的字段等于所输入的查询条件值)</option>
<option value="andGtInteger">大于 (搜索的字段大于所输入的查询条件值)</option>
<option value="andGtEqualInteger">大于或等于 (搜索的字段大于或等于所输入的查询条件值)</option>
<option value="andLtInteger">小于 (搜索的字段小于所输入的查询条件值)</option>
<option value="andLtEqualInteger">小于或等于 (搜索的字段小于或等于所输入的查询条件值)</option>
</select>
</td>
<td>
<input type="text" class="form-control" name="fsdqFamilySelectList[0].queryValue" autocomplete="off" placeholder="请填写">
</td>
</tr>
</tbody>
</table>
<table class="layui-hide" id="test" lay-filter="test"></table>
</div>
</div>
</div>
</div>
</form>
</div>
<script src="${ctxStatic }/fsdq/js/fsdqDjb.js?v=1.0.1"></script>
<script>
var cxIndex = 1;
function addTrCx(tab, row) {
var trHtml = "<tr>\n" +
" <td><input type='checkbox' name='cxId'/></td>\n" +
" <td>\n" +
" <select class=\"form-control\" name=\"fsdqFamilySelectList[" + cxIndex +"].queryField\">\n" +
" <option value=''>请选择</option>\n" +
" <option value=\"name\">母亲名字</option>\n" +
" <option value=\"age\">年龄</option>\n" +
" <option value=\"education\">文化程度</option>\n" +
" <option value=\"political\">政治面貌</option>\n" +
" <option value=\"reportdate\">申报时间</option>\n" +
" </select>\n" +
" </td>\n" +
"\n" +
" <td>\n" +
" <select class=\"form-control\" name=\"fsdqFamilySelectList[" + cxIndex +"].queryCondition\">\n" +
" <option value=''>请选择</option>\n" +
" <option value=\"\" disabled>-------- 字符型 --------</option>\n" +
" <option value=\"andLikeStr\">包含 (搜索的内容中间包含所输入的查询条件值)</option>\n" +
" <option value=\"andStr\">等于 (搜索的内容等于所输入的查询条件值)</option>\n" +
" <option value=\"andNotLikeStr\">不等于 (搜索的内容不等于所输入的查询条件值)</option>\n" +
" <option value=\"\" disabled>-------- 日期型 --------</option>\n" +
" <option value=\"andEqualDate\">等于 (搜索的日期等于所输入的查询条件值)</option>\n" +
" <option value=\"andGtDate\">晚于 (搜索的日期晚于所输入的查询条件值)</option>\n" +
" <option value=\"andGtEqualDate\">晚于或等于 (搜索的日期晚于或等于所输入的查询条件值)</option>\n" +
" <option value=\"andLtDate\">早于 (搜索的日期早于所输入的查询条件值)</option>\n" +
" <option value=\"andLtEqualDate\">早于或等于 (搜索的日期早于或等于所输入的查询条件值)</option>\n" +
" <option value=\"\" disabled>-------- 数字型 --------</option>\n" +
" <option value=\"andInteger\">等于 (搜索的字段等于所输入的查询条件值)</option>\n" +
" <option value=\"andGtInteger\">大于 (搜索的字段大于所输入的查询条件值)</option>\n" +
" <option value=\"andGtEqualInteger\">大于或等于 (搜索的字段大于或等于所输入的查询条件值)</option>\n" +
" <option value=\"andLtInteger\">小于 (搜索的字段小于所输入的查询条件值)</option>\n" +
" <option value=\"andLtEqualInteger\">小于或等于 (搜索的字段小于或等于所输入的查询条件值)</option>\n" +
" </select>\n" +
" </td>\n" +
"\n" +
" <td>\n" +
" <input type=\"text\" class=\"form-control\" name=\"fsdqFamilySelectList[" + cxIndex +"].queryValue\" autocomplete=\"off\" placeholder=\"请填写\">\n" +
" </td>\n" +
" </tr>";
addTr(tab, row, trHtml);
cxIndex++;
}
</script>
<script>
layui.use(['form','layer','laydate','table','laytpl'],function(){
var form = layui.form,
layer = parent.layer === undefined ? layui.layer : top.layer,
$ = layui.jquery,
laydate = layui.laydate,
laytpl = layui.laytpl,
table = layui.table;
table.render({
elem: '#test'
// , url: '${ctx}/fsdq/djb/composeQueryListAjax'
, data: ${composeQueryListJson}
, toolbar: '#toolbarDemo'
, title: '母亲家庭报表'
, cols: [[
{type: 'numbers',title:'序号'},
{field:'name', title: '姓名'},
{field: 'age', title: '年龄'},
{field:'birthday', title: '出生年月'},
{field:'political', title: '政治面貌'},
{field:'employment', title: '就业情况'},
{field:'education', title: '文化程度'},
{field:'income', title: '家庭月收入(元)'},
{field:'insured', title: '低保情况'},
]]
});
});
</script>
<%}%>
fsdqDjb.js
////////添加一行、删除一行封装方法///////
/**
* 为table指定行添加一行
*
* tab 表id
* row 行数,如:0->第一行 1->第二行 -2->倒数第二行 -1->最后一行
* trHtml 添加行的html代码
*
*/
function addTr(tab, row, trHtml) {
//获取table最后一行 $("#tab tr:last")
//获取table第一行 $("#tab tr").eq(0)
//获取table倒数第二行 $("#tab tr").eq(-2)
var $tr = $("#" + tab + " tr").eq(row);
$tr.after(trHtml);
}
/**
* 删除单个
* @param ckb
*/
function delTr(ckb) {
//获取选中的复选框,然后循环遍历删除
var ckbs = $("input[name=" + ckb + "]:checked");
ckbs.each(function() {
$(this).parent().parent().remove();
});
}
/**
* 全选
* allCkb 全选复选框的id
* items 复选框的name
*/
function allCheck(allCkb, items) {
$("#" + allCkb).click(function() {
$('[name=' + items + ']:checkbox').attr("checked", this.checked);
});
}
/** 删除 tr */
// function delTr(name) {
// delTr(name);
// }
使用组合对象集合接收数据
FsdqFamily.java
private List<FsdqFamilySelect> fsdqFamilySelectList;
FsdqFamilySelect.java
public class FsdqFamilySelect extends DataEntity<FsdqFamilySelect> {
private String queryField; // 查询的字段
private String queryCondition; // 查询的条件
private String queryValue; // 查询的值
public String getQueryField() {
return queryField;
}
public void setQueryField(String queryField) {
this.queryField = queryField;
}
public String getQueryCondition() {
return queryCondition;
}
public void setQueryCondition(String queryCondition) {
this.queryCondition = queryCondition;
}
public String getQueryValue() {
return queryValue;
}
public void setQueryValue(String queryValue) {
this.queryValue = queryValue;
}
@Override
public String toString() {
return "FsdqFamilySelect{" +
"queryField='" + queryField + '\'' +
", queryCondition='" + queryCondition + '\'' +
", queryValue='" + queryValue + '\'' +
'}';
}
}
接收到前端传递的参数
开始处理
/**
* 注入查询
* @author Ray
*/
public List<FsdqFamily> composeQuery(FsdqFamily fsdqFamily) {
List<FsdqFamily> fsdqFamilyList = new ArrayList<>();
try {
if (ObjectUtil.isNotNull(fsdqFamily)
&& CollectionUtil.isNotEmpty(fsdqFamily.getFsdqFamilySelectList())
&& !fsdqFamily.getFsdqFamilySelectList().isEmpty()) {
StringBuilder sqlString = new StringBuilder();
InfusionUtils utils = new InfusionUtils();
for (FsdqFamilySelect select :
fsdqFamily.getFsdqFamilySelectList()) {
if (StringUtils.isNotBlank(select.getQueryCondition())
&& StringUtils.isNotBlank(select.getQueryCondition())
&& StringUtils.isNotBlank(select.getQueryValue())) {
Method method = utils.getClass().getMethod(select.getQueryCondition(), String.class, String.class, String.class);
String sql = (String) method.invoke(utils, InfusionUtils.TABLE_ALIAS, select.getQueryField(), select.getQueryValue());
sqlString.append(sql);
}
}
if (StringUtils.isNotBlank(sqlString.toString())) {
fsdqFamilyList = fsdqFamilyDao.composeQuery(sqlString.toString());
}
}
} catch (Exception e) {
e.printStackTrace();
}
return fsdqFamilyList;
}
注意
Method method = utils.getClass().getMethod(select.getQueryCondition(), String.class, String.class, String.class);
这里我们使用了反射的知识,调用方法要指定参数(不然会找不到对应的方法,注意这个坑),这里我们的参数都是String.class
的,只是方法名不一样,而我们的方法名是由select.getQueryCondition()
控制的,跟参数耦合在一起了。
然后调用该方法,就可以得到需要注入的SQL语句
String sql = (String) method.invoke(utils, InfusionUtils.TABLE_ALIAS, select.getQueryField(), select.getQueryValue());
得到注入的SQL语句
@MyBatisDao
public interface FsdqFamilyDao extends CrudDao<FsdqFamily> {
List<FsdqFamily> composeQuery(@Param(value = "sqlString") String sqlString);
}
<select id="composeQuery" resultType="com.dfht.modules.fsdq.entity.FsdqFamily">
SELECT
<include refid="fsdqFamilyColumns"/>
FROM
fsdq_family a
<where>
a.del_flag = 0
<if test="sqlString != null and sqlString != ''">
${sqlString}
</if>
</where>
</select>
得到的SQL语句
SELECT * WHERE a.del_flag = 0 AND a.draft_flag = '1' AND a.name LIKE '%小红%' AND a.age > 22
自己封装的工具类,简单的生成注入的SQL语句
package com.dfht.modules.fsdq.util;
import org.apache.commons.lang3.StringUtils;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
/**
* @Description: sql注入,打死不改版
* @Author Ray
* @Date 2020/09/10 9:07
* @Version 1.0
*/
public class InfusionUtils {
public static final String AND = " AND ";
public static final String LIKE = " LIKE ";
public static final String NOT = " NOT ";
public static final String EQUAL = " = ";
public static final String SINGLE_QUOTES = "'";
public static final String PERCENT_SIGN = "%";
public static final String LT = " < ";
public static final String LT_EQUAL = " <= ";
public static final String GT = " > ";
public static final String GT_EQUAL = " >= ";
public static final String TO_DATE_START = "to_date('";
public static final String TO_DATE_END = "','yyyy-mm-dd hh24:mi:ss')";
public static final String PATTERN_DEFAULT = "yyyy-MM-dd HH:mm:ss";
public static final String TABLE_ALIAS = "a";
/**
* 字符串型 -- 包含 -- 搜索的内容中间包含所输入的查询条件值。
* @param alias 数据库别名
* @param sqlField 数据库字段名
* @param value 查询的值 字符串
* @return
*/
public static String andLikeStr(String alias, String sqlField, String value) {
StringBuilder sqlString = new StringBuilder();
sqlString.append(AND);
sqlString.append(StringUtils.isNotBlank(alias) ? alias + "." : "");
sqlString.append(sqlField);
sqlString.append(LIKE);
sqlString.append(SINGLE_QUOTES).append(PERCENT_SIGN)
.append(value)
.append(PERCENT_SIGN).append(SINGLE_QUOTES);
return sqlString.toString();
}
/**
* 字符串型 -- 等于 -- 搜索的内容等于所输入的查询条件值。
* @param alias 数据库别名
* @param sqlField 数据库字段名
* @param value 查询的值 字符串
* @return
*/
public static String andStr(String alias, String sqlField, String value) {
StringBuilder sqlString = new StringBuilder();
sqlString.append(AND);
sqlString.append(StringUtils.isNotBlank(alias) ? alias + "." : "");
sqlString.append(sqlField);
sqlString.append(EQUAL);
sqlString.append(SINGLE_QUOTES).append(value).append(SINGLE_QUOTES);
return sqlString.toString();
}
/**
* 字符串型 -- 不等于 -- 搜索的内容不等于所输入的查询条件值。
* @param alias 数据库别名
* @param sqlField 数据库字段名
* @param value 查询的值 字符串
* @return
*/
public static String andNotLikeStr(String alias, String sqlField, String value) {
StringBuilder sqlString = new StringBuilder();
sqlString.append(AND);
sqlString.append(StringUtils.isNotBlank(alias) ? alias + "." : "");
sqlString.append(sqlField);
sqlString.append(NOT);
sqlString.append(LIKE);
sqlString.append(SINGLE_QUOTES).append(PERCENT_SIGN)
.append(value)
.append(PERCENT_SIGN).append(SINGLE_QUOTES);
return sqlString.toString();
}
/**
* 日期型 -- 等于 -- 搜索的日期等于所输入的查询条件值。
* @param alias 数据库别名
* @param sqlField 数据库字段名
* @param value 查询的值 日期 yyyy-mm-dd hh24:mi:ss 格式
* @return
*/
public static String andEqualDate(String alias, String sqlField, String value) {
if (!isDateVail(value)) {
return "";
}
StringBuilder sqlString = new StringBuilder();
sqlString.append(AND);
sqlString.append(StringUtils.isNotBlank(alias) ? alias + "." : "");
sqlString.append(sqlField);
sqlString.append(EQUAL);
sqlString.append(TO_DATE_START).append(value).append(TO_DATE_END);
return sqlString.toString();
}
/**
* 日期型 -- 晚于 -- 搜索的日期晚于所输入的查询条件值。
* @param alias 数据库别名
* @param sqlField 数据库字段名
* @param value 查询的值 日期 yyyy-mm-dd hh24:mi:ss 格式
* @return
*/
public static String andGtDate(String alias, String sqlField, String value) {
if (!isDateVail(value)) {
return "";
}
StringBuilder sqlString = new StringBuilder();
sqlString.append(AND);
sqlString.append(StringUtils.isNotBlank(alias) ? alias + "." : "");
sqlString.append(sqlField);
sqlString.append(GT);
sqlString.append(TO_DATE_START).append(value).append(TO_DATE_END);
return sqlString.toString();
}
/**
* 日期型 -- 晚于或等于 -- 搜索的日期晚于或等于所输入的查询条件值。
* @param alias 数据库别名
* @param sqlField 数据库字段名
* @param value 查询的值 日期 yyyy-mm-dd hh24:mi:ss 格式
* @return
*/
public static String andGtEqualDate(String alias, String sqlField, String value) {
if (!isDateVail(value)) {
return "";
}
StringBuilder sqlString = new StringBuilder();
sqlString.append(AND);
sqlString.append(StringUtils.isNotBlank(alias) ? alias + "." : "");
sqlString.append(sqlField);
sqlString.append(GT_EQUAL);
sqlString.append(TO_DATE_START).append(value).append(TO_DATE_END);
return sqlString.toString();
}
/**
* 日期型 -- 早于 -- 搜索的日期早于所输入的查询条件值。
* @param alias 数据库别名
* @param sqlField 数据库字段名
* @param value 查询的值 日期 yyyy-mm-dd hh24:mi:ss 格式
* @return
*/
public static String andLtDate(String alias, String sqlField, String value) {
if (!isDateVail(value)) {
return "";
}
StringBuilder sqlString = new StringBuilder();
sqlString.append(AND);
sqlString.append(StringUtils.isNotBlank(alias) ? alias + "." : "");
sqlString.append(sqlField);
sqlString.append(LT);
sqlString.append(TO_DATE_START).append(value).append(TO_DATE_END);
return sqlString.toString();
}
/**
* 日期型 -- 早于或等于 -- 搜索的日期早于或等于所输入的查询条件值。
* @param alias 数据库别名
* @param sqlField 数据库字段名
* @param value 查询的值 日期 yyyy-mm-dd hh24:mi:ss 格式
* @return
*/
public static String andLtEqualDate(String alias, String sqlField, String value) {
if (!isDateVail(value)) {
return "";
}
StringBuilder sqlString = new StringBuilder();
sqlString.append(AND);
sqlString.append(StringUtils.isNotBlank(alias) ? alias + "." : "");
sqlString.append(sqlField);
sqlString.append(LT_EQUAL);
sqlString.append(TO_DATE_START).append(value).append(TO_DATE_END);
return sqlString.toString();
}
/**
* 数字型 -- 等于 -- 搜索的字段等于所输入的查询条件值。
* @param alias 数据库别名
* @param sqlField 数据库字段名
* @param value 查询的值 数值
* @return
*/
public static String andInteger(String alias, String sqlField, String value) {
StringBuilder sqlString = new StringBuilder();
sqlString.append(AND);
sqlString.append(StringUtils.isNotBlank(alias) ? alias + "." : "");
sqlString.append(sqlField);
sqlString.append(EQUAL);
sqlString.append(value);
return sqlString.toString();
}
/**
* 数字型 -- 大于 -- 搜索的字段大于所输入的查询条件值。
* @param alias 数据库别名
* @param sqlField 数据库字段名
* @param value 查询的值 数值
* @return
*/
public static String andGtInteger(String alias, String sqlField, String value) {
StringBuilder sqlString = new StringBuilder();
sqlString.append(AND);
sqlString.append(StringUtils.isNotBlank(alias) ? alias + "." : "");
sqlString.append(sqlField);
sqlString.append(GT);
sqlString.append(value);
return sqlString.toString();
}
/**
* 数字型 -- 大于或等于 -- 搜索的字段大于或等于所输入的查询条件值。
* @param alias 数据库别名
* @param sqlField 数据库字段名
* @param value 查询的值 数值
* @return
*/
public static String andGtEqualInteger(String alias, String sqlField, String value) {
StringBuilder sqlString = new StringBuilder();
sqlString.append(AND);
sqlString.append(StringUtils.isNotBlank(alias) ? alias + "." : "");
sqlString.append(sqlField);
sqlString.append(GT_EQUAL);
sqlString.append(value);
return sqlString.toString();
}
/**
* 数字型 -- 小于 -- 搜索的字段小于所输入的查询条件值。
* @param alias 数据库别名
* @param sqlField 数据库字段名
* @param value 查询的值 数值
* @return
*/
public static String andLtInteger(String alias, String sqlField, String value) {
StringBuilder sqlString = new StringBuilder();
sqlString.append(AND);
sqlString.append(StringUtils.isNotBlank(alias) ? alias + "." : "");
sqlString.append(sqlField);
sqlString.append(LT);
sqlString.append(value);
return sqlString.toString();
}
/**
* 数字型 -- 小于或等于 -- 搜索的字段小于或等于所输入的查询条件值。
* @param alias 数据库别名
* @param sqlField 数据库字段名
* @param value 查询的值 数值
* @return
*/
public static String andLtEqualInteger(String alias, String sqlField, String value) {
StringBuilder sqlString = new StringBuilder();
sqlString.append(AND);
sqlString.append(StringUtils.isNotBlank(alias) ? alias + "." : "");
sqlString.append(sqlField);
sqlString.append(LT_EQUAL);
sqlString.append(value);
return sqlString.toString();
}
/**
* 判断输入的字符串是否满足时间格式 : yyyy-MM-dd HH:mm:ss
* @param patternString 需要验证的字符串
* @return 合法返回 true ; 不合法返回false
*/
private static boolean isDateVail(String patternString) {
//用于指定 日期/时间 模式
DateTimeFormatter dtf = DateTimeFormatter.ofPattern(PATTERN_DEFAULT);
boolean flag = true;
try {
//Java 8 新添API 用于解析日期和时间
LocalDateTime.parse(patternString, dtf);
} catch (Exception e) {
flag = false;
}
return flag;
}
public static void main(String[] args) {
String strAnd = andStr("a", "name", "123");
System.out.println("strAnd = " + strAnd);
String andLikeStr = andLikeStr("a", "name", "123");
System.out.println("andLikeStr = " + andLikeStr);
String andNotLikeStr = andNotLikeStr("a", "name", "123");
System.out.println("andNotLikeStr = " + andNotLikeStr);
String dateAndEqual = andEqualDate("a", "REPORTDATE", "2020-09-02 00:00:00");
System.out.println("dateAndEqual = " + dateAndEqual);
String dateAndGt = andGtDate("a", "REPORTDATE", "2020-09-02 00:00:00");
System.out.println("dateAndGt = " + dateAndGt);
String dateAndGtEqual = andGtEqualDate("a", "REPORTDATE", "2020-09-02 00:00:00");
System.out.println("dateAndGtEqual = " + dateAndGtEqual);
String dateAndLt = andLtDate("a", "REPORTDATE", "2020-09-02 00:00:00");
System.out.println("dateAndLt = " + dateAndLt);
String dateAndLtEqual = andLtEqualDate("a", "REPORTDATE", "2020-09-02 00:00:00");
System.out.println("dateAndLtEqual = " + dateAndLtEqual);
String integerAnd = andInteger("a", "age", "31");
System.out.println("integerAnd = " + integerAnd);
String integerGtAnd = andGtInteger("a", "age", "31");
System.out.println("integerGtAnd = " + integerGtAnd);
String integerGtEqualAnd = andGtEqualInteger("a", "age", "31");
System.out.println("integerGtEqualAnd = " + integerGtEqualAnd);
String integerLtAnd = andLtInteger("a", "age", "31");
System.out.println("integerLtAnd = " + integerLtAnd);
String integerLtEqualAnd = andLtEqualInteger("a", "age", "31");
System.out.println("integerLtEqualAnd = " + integerLtEqualAnd);
//System.out.println(isDateVail("2020-09-02 00:00:00"));
//System.out.println(isDateVail("2020-09-02"));
}
}
传入两个实体类,可以比较指定字段或全部字段,如果值一致跳过,如果值不一致,则记录在Map里。
package com.dfht.modules.fsdq.util;
import cn.hutool.core.util.ObjectUtil;
import io.swagger.annotations.ApiModelProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.sql.Timestamp;
import java.util.*;
import java.util.function.IntFunction;
/**
* @Description: 数据差异比较
* @Author Ray
* @Date 2020/09/11 15:16
* @Version 1.0
*/
public class CompareFields {
private static Logger log = LoggerFactory.getLogger(CompareFields.class);
/**
* 比较两个实体属性值,返回一个map以有差异的属性名为key,value为一个list分别存beforeObj,afterObj此属性名的值
* @param beforeObj 进行属性比较的对象 before
* @param afterObj 进行属性比较的对象 after
* @param compareArr 选择要比较的属性数组 1.指定属性 {xxx} 2.全部属性 {} 或 null
* @param nonCompareArr 不比较的属性数字
* @return 属性差异比较结果 Map<String, Map>
*/
public static Map<String, Map> compareFields(Object beforeObj, Object afterObj, String[] compareArr, String[] nonCompareArr) {
try {
//装返回值得
Map<String, Map> map = new LinkedHashMap<>();
//需要对比的字段list
List<String> compareList = null;
//不需要对比的字段list
List<String> nonCompareList = null;
if (compareArr != null && compareArr.length > 0) {
// array转化为list
compareList = Arrays.asList(compareArr);
}
if (nonCompareArr != null && nonCompareArr.length > 0) {
// array转化为list
nonCompareList = Arrays.asList(nonCompareArr);
}
// 只有两个对象都是同一类型的才有可比性
if (beforeObj.getClass() == afterObj.getClass()) {
Class clazz = beforeObj.getClass();
// 获取object的属性描述
PropertyDescriptor[] pds = Introspector.getBeanInfo(clazz,
Object.class).getPropertyDescriptors();
// 这里就是所有的属性了
for (PropertyDescriptor pd : pds) {
// 属性名
String name = pd.getName();
// 跳过
if (nonCompareList.contains(name)) {
continue;
}
// 指定属性判断
boolean condition1 = compareList != null && compareList.contains(name);
// 全部属性判断
boolean condition2 = compareList == null;
if (condition1 || condition2) {
map.putAll(handleData(clazz, name, pd, beforeObj, afterObj, map));
}
}
}else {
log.error("对象类型不一致,不能完成对比");
}
return map;
} catch (Exception e) {
e.printStackTrace();
log.error(e.getMessage());
return null;
}
}
/**
* 封装数据处理
*/
private static Map<String, Map> handleData(Class clazz, String name, PropertyDescriptor pd, Object beforeObj, Object afterObj, Map<String, Map> map) throws Exception {
// 注解内容
Field fieldName = clazz.getDeclaredField(name);
ApiModelProperty annotation = fieldName.getAnnotation(ApiModelProperty.class);
String annotationDescribe = ObjectUtil.isNotNull(annotation) ? annotation.value() : "";
// get方法
Method readMethod = pd.getReadMethod();
// 在beforeObj上调用get方法等同于获得beforeObj的属性值
Object objBefore = readMethod.invoke(beforeObj);
// 在afterObj上调用get方法等同于获得afterObj的属性值
Object objAfter = readMethod.invoke(afterObj);
if (objBefore == null || objAfter == null) {
return map;
}
if (objBefore instanceof Timestamp) {
objBefore = new Date(((Timestamp) objBefore).getTime());
}
if (objAfter instanceof Timestamp) {
objAfter = new Date(((Timestamp) objAfter).getTime());
}
if (objBefore == null && objAfter != null) {
Map m = new LinkedHashMap();
m.put("describe",annotationDescribe);
m.put("objBefore",objBefore);
m.put("objAfter",objAfter);
map.put(name, m);
}
// 比较这两个值是否相等,不等则放入map
if (!objBefore.equals(objAfter)) {
Map m = new LinkedHashMap();
m.put("describe",annotationDescribe);
m.put("objBefore",objBefore);
m.put("objAfter",objAfter);
map.put(name, m);
}
return map;
}
public static void main(String[] args) throws InterruptedException {
UserTest user1 = new UserTest("1", "Ray", 14, new Date());
Thread.sleep(1000);
UserTest user2 = new UserTest("2", "RayK", 14, new Date());
String[] column = new String[] {"id", "name", "age"}; // 获取指定
//String[] column = new String[] {}; // 获取全部
String[] nonColumn = new String[] {}; // 获取全部
Map<String, Map> resultMap = compareFields(user1, user2, column, nonColumn);
//Map<String, Map> resultMap = compareFields(user1, user2, null); // 获取全部
System.out.println(resultMap);
}
}
class UserTest {
@ApiModelProperty(value = "主键")
private String id;
@ApiModelProperty(value = "姓名")
private String name;
@ApiModelProperty(value = "年龄")
private int age;
@ApiModelProperty(value = "生日")
private Date birthday;
public UserTest() {
}
public UserTest(String id, String name, int age, Date birthday) {
this.id = id;
this.name = name;
this.age = age;
this.birthday = birthday;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public Date getBirthday() {
return birthday;
}
public void setBirthday(Date birthday) {
this.birthday = birthday;
}
}
这里我们比较的是 "id", "name", "age"
三个字段,在两个UserTest对象中,除了age一样,其他都是不同的。
得到结果
@RequestMapping(value = "editNotesForm")
public String editNotesForm(String selectedIds, Model model) {
//System.out.println("selectedIds = " + selectedIds);
Map<String, Map> resultMap = new LinkedHashMap<>();
List<String> ids = Arrays.asList(selectedIds.split(","));
if (!ids.isEmpty()) {
List<FsdqFamilyHistoryVO> familyHistories = fsdqFamilyHistoryService.findListByOldId(ids);
if (!familyHistories.isEmpty() && familyHistories.size() == ids.size()) {
String[] column = new String[] {};
String[] nonColumn = new String[] {"id", "updateDate", "userName", "version"};
resultMap = CompareFields.compareFields(familyHistories.get(0), familyHistories.get(1), column, nonColumn);
model.addAttribute("beforeObj", familyHistories.get(0));
model.addAttribute("afterObj", familyHistories.get(1));
}
}
// 测试数据
/*Map m = new LinkedHashMap();
m.put("describe","主键");
m.put("objBefore","1");
m.put("objAfter","2");
resultMap.put("id", m);
Map m2 = new LinkedHashMap();
m2.put("describe","姓名");
m2.put("objBefore","Ray");
m2.put("objAfter","Ray2");
resultMap.put("name", m2);
Map m3 = new LinkedHashMap();
m3.put("describe","年龄");
m3.put("objBefore","12");
m3.put("objAfter","14");
resultMap.put("age", m3);*/
model.addAttribute("resultMap", resultMap);
return "modules/fsdq/djb/fsdqEditNotesForm";
}
这里使用模板引擎 beetl
<% layout("/layouts/microHplus.html",{title:"家庭成员管理"}){ %>
<style>
.form-group {
margin-bottom: 50px;
height: 5px;
}
.myLabel {
text-align: right;
line-height: 30px;
}
</style>
<div class="layui-tab layui-tab-brief">
<div class="layui-tab layui-tab-brief">
<div class="row">
<div class="col-sm-6 b-r">
<div class="ibox float-e-margins">
<div class="ibox-title">
<blockquote>
<p>版本号:<strong>${beforeObj.version!}</strong></p>
<small>修改人:${beforeObj.userName!}</small>
<small>修改时间:${!(beforeObj.updateDate,dateFormat='yyyy-MM-dd HH:mm:ss')}</small>
</blockquote>
</div>
<div class="ibox-content">
<% if(resultMap != null) {%>
<% for(itemMap in resultMap) {%>
<div class="form-group">
<% for(item in itemMap.value) {%>
<% if (item.key == 'describe') {%>
<label class="col-sm-2 control-label myLabel">${item.value}</label>
<% } %>
<% if (item.key == 'objBefore') {%>
<div class="col-sm-10">
<input type="text" class="form-control" value="${item.value}">
</div>
<% } %>
<% } %>
</div>
<% } %>
<% } %>
</div>
</div>
</div>
<!-- 分割线 -->
<div class="col-sm-6">
<div class="ibox float-e-margins">
<div class="ibox-title">
<blockquote>
<p>版本号:<strong>${afterObj.version!}</strong></p>
<small>修改人:${afterObj.userName!}</small>
<small>修改时间:${!(afterObj.updateDate,dateFormat='yyyy-MM-dd HH:mm:ss')}</small>
</blockquote>
</div>
<div class="ibox-content">
<% if(resultMap != null) {%>
<% for(itemMap in resultMap) {%>
<div class="form-group">
<% for(item in itemMap.value) {%>
<% if (item.key == 'describe') {%>
<label class="col-sm-2 control-label myLabel">${item.value}</label>
<% } %>
<% if (item.key == 'objAfter') {%>
<div class="col-sm-10">
<input type="text" class="form-control" value="${item.value}">
</div>
<% } %>
<% } %>
</div>
<% } %>
<% } %>
</div>
</div>
</div>
</div>
<% if(resultMap == null) {%>
<div class="panel panel-danger">
<div class="panel-heading">
数据错误
</div>
<div class="panel-body">
<p>数据存在问题,请检查!</p>
</div>
<div class="panel-footer">
Error
</div>
</div>
<% } %>
</div>
</div>
<script>
$(function () {
})
</script>
<%}%>