package jnpf.database.config; import com.alibaba.druid.pool.DruidDataSource; import com.baomidou.dynamic.datasource.DynamicRoutingDataSource; import com.baomidou.dynamic.datasource.annotation.DS; import com.baomidou.dynamic.datasource.aop.DynamicDataSourceAnnotationAdvisor; import com.baomidou.dynamic.datasource.enums.DdConstants; import com.baomidou.dynamic.datasource.processor.DsProcessor; import com.baomidou.dynamic.datasource.creator.DataSourceProperty; import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider; import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties; import com.baomidou.mybatisplus.autoconfigure.SpringBootVFS; import com.baomidou.mybatisplus.core.MybatisConfiguration; import com.baomidou.mybatisplus.core.config.GlobalConfig; import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator; import com.baomidou.mybatisplus.core.injector.ISqlInjector; import com.baomidou.mybatisplus.core.metadata.TableInfoHelper; import com.baomidou.mybatisplus.core.toolkit.StringPool; import com.baomidou.mybatisplus.extension.incrementer.H2KeyGenerator; import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor; import com.baomidou.mybatisplus.extension.plugins.handler.TableNameHandler; import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler; import com.baomidou.mybatisplus.extension.plugins.inner.DynamicTableNameInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor; import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor; import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean; import com.github.pagehelper.PageInterceptor; import jnpf.base.entity.SuperBaseEntity; import jnpf.config.ApplicationStartErrorCheck; import jnpf.config.ConfigValueUtil; import jnpf.constant.GlobalConst; import jnpf.database.model.entity.DbLinkEntity; import jnpf.database.plugins.*; import jnpf.database.source.DbBase; import jnpf.database.source.impl.DbOracle; import jnpf.database.util.ConnUtil; import jnpf.database.util.DataSourceUtil; import jnpf.database.util.DbTypeUtil; import jnpf.database.util.DynamicDataSourceUtil; import jnpf.exception.DataException; import jnpf.util.ClassUtil; import jnpf.util.TenantHolder; import lombok.extern.slf4j.Slf4j; import net.sf.jsqlparser.expression.Expression; import net.sf.jsqlparser.expression.LongValue; import net.sf.jsqlparser.expression.NullValue; import net.sf.jsqlparser.expression.StringValue; import org.apache.ibatis.builder.MapperBuilderAssistant; import org.apache.ibatis.logging.slf4j.Slf4jImpl; import org.apache.ibatis.mapping.MappedStatement; import org.apache.ibatis.plugin.Interceptor; import org.apache.ibatis.session.SqlSessionFactory; import org.apache.ibatis.type.JdbcType; import org.mybatis.spring.annotation.MapperScan; import org.springframework.aop.Advisor; import org.springframework.aop.Pointcut; import org.springframework.aop.aspectj.AspectJExpressionPointcut; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Qualifier; import org.springframework.boot.autoconfigure.condition.ConditionalOnClass; import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; import org.springframework.context.annotation.*; import org.springframework.core.io.Resource; import org.springframework.core.io.support.PathMatchingResourcePatternResolver; import org.springframework.core.io.support.ResourcePatternResolver; import javax.sql.DataSource; import java.io.IOException; import java.lang.reflect.Modifier; import java.net.URISyntaxException; import java.sql.SQLException; import java.util.*; /** * MybatisPlus配置类 * * @author JNPF开发平台组 * @version V3.1.0 * @copyright 引迈信息技术有限公司 * @date 2021/3/16 8:53 */ @Slf4j @Configuration @ComponentScan("jnpf") @DependsOn({"tenantDataSourceUtil", "threadPoolExecutorUtil"}) @MapperScan(basePackages = {"jnpf.*.mapper", "jnpf.mapper", "com.xxl.job.admin.dao"}) public class MybatisPlusConfig { /** * 对接数据库的实体层 */ static final String ALIASES_PACKAGE = "jnpf.*.entity;com.xxl.job.admin.core.model"; @Autowired private DataSourceUtil dataSourceUtil; @Autowired private ConfigValueUtil configValueUtil; @Primary @Bean(name = "dataSourceSystem") public DataSource dataSourceOne(DynamicDataSourceProperties properties, List providers) throws SQLException, IOException, URISyntaxException { DataSource dataSource = dynamicDataSource(properties, providers); initDynamicDataSource(dataSource, properties); return dataSource; } @Bean(name = "sqlSessionFactorySystem") public SqlSessionFactory sqlSessionFactoryOne(@Qualifier("dataSourceSystem") DataSource dataSource, @Autowired(required = false) ISqlInjector sqlInjector) throws Exception { return createSqlSessionFactory(dataSource, sqlInjector); } /** * 服务中查询其他服务的表数据, 未引用Mapper无法初始化MybatisPlus的TableInfo对象, 无法判断逻辑删除情况, 初始化MybatisPlus所有Entity对象 * 微服务的情况才进行扫描 * @param sqlSessionFactory * @return */ @Bean @ConditionalOnClass(name = "org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient") public Object scanAllEntity(SqlSessionFactory sqlSessionFactory){ ApplicationStartErrorCheck.getApplicationInitThreadPool().execute(()->{ Set> classes = ClassUtil.scanCandidateComponents("jnpf", c-> !Modifier.isAbstract(c.getModifiers()) && SuperBaseEntity.SuperTBaseEntity.class.isAssignableFrom(c) ); for (Class aClass : classes) { MapperBuilderAssistant builderAssistant = new MapperBuilderAssistant(sqlSessionFactory.getConfiguration(), "resource"); builderAssistant.setCurrentNamespace(aClass.getName()); TableInfoHelper.initTableInfo(builderAssistant, aClass); } }); return null; } public MybatisPlusInterceptor mybatisPlusInterceptor(){ MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor(); try{ //判断是否多租户 if (configValueUtil.isMultiTenancy()) { interceptor.addInnerInterceptor(myTenantLineInnerInterceptor()); interceptor.addInnerInterceptor(mySchemaInnerInterceptor()); } //开启逻辑删除插件功能 if(configValueUtil.isEnableLogicDelete()) { interceptor.addInnerInterceptor(myLogicDeleteInnerInterceptor()); } // 新版本分页必须指定数据库,否则分页不生效 // 不指定会动态生效 多数据源不能指定数据库类型 interceptor.addInnerInterceptor(new PaginationInnerInterceptor()); //乐观锁 interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor()); }catch (Exception e){ e.printStackTrace(); } return interceptor; } @Bean("myLogicDeleteInnerInterceptor") @ConditionalOnProperty(prefix = "config", name = "EnableLogicDelete", havingValue = "true") public MyLogicDeleteInnerInterceptor myLogicDeleteInnerInterceptor(){ MyLogicDeleteInnerInterceptor myLogicDeleteInnerInterceptor = new MyLogicDeleteInnerInterceptor(); myLogicDeleteInnerInterceptor.setLogicDeleteHandler(new LogicDeleteHandler() { @Override public Expression getNotDeletedValue() { Object defValue = GlobalConst.LOGIC_NO_DELETE_VALUE; if(defValue == null){ return new NullValue(); } else{ return new LongValue(defValue.toString()); } } @Override public String getLogicDeleteColumn() { return configValueUtil.getLogicDeleteColumn(); } }); return myLogicDeleteInnerInterceptor; } @Bean("myTenantLineInnerInterceptor") @ConditionalOnProperty(prefix = "config", name = "MultiTenancy", havingValue = "true") public TenantLineInnerInterceptor myTenantLineInnerInterceptor(){ TenantLineInnerInterceptor tenantLineInnerInterceptor = new MyTenantLineInnerInterceptor(); tenantLineInnerInterceptor.setTenantLineHandler(new TenantLineHandler() { @Override public Expression getTenantId() { return new StringValue(TenantHolder.getDatasourceName()); } @Override public String getTenantIdColumn() { return configValueUtil.getMultiTenantColumn(); } @Override public boolean ignoreTable(String tableName) { return configValueUtil.getMultiTenantIgnoreTable().contains(tableName.toLowerCase()); } }); return tenantLineInnerInterceptor; } @Bean("mySchemaInnerInterceptor") @ConditionalOnProperty(prefix = "config", name = "MultiTenancy", havingValue = "true") public DynamicTableNameInnerInterceptor mySchemaInnerInterceptor() throws Exception { DbLinkEntity dbLinkEntity = dataSourceUtil.init(); DbBase dbBase = DbTypeUtil.getDb(dbLinkEntity); DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new MySchemaInnerInterceptor(); HashMap map = new HashMap<>(150) ; // null空库保护 List tableNames = new ArrayList<>() ; // JdbcUtil.queryCustomMods(SqlComEnum.TABLES.getPrepSqlDto(dbLinkEntity, null), DbTableFieldModel.class) // .forEach(dbTableModel-> tableNames.add(dbTableModel.getTable().toLowerCase())); //将当前连接库的所有表保存, 在列表中的表才进行切库, 所有表名转小写, 后续比对转小写 DbBase.dynamicAllTableName = tableNames; dynamicTableNameInnerInterceptor.setTableNameHandler(dbBase.getDynamicTableNameHandler()); return dynamicTableNameInnerInterceptor; } @Bean("myTenantMasterSlaveInterceptor") @ConditionalOnProperty(prefix = "config", name = "MultiTenancy", havingValue = "true") public MyTenantMasterSlaveAutoRoutingPlugin myTenantMasterSlaveAutoRoutingPlugin(DataSource dataSource) { return new MyTenantMasterSlaveAutoRoutingPlugin(dataSource); } public MyDefaultMasterSlaveAutoRoutingPlugin myDefaultMasterSlaveAutoRoutingPlugin(DataSource dataSource) { return new MyDefaultMasterSlaveAutoRoutingPlugin(dataSource); } protected DataSource dynamicDataSource(DynamicDataSourceProperties properties, List providers) { // 动态路由数据源(关键) DynamicRoutingDataSource dataSource = new MyDynamicRoutingDataSource(providers); dataSource.setPrimary(properties.getPrimary()); dataSource.setStrict(properties.getStrict()); dataSource.setStrategy(properties.getStrategy()); dataSource.setP6spy(properties.getP6spy()); dataSource.setSeata(properties.getSeata()); //创建失败不等待 // properties.getDruid().setBreakAfterAcquireFailure(false); // properties.getDruid().setMaxWait(1000); return dataSource; } private void initDynamicDataSource(@Qualifier("dataSourceSystem") DataSource dataSource1, DynamicDataSourceProperties properties) throws DataException, SQLException, IOException, URISyntaxException { DynamicRoutingDataSource dataSource = (DynamicRoutingDataSource) dataSource1; //若未配置多数据源, 从主配置复制数据库配置填充多数据源 boolean isPresentPrimary = properties.getDatasource().entrySet().stream().anyMatch(ds-> ds.getKey().equals(properties.getPrimary()) || ds.getKey().startsWith(properties.getPrimary()+"_") || properties.getPrimary().equals(ds.getValue().getPoolName()) ); DynamicDataSourceUtil.dynamicDataSourceProperties = properties; if(!isPresentPrimary){ // null多租户空库保护 String url = ConnUtil.getUrl(dataSourceUtil, configValueUtil.isMultiTenancy() ? null : dataSourceUtil.getDbName()); DataSourceProperty dataSourceProperty = DynamicDataSourceUtil.createDataSourceProperty(dataSourceUtil, url); dataSourceProperty.getDruid().setBreakAfterAcquireFailure(false); dataSourceProperty.setLazy(false); properties.getDatasource().put(properties.getPrimary(), dataSourceProperty); } } @Bean public Advisor myDynamicDatasourceGeneratorAdvisor(DsProcessor dsProcessor) { DynamicGeneratorInterceptor interceptor = new DynamicGeneratorInterceptor(true, dsProcessor); return new DynamicDataSourceAnnotationAdvisor(interceptor, DS.class){ private final AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut(); { pointcut.setExpression("within(jnpf.database.plugins.DynamicSourceGeneratorInterface+) && @target(com.baomidou.dynamic.datasource.annotation.DS)"); } @Override public Pointcut getPointcut() { return pointcut; } }; } protected DataSource druidDataSource() throws Exception{ DbBase dbBase = DbTypeUtil.getDb(dataSourceUtil); String userName = dataSourceUtil.getUserName(); String password = dataSourceUtil.getPassword(); String driver = dbBase.getDriver(); String url = ""; if (configValueUtil.isMultiTenancy()) { url = ConnUtil.getUrl(dataSourceUtil, null); }else { url = ConnUtil.getUrl(dataSourceUtil); } DruidDataSource dataSource = new DruidDataSource(); if(dbBase.getClass() == DbOracle.class){ // Oracle特殊创建数据源方式 // String logonUer = "Default"; String logonUer = "SYSDBA"; // String logonUer = "SYSOPER"; Properties properties = DbOracle.setConnProp(logonUer, userName, password); dataSource.setConnectProperties(properties); }else { dataSource.setUsername(userName); dataSource.setPassword(password); } dataSource.setUrl(url); dataSource.setDriverClassName(driver); return dataSource; } public Resource[] resolveMapperLocations() { ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver(); List mapperLocations = new ArrayList<>(); mapperLocations.add("classpath*:mapper/*.xml"); mapperLocations.add("classpath*:mapper/*/*.xml"); mapperLocations.add("classpath*:mapper/*/*/*.xml"); mapperLocations.add("classpath*:mybatis-mapper/*.xml"); List resources = new ArrayList(); for (String mapperLocation : mapperLocations) { try { Resource[] mappers = resourceResolver.getResources(mapperLocation); resources.addAll(Arrays.asList(mappers)); } catch (IOException e) { // ignore } } return resources.toArray(new Resource[0]); } public SqlSessionFactory createSqlSessionFactory(DataSource dataSource, ISqlInjector sqlInjector) throws Exception { MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean(); bean.setDataSource(dataSource); //全局配置 GlobalConfig globalConfig = new GlobalConfig(); //配置填充器 globalConfig.setMetaObjectHandler(new MybatisPlusMetaObjectHandler()); bean.setGlobalConfig(globalConfig); if(configValueUtil.isEnableLogicDelete()) { globalConfig.setDbConfig(new GlobalConfig.DbConfig()); globalConfig.getDbConfig().setLogicDeleteField("deleteMark"); globalConfig.getDbConfig().setLogicDeleteValue(GlobalConst.LOGIC_DELETE_VALUE.toString()); globalConfig.getDbConfig().setLogicNotDeleteValue(String.valueOf(GlobalConst.LOGIC_NO_DELETE_VALUE)); } sqlInjector = new MyDefaultSqlInjector(sqlInjector, configValueUtil); globalConfig.setSqlInjector(sqlInjector); List mybatisPlugins = new ArrayList<>(); mybatisPlugins.add(new ResultSetInterceptor()); mybatisPlugins.add(new MyDynamicDataSourceAutoRollbackInterceptor()); mybatisPlugins.add(pageHelper()); if(configValueUtil.isMultiTenancy()) { mybatisPlugins.add(myTenantMasterSlaveAutoRoutingPlugin(dataSource)); } // 配置从库添加读写分离插件 if(DynamicDataSourceUtil.dynamicDataSourceProperties.getDatasource().keySet().stream().anyMatch(k -> k.startsWith(DdConstants.SLAVE))){ mybatisPlugins.add(myDefaultMasterSlaveAutoRoutingPlugin(dataSource)); } bean.setVfs(SpringBootVFS.class); bean.setTypeAliasesPackage(ALIASES_PACKAGE); bean.setMapperLocations(resolveMapperLocations()); bean.setConfiguration(configuration(dataSource)); bean.setPlugins(mybatisPlugins.toArray(new Interceptor[mybatisPlugins.size()])); return bean.getObject(); } public PageInterceptor pageHelper() { PageInterceptor pageHelper = new PageInterceptor(); // 配置PageHelper参数 Properties properties = new Properties(); properties.setProperty("dialectAlias", "kingbase8=com.github.pagehelper.dialect.helper.MySqlDialect"); properties.setProperty("autoRuntimeDialect", "true"); properties.setProperty("offsetAsPageNum", "false"); properties.setProperty("rowBoundsWithCount", "false"); properties.setProperty("pageSizeZero", "true"); properties.setProperty("reasonable", "false"); properties.setProperty("supportMethodsArguments", "false"); properties.setProperty("returnPageInfo", "none"); pageHelper.setProperties(properties); return pageHelper; } public MybatisConfiguration configuration(DataSource dataSource){ MybatisConfiguration mybatisConfiguration = new MybatisConfiguration(){ @Override public void addMappedStatement(MappedStatement ms) { // 避免Mybatis多线程初始化问题 synchronized (ALIASES_PACKAGE) { super.addMappedStatement(ms); } } }; mybatisConfiguration.setMapUnderscoreToCamelCase(false); mybatisConfiguration.setCacheEnabled(false); mybatisConfiguration.setCallSettersOnNulls(true); mybatisConfiguration.addInterceptor(mybatisPlusInterceptor()); mybatisConfiguration.setLogImpl(Slf4jImpl.class); mybatisConfiguration.setJdbcTypeForNull(JdbcType.NULL); return mybatisConfiguration; } @Bean public IKeyGenerator keyGenerator() { return new H2KeyGenerator(); } /** * 数据权限插件 * * @return DataScopeInterceptor */ // @Bean // @ConditionalOnMissingBean // public DataScopeInterceptor dataScopeInterceptor(DataSource dataSource) { // return new DataScopeInterceptor(dataSource); // } }