背景:

在项目开发中,每个mapper xml都存在大量重复的基础CRUD操作,如 selectById、deleteById 等。这些模板化的SQL语句不仅增加了开发工作量,还容易出现不一致的问题。

解决方案:

通过动态解析和修改Mybatis XML文件,自动注入缺失的通用SQL语句,实现代码复用和标准化。

核心实现:

1.设置注入后的xml

1
2
3
4
5
6
7
8
9
10
11
@Bean
public SqlSessionFactory sqlSessionFactoryBean(DataSource dataSource) throws Exception {
SqlSessionFactoryBean sessionFactory = new SqlSessionFactoryBean();
sessionFactory.setDataSource(dataSource);
List<Resource> resources = new ArrayList<>();
for (Resource resource : mybatisProperties.resolveMapperLocations()){
resources.add(MyBatisXmlProcessor.parse(resource));
}
sessionFactory.setMapperLocations(resources.toArray(new Resource[0]));
return sessionFactory.getObject();
}

2.预定义通用SQL模板,XML解析与语句注入

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
@Slf4j
public class MyBatisXmlProcessor {
private static final Map<String, String> DEFAULT_STATEMENTS = new HashMap<>();

static {
DEFAULT_STATEMENTS.put("selectById",
"<select id=\"selectById\" resultMap=\"BaseResultMap\">\n" +
" select * from <include refid=\"BaseTable\"/> where id = #{id}\n" +
"</select>");
}

public static Resource parse(Resource resource) {
try {

// 获取所有已存在的语句ID
Set<String> existingIds = getExistingStatementIds(resource);

// 检查哪些默认语句需要添加
Map<String, String> statementsToAdd = DEFAULT_STATEMENTS.entrySet().stream().filter(entry -> !existingIds.contains(entry.getKey())).collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));

// 如果没有需要添加的语句,直接返回原内容
if (statementsToAdd.isEmpty()) {
return resource;
}

// 在</mapper>前插入新语句
String modifiedXml = insertStatementsBeforeMapperEnd(resource, statementsToAdd);

log.info("Modified XML:\n{}", modifiedXml);

return new ByteArrayResource(modifiedXml.getBytes(StandardCharsets.UTF_8));
} catch (Exception e) {
throw new RuntimeException("Failed to parse and modify MyBatis XML", e);
}
}

/**
* 获取XML中所有已存在的语句ID
*/
private static Set<String> getExistingStatementIds(Resource resource) throws ParserConfigurationException, IOException, SAXException {

DocumentBuilderFactory factory = DocumentBuilderFactory.newInstance();
DocumentBuilder builder = factory.newDocumentBuilder();

// 解析XML
Document document = builder.parse(resource.getInputStream());
Element root = document.getDocumentElement();

if (!"mapper".equals(root.getTagName())) {
throw new IllegalArgumentException("Invalid MyBatis XML: root element is not <mapper>");
}

NodeList children = root.getChildNodes();

return IntStream.range(0, children.getLength())
.mapToObj(children::item)
.filter(node -> node.getNodeType() == Node.ELEMENT_NODE)
.map(node -> ((Element) node).getAttribute("id"))
.filter(id -> id != null && !id.isEmpty())
.collect(Collectors.toSet());
}

/**
* 在</mapper>前插入语句
*/
private static String insertStatementsBeforeMapperEnd(Resource resource, Map<String, String> statementsToAdd) throws IOException {
String xmlContent = StreamUtils.copyToString(resource.getInputStream(), StandardCharsets.UTF_8);
int mapperEndIndex = xmlContent.lastIndexOf("</mapper>");
if (mapperEndIndex == -1) {
throw new IllegalArgumentException("Invalid MyBatis XML: </mapper> tag not found");
}

StringBuilder modifiedXml = new StringBuilder();

// 提取</mapper>前的部分
modifiedXml.append(xmlContent, 0, mapperEndIndex);

// 添加新语句
statementsToAdd.values().forEach(o-> modifiedXml.append(o).append("\n"));

modifiedXml.append(xmlContent.substring(mapperEndIndex));

return modifiedXml.toString();
}
}