动态刷新mapper看过来

提供一个mybatisplus的mapper文件动态刷新配置类
方便开发时使用,不用每次修改xml文件后都要去重启应用


package com.xxx.config;

import java.util.Arrays;
import java.util.List;
import java.util.Set;

import org.apache.commons.collections4.IteratorUtils;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.pagination.optimize.JsqlParserCountOptimize;
import com.google.common.collect.Sets;

@Configuration
@EnableTransactionManagement
@MapperScan({ "com.xxx.*.dao", "com.xxx.dao" })
@ConditionalOnClass(PaginationInterceptor.class)
public class MyBatisPlusConfig {

	@Autowired
	private MybatisPlusProperties mybatisPlusProperties;

	// 分页插件
	@Bean
	public PaginationInterceptor paginationInterceptor() {
		PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
		// 设置最大单页限制数量,默认 500 条,-1 不受限制
		paginationInterceptor.setLimit(Integer.MAX_VALUE);
		// 开启 count 的 join 优化,只针对部分 left join
		paginationInterceptor.setCountSqlParser(new JsqlParserCountOptimize(true));
		// 设置方言
		paginationInterceptor.setDialectType(DbType.MYSQL.getDb());
		return paginationInterceptor;
	}

	/**
	 * 自动刷新插件
	 *
	 * @return
	 */
	//@ConditionalOnProperty("mybatis-plus.global-config.refresh")
	@Bean
	public MybatisMapperRefresh mybatisMapperRefresh(ApplicationContext applicationContext,
			SqlSessionFactory sqlSessionFactory) {
		Set<Resource> mapperLocations = Sets.newLinkedHashSet();
		for (String xx : mybatisPlusProperties.getMapperLocations()) {
			try {
				mapperLocations.addAll(Arrays.asList(applicationContext.getResources(xx)));
			} catch (Exception e) {
				continue;
			}
		}
		List<Resource> list = IteratorUtils.toList(mapperLocations.iterator());
		Resource[] array = list.toArray(new Resource[list.size()]);
		MybatisMapperRefresh mybatisMapperRefresh = new MybatisMapperRefresh(array, sqlSessionFactory, 10, 5, true);
		return mybatisMapperRefresh;
	}

}



package com.xxx.config;

import java.io.File;
import java.io.IOException;
import java.lang.reflect.Field;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.apache.ibatis.binding.MapperRegistry;
import org.apache.ibatis.builder.xml.XMLMapperBuilder;
import org.apache.ibatis.builder.xml.XMLMapperEntityResolver;
import org.apache.ibatis.executor.ErrorContext;
import org.apache.ibatis.executor.keygen.SelectKeyGenerator;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.logging.Log;
import org.apache.ibatis.logging.LogFactory;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.parsing.XNode;
import org.apache.ibatis.parsing.XPathParser;
import org.apache.ibatis.session.Configuration;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.core.io.FileSystemResource;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.util.ResourceUtils;

import com.baomidou.mybatisplus.core.config.GlobalConfig;
import com.baomidou.mybatisplus.core.toolkit.GlobalConfigUtils;
import com.baomidou.mybatisplus.core.toolkit.SystemClock;
import com.google.common.collect.Lists;

/**
 * 切莫用于生产环境(后果自负),
 * <p>Mybatis 映射文件热加载(发生变动后自动重新加载).</p>
 * <p>方便开发时使用,不用每次修改xml文件后都要去重启应用.</p>
 */
public class MybatisMapperRefresh implements Runnable {
	private static final Log logger = LogFactory.getLog(MybatisMapperRefresh.class);
	/**
	 * 记录jar包存在的mapper
	 */
	private static final Map<String, List<Resource>> jarMapper = new HashMap<>();
	private SqlSessionFactory sqlSessionFactory;
	private Resource[] mapperLocations;
	private volatile Long beforeTime = 0L;
	private Configuration configuration;
	/**
	 * 是否开启刷新mapper
	 */
	private boolean enabled;
	/**
	 * xml文件目录
	 */
	private Set<String> fileSet;
	/**
	 * 延迟加载时间
	 */
	private int delaySeconds = 10;
	/**
	 * 刷新间隔时间
	 */
	private int sleepSeconds = 20;

	public MybatisMapperRefresh(Resource[] mapperLocations, SqlSessionFactory sqlSessionFactory, int delaySeconds,
			int sleepSeconds, boolean enabled) {
		this.mapperLocations = mapperLocations.clone();
		this.sqlSessionFactory = sqlSessionFactory;
		this.delaySeconds = delaySeconds;
		this.enabled = enabled;
		this.sleepSeconds = sleepSeconds;
		this.configuration = sqlSessionFactory.getConfiguration();
		this.run();
	}

	public MybatisMapperRefresh(Resource[] mapperLocations, SqlSessionFactory sqlSessionFactory, boolean enabled) {
		this.mapperLocations = mapperLocations.clone();
		this.sqlSessionFactory = sqlSessionFactory;
		this.enabled = enabled;
		this.configuration = sqlSessionFactory.getConfiguration();
		this.run();
	}

	@Override
	public void run() {
		final GlobalConfig globalConfig = GlobalConfigUtils.getGlobalConfig(configuration);
		/*
		 * 启动 XML 热加载
		 */
		if (enabled) {
			beforeTime = SystemClock.now();
			final MybatisMapperRefresh runnable = this;
			new Thread(new Runnable() {

				@Override
				public void run() {
					if (fileSet == null) {
						fileSet = new HashSet<>();
						if (mapperLocations != null) {
							for (Resource mapperLocation : mapperLocations) {
								try {
									if (ResourceUtils.isJarURL(mapperLocation.getURL())) {
										String key = new UrlResource(
												ResourceUtils.extractJarFileURL(mapperLocation.getURL())).getFile()
														.getPath();
										fileSet.add(key);
										if (jarMapper.get(key) != null) {
											jarMapper.get(key).add(mapperLocation);
										} else {
											List<Resource> resourcesList = new ArrayList<>();
											resourcesList.add(mapperLocation);
											jarMapper.put(key, resourcesList);
										}
									} else {
										fileSet.add(mapperLocation.getFile().getPath());
									}
								} catch (IOException ioException) {
									ioException.printStackTrace();
								}
							}
						}
					}
					try {
						Thread.sleep(delaySeconds * 1000);
					} catch (InterruptedException interruptedException) {
						interruptedException.printStackTrace();
					}
					do {
						try {
							for (String filePath : fileSet) {
								File file = new File(filePath);
								if (file.isFile() && file.lastModified() > beforeTime) {
									// 记录上次重新加载时间防止重复加载已经重载的文件
									beforeTime = file.lastModified();
									List<Resource> removeList = jarMapper.get(filePath);
									if (removeList != null && !removeList.isEmpty()) {
										for (Resource resource : removeList) {
											runnable.refresh(resource);
										}
									} else {
										runnable.refresh(new FileSystemResource(file));
									}
								}
							}
						} catch (Exception exception) {
							exception.printStackTrace();
						}
						try {
							Thread.sleep(sleepSeconds * 1000);
						} catch (InterruptedException interruptedException) {
							interruptedException.printStackTrace();
						}

					} while (true);
				}
			}, "mybatis-plus MapperRefresh").start();
		}
	}

	/**
	 * 刷新mapper
	 *
	 * @throws Exception
	 */
	@SuppressWarnings("rawtypes")
	private void refresh(Resource resource)
			throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
		this.configuration = sqlSessionFactory.getConfiguration();
		boolean isSupper = configuration.getClass().getSuperclass() == Configuration.class;
		try {
			Field loadedResourcesField = isSupper
					? configuration.getClass().getSuperclass().getDeclaredField("loadedResources")
					: configuration.getClass().getDeclaredField("loadedResources");
			loadedResourcesField.setAccessible(true);
			Set loadedResourcesSet = ((Set) loadedResourcesField.get(configuration));
			XPathParser xPathParser = new XPathParser(resource.getInputStream(), true, configuration.getVariables(),
					new XMLMapperEntityResolver());
			XNode context = xPathParser.evalNode("/mapper");
			String namespace = context.getStringAttribute("namespace");
			Field field = MapperRegistry.class.getDeclaredField("knownMappers");
			field.setAccessible(true);
			Map mapConfig = (Map) field.get(configuration.getMapperRegistry());
			Collection<String> mappedStatementNames = configuration.getMappedStatementNames();

			mapConfig.remove(Resources.classForName(namespace));
			loadedResourcesSet.remove(resource.toString());
			configuration.getCacheNames().remove(namespace);

			cleanParameterMap(context.evalNodes("/mapper/parameterMap"), namespace);
			cleanResultMap(context.evalNodes("/mapper/resultMap"), namespace);
			cleanKeyGenerators(context.evalNodes("insert|update|select|delete"), namespace);
			cleanSqlElement(context.evalNodes("/mapper/sql"), namespace);
			XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(resource.getInputStream(),
					sqlSessionFactory.getConfiguration(), resource.toString(),
					sqlSessionFactory.getConfiguration().getSqlFragments());
			xmlMapperBuilder.parse();
			logger.debug("refresh: '" + resource + "', success!");
		} catch (IOException e) {
			logger.error("Refresh IOException :" + e.getMessage());
		} finally {
			ErrorContext.instance().reset();
		}
	}

	/**
	 * 清理parameterMap
	 *
	 * @param list
	 * @param namespace
	 */
	private void cleanParameterMap(List<XNode> list, String namespace) {
		for (XNode parameterMapNode : list) {
			String id = parameterMapNode.getStringAttribute("id");
			configuration.getParameterMaps().remove(namespace + "." + id);
		}
	}

	/**
	 * 清理resultMap
	 *
	 * @param list
	 * @param namespace
	 */
	private void cleanResultMap(List<XNode> list, String namespace) {
		for (XNode resultMapNode : list) {
			String id = resultMapNode.getStringAttribute("id", resultMapNode.getValueBasedIdentifier());
			configuration.getResultMapNames().remove(id);
			configuration.getResultMapNames().remove(namespace + "." + id);
			clearResultMap(resultMapNode, namespace);
		}
	}

	private void clearResultMap(XNode xNode, String namespace) {
		for (XNode resultChild : xNode.getChildren()) {
			if ("association".equals(resultChild.getName()) || "collection".equals(resultChild.getName())
					|| "case".equals(resultChild.getName())) {
				if (resultChild.getStringAttribute("select") == null) {
					configuration.getResultMapNames()
							.remove(resultChild.getStringAttribute("id", resultChild.getValueBasedIdentifier()));
					configuration.getResultMapNames().remove(namespace + "."
							+ resultChild.getStringAttribute("id", resultChild.getValueBasedIdentifier()));
					if (resultChild.getChildren() != null && !resultChild.getChildren().isEmpty()) {
						clearResultMap(resultChild, namespace);
					}
				}
			}
		}
	}

	/**
	 * 清理selectKey
	 *
	 * @param list
	 * @param namespace
	 */
	private void cleanKeyGenerators(List<XNode> list, String namespace) {
		for (XNode context : list) {
			String id = context.getStringAttribute("id");
			configuration.getKeyGeneratorNames().remove(id + SelectKeyGenerator.SELECT_KEY_SUFFIX);
			configuration.getKeyGeneratorNames().remove(namespace + "." + id + SelectKeyGenerator.SELECT_KEY_SUFFIX);

			Collection<MappedStatement> mappedStatements = configuration.getMappedStatements();
			List<MappedStatement> objects = Lists.newArrayList();
			Iterator<MappedStatement> it = mappedStatements.iterator();
			while (it.hasNext()) {
				Object object = it.next();
				if (object instanceof org.apache.ibatis.mapping.MappedStatement) {
					MappedStatement mappedStatement = (MappedStatement) object;
					if (mappedStatement.getId().equals(namespace + "." + id)) {
						objects.add(mappedStatement);
					}
				}
			}
			mappedStatements.removeAll(objects);
		}
	}

	/**
	 * 清理sql节点缓存
	 *
	 * @param list
	 * @param namespace
	 */
	private void cleanSqlElement(List<XNode> list, String namespace) {
		for (XNode context : list) {
			String id = context.getStringAttribute("id");
			configuration.getSqlFragments().remove(id);
			configuration.getSqlFragments().remove(namespace + "." + id);
		}
	}
}


最后配置文件配置


mybatis-plus.global-config.refresh = true

  • 1
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
在Spring Boot中使用动态代理来代理Mapper方法可以通过使用MyBatis提供的Mapper动态代理来实现。以下是一个使用Spring Boot和MyBatis的示例: 首先,确保你的Spring Boot项目中已经添加了MyBatis的依赖。例如,可以在pom.xml文件中添加以下依赖: ```xml <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> <version>2.2.0</version> </dependency> ``` 接下来,创建一个Mapper接口,例如`UserMapper`,定义需要代理的方法。例如: ```java @Mapper public interface UserMapper { List<User> getAllUsers(); User getUserById(Long id); void insertUser(User user); void updateUser(User user); void deleteUser(Long id); // 其他方法... } ``` 然后,在你的Spring Boot配置类上使用`@MapperScan`注解来扫描Mapper接口。例如: ```java @Configuration @MapperScan("com.example.mapper") // 指定Mapper接口所在的包路径 public class MyBatisConfig { // 其他配置... } ``` 最后,在你的Service或Controller中注入`UserMapper`接口,并调用其方法即可。例如: ```java @Service public class UserService { private final UserMapper userMapper; public UserService(UserMapper userMapper) { this.userMapper = userMapper; } public List<User> getAllUsers() { return userMapper.getAllUsers(); } public User getUserById(Long id) { return userMapper.getUserById(id); } public void insertUser(User user) { userMapper.insertUser(user); } public void updateUser(User user) { userMapper.updateUser(user); } public void deleteUser(Long id) { userMapper.deleteUser(id); } // 其他方法... } ``` 这样,你就可以使用动态代理来调用Mapper方法了。注意,动态代理的具体实现是由MyBatis框架来完成的,Spring Boot只是提供了集成的支持。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值