SpringBoot中使用泛型&反射进行多表数据同步

1.业务描述

一次开发中,遇到接收多个表的数据,并批量插入数据库的需求,在此记录 代码逻辑

2.实际开发

因为涉及到的表有很多,差不多4,50个,所以考虑使用泛型反射来简化代码,提高开发效率

2.1 接收数据bean

样例中会精简部分代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21

/**
* 数据同步到保信--请求参数
* Created by Youdmeng on 2019/6/4 0004.
*/
@Data
public class SaveToBaoxin {

private String syncId;

/**
* 审核信息表
*/
@Valid
private List<CheckInfoEO> checkInfoEOList;

/**
* 组织机构表
*/
private List<OrgInfoEO> orgInfoEOList;
}

2.2 取出个属性的列表对象

使用反射依次获取每组数据,

因为每组数据的类型不尽相同,所有使用泛型T来接收

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void dataSave(SaveToBaoxin saveToBaoxin) {
List<SyncSuccessItemEO> successItemEOS = new ArrayList<>();
syncDate = new Date();
Class cls = saveToBaoxin.getClass();
Field[] fields = cls.getDeclaredFields();

for (Field field : fields) {
if (StringUtils.equals(field.getName(), "syncId")) {
continue;
}
field.setAccessible(true);
Object object = ReflectionUtils.getFieldValue(saveToBaoxin, field.getName());
List<T> list = (List<T>) object;
save(list, successItemEOS);
}
try {
baoXinDao.insertSuccessItemList(successItemEOS);
} catch (Exception e) {
logger.error("成功数据记录存储失败", e);
//手动回滚事务
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
}
}

2.3 核心保存代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
private <T> void save(List<T> list, List<SyncSuccessItemEO> successItemEOS) {

List<String> checkInfoEOKeyList = new ArrayList<>();
String table = null;
String tableDesc = "";
String dataDesc = "";//审核信息表
for (T t : list) {
Class<?> dataBean = t.getClass();
try {
table = SyncTableInfoEnums.getByLowerTableName(t.getClass().getSimpleName()).getTable();
tableDesc = SyncTableInfoEnums.getByLowerTableName(t.getClass().getSimpleName()).getTableDesc();
// dataDesc = t.getService() + "数据主键:" + checkInfoEO.getKeyId();
if (null == ReflectionUtils.getFieldValue(t, "id") || null == ReflectionUtils.getFieldValue(t, "systemId")) {
logger.error("|数据同步失败| " + " [" + table + "] [" + tableDesc + "] [" + dataDesc + "] [缺失数据主键]");
syncLogService.errorLog(t, table, tableDesc, dataDesc, "缺失数据主键");
} else {
ReflectionUtils.setFieldValue(t, "syncFlag", 2);
Class<?> syncDao = Class.forName("com.adc.da.manual.dao.manage.BaoxinSyncDao");
try {
ReflectionUtils
.invokeMethod(sqlSession.getMapper(syncDao), "insert" + StringBuiltifer.formateClassName(table) + "EO", dataBean, t);
} catch (Exception e) {
if (e.getCause() instanceof DuplicateKeyException) {
logger.info("主键已存在,进行数据更新");
ReflectionUtils
.invokeMethod(
sqlSession.getMapper(syncDao),
"updateByPrimaryKey" + StringBuiltifer.formateClassName(table) + "EO",
dataBean,
t);
}else {
throw e;
}
}
}
checkInfoEOKeyList.add(ReflectionUtils.getFieldValue(t, "id") + "");
} catch (Exception e) {
logger.error("|数据同步失败| " + " [" + table + "] [" + tableDesc + "] [" + dataDesc + "] [" + e.getMessage() + "]", e);
syncLogService.errorLog(t, table, tableDesc, dataDesc, e.getMessage());
}

}
successItemEOS.add(putSuccessItem(table, checkInfoEOKeyList));
}

代码解析:
Class<?> syncDao = Class.forName(“com.adc.da.manual.dao.manage.BaoxinSyncDao”) 之前的部分,是取出数据,并根据业务逻辑进行校验,
本段代码,是获取要存储数据的dao层接口

1
2
3
4
5
6
7
@Repository
public interface BaoxinSyncDao {

void insertcheckInfoEO(CheckInfoEO checkInfoEO);
void insertorgInfoEO (OrgInfoEO orgInfoEO);

}

核心插入代码 :

1
ReflectionUtils.invokeMethod(sqlSession.getMapper(syncDao), "insert" + StringBuiltifer.formateClassName(table) + "EO", dataBean, t);

本段代码等同于:

1
2
Method method = syncDao.getDeclaredMethod("insertcheckInfoEO", new Class[] { List.class });
method.invoke(sqlSession.getMapper(syncDao),t);

在开发过程中,我们会遇到需要使用使用反射来规范化批量操作的情况,但是当我们直接操作对应dao时,是不对的,我们需要反射sqlSession,来获取dao层控制权

2.4 反射工具类ReflectionUtils

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
/**
* 反射工具类.
* <p>
* 提供访问私有变量,获取泛型类型Class, 提取集合中元素的属性, 转换字符串到对象等Util函数.
*/
public class ReflectionUtils {

private static final Logger logger = LoggerFactory.getLogger(ReflectionUtils.class);

/**
* 调用Getter方法.
*/
public static Object invokeGetterMethod(Object obj, String propertyName) {
String getterMethodName = "get" + StringUtils.capitalize(propertyName);
return invokeMethod(obj, getterMethodName, null, new Object[] {});
}

/**
* 调用Setter方法.使用value的Class来查找Setter方法.
*/
public static void invokeSetterMethod(Object obj, String propertyName, Object value) {
invokeSetterMethod(obj, propertyName, value, null);
}

/**
* 调用Setter方法.
*
* @param propertyType 用于查找Setter方法,为空时使用value的Class替代.
*/
public static void invokeSetterMethod(Object obj, String propertyName, Object value, Class<?> propertyType) {
Class<?> type = propertyType != null ? propertyType : value.getClass();
String setterMethodName = "set" + StringUtils.capitalize(propertyName);
invokeMethod(obj, setterMethodName, type, new Object[] { value });
}

/**
* 直接读取对象属性值, 无视private/protected修饰符, 不经过getter函数.
*/
public static Object getFieldValue(final Object obj, final String fieldName) {
Field field = getAccessibleField(obj, fieldName);

if (field == null) {
throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
}

Object result = null;
try {
result = field.get(obj);
} catch (IllegalAccessException e) {
logger.error("不可能抛出的异常{}", e.getMessage());
}
return result;
}

/**
* 直接设置对象属性值, 无视private/protected修饰符, 不经过setter函数.
*/
public static void setFieldValue(final Object obj, final String fieldName, final Object value) {
Field field = getAccessibleField(obj, fieldName);

if (field == null) {
throw new IllegalArgumentException("Could not find field [" + fieldName + "] on target [" + obj + "]");
}

try {
field.set(obj, value);
} catch (IllegalAccessException e) {
logger.error("不可能抛出的异常:{}", e.getMessage());
}
}

/**
* 循环向上转型, 获取对象的DeclaredField, 并强制设置为可访问.
* <p>
* 如向上转型到Object仍无法找到, 返回null.
*/
public static Field getAccessibleField(final Object obj, final String fieldName) {
Assert.notNull(obj, "object不能为空");
Assert.hasText(fieldName, "fieldName");
for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) {
try {
Field field = superClass.getDeclaredField(fieldName);
field.setAccessible(true);
return field;
} catch (NoSuchFieldException e) {//NOSONAR
// Field不在当前类定义,继续向上转型
}
}
return null;
}

/**
* 直接调用对象方法, 无视private/protected修饰符.
* 用于一次性调用的情况.
*/
public static Object invokeMethod(final Object obj, final String methodName, final Class<?> tClass, final Object args) {
Method method = getAccessibleMethod(obj, methodName, tClass);
if (method == null) {
throw new IllegalArgumentException("Could not find method [" + methodName + "] on target [" + obj + "]");
}

try {
return method.invoke(obj, args);
} catch (Exception e) {
throw convertReflectionExceptionToUnchecked(e);
}
}

/**
* 循环向上转型, 获取对象的DeclaredMethod,并强制设置为可访问.
* 如向上转型到Object仍无法找到, 返回null.
* <p>
* 用于方法需要被多次调用的情况. 先使用本函数先取得Method,然后调用Method.invoke(Object obj, Object... args)
*/
public static Method getAccessibleMethod(final Object obj, final String methodName, final Class<?> tClass) {

Assert.notNull(obj, "object不能为空");

for (Class<?> superClass = obj.getClass(); superClass != Object.class; superClass = superClass.getSuperclass()) {
try {
Method method = superClass.getDeclaredMethod(methodName, new Class[] { tClass });

method.setAccessible(true);

return method;

} catch (NoSuchMethodException e) {//NOSONAR
// Method不在当前类定义,继续向上转型
}
}
return null;
}

/**
* 通过反射, 获得Class定义中声明的父类的泛型参数的类型.
* 如无法找到, 返回Object.class.
* eg.
* public UserDao extends HibernateDao<User>
*
* @param clazz The class to introspect
* @return the first generic declaration, or Object.class if cannot be determined
*/
@SuppressWarnings({ "unchecked", "rawtypes" })
public static <T> Class<T> getSuperClassGenricType(final Class clazz) {
return getSuperClassGenricType(clazz, 0);
}

/**
* 通过反射, 获得Class定义中声明的父类的泛型参数的类型.
* 如无法找到, 返回Object.class.
* <p>
* 如public UserDao extends HibernateDao<User,Long>
*
* @param clazz clazz The class to introspect
* @param index the Index of the generic ddeclaration,start from 0.
* @return the index generic declaration, or Object.class if cannot be determined
*/
@SuppressWarnings("rawtypes")
public static Class getSuperClassGenricType(final Class clazz, final int index) {

Type genType = clazz.getGenericSuperclass();

if (!(genType instanceof ParameterizedType)) {
logger.warn(clazz.getSimpleName() + "'s superclass not ParameterizedType");
return Object.class;
}

Type[] params = ((ParameterizedType) genType).getActualTypeArguments();

if (index >= params.length || index < 0) {
logger.warn("Index: " + index + ", Size of " + clazz.getSimpleName() + "'s Parameterized Type: "
+ params.length);
return Object.class;
}
if (!(params[index] instanceof Class)) {
logger.warn(clazz.getSimpleName() + " not set the actual class on superclass generic parameter");
return Object.class;
}

return (Class) params[index];
}

/**
* 将反射时的checked exception转换为unchecked exception.
*/
public static RuntimeException convertReflectionExceptionToUnchecked(Exception e) {
if (e instanceof IllegalAccessException || e instanceof IllegalArgumentException
|| e instanceof NoSuchMethodException) {
return new IllegalArgumentException("Reflection Exception.", e);
} else if (e instanceof InvocationTargetException) {
return new RuntimeException("Reflection Exception.", ((InvocationTargetException) e).getTargetException());
} else if (e instanceof RuntimeException) {
return (RuntimeException) e;
}
return new RuntimeException("Unexpected Checked Exception.", e);
}
}





更多好玩好看的内容,欢迎到我的博客交流,共同进步        WaterMin