在实际mis项目中增删改查必不可少,针对"改"的操作,重要的项目中都要有变更历史记录.本实例提供了一个实体属性变更历史记录工具类,只要写很少的代码就能实现强大的变更历史记录功能.本工具类的主要优点是1.工具类对实体对象没有依赖性,任何对象都能记录历史.2.只要编写很少的代码就能实现需求.
上篇文章介绍了变更历史记录的核心逻辑(http://blog.youkuaiyun.com/lk_blog/article/details/8007777),本为在上一篇的基础上补充功能,介绍如何实现下面的需求呢?
1.所有属性都记录变更历史太多,有些属性我不想记录变更历史.
2.实体属性变化前后的值需要把编码转成有意义的文字.例如:实体类中性别用0,1表示,而希望变更历史中记录前后变化的值是"男","女".
3.传入实体对象自动识别实体Id,而不需要手工传入.
输出结果:
上篇文章介绍了变更历史记录的核心逻辑(http://blog.youkuaiyun.com/lk_blog/article/details/8007777),本为在上一篇的基础上补充功能,介绍如何实现下面的需求呢?
1.所有属性都记录变更历史太多,有些属性我不想记录变更历史.
2.实体属性变化前后的值需要把编码转成有意义的文字.例如:实体类中性别用0,1表示,而希望变更历史中记录前后变化的值是"男","女".
3.传入实体对象自动识别实体Id,而不需要手工传入.
4.实体的属性名不好记,想加一个好记别名,这样便于查询.
使用步骤和上次基本一样,但在实体上需要加入注解:
1.得到变化前后的对象.
2.调用下面方法传入第一步中的值.
HistoryUtil util = new HistoryUtil();
util.record(类名, 变化前的对象, 变化后的对象,修改人);
实体类及属性上的注解:
package com.tgb.lk.history2;
public class Person {
@HistoryId //标识Id
private int id;
@HistoryAlias(alias = "姓名") //加入别名
private String name;
@HistoryAlias(alias = "性别") //加入别名
private String sex;
@HistoryNotRecord //设置即使属性发生变化也不记录历史
private String clazz;
//get和set方法略
@Override
public String toString() {
return "Person [clazz=" + clazz + ", id=" + id + ", name=" + name
+ ", sex=" + sex + "]";
}
}
测试例子:package com.tgb.lk.history2;
public class HistoryTest {
public static void main(String[] args) {
Person s = new Person();
s.setId(1);
s.setName("李坤");
s.setSex("1");// 1代表"男"
s.setClazz("五期提高班");
// 可以使用commons-beanutils-xxx.jar中的这个方法来保留原对象.
// BeanUtils.copyProperties(dest, src);
Person s2 = new Person();
s2.setId(1);
s2.setName("李佳");
s2.setSex("0");// 0代表"女"
HistoryUtil util = new HistoryUtil();
util.record(Person.class, s, s2, "admin");
}
}
输出结果:
核心代码:
注解:
(1)HistoryId
package com.tgb.lk.history2;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target( { java.lang.annotation.ElementType.FIELD })
public @interface HistoryId {
}
(2)HistoryAlias:
package com.tgb.lk.history2;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target( { java.lang.annotation.ElementType.FIELD })
public @interface HistoryAlias {
public abstract String alias();
}
(3)HistoryNotRecord:
package com.tgb.lk.history2;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Retention(RetentionPolicy.RUNTIME)
@Target( { java.lang.annotation.ElementType.FIELD })
public @interface HistoryNotRecord {
}
HistoryUtil<T>:
package com.tgb.lk.history2;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
/*
* @author likun
* 记录属性变更历史的工具类,使用步骤见main方法.
* HistoryUtil util = new HistoryUtil();
* util.record(Person.class, s, s2, "admin");
*/
public class HistoryUtil<T> {
/**
* 支持自己定义一个History对象并保存入库.
*
* @param history
*/
public void record(History history) {
System.out.println(history);
// 调用添加入库方法.
}
// 批量保存到数据库中,考虑到对象修改的属性可能较多,所以采用批量导入效率会高一些.
public void record(List<History> historys) {
System.out.println("原始记录:" + historys);
Map<String, String> sexMap = new HashMap<String, String>();
sexMap.put("1", "男");
sexMap.put("0", "女");
for (History history : historys) {
if (history.getEntity().equals("class com.tgb.lk.history2.Person")) {
// 处理sex字段值中含义不明确的0,1变为含义明确的"男","女"
if (history.getProperty().equals("sex")) {
history.setOldValue(sexMap.get(history.getOldValue()));
history.setNewValue(sexMap.get(history.getNewValue()));
}
// 其他逻辑的处理.....
}
}
System.out.println("处理后的记录:" + historys);
// 调用批量添加入库方法.
}
/**
* 比较两个对象哪些属性发生变化,将变化的属性保存为History对象. 实体中用@HistoryId注解的自动保存到实体类的Id字段中.
* 实体中有@HistoryAlias的注解自动解析为指定的别名.
*
* @param clazz
* 修改类
* @param oldObj
* 老对象
* @param newObj
* 新对象
* @param user
* 修改人
*/
public void record(Class<T> clazz, T oldObj, T newObj, String user) {
if (oldObj == newObj) {
return;// 如果两个对象相同直接退出
}
List<History> list = new ArrayList<History>();
Field[] fields = clazz.getDeclaredFields();// 得到指定类的所有属性Field.
List<Field> allFields = new ArrayList<Field>();
Field idField = null;
// 找出Id字段,便于记录
for (Field field : fields) {
field.setAccessible(true);// 设置类的私有字段属性可访问.
if (field.isAnnotationPresent(HistoryId.class)) {
idField = field;
}
// 设置了不记录变化的注解字段不记录变更历史.
if (!field.isAnnotationPresent(HistoryNotRecord.class)) {
allFields.add(field);
}
}
// 比较实体对象的属性值,每个属性值不同的都新建一个History对象并保存入库.
for (Field field : allFields) {
field.setAccessible(true);// 设置类的私有字段属性可访问.
field.getAnnotation(HistoryAlias.class);
try {
// ^是异或运算符
if ((field.get(oldObj) != null ^ field.get(newObj) != null)
|| (!field.get(oldObj).equals(field.get(newObj)))) {
History history = new History();
history.setEntity(clazz.toString());
history.setProperty(field.getName());
if (field.isAnnotationPresent(HistoryAlias.class)) {
history.setAlias(field
.getAnnotation(HistoryAlias.class).alias());
}
history.setOldValue(String.valueOf(field.get(oldObj)));
history.setNewValue(String.valueOf(field.get(newObj)));
history.setModifyDate(new Date());
if (idField != null) {
history
.setEntityId(String
.valueOf(idField.get(oldObj)));// 记录修改的对象的主键Id.
}
history.setUser(user);// 记录修改者
list.add(history);
}
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
record(list);// 调用保存入库的方法.
}
}
实体属性变更历史记录框架(一)-变更历史记录从此无忧(http://blog.youkuaiyun.com/lk_blog/article/details/8007777)
实体属性变更历史记录框架(二)-变更历史记录从此无忧(http://blog.youkuaiyun.com/lk_blog/article/details/8092925)
限于本人水平有限,很多地方写的并不完美,希望大家不吝赐教.如果觉得本文对您有帮助请顶支持一下,如果有不足之处欢迎留言交流,希望在和大家的交流中得到提高.
代码下载:http://download.youkuaiyun.com/detail/lk_blog/4667643