Spring+Mybatis之Mapper热部署
引言
 Spring+Mybatis经常用,在项目中最痛苦的就是修改mapper文件的时候需要重启一下项目,每修改一次就需要重启一次项目。项目小还好,如果项目大,重启一次项目简直是要命。所以,去网上查资料看有没有办法让mybatis热部署,每次更新mapper文件不需要重启项目。
 功夫不负有心人,终于找到了,这玩意只要发现mapper文件被修改,就会重新加载被修改的mapper文件。且只加载被修改的mapper文件!这个可省事了,效率又高,简直爽到爆。

创建MapperRefresh刷新类
在src下创建一个util包,包下面创建一个类,类名为:MapperRefresh
 
代码为下面的一串,注意修改下mybatis-refresh.properties 的路径。
| 1 | package com.talkweb.nets.netsTestLib.data.util; | 
重写SqlSessionFactoryBean
MyBatis有几个不太好的地方,是当实体类别名重名的时候,Mapper XML有错误的时候,系统启动时会一直等待无法正常启动(其实是加载失败后又重新加载,进入了死循环),这里重写下SqlSessionFactoryBean.java文件,解决这个问题,在这个文件里也加入启动上面写的线程类:
1、修改实体类重名的时候抛出并打印异常,否则系统会一直递归造成无法启动。
2、MapperXML有错误的时候抛出并打印异常,否则系统会一直递归造成无法启动。
3、加入启动MapperRefresh.java线程服务。
思路就是用我们自己重写的SqlSessionFactoryBean.class替换mybatis-spring-1.2.2.jar中的SqlSessionFactoryBean.class。
- 在当前项目下新建一个包:右键 src > new Package > org.mybatis.spring,创建SqlSessionFactoryBean.java类。   
- 复制下面一串代码到SqlSessionFactoryBean.java,注意导入 - MapperRefresh正确的包。- 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
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
 100
 101
 102
 103
 104
 105
 106
 107
 108
 109
 110
 111
 112
 113
 114
 115
 116
 117
 118
 119
 120
 121
 122
 123
 124
 125
 126
 127
 128
 129
 130
 131
 132
 133
 134
 135
 136
 137
 138
 139
 140
 141
 142
 143
 144
 145
 146
 147
 148
 149
 150
 151
 152
 153
 154
 155
 156
 157
 158
 159
 160
 161
 162
 163
 164
 165
 166
 167
 168
 169
 170
 171
 172
 173
 174
 175
 176
 177
 178
 179
 180
 181
 182
 183
 184
 185
 186
 187
 188
 189
 190
 191
 192
 193
 194
 195
 196
 197
 198
 199
 200
 201
 202
 203
 204
 205
 206
 207
 208
 209
 210
 211
 212
 213
 214
 215
 216
 217
 218
 219
 220
 221
 222
 223
 224
 225
 226
 227
 228
 229
 230
 231
 232
 233
 234
 235
 236
 237
 238
 239
 240
 241
 242
 243
 244
 245
 246
 247
 248
 249
 250
 251
 252
 253
 254
 255
 256
 257
 258
 259
 260
 261
 262
 263
 264
 265
 266
 267
 268
 269
 270
 271
 272
 273
 274
 275
 276
 277
 278
 279
 280
 281
 282
 283
 284
 285
 286
 287
 288
 289
 290
 291
 292
 293
 294
 295
 296
 297
 298
 299
 300
 301
 302
 303
 304
 305
 306
 307
 308
 309
 310
 311
 312
 313- package org.mybatis.spring; 
 import java.io.IOException;
 import java.sql.SQLException;
 import java.util.Properties;
 import javax.sql.DataSource;
 import org.apache.ibatis.builder.xml.XMLConfigBuilder;
 import org.apache.ibatis.builder.xml.XMLMapperBuilder;
 import org.apache.ibatis.executor.ErrorContext;
 import org.apache.ibatis.logging.Log;
 import org.apache.ibatis.logging.LogFactory;
 import org.apache.ibatis.mapping.DatabaseIdProvider;
 import org.apache.ibatis.mapping.Environment;
 import org.apache.ibatis.plugin.Interceptor;
 import org.apache.ibatis.reflection.factory.ObjectFactory;
 import org.apache.ibatis.reflection.wrapper.ObjectWrapperFactory;
 import org.apache.ibatis.session.Configuration;
 import org.apache.ibatis.session.SqlSessionFactory;
 import org.apache.ibatis.session.SqlSessionFactoryBuilder;
 import org.apache.ibatis.transaction.TransactionFactory;
 import org.apache.ibatis.type.TypeAliasRegistry;
 import org.apache.ibatis.type.TypeHandler;
 import org.apache.ibatis.type.TypeHandlerRegistry;
 import org.mybatis.spring.transaction.SpringManagedTransactionFactory;
 import org.springframework.beans.factory.FactoryBean;
 import org.springframework.beans.factory.InitializingBean;
 import org.springframework.context.ApplicationEvent;
 import org.springframework.context.ApplicationListener;
 import org.springframework.context.event.ContextRefreshedEvent;
 import org.springframework.core.NestedIOException;
 import org.springframework.core.io.Resource;
 import org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy;
 import org.springframework.util.Assert;
 import org.springframework.util.ObjectUtils;
 import org.springframework.util.StringUtils;
 import com.talkweb.nets.netsTestLib.data.util.MapperRefresh;
 public class SqlSessionFactoryBean
 implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ApplicationEvent> {
 private static final Log logger = LogFactory.getLog(SqlSessionFactoryBean.class);
 private Resource configLocation;
 private Resource[] mapperLocations;
 private DataSource dataSource;
 private TransactionFactory transactionFactory;
 private Properties configurationProperties;
 private SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
 private SqlSessionFactory sqlSessionFactory;
 private String environment = SqlSessionFactoryBean.class.getSimpleName();
 private boolean failFast;
 private Interceptor[] plugins;
 private TypeHandler<?>[] typeHandlers;
 private String typeHandlersPackage;
 private Class<?>[] typeAliases;
 private String typeAliasesPackage;
 private Class<?> typeAliasesSuperType;
 private DatabaseIdProvider databaseIdProvider;
 private ObjectFactory objectFactory;
 private ObjectWrapperFactory objectWrapperFactory;
 public void setObjectFactory(ObjectFactory objectFactory) {
 this.objectFactory = objectFactory;
 }
 public void setObjectWrapperFactory(ObjectWrapperFactory objectWrapperFactory) {
 this.objectWrapperFactory = objectWrapperFactory;
 }
 public DatabaseIdProvider getDatabaseIdProvider() {
 return this.databaseIdProvider;
 }
 public void setDatabaseIdProvider(DatabaseIdProvider databaseIdProvider) {
 this.databaseIdProvider = databaseIdProvider;
 }
 public void setPlugins(Interceptor[] plugins) {
 this.plugins = plugins;
 }
 public void setTypeAliasesPackage(String typeAliasesPackage) {
 this.typeAliasesPackage = typeAliasesPackage;
 }
 public void setTypeAliasesSuperType(Class<?> typeAliasesSuperType) {
 this.typeAliasesSuperType = typeAliasesSuperType;
 }
 public void setTypeHandlersPackage(String typeHandlersPackage) {
 this.typeHandlersPackage = typeHandlersPackage;
 }
 public void setTypeHandlers(TypeHandler<?>[] typeHandlers) {
 this.typeHandlers = typeHandlers;
 }
 public void setTypeAliases(Class<?>[] typeAliases) {
 this.typeAliases = typeAliases;
 }
 public void setFailFast(boolean failFast) {
 this.failFast = failFast;
 }
 public void setConfigLocation(Resource configLocation) {
 this.configLocation = configLocation;
 }
 public void setMapperLocations(Resource[] mapperLocations) {
 this.mapperLocations = mapperLocations;
 }
 public void setConfigurationProperties(Properties sqlSessionFactoryProperties) {
 this.configurationProperties = sqlSessionFactoryProperties;
 }
 public void setDataSource(DataSource dataSource) {
 if ((dataSource instanceof TransactionAwareDataSourceProxy)) {
 this.dataSource = ((TransactionAwareDataSourceProxy) dataSource).getTargetDataSource();
 } else
 this.dataSource = dataSource;
 }
 public void setSqlSessionFactoryBuilder(SqlSessionFactoryBuilder sqlSessionFactoryBuilder) {
 this.sqlSessionFactoryBuilder = sqlSessionFactoryBuilder;
 }
 public void setTransactionFactory(TransactionFactory transactionFactory) {
 this.transactionFactory = transactionFactory;
 }
 public void setEnvironment(String environment) {
 this.environment = environment;
 }
 public void afterPropertiesSet() throws Exception {
 Assert.notNull(this.dataSource, "Property 'dataSource' is required");
 Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
 this.sqlSessionFactory = buildSqlSessionFactory();
 }
 protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
 XMLConfigBuilder xmlConfigBuilder = null;
 Configuration configuration;
 if (this.configLocation != null) {
 xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null,
 this.configurationProperties);
 configuration = xmlConfigBuilder.getConfiguration();
 } else {
 if (logger.isDebugEnabled()) {
 logger.debug("Property 'configLocation' not specified, using default MyBatis Configuration");
 }
 configuration = new Configuration();
 configuration.setVariables(this.configurationProperties);
 }
 if (this.objectFactory != null) {
 configuration.setObjectFactory(this.objectFactory);
 }
 if (this.objectWrapperFactory != null) {
 configuration.setObjectWrapperFactory(this.objectWrapperFactory);
 }
 if (StringUtils.hasLength(this.typeAliasesPackage)) {
 String[] typeAliasPackageArray = StringUtils.tokenizeToStringArray(this.typeAliasesPackage, ",; \t\n");
 for (String packageToScan : typeAliasPackageArray) {
 // 修改处:ThinkGem 修改实体类重名的时候抛出并打印异常,否则系统会一直递归造成无法启动
 try {
 configuration.getTypeAliasRegistry().registerAliases(packageToScan,
 typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
 } catch (Exception ex) {
 logger.error("Scanned package: '" + packageToScan + "' for aliases", ex);
 throw new NestedIOException("Scanned package: '" + packageToScan + "' for aliases", ex);
 } finally {
 ErrorContext.instance().reset();
 }
 // 修改处:ThinkGem end
 if (logger.isDebugEnabled()) {
 logger.debug("Scanned package: '" + packageToScan + "' for aliases");
 }
 }
 }
 if (!ObjectUtils.isEmpty(this.typeAliases)) {
 for (Class typeAlias : this.typeAliases) {
 configuration.getTypeAliasRegistry().registerAlias(typeAlias);
 if (logger.isDebugEnabled()) {
 logger.debug("Registered type alias: '" + typeAlias + "'");
 }
 }
 }
 if (!ObjectUtils.isEmpty(this.plugins)) {
 for (Interceptor plugin : this.plugins) {
 configuration.addInterceptor(plugin);
 if (logger.isDebugEnabled()) {
 logger.debug("Registered plugin: '" + plugin + "'");
 }
 }
 }
 if (StringUtils.hasLength(this.typeHandlersPackage)) {
 String[] typeHandlersPackageArray = StringUtils.tokenizeToStringArray(this.typeHandlersPackage, ",; \t\n");
 for (String packageToScan : typeHandlersPackageArray) {
 configuration.getTypeHandlerRegistry().register(packageToScan);
 if (logger.isDebugEnabled()) {
 logger.debug("Scanned package: '" + packageToScan + "' for type handlers");
 }
 }
 }
 if (!ObjectUtils.isEmpty(this.typeHandlers)) {
 for (TypeHandler typeHandler : this.typeHandlers) {
 configuration.getTypeHandlerRegistry().register(typeHandler);
 if (logger.isDebugEnabled()) {
 logger.debug("Registered type handler: '" + typeHandler + "'");
 }
 }
 }
 if (xmlConfigBuilder != null) {
 try {
 xmlConfigBuilder.parse();
 if (logger.isDebugEnabled())
 logger.debug("Parsed configuration file: '" + this.configLocation + "'");
 } catch (Exception ex) {
 throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
 } finally {
 ErrorContext.instance().reset();
 }
 }
 if (this.transactionFactory == null) {
 this.transactionFactory = new SpringManagedTransactionFactory();
 }
 Environment environment = new Environment(this.environment, this.transactionFactory, this.dataSource);
 configuration.setEnvironment(environment);
 if (this.databaseIdProvider != null) {
 try {
 configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
 } catch (SQLException e) {
 throw new NestedIOException("Failed getting a databaseId", e);
 }
 }
 if (!ObjectUtils.isEmpty(this.mapperLocations)) {
 for (Resource mapperLocation : this.mapperLocations) {
 if (mapperLocation == null) {
 continue;
 }
 try {
 XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
 configuration, mapperLocation.toString(), configuration.getSqlFragments());
 xmlMapperBuilder.parse();
 } catch (Exception e) {
 // 修改处:ThinkGem MapperXML有错误的时候抛出并打印异常,否则系统会一直递归造成无法启动
 logger.error("Failed to parse mapping resource: '" + mapperLocation + "'", e);
 throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
 } finally {
 ErrorContext.instance().reset();
 }
 if (logger.isDebugEnabled()) {
 logger.debug("Parsed mapper file: '" + mapperLocation + "'");
 }
 }
 // 修改处:ThinkGem 启动刷新MapperXML定时器(有助于开发者调试)。
 new MapperRefresh(this.mapperLocations, configuration).run();
 } else if (logger.isDebugEnabled()) {
 logger.debug("Property 'mapperLocations' was not specified or no matching resources found");
 }
 return this.sqlSessionFactoryBuilder.build(configuration);
 }
 public SqlSessionFactory getObject() throws Exception {
 if (this.sqlSessionFactory == null) {
 afterPropertiesSet();
 }
 return this.sqlSessionFactory;
 }
 public Class<? extends SqlSessionFactory> getObjectType() {
 return this.sqlSessionFactory == null ? SqlSessionFactory.class : this.sqlSessionFactory.getClass();
 }
 public boolean isSingleton() {
 return true;
 }
 public void onApplicationEvent(ApplicationEvent event) {
 if ((this.failFast) && ((event instanceof ContextRefreshedEvent))) {
 this.sqlSessionFactory.getConfiguration().getMappedStatementNames();
 }
 }
 }- 接下来我们就需要把这个SqlSessionFactoryBean.java文件编译成class文件,然后再复制到mybatis-spring-1.2.2.jar包里面 。重新部署当前项目 Servers > Tomcat 8.x > 右键你的项目 Remove deployment 然后再 Add Deployment…你的项目。 
- 去Tomcat 8的根目录找到对应的SqlSessionFactoryBean.class文件复制出来。   
- 这里记得检查一下编译过的class文件是否正确,将你编译好的SqlSessionFactoryBean.class文件再次拖入,用jd-gui.exe(一款JAVA反编译工具)比较是不是和上面写的代码对应!!!! - 检查无误之后,把SqlSessionFactoryBean.class复制到mybatis-spring-1.2.2.jar(是你本地项目中的jar)包中,替换原来的class文件。   - 创建mybatis-refresh.properties文件- 一切准备就绪,还剩下最后一个属性文件, 创建mybatis-refresh.properties文件,记得把文件格式改成UTF-8。   - mybatis-refresh.properties文件内容为: - 1 
 2
 3
 4
 5
 6
 7
 8- #是否开启刷新线程 
 enabled=true
 #延迟启动刷新程序的秒数
 delaySeconds=60
 #刷新扫描间隔的时长秒数
 sleepSeconds=3
 #扫描Mapper文件的资源路径
 mappingPath=mapper- 测试- 删除org.mybatis.spring包及下面的SqlSessionFactoryBean.java文件。 
- 启动项目,然后随便修改一个mapper.xml文件,然后稍等片刻,在控制台出现如下输出,就表示你成功啦!这样就不用重启项目,也能加载到你修改的mapper.xml文件了 。   
 - 注意- 注意各个文件的位置和名称。
- 注意MapperRefresh.java文件中mybatis-refresh.properties的路径。
- 注意用jd-gui.exe检查编译后的SqlSessionFactoryBean.class文件。