需求:mybatis的sql文件暴露在外面,但是当我们修改了对应的mapper.xml文件后,怎样才能避免重启项目实现热加载这类配置文件呢?
环境:springMVC(其中spring3.1.1、mybatis3.2.1)
1.首先看一下mybatis映射层目录结构图如下(Users.java是实体类、SqlMapConfig.xml是管理加载所有的mapper.xml文件、Users.xml是mapper.xml文件):
Users.java内容如下:
- package com.pingpeng.misp.models;
-
- public class Users implements java.io.Serializable {
-
- private static final long serialVersionUID = -6057344509602820411L;
- private Integer id;
- private Integer enable;
- private String password;
- private String account;
-
- // 四个属性的get、set方法请自行添加,此处不写了
-
- }
SqlMapConfig.xml内容如下:
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE configuration
- PUBLIC "-//ibatis.//DTD Config 3.0//EN"
- "http://ibatis./dtd/ibatis-3-config.dtd">
- <configuration>
- <typeAliases>
- <!-- Entities 参数实体 -->
- <!-- com.pingpeng.misp.models 包中的 所有Bean, 取个别名 -->
- <!-- 比如Resources类就用resource来表示 -->
- <typeAlias type="com.pingpeng.misp.models.Users" alias="users" />
- </typeAliases>
- <mappers>
- <!-- 对应Bean类的xml配置文件的路径信息 -->
- <mapper resource="com/pingpeng/misp/models/mybatis/mapper/Users.xml" />
- </mappers>
- </configuration>
Users.xml内容如下:
- <?xml version="1.0" encoding="UTF-8"?>
- <!DOCTYPE mapper
- PUBLIC "-//ibatis.//DTD Mapper 3.0//EN"
- "http://ibatis./dtd/ibatis-3-mapper.dtd">
- <mapper namespace="com.pingpeng.misp.models.mybatis.mapper.Users">
- <!-- 定义一条查询语句,在bean的implementation中会引用此语句的id -->
- <select id="findByName" parameterType="String" resultType="users">
- select * from users where account = #{account}
- </select>
- <select id="findAll" resultType="com.pingpeng.misp.models.Users">
- select * from users
- </select>
- </mapper>
2.再看看application.xml中关于mabatis的配置(什么数据源之类的就不贴出来了):
- <!-- MyBatis sqlSessionFactory 配置 mybatis-->
- <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
- <property name="configLocation" value="classpath:/com/pingpeng/misp/models/mybatis/SqlMapConfig.xml" />
- <property name="dataSource" ref="dataSource" />
- </bean>
- <bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
- <constructor-arg index="0" ref="sqlSessionFactory" />
- </bean>
- <bean id="sqlSessionCache" class="com.pingpeng.misp.common.SqlSessionCache" init-method="refreshMapper">
- <!-- 扫描的映射mapper.xml的文件路径-->
- <property name="packageSearchPath" value="classpath*:com/pingpeng/misp/models/mybatis/mapper/**/*.xml"></property>
- <property name="sqlSessionFactory" ref="sqlSessionFactory"></property>
- </bean>
3.最后看看com.pingpeng.misp.common.SqlSessionCache这个缓存类refreshMapper方法是如何实现刷新的:
- package com.pingpeng.misp.common;
-
- import java.io.IOException;
- import java.lang.reflect.Field;
- import java.util.HashMap;
- import java.util.Map;
- import java.util.Set;
-
- import org.apache.commons.logging.Log;
- import org.apache.commons.logging.LogFactory;
- import org.apache.ibatis.builder.xml.XMLMapperBuilder;
- import org.apache.ibatis.session.Configuration;
- import org.apache.ibatis.session.SqlSessionFactory;
- import org.springframework.core.io.Resource;
- import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
-
- public class SqlSessionCache {
- private Log log = LogFactory.getLog(SqlSessionCache.class);
-
- private SqlSessionFactory sqlSessionFactory;
- private Resource[] mapperLocations;
- private String packageSearchPath;
- private HashMap<String, Long> fileMapping = new HashMap<String, Long>();// 记录文件是否变化
-
- public void refreshMapper() {
- try {
- Configuration configuration = this.sqlSessionFactory.getConfiguration();
-
- // step.1 扫描文件
- try {
- this.scanMapperXml();
- } catch (IOException e) {
- log.error("packageSearchPath扫描包路径配置错误");
- return;
- }
-
- System.out.println("==============刷新前mapper中的内容===============");
- for (String name : configuration.getMappedStatementNames()) {
- System.out.println(name);
- }
-
- // step.2 判断是否有文件发生了变化
- if (this.isChanged()) {
- // step.2.1 清理
- this.removeConfig(configuration);
-
- // step.2.2 重新加载
- for (Resource configLocation : mapperLocations) {
- try {
- XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configLocation.getInputStream(), configuration, configLocation.toString(), configuration.getSqlFragments());
- xmlMapperBuilder.parse();
- log.info("mapper文件[" + configLocation.getFilename() + "]缓存加载成功");
- } catch (IOException e) {
- log.error("mapper文件[" + configLocation.getFilename() + "]不存在或内容格式不对");
- continue;
- }
- }
- }
-
- System.out.println("==============刷新后mapper中的内容===============");
- for (String name : configuration.getMappedStatementNames()) {
- System.out.println(name);
- }
- } catch (Exception e) {
- e.printStackTrace();
- }
- }
-
- public void setPackageSearchPath(String packageSearchPath) {
- this.packageSearchPath = packageSearchPath;
- }
-
- public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
- this.sqlSessionFactory = sqlSessionFactory;
- }
-
- /**
- * 扫描xml文件所在的路径
- * @throws IOException
- */
- private void scanMapperXml() throws IOException {
- this.mapperLocations = new PathMatchingResourcePatternResolver().getResources(packageSearchPath);
- }
-
- /**
- * 清空Configuration中几个重要的缓存
- * @param configuration
- * @throws Exception
- */
- private void removeConfig(Configuration configuration) throws Exception {
- Class<?> classConfig = configuration.getClass();
- clearMap(classConfig, configuration, "mappedStatements");
- clearMap(classConfig, configuration, "caches");
- clearMap(classConfig, configuration, "resultMaps");
- clearMap(classConfig, configuration, "parameterMaps");
- clearMap(classConfig, configuration, "keyGenerators");
- clearMap(classConfig, configuration, "sqlFragments");
-
- clearSet(classConfig, configuration, "loadedResources");
-
- }
-
- @SuppressWarnings("rawtypes")
- private void clearMap(Class<?> classConfig, Configuration configuration, String fieldName) throws Exception {
- Field field = classConfig.getDeclaredField(fieldName);
- field.setAccessible(true);
- Map mapConfig = (Map) field.get(configuration);
- mapConfig.clear();
- }
-
- @SuppressWarnings("rawtypes")
- private void clearSet(Class<?> classConfig, Configuration configuration, String fieldName) throws Exception {
- Field field = classConfig.getDeclaredField(fieldName);
- field.setAccessible(true);
- Set setConfig = (Set) field.get(configuration);
- setConfig.clear();
- }
-
- /**
- * 判断文件是否发生了变化
- * @param resource
- * @return
- * @throws IOException
- */
- private boolean isChanged() throws IOException {
- boolean flag = false;
- for (Resource resource : mapperLocations) {
- String resourceName = resource.getFilename();
-
- boolean addFlag = !fileMapping.containsKey(resourceName);// 此为新增标识
-
- // 修改文件:判断文件内容是否有变化
- Long compareFrame = fileMapping.get(resourceName);
- long lastFrame = resource.contentLength() + resource.lastModified();
- boolean modifyFlag = null != compareFrame && compareFrame.longValue() != lastFrame;// 此为修改标识
-
- // 新增或是修改时,存储文件
- if(addFlag || modifyFlag) {
- fileMapping.put(resourceName, Long.valueOf(lastFrame));// 文件内容帧值
- flag = true;
- }
- }
- return flag;
- }
- }
总结:
1.可以写个定时器每隔一段时间执行SqlSessionCache中refreshMapper()方法,这样就实现热部署功能
2.注意<property name="packageSearchPath" value="classpath*:com/pingpeng/misp/models/mybatis/mapper/**/*.xml"></property>配置的路径一定要是所有的mapper映射文件,而不是总管理文件
3.当前实现如果只改动了一个文件,那么也只重新加载所有文件,如何实现只加载更新的文件呢,这个有点复杂,请自行研究
|