mybatis 在增量热加载

application.yml中

mybatis:
  configuration:
    map-underscore-to-camel-case: true
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
  mapper-locations: mybatis/**/*Mapper.xml
  typeAliasesPackage:**.domain
  mapper:
    reload: true

本例子是在springboot中,如果不是springboot的项目可以根据自己情况修改。

具体代码如下:

@Component
public class MapperHotDeployPlugin {
    protected final Log log = LogFactory.getLog(getClass());
    @Value("${mybatis.mapper-locations}")
    private String packageSerchPath;
    @Autowired
    private SqlSessionFactory sqlSessionFactory;
    private List<Resource> mapperLocations = new ArrayList<>();
    private Configuration configuration;
    private HashMap<String, Long> fileMapping = new HashMap<String, Long>();// 记录文件是否变化
    @Value("${mybatis.mapper.reload}")
    private Boolean reloadFlag;
    private static Boolean refresh;

    @PostConstruct
    public void init() {
        try {
            if(!prepareEnv()) return;
            Runnable runnable = new Runnable() {
                public void run() {
                    changeCompare();
                }
            };
            ScheduledExecutorService service = Executors.newSingleThreadScheduledExecutor();
            // 第二个参数为首次执行的延时时间,第三个参数为定时执行的间隔时间
            service.scheduleAtFixedRate(runnable, 1, 10, TimeUnit.SECONDS);
            log.info("============mapper热部署已生效=============");
        } catch (Exception e) {
            log.error("packageSearchPath扫描包路径配置错误");
            return;
        }
    }
    /**
     * 功能描述: 准备赋值环境变量
     * @param:
     * @return:
     * @auther: zenglingsheng@baiyyy.com.cn
     * @date: 2020/10/21 8:43
     */
    public boolean prepareEnv() throws Exception{
        if(!reloadFlag){
            return reloadFlag;
        }
        this.configuration = sqlSessionFactory.getConfiguration();
        String [] packagePaths=packageSerchPath.split(",");
        for(String item:packagePaths){
            Resource[] mapperLocation = new PathMatchingResourcePatternResolver().getResources(item);
            for (Resource resource : mapperLocation) {
                String resourceName = resource.getFilename();
                long lastFrame = resource.contentLength() + resource.lastModified();
                fileMapping.put(resourceName, Long.valueOf(lastFrame));// 文件内容帧值
            }
            this.mapperLocations.addAll(Arrays.asList(mapperLocation));
        }
        return reloadFlag;
    }
    /**
     * 功能描述:判断xml文件是否修改方法
     * @param:
     * @return:
     * @auther: zenglingsheng@baiyyy.com.cn
     * @date: 2020/10/21 8:35
     */
    public void  changeCompare(){
        // task to run goes here
        try {
            refresh=true;
            // 判断是否有文件发生了变化
            if (isChanged()) {
                log.info("-------mapper文件已全部更新-------");
            }
        } catch (Exception e) {
            // TODO Auto-generated catch block
            e.printStackTrace();
        }
    }
    /**
     * 判断文件是否发生了变化      * @param resource      * @return      * @throws IOException
     */
    boolean isChanged() throws IOException {
        List<Resource> changedResource=new ArrayList<>();
        boolean flag = false;
        for (Resource resource : mapperLocations) {
            String resourceName = resource.getFilename();
            boolean addFlag = !fileMapping.isEmpty() && !fileMapping.containsKey(resourceName);// 此为新增标识
            // 修改文件:判断文件内容是否有变化
            Long compareFrame = fileMapping.get(resourceName);
            long lastFrame = resource.contentLength() + resource.lastModified();
            boolean modifyFlag = null != compareFrame && compareFrame != lastFrame;// 此为修改标识
            /* System.out.println("resourceName 名字 "+resourceName+"  新大小"+lastFrame+",老大小 "+compareFrame);*/
            fileMapping.put(resourceName, Long.valueOf(lastFrame));// 文件内容帧值
            // 新增或是修改时,存储文件
            if (addFlag || modifyFlag) {
                flag = true;
                changedResource.add(resource);
                log.info("-------" + resourceName + "文件 已修改-------");
            }
        }
        if(flag){
            refresh(changedResource);
        }
        return flag;
    }
    /**
     * 功能描述: 只更新修改的xml文件在configuration中的数据
     * @param:
     * @return:
     * @auther: zenglingsheng@baiyyy.com.cn
     * @date: 2020/12/29 9:17
     */
    private void refresh(List<Resource> changedResource)  {
        for (int i = 0; i < changedResource.size(); i++) {
            String resource = changedResource.get(i).toString();
            try {
                InputStream inputStream = changedResource.get(i).getInputStream();
                // 清理原有资源,更新为自己的StrictMap方便,增量重新加载
                String[] mapFieldNames = new String[]{
                        "mappedStatements", "caches",
                        "resultMaps", "parameterMaps",
                        "keyGenerators", "sqlFragments"
                };
                for (String fieldName : mapFieldNames){
                    Field field = configuration.getClass().getDeclaredField(fieldName);
                    field.setAccessible(true);
                    Map map = ((Map)field.get(configuration));
                    if (!(map instanceof MapperHotDeployPlugin.StrictMap)){
                        Map newMap = new MapperHotDeployPlugin.StrictMap(StringUtils.capitalize(fieldName) + "collection");
                        for (Object key : map.keySet()){
                            try {
                                newMap.put(key, map.get(key));
                            }catch(IllegalArgumentException ex){
                                newMap.put(key, ex.getMessage());
                            }
                        }
                        field.set(configuration, newMap);
                    }
                }

                // 清理已加载的资源标识,方便让它重新加载。
                Field loadedResourcesField = configuration.getClass().getDeclaredField("loadedResources");
                loadedResourcesField.setAccessible(true);
                Set loadedResourcesSet = ((Set)loadedResourcesField.get(configuration));
                loadedResourcesSet.remove(resource);

                //重新编译加载资源文件。
                XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(inputStream, configuration,
                        resource, configuration.getSqlFragments());
                xmlMapperBuilder.parse();
            } catch (Exception e) {
               log.error("Failed to parse mapping resource: '" + resource + "'", e);
            } finally {
                ErrorContext.instance().reset();
            }
        }
    }
    /**
     * 重写 org.apache.ibatis.session.Configuration.StrictMap 类
     * 来自 MyBatis3.4.0版本,修改 put 方法,允许反复 put更新。
     */
    public static class StrictMap<V> extends HashMap<String, V> {
        protected final Log log = LogFactory.getLog(getClass());
        private static final long serialVersionUID = -4950446264854982944L;
        private String name;

        public StrictMap(String name, int initialCapacity, float loadFactor) {
            super(initialCapacity, loadFactor);
            this.name = name;
        }

        public StrictMap(String name, int initialCapacity) {
            super(initialCapacity);
            this.name = name;
        }

        public StrictMap(String name) {
            super();
            this.name = name;
        }

        public StrictMap(String name, Map<String, ? extends V> m) {
            super(m);
            this.name = name;
        }

        @SuppressWarnings("unchecked")
        public V put(String key, V value) {
            // 如果现在状态为刷新,则刷新(先删除后添加)
            if (MapperHotDeployPlugin.refresh) {
                remove(key);
                log.debug("refresh key:" + key.substring(key.lastIndexOf(".") + 1));
            }
            // end
            if (containsKey(key)) {
                throw new IllegalArgumentException(name + " already contains value for " + key);
            }
            if (key.contains(".")) {
                final String shortKey = getShortName(key);
                if (super.get(shortKey) == null) {
                    super.put(shortKey, value);
                } else {
                    super.put(shortKey, (V) new MapperHotDeployPlugin.StrictMap.Ambiguity(shortKey));
                }
            }
            return super.put(key, value);
        }

        public V get(Object key) {
            V value = super.get(key);
            if (value == null) {
                throw new IllegalArgumentException(name + " does not contain value for " + key);
            }
            if (value instanceof MapperHotDeployPlugin.StrictMap.Ambiguity) {
                throw new IllegalArgumentException(((MapperHotDeployPlugin.StrictMap.Ambiguity) value).getSubject() + " is ambiguous in " + name
                        + " (try using the full name including the namespace, or rename one of the entries)");
            }
            return value;
        }

        private String getShortName(String key) {
            final String[] keyparts = key.split("\\.");
            return keyparts[keyparts.length - 1];
        }

        protected static class Ambiguity {
            private String subject;

            public Ambiguity(String subject) {
                this.subject = subject;
            }

            public String getSubject() {
                return subject;
            }
        }
    }
}

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 3
    评论
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值