MybatisPlusConfig.java 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423
  1. package jnpf.database.config;
  2. import com.alibaba.druid.pool.DruidDataSource;
  3. import com.baomidou.dynamic.datasource.DynamicRoutingDataSource;
  4. import com.baomidou.dynamic.datasource.annotation.DS;
  5. import com.baomidou.dynamic.datasource.aop.DynamicDataSourceAnnotationAdvisor;
  6. import com.baomidou.dynamic.datasource.enums.DdConstants;
  7. import com.baomidou.dynamic.datasource.processor.DsProcessor;
  8. import com.baomidou.dynamic.datasource.creator.DataSourceProperty;
  9. import com.baomidou.dynamic.datasource.provider.DynamicDataSourceProvider;
  10. import com.baomidou.dynamic.datasource.spring.boot.autoconfigure.DynamicDataSourceProperties;
  11. import com.baomidou.mybatisplus.autoconfigure.SpringBootVFS;
  12. import com.baomidou.mybatisplus.core.MybatisConfiguration;
  13. import com.baomidou.mybatisplus.core.config.GlobalConfig;
  14. import com.baomidou.mybatisplus.core.incrementer.IKeyGenerator;
  15. import com.baomidou.mybatisplus.core.injector.ISqlInjector;
  16. import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
  17. import com.baomidou.mybatisplus.core.toolkit.StringPool;
  18. import com.baomidou.mybatisplus.extension.incrementer.H2KeyGenerator;
  19. import com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor;
  20. import com.baomidou.mybatisplus.extension.plugins.handler.TableNameHandler;
  21. import com.baomidou.mybatisplus.extension.plugins.handler.TenantLineHandler;
  22. import com.baomidou.mybatisplus.extension.plugins.inner.DynamicTableNameInnerInterceptor;
  23. import com.baomidou.mybatisplus.extension.plugins.inner.OptimisticLockerInnerInterceptor;
  24. import com.baomidou.mybatisplus.extension.plugins.inner.PaginationInnerInterceptor;
  25. import com.baomidou.mybatisplus.extension.plugins.inner.TenantLineInnerInterceptor;
  26. import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
  27. import com.github.pagehelper.PageInterceptor;
  28. import jnpf.base.entity.SuperBaseEntity;
  29. import jnpf.config.ApplicationStartErrorCheck;
  30. import jnpf.config.ConfigValueUtil;
  31. import jnpf.constant.GlobalConst;
  32. import jnpf.database.model.entity.DbLinkEntity;
  33. import jnpf.database.plugins.*;
  34. import jnpf.database.source.DbBase;
  35. import jnpf.database.source.impl.DbOracle;
  36. import jnpf.database.util.ConnUtil;
  37. import jnpf.database.util.DataSourceUtil;
  38. import jnpf.database.util.DbTypeUtil;
  39. import jnpf.database.util.DynamicDataSourceUtil;
  40. import jnpf.exception.DataException;
  41. import jnpf.util.ClassUtil;
  42. import jnpf.util.TenantHolder;
  43. import lombok.extern.slf4j.Slf4j;
  44. import net.sf.jsqlparser.expression.Expression;
  45. import net.sf.jsqlparser.expression.LongValue;
  46. import net.sf.jsqlparser.expression.NullValue;
  47. import net.sf.jsqlparser.expression.StringValue;
  48. import org.apache.ibatis.builder.MapperBuilderAssistant;
  49. import org.apache.ibatis.logging.slf4j.Slf4jImpl;
  50. import org.apache.ibatis.mapping.MappedStatement;
  51. import org.apache.ibatis.plugin.Interceptor;
  52. import org.apache.ibatis.session.SqlSessionFactory;
  53. import org.apache.ibatis.type.JdbcType;
  54. import org.mybatis.spring.annotation.MapperScan;
  55. import org.springframework.aop.Advisor;
  56. import org.springframework.aop.Pointcut;
  57. import org.springframework.aop.aspectj.AspectJExpressionPointcut;
  58. import org.springframework.beans.factory.annotation.Autowired;
  59. import org.springframework.beans.factory.annotation.Qualifier;
  60. import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
  61. import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
  62. import org.springframework.context.annotation.*;
  63. import org.springframework.core.io.Resource;
  64. import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
  65. import org.springframework.core.io.support.ResourcePatternResolver;
  66. import javax.sql.DataSource;
  67. import java.io.IOException;
  68. import java.lang.reflect.Modifier;
  69. import java.net.URISyntaxException;
  70. import java.sql.SQLException;
  71. import java.util.*;
  72. /**
  73. * MybatisPlus配置类
  74. *
  75. * @author JNPF开发平台组
  76. * @version V3.1.0
  77. * @copyright 引迈信息技术有限公司
  78. * @date 2021/3/16 8:53
  79. */
  80. @Slf4j
  81. @Configuration
  82. @ComponentScan("jnpf")
  83. @DependsOn({"tenantDataSourceUtil", "threadPoolExecutorUtil"})
  84. @MapperScan(basePackages = {"jnpf.*.mapper", "jnpf.mapper", "com.xxl.job.admin.dao"})
  85. public class MybatisPlusConfig {
  86. /**
  87. * 对接数据库的实体层
  88. */
  89. static final String ALIASES_PACKAGE = "jnpf.*.entity;com.xxl.job.admin.core.model";
  90. @Autowired
  91. private DataSourceUtil dataSourceUtil;
  92. @Autowired
  93. private ConfigValueUtil configValueUtil;
  94. @Primary
  95. @Bean(name = "dataSourceSystem")
  96. public DataSource dataSourceOne(DynamicDataSourceProperties properties, List<DynamicDataSourceProvider> providers) throws SQLException, IOException, URISyntaxException {
  97. DataSource dataSource = dynamicDataSource(properties, providers);
  98. initDynamicDataSource(dataSource, properties);
  99. return dataSource;
  100. }
  101. @Bean(name = "sqlSessionFactorySystem")
  102. public SqlSessionFactory sqlSessionFactoryOne(@Qualifier("dataSourceSystem") DataSource dataSource, @Autowired(required = false) ISqlInjector sqlInjector) throws Exception {
  103. return createSqlSessionFactory(dataSource, sqlInjector);
  104. }
  105. /**
  106. * 服务中查询其他服务的表数据, 未引用Mapper无法初始化MybatisPlus的TableInfo对象, 无法判断逻辑删除情况, 初始化MybatisPlus所有Entity对象
  107. * 微服务的情况才进行扫描
  108. * @param sqlSessionFactory
  109. * @return
  110. */
  111. @Bean
  112. @ConditionalOnClass(name = "org.springframework.cloud.loadbalancer.annotation.LoadBalancerClient")
  113. public Object scanAllEntity(SqlSessionFactory sqlSessionFactory){
  114. ApplicationStartErrorCheck.getApplicationInitThreadPool().execute(()->{
  115. Set<Class<?>> classes = ClassUtil.scanCandidateComponents("jnpf", c->
  116. !Modifier.isAbstract(c.getModifiers()) && SuperBaseEntity.SuperTBaseEntity.class.isAssignableFrom(c)
  117. );
  118. for (Class<?> aClass : classes) {
  119. MapperBuilderAssistant builderAssistant = new MapperBuilderAssistant(sqlSessionFactory.getConfiguration(), "resource");
  120. builderAssistant.setCurrentNamespace(aClass.getName());
  121. TableInfoHelper.initTableInfo(builderAssistant, aClass);
  122. }
  123. });
  124. return null;
  125. }
  126. public MybatisPlusInterceptor mybatisPlusInterceptor(){
  127. MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
  128. try{
  129. //判断是否多租户
  130. if (configValueUtil.isMultiTenancy()) {
  131. interceptor.addInnerInterceptor(myTenantLineInnerInterceptor());
  132. interceptor.addInnerInterceptor(mySchemaInnerInterceptor());
  133. }
  134. //开启逻辑删除插件功能
  135. if(configValueUtil.isEnableLogicDelete()) {
  136. interceptor.addInnerInterceptor(myLogicDeleteInnerInterceptor());
  137. }
  138. // 新版本分页必须指定数据库,否则分页不生效
  139. // 不指定会动态生效 多数据源不能指定数据库类型
  140. interceptor.addInnerInterceptor(new PaginationInnerInterceptor());
  141. //乐观锁
  142. interceptor.addInnerInterceptor(new OptimisticLockerInnerInterceptor());
  143. }catch (Exception e){
  144. e.printStackTrace();
  145. }
  146. return interceptor;
  147. }
  148. @Bean("myLogicDeleteInnerInterceptor")
  149. @ConditionalOnProperty(prefix = "config", name = "EnableLogicDelete", havingValue = "true")
  150. public MyLogicDeleteInnerInterceptor myLogicDeleteInnerInterceptor(){
  151. MyLogicDeleteInnerInterceptor myLogicDeleteInnerInterceptor = new MyLogicDeleteInnerInterceptor();
  152. myLogicDeleteInnerInterceptor.setLogicDeleteHandler(new LogicDeleteHandler() {
  153. @Override
  154. public Expression getNotDeletedValue() {
  155. Object defValue = GlobalConst.LOGIC_NO_DELETE_VALUE;
  156. if(defValue == null){
  157. return new NullValue();
  158. } else{
  159. return new LongValue(defValue.toString());
  160. }
  161. }
  162. @Override
  163. public String getLogicDeleteColumn() {
  164. return configValueUtil.getLogicDeleteColumn();
  165. }
  166. });
  167. return myLogicDeleteInnerInterceptor;
  168. }
  169. @Bean("myTenantLineInnerInterceptor")
  170. @ConditionalOnProperty(prefix = "config", name = "MultiTenancy", havingValue = "true")
  171. public TenantLineInnerInterceptor myTenantLineInnerInterceptor(){
  172. TenantLineInnerInterceptor tenantLineInnerInterceptor = new MyTenantLineInnerInterceptor();
  173. tenantLineInnerInterceptor.setTenantLineHandler(new TenantLineHandler() {
  174. @Override
  175. public Expression getTenantId() {
  176. return new StringValue(TenantHolder.getDatasourceName());
  177. }
  178. @Override
  179. public String getTenantIdColumn() {
  180. return configValueUtil.getMultiTenantColumn();
  181. }
  182. @Override
  183. public boolean ignoreTable(String tableName) {
  184. return configValueUtil.getMultiTenantIgnoreTable().contains(tableName.toLowerCase());
  185. }
  186. });
  187. return tenantLineInnerInterceptor;
  188. }
  189. @Bean("mySchemaInnerInterceptor")
  190. @ConditionalOnProperty(prefix = "config", name = "MultiTenancy", havingValue = "true")
  191. public DynamicTableNameInnerInterceptor mySchemaInnerInterceptor() throws Exception {
  192. DbLinkEntity dbLinkEntity = dataSourceUtil.init();
  193. DbBase dbBase = DbTypeUtil.getDb(dbLinkEntity);
  194. DynamicTableNameInnerInterceptor dynamicTableNameInnerInterceptor = new MySchemaInnerInterceptor();
  195. HashMap<String, TableNameHandler> map = new HashMap<>(150) ;
  196. // null空库保护
  197. List<String> tableNames = new ArrayList<>() ;
  198. // JdbcUtil.queryCustomMods(SqlComEnum.TABLES.getPrepSqlDto(dbLinkEntity, null), DbTableFieldModel.class)
  199. // .forEach(dbTableModel-> tableNames.add(dbTableModel.getTable().toLowerCase()));
  200. //将当前连接库的所有表保存, 在列表中的表才进行切库, 所有表名转小写, 后续比对转小写
  201. DbBase.dynamicAllTableName = tableNames;
  202. dynamicTableNameInnerInterceptor.setTableNameHandler(dbBase.getDynamicTableNameHandler());
  203. return dynamicTableNameInnerInterceptor;
  204. }
  205. @Bean("myTenantMasterSlaveInterceptor")
  206. @ConditionalOnProperty(prefix = "config", name = "MultiTenancy", havingValue = "true")
  207. public MyTenantMasterSlaveAutoRoutingPlugin myTenantMasterSlaveAutoRoutingPlugin(DataSource dataSource) {
  208. return new MyTenantMasterSlaveAutoRoutingPlugin(dataSource);
  209. }
  210. public MyDefaultMasterSlaveAutoRoutingPlugin myDefaultMasterSlaveAutoRoutingPlugin(DataSource dataSource) {
  211. return new MyDefaultMasterSlaveAutoRoutingPlugin(dataSource);
  212. }
  213. protected DataSource dynamicDataSource(DynamicDataSourceProperties properties, List<DynamicDataSourceProvider> providers) {
  214. // 动态路由数据源(关键)
  215. DynamicRoutingDataSource dataSource = new MyDynamicRoutingDataSource(providers);
  216. dataSource.setPrimary(properties.getPrimary());
  217. dataSource.setStrict(properties.getStrict());
  218. dataSource.setStrategy(properties.getStrategy());
  219. dataSource.setP6spy(properties.getP6spy());
  220. dataSource.setSeata(properties.getSeata());
  221. //创建失败不等待
  222. // properties.getDruid().setBreakAfterAcquireFailure(false);
  223. // properties.getDruid().setMaxWait(1000);
  224. return dataSource;
  225. }
  226. private void initDynamicDataSource(@Qualifier("dataSourceSystem") DataSource dataSource1, DynamicDataSourceProperties properties) throws DataException, SQLException, IOException, URISyntaxException {
  227. DynamicRoutingDataSource dataSource = (DynamicRoutingDataSource) dataSource1;
  228. //若未配置多数据源, 从主配置复制数据库配置填充多数据源
  229. boolean isPresentPrimary = properties.getDatasource().entrySet().stream().anyMatch(ds->
  230. ds.getKey().equals(properties.getPrimary()) || ds.getKey().startsWith(properties.getPrimary()+"_") || properties.getPrimary().equals(ds.getValue().getPoolName())
  231. );
  232. DynamicDataSourceUtil.dynamicDataSourceProperties = properties;
  233. if(!isPresentPrimary){
  234. // null多租户空库保护
  235. String url = ConnUtil.getUrl(dataSourceUtil, configValueUtil.isMultiTenancy() ? null : dataSourceUtil.getDbName());
  236. DataSourceProperty dataSourceProperty = DynamicDataSourceUtil.createDataSourceProperty(dataSourceUtil, url);
  237. dataSourceProperty.getDruid().setBreakAfterAcquireFailure(false);
  238. dataSourceProperty.setLazy(false);
  239. properties.getDatasource().put(properties.getPrimary(), dataSourceProperty);
  240. }
  241. }
  242. @Bean
  243. public Advisor myDynamicDatasourceGeneratorAdvisor(DsProcessor dsProcessor) {
  244. DynamicGeneratorInterceptor interceptor = new DynamicGeneratorInterceptor(true, dsProcessor);
  245. return new DynamicDataSourceAnnotationAdvisor(interceptor, DS.class){
  246. private final AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
  247. {
  248. pointcut.setExpression("within(jnpf.database.plugins.DynamicSourceGeneratorInterface+) && @target(com.baomidou.dynamic.datasource.annotation.DS)");
  249. }
  250. @Override
  251. public Pointcut getPointcut() {
  252. return pointcut;
  253. }
  254. };
  255. }
  256. protected DataSource druidDataSource() throws Exception{
  257. DbBase dbBase = DbTypeUtil.getDb(dataSourceUtil);
  258. String userName = dataSourceUtil.getUserName();
  259. String password = dataSourceUtil.getPassword();
  260. String driver = dbBase.getDriver();
  261. String url = "";
  262. if (configValueUtil.isMultiTenancy()) {
  263. url = ConnUtil.getUrl(dataSourceUtil, null);
  264. }else {
  265. url = ConnUtil.getUrl(dataSourceUtil);
  266. }
  267. DruidDataSource dataSource = new DruidDataSource();
  268. if(dbBase.getClass() == DbOracle.class){
  269. // Oracle特殊创建数据源方式
  270. // String logonUer = "Default";
  271. String logonUer = "SYSDBA";
  272. // String logonUer = "SYSOPER";
  273. Properties properties = DbOracle.setConnProp(logonUer, userName, password);
  274. dataSource.setConnectProperties(properties);
  275. }else {
  276. dataSource.setUsername(userName);
  277. dataSource.setPassword(password);
  278. }
  279. dataSource.setUrl(url);
  280. dataSource.setDriverClassName(driver);
  281. return dataSource;
  282. }
  283. public Resource[] resolveMapperLocations() {
  284. ResourcePatternResolver resourceResolver = new PathMatchingResourcePatternResolver();
  285. List<String> mapperLocations = new ArrayList<>();
  286. mapperLocations.add("classpath*:mapper/*.xml");
  287. mapperLocations.add("classpath*:mapper/*/*.xml");
  288. mapperLocations.add("classpath*:mapper/*/*/*.xml");
  289. mapperLocations.add("classpath*:mybatis-mapper/*.xml");
  290. List<Resource> resources = new ArrayList<Resource>();
  291. for (String mapperLocation : mapperLocations) {
  292. try {
  293. Resource[] mappers = resourceResolver.getResources(mapperLocation);
  294. resources.addAll(Arrays.asList(mappers));
  295. } catch (IOException e) {
  296. // ignore
  297. }
  298. }
  299. return resources.toArray(new Resource[0]);
  300. }
  301. public SqlSessionFactory createSqlSessionFactory(DataSource dataSource, ISqlInjector sqlInjector) throws Exception {
  302. MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
  303. bean.setDataSource(dataSource);
  304. //全局配置
  305. GlobalConfig globalConfig = new GlobalConfig();
  306. //配置填充器
  307. globalConfig.setMetaObjectHandler(new MybatisPlusMetaObjectHandler());
  308. bean.setGlobalConfig(globalConfig);
  309. if(configValueUtil.isEnableLogicDelete()) {
  310. globalConfig.setDbConfig(new GlobalConfig.DbConfig());
  311. globalConfig.getDbConfig().setLogicDeleteField("deleteMark");
  312. globalConfig.getDbConfig().setLogicDeleteValue(GlobalConst.LOGIC_DELETE_VALUE.toString());
  313. globalConfig.getDbConfig().setLogicNotDeleteValue(String.valueOf(GlobalConst.LOGIC_NO_DELETE_VALUE));
  314. }
  315. sqlInjector = new MyDefaultSqlInjector(sqlInjector, configValueUtil);
  316. globalConfig.setSqlInjector(sqlInjector);
  317. List<Interceptor> mybatisPlugins = new ArrayList<>();
  318. mybatisPlugins.add(new ResultSetInterceptor());
  319. mybatisPlugins.add(new MyDynamicDataSourceAutoRollbackInterceptor());
  320. mybatisPlugins.add(pageHelper());
  321. if(configValueUtil.isMultiTenancy()) {
  322. mybatisPlugins.add(myTenantMasterSlaveAutoRoutingPlugin(dataSource));
  323. }
  324. // 配置从库添加读写分离插件
  325. if(DynamicDataSourceUtil.dynamicDataSourceProperties.getDatasource().keySet().stream().anyMatch(k -> k.startsWith(DdConstants.SLAVE))){
  326. mybatisPlugins.add(myDefaultMasterSlaveAutoRoutingPlugin(dataSource));
  327. }
  328. bean.setVfs(SpringBootVFS.class);
  329. bean.setTypeAliasesPackage(ALIASES_PACKAGE);
  330. bean.setMapperLocations(resolveMapperLocations());
  331. bean.setConfiguration(configuration(dataSource));
  332. bean.setPlugins(mybatisPlugins.toArray(new Interceptor[mybatisPlugins.size()]));
  333. return bean.getObject();
  334. }
  335. public PageInterceptor pageHelper() {
  336. PageInterceptor pageHelper = new PageInterceptor();
  337. // 配置PageHelper参数
  338. Properties properties = new Properties();
  339. properties.setProperty("dialectAlias", "kingbase8=com.github.pagehelper.dialect.helper.MySqlDialect");
  340. properties.setProperty("autoRuntimeDialect", "true");
  341. properties.setProperty("offsetAsPageNum", "false");
  342. properties.setProperty("rowBoundsWithCount", "false");
  343. properties.setProperty("pageSizeZero", "true");
  344. properties.setProperty("reasonable", "false");
  345. properties.setProperty("supportMethodsArguments", "false");
  346. properties.setProperty("returnPageInfo", "none");
  347. pageHelper.setProperties(properties);
  348. return pageHelper;
  349. }
  350. public MybatisConfiguration configuration(DataSource dataSource){
  351. MybatisConfiguration mybatisConfiguration = new MybatisConfiguration(){
  352. @Override
  353. public void addMappedStatement(MappedStatement ms) {
  354. // 避免Mybatis多线程初始化问题
  355. synchronized (ALIASES_PACKAGE) {
  356. super.addMappedStatement(ms);
  357. }
  358. }
  359. };
  360. mybatisConfiguration.setMapUnderscoreToCamelCase(false);
  361. mybatisConfiguration.setCacheEnabled(false);
  362. mybatisConfiguration.setCallSettersOnNulls(true);
  363. mybatisConfiguration.addInterceptor(mybatisPlusInterceptor());
  364. mybatisConfiguration.setLogImpl(Slf4jImpl.class);
  365. mybatisConfiguration.setJdbcTypeForNull(JdbcType.NULL);
  366. return mybatisConfiguration;
  367. }
  368. @Bean
  369. public IKeyGenerator keyGenerator() {
  370. return new H2KeyGenerator();
  371. }
  372. /**
  373. * 数据权限插件
  374. *
  375. * @return DataScopeInterceptor
  376. */
  377. // @Bean
  378. // @ConditionalOnMissingBean
  379. // public DataScopeInterceptor dataScopeInterceptor(DataSource dataSource) {
  380. // return new DataScopeInterceptor(dataSource);
  381. // }
  382. }