目录
- 问题
- 原因
- 查看两个类源码找到对应的bean
- 注解解释
摘要:现在在项目中使用的MybatisPlus,最近研究了一下流程框架Flowable,看了很多技术文档博客,打算直接整合进去,先记录一下遇到的问题:
问题
Description:
file [D:\project\carshow-server\server-flowable\flowable-admin\target\classes\com\carshow\flowable\mapper\IFlowableCommentMapper.class] required a single bean, but 2 were found:
- sqlSessionFactory: defined by method 'sqlSessionFactory' in class path resource [com/baomidou/mybatisplus/autoconfigure/MybatisPlusAutoConfiguration.class]
- modelerSqlSessionFactory: defined by method 'modelerSqlSessionFactory' in class path resource [org/flowable/ui/modeler/conf/ModelerDatabaseConfiguration.class]
Action:
Consider marking one of the beans as @Primary, updating the consumer to accept multiple beans, or using @Qualifier to identify the bean that should be consumed
原因
整合的bean冲突,spring找到了两个
查看两个类源码找到对应的bean
MybatisPlus: MybatisPlusAutoConfiguration.class
Flowable:ModelerDatabaseConfiguration.class
注解解释
@ConditionalOnMissingBean:它是修饰bean的一个注解,主要实现的是,当你的bean被注册之后,如果有注册相同类型的bean,就不会成功,它会保证你的bean只有一个,即你的实例只有一个,当你注册多个相同的bean时,会出现异常,以此来告诉开发人员。
所以问题就来了,找到了这两个bean冲突,接下来如何进行解决呢,查看了一下源码并查阅了其他大佬的技术博客,记录一下
1. 环境:Flowable6.6
2. 解决:
- 重写mybatis-plus 自动配置类(由于 flowable-modeler 引入时候,会初始化 mybatis的Template和SqlFactory,这导致 mybatis-plus 本身的autoconfig 无法生效,所以需要重写),从源码中拆写代码:
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties; | |
import com.baomidou.mybatisplus.autoconfigure.SpringBootVFS; | |
import com.baomidou.mybatisplus.core.MybatisConfiguration; | |
import com.baomidou.mybatisplus.core.config.GlobalConfig; | |
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler; | |
import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator; | |
import com.baomidou.mybatisplus.core.injector.ISqlInjector; | |
import com.baomidou.mybatisplus.core.toolkit.ObjectUtils; | |
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean; | |
import org.apache.ibatis.mapping.DatabaseIdProvider; | |
import org.apache.ibatis.plugin.Interceptor; | |
import org.apache.ibatis.session.ExecutorType; | |
import org.apache.ibatis.session.SqlSessionFactory; | |
import org.mybatis.spring.SqlSessionTemplate; | |
import org.springframework.context.ApplicationContext; | |
import org.springframework.core.io.ResourceLoader; | |
import org.springframework.util.StringUtils; | |
import javax.sql.DataSource; | |
import java.util.List; | |
/** | |
* @author xw | |
* @description 重写mybatis-plus 自动配置类 | |
* @date/1/7 14:06 | |
*/ | |
public class AbstractMybatisPlusConfiguration { | |
protected SqlSessionFactory getSqlSessionFactory( | |
DataSource dataSource, | |
MybatisPlusProperties properties, | |
ResourceLoader resourceLoader, | |
Interceptor[] interceptors, | |
DatabaseIdProvider databaseIdProvider, | |
ApplicationContext applicationContext | |
) throws Exception { | |
MybatisSqlSessionFactoryBean factory = new MybatisSqlSessionFactoryBean(); | |
factory.setDataSource(dataSource); | |
factory.setVfs(SpringBootVFS.class); | |
if (StringUtils.hasText(properties.getConfigLocation())) { | |
factory.setConfigLocation(resourceLoader.getResource(properties.getConfigLocation())); | |
} | |
applyConfiguration(factory, properties); | |
if (properties.getConfigurationProperties() != null) { | |
factory.setConfigurationProperties(properties.getConfigurationProperties()); | |
} | |
if (!ObjectUtils.isEmpty(interceptors)) { | |
factory.setPlugins(interceptors); | |
} | |
if (databaseIdProvider != null) { | |
factory.setDatabaseIdProvider(databaseIdProvider); | |
} | |
if (StringUtils.hasLength(properties.getTypeAliasesPackage())) { | |
factory.setTypeAliasesPackage(properties.getTypeAliasesPackage()); | |
} | |
// TODO 自定义枚举包 | |
if (StringUtils.hasLength(properties.getTypeEnumsPackage())) { | |
factory.setTypeEnumsPackage(properties.getTypeEnumsPackage()); | |
} | |
if (properties.getTypeAliasesSuperType() != null) { | |
factory.setTypeAliasesSuperType(properties.getTypeAliasesSuperType()); | |
} | |
if (StringUtils.hasLength(properties.getTypeHandlersPackage())) { | |
factory.setTypeHandlersPackage(properties.getTypeHandlersPackage()); | |
} | |
if (!ObjectUtils.isEmpty(properties.resolveMapperLocations())) { | |
factory.setMapperLocations(properties.resolveMapperLocations()); | |
} | |
// TODO 此处必为非 NULL | |
GlobalConfig globalConfig = properties.getGlobalConfig(); | |
//注入填充器 | |
if (applicationContext.getBeanNamesForType(MetaObjectHandler.class, | |
false, false).length >) { | |
MetaObjectHandler metaObjectHandler = applicationContext.getBean(MetaObjectHandler.class); | |
globalConfig.setMetaObjectHandler(metaObjectHandler); | |
} | |
//注入主键生成器 | |
if (applicationContext.getBeanNamesForType(IKeyGenerator.class, false, | |
false).length >) { | |
IKeyGenerator keyGenerator = applicationContext.getBean(IKeyGenerator.class); | |
globalConfig.getDbConfig().setKeyGenerators((List<IKeyGenerator>) keyGenerator); | |
} | |
//注入sql注入器 | |
if (applicationContext.getBeanNamesForType(ISqlInjector.class, false, | |
false).length >) { | |
ISqlInjector iSqlInjector = applicationContext.getBean(ISqlInjector.class); | |
globalConfig.setSqlInjector(iSqlInjector); | |
} | |
factory.setGlobalConfig(globalConfig); | |
return factory.getObject(); | |
} | |
private void applyConfiguration(MybatisSqlSessionFactoryBean factory, MybatisPlusProperties properties) { | |
MybatisConfiguration configuration = properties.getConfiguration(); | |
if (configuration == null && !StringUtils.hasText(properties.getConfigLocation())) { | |
configuration = new MybatisConfiguration(); | |
} | |
// if (configuration != null && !CollectionUtils.isEmpty(this.configurationCustomizers)) { | |
// for (ConfigurationCustomizer customizer : this.configurationCustomizers) { | |
// customizer.customize(configuration); | |
// } | |
// } | |
factory.setConfiguration(configuration); | |
} | |
public SqlSessionTemplate getSqlSessionTemplate(SqlSessionFactory sqlSessionFactory, MybatisPlusProperties properties) { | |
ExecutorType executorType = properties.getExecutorType(); | |
if (executorType != null) { | |
return new SqlSessionTemplate(sqlSessionFactory, executorType); | |
} else { | |
return new SqlSessionTemplate(sqlSessionFactory); | |
} | |
} | |
} | |
- 继承重写的配置类
import com.baomidou.mybatisplus.autoconfigure.MybatisPlusProperties; | |
import org.apache.ibatis.session.SqlSessionFactory; | |
import org.mybatis.spring.SqlSessionTemplate; | |
import org.mybatis.spring.annotation.MapperScan; | |
import org.springframework.beans.factory.annotation.Qualifier; | |
import org.springframework.boot.context.properties.EnableConfigurationProperties; | |
import org.springframework.context.ApplicationContext; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.context.annotation.Configuration; | |
import org.springframework.core.io.ResourceLoader; | |
import javax.sql.DataSource; | |
/** | |
* @author xw | |
* @description | |
* @date/1/7 16:24 | |
*/ | |
public class MybatisPlusConfiguration extends AbstractMybatisPlusConfiguration { | |
public SqlSessionFactory sqlSessionFactoryBean(DataSource dataSource, | |
MybatisPlusProperties properties, | |
ResourceLoader resourceLoader, | |
ApplicationContext applicationContext) throws Exception { | |
return getSqlSessionFactory(dataSource, | |
properties, | |
resourceLoader, | |
null, | |
null, | |
applicationContext); | |
} | |
public SqlSessionTemplate sqlSessionTemplate(MybatisPlusProperties properties, | |
SqlSessionFactory sqlSessionFactory) { | |
return getSqlSessionTemplate(sqlSessionFactory, properties); | |
} | |
} | |
- 重写 flowable-modeler 中 ModelerDatabaseConfiguration,用 @Primary 指定框架内部的mybatis 作为默认的
import liquibase.Liquibase; | |
import liquibase.database.Database; | |
import liquibase.database.DatabaseConnection; | |
import liquibase.database.DatabaseFactory; | |
import liquibase.database.jvm.JdbcConnection; | |
import liquibase.exception.DatabaseException; | |
import liquibase.resource.ClassLoaderResourceAccessor; | |
import lombok.extern.slfj.Slf4j; | |
import org.apache.ibatis.session.SqlSessionFactory; | |
import org.flowable.common.engine.api.FlowableException; | |
import org.flowable.ui.common.service.exception.InternalServerErrorException; | |
import org.flowable.ui.modeler.properties.FlowableModelerAppProperties; | |
import org.mybatis.spring.SqlSessionFactoryBean; | |
import org.mybatis.spring.SqlSessionTemplate; | |
import org.springframework.beans.factory.annotation.Autowired; | |
import org.springframework.context.annotation.Bean; | |
import org.springframework.context.annotation.Configuration; | |
import org.springframework.context.annotation.Primary; | |
import org.springframework.core.io.ResourceLoader; | |
import org.springframework.core.io.support.ResourcePatternUtils; | |
import javax.sql.DataSource; | |
import java.sql.Connection; | |
import java.sql.DatabaseMetaData; | |
import java.sql.SQLException; | |
import java.util.Properties; | |
/** | |
* @author xw | |
* @description 重写 flowable-modeler 中 ModelerDatabaseConfiguration | |
* @date/1/7 16:31 | |
*/ | |
public class DatabaseConfiguration { | |
protected static final String LIQUIBASE_CHANGELOG_PREFIX = "ACT_DE_"; | |
protected FlowableModelerAppProperties modelerAppProperties; | |
protected ResourceLoader resourceLoader; | |
protected static Properties databaseTypeMappings = getDefaultDatabaseTypeMappings(); | |
public static final String DATABASE_TYPE_H = "h2"; | |
public static final String DATABASE_TYPE_HSQL = "hsql"; | |
public static final String DATABASE_TYPE_MYSQL = "mysql"; | |
public static final String DATABASE_TYPE_ORACLE = "oracle"; | |
public static final String DATABASE_TYPE_POSTGRES = "postgres"; | |
public static final String DATABASE_TYPE_MSSQL = "mssql"; | |
public static final String DATABASE_TYPE_DB = "db2"; | |
public static Properties getDefaultDatabaseTypeMappings() { | |
Properties databaseTypeMappings = new Properties(); | |
databaseTypeMappings.setProperty("H", DATABASE_TYPE_H2); | |
databaseTypeMappings.setProperty("HSQL Database Engine", DATABASE_TYPE_HSQL); | |
databaseTypeMappings.setProperty("MySQL", DATABASE_TYPE_MYSQL); | |
databaseTypeMappings.setProperty("Oracle", DATABASE_TYPE_ORACLE); | |
databaseTypeMappings.setProperty("PostgreSQL", DATABASE_TYPE_POSTGRES); | |
databaseTypeMappings.setProperty("Microsoft SQL Server", DATABASE_TYPE_MSSQL); | |
databaseTypeMappings.setProperty(DATABASE_TYPE_DB, DATABASE_TYPE_DB2); | |
databaseTypeMappings.setProperty("DB", DATABASE_TYPE_DB2); | |
databaseTypeMappings.setProperty("DB/NT", DATABASE_TYPE_DB2); | |
databaseTypeMappings.setProperty("DB/NT64", DATABASE_TYPE_DB2); | |
databaseTypeMappings.setProperty("DB UDP", DATABASE_TYPE_DB2); | |
databaseTypeMappings.setProperty("DB/LINUX", DATABASE_TYPE_DB2); | |
databaseTypeMappings.setProperty("DB/LINUX390", DATABASE_TYPE_DB2); | |
databaseTypeMappings.setProperty("DB/LINUXX8664", DATABASE_TYPE_DB2); | |
databaseTypeMappings.setProperty("DB/LINUXZ64", DATABASE_TYPE_DB2); | |
databaseTypeMappings.setProperty("DB/LINUXPPC64", DATABASE_TYPE_DB2); | |
databaseTypeMappings.setProperty("DB/400 SQL", DATABASE_TYPE_DB2); | |
databaseTypeMappings.setProperty("DB/6000", DATABASE_TYPE_DB2); | |
databaseTypeMappings.setProperty("DB UDB iSeries", DATABASE_TYPE_DB2); | |
databaseTypeMappings.setProperty("DB/AIX64", DATABASE_TYPE_DB2); | |
databaseTypeMappings.setProperty("DB/HPUX", DATABASE_TYPE_DB2); | |
databaseTypeMappings.setProperty("DB/HP64", DATABASE_TYPE_DB2); | |
databaseTypeMappings.setProperty("DB/SUN", DATABASE_TYPE_DB2); | |
databaseTypeMappings.setProperty("DB/SUN64", DATABASE_TYPE_DB2); | |
databaseTypeMappings.setProperty("DB/PTX", DATABASE_TYPE_DB2); | |
databaseTypeMappings.setProperty("DB/2", DATABASE_TYPE_DB2); | |
databaseTypeMappings.setProperty("DB UDB AS400", DATABASE_TYPE_DB2); | |
return databaseTypeMappings; | |
} | |
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) { | |
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); | |
sqlSessionFactoryBean.setDataSource(dataSource); | |
String databaseType = initDatabaseType(dataSource); | |
if (databaseType == null) { | |
throw new FlowableException("couldn't deduct database type"); | |
} | |
try { | |
Properties properties = new Properties(); | |
properties.put("prefix", modelerAppProperties.getDataSourcePrefix()); | |
properties.put("blobType", "BLOB"); | |
properties.put("boolValue", "TRUE"); | |
properties.load(this.getClass().getClassLoader().getResourceAsStream("org/flowable/db/properties/" + databaseType + ".properties")); | |
sqlSessionFactoryBean.setConfigurationProperties(properties); | |
sqlSessionFactoryBean | |
.setMapperLocations(ResourcePatternUtils.getResourcePatternResolver(resourceLoader).getResources("classpath:/META-INF/modeler-mybatis-mappings/*.xml")); | |
sqlSessionFactoryBean.afterPropertiesSet(); | |
return sqlSessionFactoryBean.getObject(); | |
} catch (Exception e) { | |
throw new FlowableException("Could not create sqlSessionFactory", e); | |
} | |
} | |
// destroyMethod: see https://github.com/mybatis/old-google-code-issues/issues/ | |
public SqlSessionTemplate SqlSessionTemplate(SqlSessionFactory sqlSessionFactory) { | |
return new SqlSessionTemplate(sqlSessionFactory); | |
} | |
public Liquibase liquibase(DataSource dataSource) { | |
log.info("Configuring Liquibase"); | |
Liquibase liquibase = null; | |
try { | |
DatabaseConnection connection = new JdbcConnection(dataSource.getConnection()); | |
Database database = DatabaseFactory.getInstance().findCorrectDatabaseImplementation(connection); | |
database.setDatabaseChangeLogTableName(LIQUIBASE_CHANGELOG_PREFIX + database.getDatabaseChangeLogTableName()); | |
database.setDatabaseChangeLogLockTableName(LIQUIBASE_CHANGELOG_PREFIX + database.getDatabaseChangeLogLockTableName()); | |
liquibase = new Liquibase("META-INF/liquibase/flowable-modeler-app-db-changelog.xml", new ClassLoaderResourceAccessor(), database); | |
liquibase.update("flowable"); | |
return liquibase; | |
} catch (Exception e) { | |
throw new InternalServerErrorException("Error creating liquibase database", e); | |
} finally { | |
closeDatabase(liquibase); | |
} | |
} | |
protected String initDatabaseType(DataSource dataSource) { | |
String databaseType = null; | |
Connection connection = null; | |
try { | |
connection = dataSource.getConnection(); | |
DatabaseMetaData databaseMetaData = connection.getMetaData(); | |
String databaseProductName = databaseMetaData.getDatabaseProductName(); | |
log.info("database product name: '{}'", databaseProductName); | |
databaseType = databaseTypeMappings.getProperty(databaseProductName); | |
if (databaseType == null) { | |
throw new FlowableException("couldn't deduct database type from database product name '" + databaseProductName + "'"); | |
} | |
log.info("using database type: {}", databaseType); | |
} catch (SQLException e) { | |
log.error("Exception while initializing Database connection", e); | |
} finally { | |
try { | |
if (connection != null) { | |
connection.close(); | |
} | |
} catch (SQLException e) { | |
log.error("Exception while closing the Database connection", e); | |
} | |
} | |
return databaseType; | |
} | |
private void closeDatabase(Liquibase liquibase) { | |
if (liquibase != null) { | |
Database database = liquibase.getDatabase(); | |
if (database != null) { | |
try { | |
database.close(); | |
} catch (DatabaseException e) { | |
log.warn("Error closing database", e); | |
} | |
} | |
} | |
} | |
} |