解决问题
实际开发过程中使用mybatis自定义sql, 在项目运行过程中发现sql存在问题或者需要对已有sql进行优化, 这时候改完sql都需要进行项目的重启才能把最新的sql应用到项目中, 这样会因为修改一个sql而进行项目重启非常的不方便, 那么mybatis中的xml可以在不重启项目的情况下进行重新加载吗?
mybatis是如何进行xml加载的
- mybatis首先会读取mybatis-config.xml,并进行解析mapper节点得到需要加载 mapper.xml
XmlConfigBuilder.java
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
- mapperElement 对mappers节点进行解析将 xml配置加载到mybatis核心configuration中
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
/*byte[] bytes = new byte[1024];
while ((inputStream.read(bytes))>0){
System.out.println(new String(bytes));
}*/
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
}
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
//获取配置的mybatis-config.xml文件输入流(无缓存)
try(InputStream inputStream = Resources.getUrlAsStream(url)){
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
}
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
这里可以看出mybatis-config.xml中的mapper.xml配置是支持三种方式:resource,url,mapperClass的,这里以url为例
public void parse() {
//查询是否已经存在已有的文档加载对象,如果没有则进行MapperRegistry初始化
if (!configuration.isResourceLoaded(resource)) {
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
这里是对xml进行解析和加载
3.当mybatis加载完配置以后会生成SqlSessionFactory然后通过SqlSessionFactory去获取数据库连接会话了,之后就是进行数据库操作就与本文无关了, 既然我们知道了mapper.xml的加载过程, 那么我们就可以在程序运行过程中对mapper.xml进行二次加载, 我们需要对mybatis的核心配置类Configuration.java中的一些存储xml配置的缓存进行清除并重新加载最新的xml到容器中(mapperRegistry,mappedStatements)
protected String databaseId;
/**
* Configuration factory class.
* Used to create Configuration for loading deserialized unread properties.
*
* @see <a href='https://github.com/mybatis/old-google-code-issues/issues/300'>Issue 300 (google code)</a>
*/
protected Class<?> configurationFactory;
//mapperRegistry mapper注册表
protected MapperRegistry mapperRegistry = new MapperRegistry(this);
protected final InterceptorChain interceptorChain = new InterceptorChain();
protected final TypeHandlerRegistry typeHandlerRegistry = new TypeHandlerRegistry(this);
protected final TypeAliasRegistry typeAliasRegistry = new TypeAliasRegistry();
protected final LanguageDriverRegistry languageRegistry = new LanguageDriverRegistry();
//mapper 描述信息
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());
protected final Map<String, Cache> caches = new StrictMap<>("Caches collection");
protected final Map<String, ResultMap> resultMaps = new StrictMap<>("Result Maps collection");
protected final Map<String, ParameterMap> parameterMaps = new StrictMap<>("Parameter Maps collection");
protected final Map<String, KeyGenerator> keyGenerators = new StrictMap<>("Key Generators collection");
protected final Set<String> loadedResources = new HashSet<>();
protected final Map<String, XNode> sqlFragments = new StrictMap<>("XML fragments parsed from previous mappers");
protected final Collection<XMLStatementBuilder> incompleteStatements = new LinkedList<>();
protected final Collection<CacheRefResolver> incompleteCacheRefs = new LinkedList<>();
protected final Collection<ResultMapResolver> incompleteResultMaps = new LinkedList<>();
protected final Collection<MethodResolver> incompleteMethods = new LinkedList<>();
4.源码中这个两个对象并没有提供清除和修改值的方法所以需要手动修改源码添加(或者利用反射进行设置值)
public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
public MapperRegistry(Configuration config) {
this.config = config;
}
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//从mapperRegistry中获取对应mapper的接口代理对象
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
//动态代理创建mapper接口实现类实例,并重写对应得xml接口方法与查询实现
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
//自定义增强方法
public void clearCache(){
knownMappers.clear();
}
public class Configuration {
//自定义增强方法
public void clearMappedStatement() {
mappedStatements.clear();
}
//自定义增强方法
public void setMapperRegistry(MapperRegistry mapperRegistry) {
this.mapperRegistry = mapperRegistry;
}
//自定义增强方法
public void addMappedStatements(Collection<String> keys,Configuration configuration) {
for (String key : keys){
mappedStatements.put(key, configuration.getMappedStatement(key));
}
}
//自定义增强方法
public void addLoadedResources(Set<String> resources) {
for (String resource : resources){
addLoadedResource(resource);
}
}
5.到此源码修改结束进行测试
public class App {
public static void main( String[] args ) throws IOException {
Scanner scanner = new Scanner(System.in);
//1\读取配置文件
String resource = "mybatis-config.xml";
String resourceMapper = "mybatis-config-mapper.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//2、初始化mybatis,创建SqlSessionFactory类实例
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
Configuration configuration = sqlSessionFactory.getConfiguration();
configuration.setMapUnderscoreToCamelCase(true);
//3、创建Session实例
SqlSession session = sqlSessionFactory.openSession();
AMapper aMapper = session.getMapper(AMapper.class);
System.out.println(aMapper.getName());
while ((scanner.nextInt()==1)){
//清除xml缓存
sqlSessionFactory.getConfiguration().getMapperRegistry().clearCache();
sqlSessionFactory.getConfiguration().clearMappedStatement();
//重新加载xml
InputStream inputStream2 = Resources.getResourceAsStream(resource);
XMLConfigBuilder mapperBuilder = new XMLConfigBuilder(inputStream2, null, null);
configuration = mapperBuilder.parseMapper();
sqlSessionFactory.getConfiguration().setMapperRegistry(configuration.getMapperRegistry());
sqlSessionFactory.getConfiguration().addMappedStatements(configuration.getMappedStatementNames(),configuration);
sqlSessionFactory.getConfiguration().addLoadedResources(configuration.getLoadedResources());
//3、创建Session实例
session = sqlSessionFactory.openSession();
AMapper aMapper1 = session.getMapper(AMapper.class);
System.out.println(aMapper1.getName());
}
}
}
如下是没有重启应用的结果
Opening JDBC Connection
Created connection 1763344271.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@691a7f8f]
==> Preparing: select name from a where id = 2
==> Parameters:
<== Columns: name
<== Row: tom
<== Total: 1
tom
1
Opening JDBC Connection
Created connection 1405747618.
Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@53ca01a2]
==> Preparing: select name from a where id = 1
==> Parameters:
<== Columns: name
<== Row: timi
<== Total: 1
timi
执行sql已经发生变化