Activiti 数据库表结构大小写转换
- 背景
- 实操
- 修改数据库服务
- 利用拦截器处理
- 拦截器使用
- 通过源码窥探实现方案
- DbSqlSessionFactory&sqlSessionFactory
背景
项目中使用了activiti,activiti使用mysql
作为数据源。上线时被DBA卡住了,原因是大写的表结构不符合sql规范,一番沟通后无果,无奈只能自己想办法解决了。经过一番探索,最终成功解决表结构大写问题,现总结如下。
注:首先你肯定需要自己将建表语句改为小写,然后创建对应的表,这里需要注意的一点是
act_ge_property
需要有初始化数据,否则项目无法正常启动。
实操
修改数据库服务
这个是最直接也最简单的方式,直接修改数据库服务,使其大小写不敏感。
- 打开my.cnf文件,加入以下语句后重启。
lower_case_table_names = 1
但是此方法受是否可操作服务器的局限。很明显,这个方法在我这这里行不通。
利用拦截器处理
使用mybatis
的拦截器在向数据库发送sql的时候进行拦截,将其转换为小写的语句。
拦截器使用
- 定义拦截器
xxInterceptor
使其实现 mybatis的Interceptor
接口; - 在mybaits的全局配置文件中,声明这个插件。
@Intercepts({
@Signature(
type = StatementHandler.class, method = "prepare", args = {Connection.class, Integer.class
})
})
@Component
public class MySqlInterceptor implements Interceptor {
@Override
public Object intercept(Invocation invocation) throws Throwable {
StatementHandler statementHandler = (StatementHandler) invocation.getTarget();
BoundSql boundSql = statementHandler.getBoundSql();
//获取到原始sql语句
String sql = boundSql.getSql();
sql = convertTableNameToLower(sql);
Field field = boundSql.getClass().getDeclaredField("sql");
field.setAccessible(true);
field.set(boundSql, sql);
return invocation.proceed();
}
@Override
public Object plugin(Object o) {
return Plugin.wrap(o, this);
}
private String convertTableNameToLower(String sql) {
//TODO 对sql进行处理
return sql;
}
@Override
public void setProperties(Properties properties) {
}
}
<!-- MyBatis全局配置文件:mybatis-config.xml -->
<plugins>
<plugin interceptor="xxx.xxx.xxx.xxx.MySqlInterceptor">
<!--此处可以定义一些用得着的属性-->
<property name="propertyXXX" value="valueXXX"/>
</plugin>
</plugins>
OK,此时一个sql拦截器就定义好了,启动项目试试吧。
没错,正如你期待的那样。如果你的表结构此时已经改成小写了,那么在启动的时候项目会报错,告诉你表不存在。但是,到这肯定不甘心啊,我的拦截器到底生效了没,是不是白搞了。于是,我为了项目能起来,做了如下操作
- 先把表全部干掉,让activiti启动的时候自己创建表,
- 等项目起来以后,再把系统自动创建的大写的表干掉,改成自己定义的小写表。
此时再测试一下(在自己定义的拦截器那块加个断点试试),没错,进来了,数据也正常写入了。至少取得了阶段性胜利。
思考:为什么项目启动的时候拦截器未生效,而在正常启动之后可以正常运行?
补充:拦截器的添加是通过sqlSessionFactory添加的
sqlSessionFactory.getConfiguration().addInterceptor(interceptor);
猜测是activiti自己对mybatis的SqlSessionFactory
做了封装,并且与mybatis的默认DefaultSqlSessionFactory
建立了联系。在启动的时候使用自己的SqlSessionFactory
,项目启动之后DefaultSqlSessionFactory
也初始化完成,此时的DefaultSqlSessionFactory
已经加载了自定义的拦截器。自定义的与默认的又建立了联系,所以启动后可以正常使用。
后来的探索也验证了这一点。activiti自己维护了一个DbSqlSessionFactory
。
那如果我能在DbSqlSessionFactory这个上面添加我自定义的拦截器,那应该就可以正常使用了。
通过源码窥探实现方案
从这里入手
org.activiti.engine.impl.cfg.ProcessEngineConfigurationImpl#init
public void init() {
this.initConfigurators();
this.configuratorsBeforeInit();
// ……
this.configuratorsAfterInit();
}
查看initConfigurators
方法
public void initConfigurators() {
this.allConfigurators = new ArrayList();
//加载默认的configurators
if (this.configurators != null) {
Iterator var1 = this.configurators.iterator();
while(var1.hasNext()) {
ProcessEngineConfigurator configurator = (ProcessEngineConfigurator)var1.next();
this.allConfigurators.add(configurator);
}
}
if (this.enableConfiguratorServiceLoader) {
ClassLoader classLoader = this.getClassLoader();
//加载自定义的configurators
ServiceLoader<ProcessEngineConfigurator> configuratorServiceLoader = ServiceLoader.load(ProcessEngineConfigurator.class, classLoader);
for(var4 = configuratorServiceLoader.iterator(); var4.hasNext(); ++nrOfServiceLoadedConfigurators) {
// ……
this.allConfigurators.add(configurator);
}
//……
}
}
为了查看清晰,代码仅保留了我们需要关注的内容,此处主要做了两件事情
- 加载默认的configurators添加至allConfigurators
- 加载自定义的configurators添加至allConfigurators
加载自定义配置的时候,注意到ProcessEngineConfigurator
public interface ProcessEngineConfigurator {
void beforeInit(ProcessEngineConfigurationImpl var1);
void configure(ProcessEngineConfigurationImpl var1);
int getPriority();
}
然后在configuratorsBeforeInit()
及configuratorsAfterInit()
中分别调用了beforeInit()及configure()方法。
SqlSessionFactory sqlSessionFactory = ProcessEngineConfigurationImpl.getSqlSessionFactory();
拿到sqlSessionFactory就好办了。
正如上文 “补充” 中提到,只要通过
sqlSessionFactory.getConfiguration().addInterceptor(interceptor)
添加拦截器即可。接下来开始自定义configurators
- 实现ProcessEngineConfigurator(这里选择
extend
ProcessEngineConfigurator
的抽象类AbstractProcessEngineConfigurator
)
public class MyProcessEngineConfigurator extends AbstractProcessEngineConfigurator {
public void configure(ProcessEngineConfigurationImpl processEngineConfiguration) {
SqlSessionFactory sqlSessionFactory = processEngineConfiguration.getSqlSessionFactory();
//这里是自定义的MySqlInterceptor,跟上文一样
sqlSessionFactory.getConfiguration().addInterceptor(new MySqlInterceptor());
}
}
- 添加到配置文件
<bean id="processEngineConfiguration" class="org.activiti.spring.SpringProcessEngineConfiguration">
<!--
……
-->
<property name="configurators">
<list>
<bean class="com.xxx.xxx.MyProcessEngineConfigurator"></bean>
</list>
</property>
</bean>
如果是springBoot的项目,直接使用配置类的方式
@Configuration
public class MyAbstractProcessEngineConfigurator implements ProcessEngineConfigurationConfigurer {
@Override
public void configure(SpringProcessEngineConfiguration springProcessEngineConfiguration) {
List<ProcessEngineConfigurator> list = new ArrayList<>();
list.add(new MyProcessEngineConfigurator());
springProcessEngineConfiguration.setConfigurators(list);
}
}
这样,在项目启动的时候,就可以将自定义的拦截器加载进activiti内,至此,改造结束。
DbSqlSessionFactory&sqlSessionFactory
activiti自己维护的DbSqlSessionFactory
与mybatis的sqlSessionFactory
的关系是如何维护的呢?
public void init() {
this.initConfigurators();
this.configuratorsBeforeInit();
// ……
if (this.usingRelationalDatabase) {
this.initSqlSessionFactory();
}
this.initSessionFactories();
// ……
this.configuratorsAfterInit();
}
看到这里initSqlSessionFactory
,创建mybatis的DefaultSqlSessionFactory
public void initSqlSessionFactory() {
if (this.sqlSessionFactory == null) {
InputStream inputStream = null;
try {
inputStream = this.getMyBatisXmlConfigurationStream();
Environment environment = new Environment("default", this.transactionFactory, this.dataSource);
Reader reader = new InputStreamReader(inputStream);
Properties properties = new Properties();
properties.put("prefix", this.databaseTablePrefix);
String wildcardEscapeClause = "";
if (this.databaseWildcardEscapeCharacter != null && this.databaseWildcardEscapeCharacter.length() != 0) {
wildcardEscapeClause = " escape '" + this.databaseWildcardEscapeCharacter + "'";
}
properties.put("wildcardEscapeClause", wildcardEscapeClause);
properties.put("limitBefore", "");
properties.put("limitAfter", "");
properties.put("limitBetween", "");
properties.put("limitOuterJoinBetween", "");
properties.put("limitBeforeNativeQuery", "");
properties.put("orderBy", "order by ${orderByColumns}");
properties.put("blobType", "BLOB");
properties.put("boolValue", "TRUE");
if (this.databaseType != null) {
properties.load(this.getResourceAsStream("org/activiti/db/properties/" + this.databaseType + ".properties"));
}
Configuration configuration = this.initMybatisConfiguration(environment, reader, properties);
this.sqlSessionFactory = new DefaultSqlSessionFactory(configuration);
} catch (Exception var10) {
throw new ActivitiException("Error while building ibatis SqlSessionFactory: " + var10.getMessage(), var10);
} finally {
IoUtil.closeSilently(inputStream);
}
}
}
然后将DefaultSqlSessionFactory赋给dbSqlSessionFactory
public void initSessionFactories() {
if (this.sessionFactories == null) {
this.sessionFactories = new HashMap();
if (this.usingRelationalDatabase) {
this.initDbSqlSessionFactory();
}
this.addSessionFactory(new GenericManagerFactory(EntityCache.class, EntityCacheImpl.class));
}
if (this.customSessionFactories != null) {
Iterator var1 = this.customSessionFactories.iterator();
while(var1.hasNext()) {
SessionFactory sessionFactory = (SessionFactory)var1.next();
this.addSessionFactory(sessionFactory);
}
}
}
public void initDbSqlSessionFactory() {
if (this.dbSqlSessionFactory == null) {
this.dbSqlSessionFactory = this.createDbSqlSessionFactory();
}
this.dbSqlSessionFactory.setDatabaseType(this.databaseType);
this.dbSqlSessionFactory.setIdGenerator(this.idGenerator);
//这里添加
this.dbSqlSessionFactory.setSqlSessionFactory(this.sqlSessionFactory);
this.dbSqlSessionFactory.setDbIdentityUsed(this.isDbIdentityUsed);
this.dbSqlSessionFactory.setDbHistoryUsed(this.isDbHistoryUsed);
this.dbSqlSessionFactory.setDatabaseTablePrefix(this.databaseTablePrefix);
this.dbSqlSessionFactory.setTablePrefixIsSchema(this.tablePrefixIsSchema);
this.dbSqlSessionFactory.setDatabaseCatalog(this.databaseCatalog);
this.dbSqlSessionFactory.setDatabaseSchema(this.databaseSchema);
this.dbSqlSessionFactory.setBulkInsertEnabled(this.isBulkInsertEnabled, this.databaseType);
this.dbSqlSessionFactory.setMaxNrOfStatementsInBulkInsert(this.maxNrOfStatementsInBulkInsert);
this.addSessionFactory(this.dbSqlSessionFactory);
}