abf1dcd36fd0117a364468feace96af27fe53a8c
01.\345\274\200\345\217\221\346\211\213\345\206\214/06.\345\270\270\350\247\201\351\227\256\351\242\230QA/01.\345\270\270\350\247\201\351\227\256\351\242\230QA.md
... | ... | @@ -1,133 +1,192 @@ |
1 | ----
|
|
2 | -title: 常见问题QA
|
|
3 | -date: 2023-09-25 17:31:35
|
|
4 | -permalink: /pages/c7b051/
|
|
5 | ----
|
|
6 | -# 常见问题QA
|
|
7 | -
|
|
8 | -## 问题1:为什么配置视图文件总是不生效?
|
|
9 | -
|
|
10 | -解答:我们系统视图分为默认视图,手动配置视图。如果是默认视图,我们是没有view.json文件的。如果是手动配置视图,那么在我们的工程项目中,建立views目录建立mode.json视图文件。menu.json菜单视图配置项目中,菜单view属性配置视图模型对应的key值。app.json文件中也一同配置访问视图地址。这个时候需要重新打包我们的工程,重新跑起来即可。
|
|
11 | -
|
|
12 | -
|
|
13 | -
|
|
14 | -## 问题2:为什么linux配置日志级别不生效?
|
|
15 | -
|
|
16 | -解答:logback日志文件默认读取顺序为logging.xml、application.properties、logging-spring.xml,检查项目application.properties是否指定了logback配置文件,检查logback配置文件名称是否对得上,将logback配置文件对上,重启即可。
|
|
17 | -
|
|
18 | -
|
|
19 | -
|
|
20 | -## 问题3:如何排查linux中jvm加载的是哪个日志配置文件?
|
|
21 | -
|
|
22 | -解答:使用arthas工具排查,通过打印类的相关命令:
|
|
23 | -
|
|
24 | -1.启动arthas,监控jvm,命令:./as.sh
|
|
25 | -
|
|
26 | -
|
|
27 | -
|
|
28 | -2.查看jvm加载的日志组件,命令:logger
|
|
29 | -
|
|
30 | -
|
|
31 | -
|
|
32 | -3.获取classLoaderHash,命令:sc -d com.sie.iiot.apps.alarm.model.AlarmManager
|
|
33 | -
|
|
34 | -
|
|
35 | -
|
|
36 | -4.查看类加载的logger信息,命令:ognl -c 10dba097 '@com.sie.iiot.apps.alarm.model.AlarmManager@logger'
|
|
37 | -
|
|
38 | -
|
|
39 | -
|
|
40 | -5.查看jvm加载的logger配置文件信息,命令:ognl -c 1be6f5c3 '#map1=@org.slf4j.LoggerFactory@getLogger("root").loggerContext.objectMap, #map1.get("CONFIGURATION_WATCH_LIST")'
|
|
41 | -
|
|
42 | -
|
|
43 | -
|
|
44 | -可以看到加载的是这个配置文件/root/java-iiot/jar/sie-iiot-server-1.0-SNAPSHOT.jar!/BOOT-INF/classes!/logback.xml
|
|
45 | -
|
|
46 | -## 问题4: 查询性能优化
|
|
47 | -
|
|
48 | -如果模型中的字段有 ER 关系。引擎自动会做懒加载,但是在查询的时候,使用懒加载可能会导致性能比较差。
|
|
49 | -
|
|
50 | -以下模型作为示例
|
|
51 | -
|
|
52 | -```java
|
|
53 | -@Model
|
|
54 | -public class Staff extends BaseModel<Staff> {
|
|
55 | -
|
|
56 | - @ManyToOne(displayName = "部门")
|
|
57 | - @JoinColumn
|
|
58 | - private Dept dept;
|
|
59 | -
|
|
60 | - @OneToMany(displayName = "学习记录")
|
|
61 | - private List<LearningRecord> learningRecords;
|
|
62 | -
|
|
63 | - @Property(displayName = "附件", dataType = DataType.FILE, multiple = true, length = 10)
|
|
64 | - private List<String> file;
|
|
65 | -
|
|
66 | - public List<LearningRecord> getLearningRecords() {
|
|
67 | - return (List<LearningRecord>) get("learningRecords");
|
|
68 | - }
|
|
69 | -}
|
|
70 | -```
|
|
71 | -
|
|
72 | -```java
|
|
73 | -@Model
|
|
74 | -public class LearningRecord extends BaseModel<LearningRecord> {
|
|
75 | -
|
|
76 | - @ManyToOne(displayName = "员工")
|
|
77 | - @JoinColumn
|
|
78 | - private Staff staff;
|
|
79 | -
|
|
80 | - @Property(displayName = "内容")
|
|
81 | - private String content;
|
|
82 | -}
|
|
83 | -```
|
|
84 | -
|
|
85 | -### 4.1 不要在循环中使用懒加载
|
|
86 | -
|
|
87 | -错误做法
|
|
88 | -
|
|
89 | -```java
|
|
90 | -for (Staff staff : staffList) {
|
|
91 | - List<LearningRecord> learningRecords = staff.getLearningRecords();
|
|
92 | -}
|
|
93 | -```
|
|
94 | -
|
|
95 | -如果 staffList 有 100 条数据,那么会执行 100 次查询 LearningRecord 的语句。
|
|
96 | -
|
|
97 | -正确做法应该是一次性查询所有相关的 LearningRecord,然后再进行下一步处理。
|
|
98 | -
|
|
99 | -```java
|
|
100 | -Set<String> staffIds = staffList.stream()
|
|
101 | -.map(BaseModel::getId)
|
|
102 | -.collect(Collectors.toSet());
|
|
103 | -
|
|
104 | -List<LearningRecord> learningRecords = new ArrayList<>();
|
|
105 | -if (CollectionUtil.isNotEmpty(staffIds)) {
|
|
106 | - learningRecords = new LearningRecord().search(Filter.in("staff", staffIds), Collections.singletonList("*"), 0, 0, null);
|
|
107 | -}
|
|
108 | -
|
|
109 | -for (Staff staff : staffList) {
|
|
110 | - List<LearningRecord> records = learningRecords.stream()
|
|
111 | - .filter(r -> staff.getId().equals(r.getOrDefault("staff", "")))
|
|
112 | - .collect(Collectors.toList());
|
|
113 | - // 其他处理逻辑
|
|
114 | -}
|
|
115 | -```
|
|
116 | -
|
|
117 | -### 4.2 尽量不查询类型为 File 的字段
|
|
118 | -
|
|
119 | -dataType = File 的字段跟 @OneToMany 类似。如果在做表格查询、导出等功能,那么不要去查询这些字段。
|
|
120 | -
|
|
121 | -错误做法
|
|
122 | -
|
|
123 | -```java
|
|
124 | -new Staff().search(null, Collections.singletonList("*"), 0, 0, null);
|
|
125 | -```
|
|
126 | -
|
|
127 | -引擎在处理上面的查询时,会发出大量的查询语句去查询 MetaAttachment 模型。
|
|
128 | -
|
|
129 | -正确的做法
|
|
130 | -
|
|
131 | -```java
|
|
132 | -new Staff().search(null, Arrays.asList("id", "dept"), 0, 0, null);
|
|
133 | -```
|
|
1 | +--- |
|
2 | +title: 常见问题QA |
|
3 | +date: 2023-09-25 17:31:35 |
|
4 | +permalink: /pages/c7b051/ |
|
5 | +--- |
|
6 | +# 常见问题QA |
|
7 | + |
|
8 | +## 问题1:为什么配置视图文件总是不生效? |
|
9 | + |
|
10 | +解答:我们系统视图分为默认视图,手动配置视图。如果是默认视图,我们是没有view.json文件的。如果是手动配置视图,那么在我们的工程项目中,建立views目录建立mode.json视图文件。menu.json菜单视图配置项目中,菜单view属性配置视图模型对应的key值。app.json文件中也一同配置访问视图地址。这个时候需要重新打包我们的工程,重新跑起来即可。 |
|
11 | + |
|
12 | + |
|
13 | + |
|
14 | +## 问题2:为什么linux配置日志级别不生效? |
|
15 | + |
|
16 | +解答:logback日志文件默认读取顺序为logging.xml、application.properties、logging-spring.xml,检查项目application.properties是否指定了logback配置文件,检查logback配置文件名称是否对得上,将logback配置文件对上,重启即可。 |
|
17 | + |
|
18 | + |
|
19 | + |
|
20 | +## 问题3:如何排查linux中jvm加载的是哪个日志配置文件? |
|
21 | + |
|
22 | +解答:使用arthas工具排查,通过打印类的相关命令: |
|
23 | + |
|
24 | +1.启动arthas,监控jvm,命令:./as.sh |
|
25 | + |
|
26 | + |
|
27 | + |
|
28 | +2.查看jvm加载的日志组件,命令:logger |
|
29 | + |
|
30 | + |
|
31 | + |
|
32 | +3.获取classLoaderHash,命令:sc -d com.sie.iiot.apps.alarm.model.AlarmManager |
|
33 | + |
|
34 | + |
|
35 | + |
|
36 | +4.查看类加载的logger信息,命令:ognl -c 10dba097 '@com.sie.iiot.apps.alarm.model.AlarmManager@logger' |
|
37 | + |
|
38 | + |
|
39 | + |
|
40 | +5.查看jvm加载的logger配置文件信息,命令:ognl -c 1be6f5c3 '#map1=@org.slf4j.LoggerFactory@getLogger("root").loggerContext.objectMap, #map1.get("CONFIGURATION_WATCH_LIST")' |
|
41 | + |
|
42 | + |
|
43 | + |
|
44 | +可以看到加载的是这个配置文件/root/java-iiot/jar/sie-iiot-server-1.0-SNAPSHOT.jar!/BOOT-INF/classes!/logback.xml |
|
45 | + |
|
46 | +## 问题4: 查询性能优化 |
|
47 | + |
|
48 | +如果模型中的字段有 ER 关系。引擎自动会做懒加载,但是在查询的时候,使用懒加载可能会导致性能比较差。 |
|
49 | + |
|
50 | +以下模型作为示例 |
|
51 | + |
|
52 | +```java |
|
53 | +@Model |
|
54 | +public class Staff extends BaseModel<Staff> { |
|
55 | + |
|
56 | + @ManyToOne(displayName = "部门") |
|
57 | + @JoinColumn |
|
58 | + private Dept dept; |
|
59 | + |
|
60 | + @OneToMany(displayName = "学习记录") |
|
61 | + private List<LearningRecord> learningRecords; |
|
62 | + |
|
63 | + @Property(displayName = "附件", dataType = DataType.FILE, multiple = true, length = 10) |
|
64 | + private List<String> file; |
|
65 | + |
|
66 | + public List<LearningRecord> getLearningRecords() { |
|
67 | + return (List<LearningRecord>) get("learningRecords"); |
|
68 | + } |
|
69 | +} |
|
70 | +``` |
|
71 | + |
|
72 | +```java |
|
73 | +@Model |
|
74 | +public class LearningRecord extends BaseModel<LearningRecord> { |
|
75 | + |
|
76 | + @ManyToOne(displayName = "员工") |
|
77 | + @JoinColumn |
|
78 | + private Staff staff; |
|
79 | + |
|
80 | + @Property(displayName = "内容") |
|
81 | + private String content; |
|
82 | +} |
|
83 | +``` |
|
84 | + |
|
85 | +### 4.1 不要在循环中使用懒加载 |
|
86 | + |
|
87 | +错误做法 |
|
88 | + |
|
89 | +```java |
|
90 | +for (Staff staff : staffList) { |
|
91 | + List<LearningRecord> learningRecords = staff.getLearningRecords(); |
|
92 | +} |
|
93 | +``` |
|
94 | + |
|
95 | +如果 staffList 有 100 条数据,那么会执行 100 次查询 LearningRecord 的语句。 |
|
96 | + |
|
97 | +正确做法应该是一次性查询所有相关的 LearningRecord,然后再进行下一步处理。 |
|
98 | + |
|
99 | +```java |
|
100 | +Set<String> staffIds = staffList.stream() |
|
101 | +.map(BaseModel::getId) |
|
102 | +.collect(Collectors.toSet()); |
|
103 | + |
|
104 | +List<LearningRecord> learningRecords = new ArrayList<>(); |
|
105 | +if (CollectionUtil.isNotEmpty(staffIds)) { |
|
106 | + learningRecords = new LearningRecord().search(Filter.in("staff", staffIds), Collections.singletonList("*"), 0, 0, null); |
|
107 | +} |
|
108 | + |
|
109 | +for (Staff staff : staffList) { |
|
110 | + List<LearningRecord> records = learningRecords.stream() |
|
111 | + .filter(r -> staff.getId().equals(r.getOrDefault("staff", ""))) |
|
112 | + .collect(Collectors.toList()); |
|
113 | + // 其他处理逻辑 |
|
114 | +} |
|
115 | +``` |
|
116 | + |
|
117 | +### 4.2 尽量不查询类型为 File 的字段 |
|
118 | + |
|
119 | +dataType = File 的字段跟 @OneToMany 类似。如果在做表格查询、导出等功能,那么不要去查询这些字段。 |
|
120 | + |
|
121 | +错误做法 |
|
122 | + |
|
123 | +```java |
|
124 | +new Staff().search(null, Collections.singletonList("*"), 0, 0, null); |
|
125 | +``` |
|
126 | + |
|
127 | +引擎在处理上面的查询时,会发出大量的查询语句去查询 MetaAttachment 模型。 |
|
128 | + |
|
129 | +正确的做法 |
|
130 | + |
|
131 | +```java |
|
132 | +new Staff().search(null, Arrays.asList("id", "dept"), 0, 0, null); |
|
133 | +``` |
|
134 | + |
|
135 | +### 5 常用Arthas命令 |
|
136 | + |
|
137 | +``` |
|
138 | +1.查看耗时>200ms的方法的参数和返回值 |
|
139 | +watch com.sie.snest.engine.model.property.SelectionProperty convertToRead '{params[0],params[3],returnObj}' '#cost>200' -n 100 -x 3 |
|
140 | + |
|
141 | + |
|
142 | +2. 统计耗时 |
|
143 | +trace com.sie.snest.engine.data.access.BussModelDataAccess search 'params[0].getModel().getName()=="equip_lubrication_calibration"' -n 100 --skipJDKMethod false |
|
144 | + |
|
145 | +trace com.sie.mbm.edo.calibration.models.Calibration periodUnitList -n 100 --skipJDKMethod false |
|
146 | + |
|
147 | +3.查看参数 |
|
148 | +watch com.sie.mi.ioc.attribution.strategy.models.AttributionStrategy calcAttribution '{params,returnObj,throwExp}' -n 20 -x 3 |
|
149 | + |
|
150 | +watch com.sie.mi.ioc.attribution.strategy.models.AttributionStrategy calcAttribution '{params,returnObj,throwExp}' 'params[0]=="04dkj8y9h3kny"' -n 20 -x 3 |
|
151 | + |
|
152 | +4.其他示例 |
|
153 | + |
|
154 | +watch com.sie.snest.engine.api.response.ResponseHandle beforeBodyWrite '{params,returnObj,throwExp}' -n 100 --skipJDKMethod false |
|
155 | + |
|
156 | + ognl --classLoaderClass org.springframework.boot.loader.LaunchedURLClassLoader '@com.sie.snest.engine.container.EngineContainer@getBussinessAppGroupContainer().getAppDataInfoMap().keySet()' |
|
157 | + |
|
158 | + |
|
159 | + watch com.sie.snest |
|
160 | +trace com.sie.snest.engine.api.distributed.RpcInvocation invoke -n 5 --skipJDKMethod false |
|
161 | + |
|
162 | +trace com.sie.snest.engine.api.distributed.RpcInvocation invoke 'ModelMeta == "mbm_mes_process_match_rule"' |
|
163 | + |
|
164 | +stack com.sie.snest.engine.api.response.ResponseHandle beforeBodyWrite -n 20 --skipJDKMethod false |
|
165 | + |
|
166 | + |
|
167 | +trace org.springframework.web.servlet.DispatcherServlet doDispatch -n 20 --skipJDKMethod false |
|
168 | + |
|
169 | + |
|
170 | +watch com.sie.mi.ioc.attribution.strategy.models.AttributionStrategy calcAttribution '{params,returnObj.reportResult.candidateResult.dimList[0].dataList[0].reportData,throwExp}' 'params[0]=="04dkj8y9h3kny"' -n 20 -x 3 |
|
171 | + |
|
172 | +watch com.sie.mi.ioc.attribution.strategy.models.AttributionStrategy calcAttribution '{params,returnObj,throwExp}' 'params[0]=="04dkj8y9h3kny"' -n 20 -x 3 |
|
173 | + |
|
174 | +watch com.sie.snest.engine.api.RpcController service '{params,returnObj,throwExp}' -n 20 -x 3 |
|
175 | + |
|
176 | +watch com.sie.snest.engine.api.response.ResponseHandle beforeBodyWrite '{params,returnObj,throwExp}' -n 100 -x 3 |
|
177 | + |
|
178 | +trace com.sie.snest.engine.api.distributed.RpcInvocation invoke 'params[3]=="mbm_mes_process_match_rule"' -n 100 --skipJDKMethod false |
|
179 | + |
|
180 | + |
|
181 | +trace com.sie.snest.engine.api.RpcController service 'params[1].getParameter("businessIndex")=="04dkj8y9h3kny"' -n 20 --skipJDKMethod false |
|
182 | + |
|
183 | +watch com.sie.snest.engine.api.RpcController service '{params,returnObj,throwExp}' 'params[1].getParameter("businessIndex")=="04dkj8y9h3kny"' -n 100 -x 3 |
|
184 | + |
|
185 | +trace com.sie.snest.engine.api.RpcController service 'params[1].getParameter("businessIndex")=="04dkj8y9h3kny"' -n 20 --skipJDKMethod false |
|
186 | + |
|
187 | + |
|
188 | +trace com.sie.snest.engine.api.response.ResponseHandle beforeBodyWrite -n 20 --skipJDKMethod false |
|
189 | + |
|
190 | + |
|
191 | +``` |
|
192 | + |