双数据源,整合Springboot、Mybatis-plus、shiro、Activiti7的过程、经验总结以及遇到的一些bug

项目场景:

在做审批中心的时候,原项目使用的是jpa,我想要改成mybatis-plus,并且自己整理思路重新写一下审批模块,所以有了这篇文章

整合步骤

pom依赖

因为我这里还使用了eureka、feign、sentry,所以引入了这些依赖,如果不需要的话可以去除,第二、第三个依赖是我自己的子模块,注意:整合activiti7和mybatis-plus的话一定要将activiti7中的mybatis依赖给去除,不然会造成版本冲突!

		<dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <dependency>
            <groupId>com.ihrm</groupId>
            <artifactId>ihrm_common</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>com.ihrm</groupId>
            <artifactId>ihrm_common_model</artifactId>
            <version>1.0-SNAPSHOT</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.cloud</groupId>
            <artifactId>spring-cloud-starter-openfeign</artifactId>
        </dependency>
        <dependency>
            <groupId>org.activiti</groupId>
            <artifactId>activiti-spring-boot-starter</artifactId>
            <version>7.1.0.M1</version>
            <exclusions>
                <exclusion>
                    <groupId>org.mybatis</groupId>
                    <artifactId>mybatis</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-boot-starter</artifactId>
            <version>3.4.3</version>
        </dependency>
        <dependency>
            <groupId>io.sentry</groupId>
            <artifactId>sentry-spring</artifactId>
            <version>1.7.30</version>
        </dependency>
        <dependency>
            <groupId>com.github.pagehelper</groupId>
            <artifactId>pagehelper</artifactId>
            <version>5.1.6</version>
        </dependency>

yml文件

#spring配置
spring:
  #1.应用配置
  application:
    name: ihrm-myaudit #指定服务名
  #2.数据库连接池
  datasource:
    ihrm:
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://127.0.0.1:3306/ihrm?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
      username: root
      password: aaaaaa
      hikari:
        maximum-pool-size: 2
    activiti:
      driver-class-name: com.mysql.cj.jdbc.Driver
      jdbc-url: jdbc:mysql://127.0.0.1:3306/activiti?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true
      username: root
      password: aaaaaa
      hikari:
        aximum-pool-size: 2
  activiti:
    history-level: full
    db-history-used: true
    database-schema-update: false
  mybatis-plus:
     # xml扫描,多个目录用逗号或者分号分隔(告诉 Mapper 所对应的 XML 文件位置)
    mapper-locations: classpath:mapper/*.xml
      # 以下配置均有默认值,可以不设置
      #  global-config:
      #    db-config:
      #      #主键类型 AUTO:"数据库ID自增" INPUT:"用户输入ID",ID_WORKER:"全局唯一ID (数字类型唯一ID)", UUID:"全局唯一ID UUID";
      #      id-type: auto
      #      #字段策略 IGNORED:"忽略判断"  NOT_NULL:"非 NULL 判断")  NOT_EMPTY:"非空判断"
      #      field-strategy: NOT_EMPTY
      #      #数据库类型
#      db-type: MYSQL
    configuration:
      # 是否开启自动驼峰命名规则映射:从数据库列名到Java属性驼峰命名的类似映射
      map-underscore-to-camel-case: true
      # 如果查询结果中包含空值的列,则 MyBatis 在映射的时候,不会映射这个字段
      call-setters-on-nulls: true
      # 这个配置会将执行的sql打印出来,在开发或测试的时候可以用
      log-impl: org.apache.ibatis.logging.stdout.StdOutImpl


  redis:
    host: 127.0.0.1
    port: 6379
#注册到eureka的服务地址
eureka:
  client:
    service-url:
      defaultZone: http://127.0.0.1:6868/eureka/
  instance:
    preferIpAddress : true
    instance-id: ${spring.cloud.client.ip-address}:${spring.application.name}:${server.port}
#服务配置
server:
  port: 9007

主数据源配置(设置Activiti为主数据源)

package com.ihrm.myaudit;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;


@Configuration
@MapperScan(basePackages = "com.ihrm.myaudit.mapper", sqlSessionFactoryRef = "activitiSqlSessionFactory")
public class ActivitiDatabaseConfig {
    @Bean(name = "activitiDataSource")
    @Primary
    @ConfigurationProperties(prefix = "spring.datasource.activiti")
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }

    @Bean(name = "activitiSqlSessionFactory")
    @Primary
    public SqlSessionFactory basicSqlSessionFactory(@Qualifier("activitiDataSource") DataSource datasource) throws Exception {
        SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
        bean.setDataSource(datasource);
        bean.setMapperLocations(
                // 设置mybatis的xml所在位置
                new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/*.xml"));
        return bean.getObject();

    }

    @Bean(name = "activitiSqlSessionTemplate")
    @Primary
    public SqlSessionTemplate testSqlSessionTemplate(
            @Qualifier("activitiSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

从数据源配置

package com.ihrm.myaudit;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import javax.sql.DataSource;

@Configuration
@MapperScan(basePackages ="com.ihrm.myaudit.mapper.ihrm", sqlSessionFactoryRef = "ihrmSqlSessionFactory")
public class IhrmDatabaseConfig {
    @Bean(name = "ihrmDataSource")
    
    @ConfigurationProperties(prefix = "spring.datasource.ihrm")
    public DataSource dataSource() {
        return DataSourceBuilder.create().build();
    }

    
    @Bean(name = "ihrmSqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("ihrmDataSource") DataSource dataSource) throws Exception {
        SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
        factoryBean.setDataSource(dataSource);
        factoryBean.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/ihrm/*.xml"));

        return factoryBean.getObject();
    }

    
    @Bean(name = "ihrmSqlSessionTemplate")
    public SqlSessionTemplate testSqlSessionTemplate(
            @Qualifier("ihrmSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
        return new SqlSessionTemplate(sqlSessionFactory);
    }
}

shiro配置

package com.ihrm.myaudit;

import com.ihrm.common.shiro.realm.IhrmRealm;
import com.ihrm.common.shiro.session.CustomSessionManager;
import org.apache.shiro.mgt.SecurityManager;
import org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor;
import org.apache.shiro.spring.web.ShiroFilterFactoryBean;
import org.apache.shiro.web.mgt.DefaultWebSecurityManager;
import org.apache.shiro.web.session.mgt.DefaultWebSessionManager;
import org.crazycake.shiro.RedisCacheManager;
import org.crazycake.shiro.RedisManager;
import org.crazycake.shiro.RedisSessionDAO;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import java.util.LinkedHashMap;
import java.util.Map;

@Configuration
public class ShiroConfiguration {

    //1.创建realm
    @Bean
    public IhrmRealm getRealm() {
        return new IhrmRealm();
    }

    //2.创建安全管理器
    @Bean
    public SecurityManager getSecurityManager(IhrmRealm realm) {
        DefaultWebSecurityManager securityManager = new DefaultWebSecurityManager();
        securityManager.setRealm(realm);

        //将自定义的会话管理器注册到安全管理器中
        securityManager.setSessionManager(sessionManager());
        //将自定义的redis缓存管理器注册到安全管理器中
        securityManager.setCacheManager(cacheManager());

        return securityManager;
    }

    //3.配置shiro的过滤器工厂

    /**
     * 再web程序中,shiro进行权限控制全部是通过一组过滤器集合进行控制
     *
     */
    @Bean
    public ShiroFilterFactoryBean shiroFilter(SecurityManager securityManager) {
        //1.创建过滤器工厂
        ShiroFilterFactoryBean filterFactory = new ShiroFilterFactoryBean();
        //2.设置安全管理器
        filterFactory.setSecurityManager(securityManager);
        //3.通用配置(跳转登录页面,未授权跳转的页面)
        filterFactory.setLoginUrl("/autherror?code=1");//跳转url地址
        filterFactory.setUnauthorizedUrl("/autherror?code=2");//未授权的url
        //4.设置过滤器集合
        Map<String,String> filterMap = new LinkedHashMap<>();
        //anon -- 匿名访问
        filterMap.put("/sys/login","anon");
        filterMap.put("/autherror","anon");
        //注册
        //authc -- 认证之后访问(登录)
        filterMap.put("/**","authc");
        //perms -- 具有某中权限 (使用注解配置授权)
        filterFactory.setFilterChainDefinitionMap(filterMap);

        return filterFactory;
    }


    @Value("${spring.redis.host}")
    private String host;
    @Value("${spring.redis.port}")
    private int port;

    /**
     * 1.redis的控制器,操作redis
     */
    public RedisManager redisManager() {
        RedisManager redisManager = new RedisManager();
        redisManager.setHost(host);
        redisManager.setPort(port);
        return redisManager;
    }

    /**
     * 2.sessionDao
     */
    public RedisSessionDAO redisSessionDAO() {
        RedisSessionDAO sessionDAO = new RedisSessionDAO();
        sessionDAO.setRedisManager(redisManager());
        return sessionDAO;
    }

    /**
     * 3.会话管理器
     */
    public DefaultWebSessionManager sessionManager() {
        CustomSessionManager sessionManager = new CustomSessionManager();
        //sessionManager.setSessionIdCookieEnabled(false);
        sessionManager.setSessionIdUrlRewritingEnabled(false);
        sessionManager.setSessionDAO(redisSessionDAO());
        return sessionManager;
    }

    /**
     * 4.缓存管理器
     */
    public RedisCacheManager cacheManager() {
        RedisCacheManager redisCacheManager = new RedisCacheManager();
        redisCacheManager.setRedisManager(redisManager());
        return redisCacheManager;
    }




    //开启对shior注解的支持
    @Bean
    public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
        AuthorizationAttributeSourceAdvisor advisor = new AuthorizationAttributeSourceAdvisor();
        advisor.setSecurityManager(securityManager);
        return advisor;
    }
}

启动类

注意去除不需要的@Bean

package com.ihrm.myaudit;

import com.ihrm.common.utils.IdWorker;
import com.ihrm.common.utils.JwtUtils;
import io.sentry.Sentry;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.impl.RepositoryServiceImpl;
import org.activiti.engine.impl.RuntimeServiceImpl;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.domain.EntityScan;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.security.servlet.SecurityAutoConfiguration;
import org.springframework.boot.web.servlet.ServletContextInitializer;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;
import org.springframework.cloud.netflix.eureka.EnableEurekaClient;
import org.springframework.cloud.openfeign.EnableFeignClients;
import org.springframework.context.annotation.Bean;
import org.springframework.orm.jpa.support.OpenEntityManagerInViewFilter;
import org.springframework.web.servlet.HandlerExceptionResolver;

//1.配置springboot的包扫描
@SpringBootApplication(scanBasePackages = "com.ihrm",exclude ={ SecurityAutoConfiguration.class})
@EntityScan(value="com.ihrm.entity")
@EnableEurekaClient
@EnableDiscoveryClient
@EnableFeignClients
public class MyAuditApplication {
    /**
     * 启动方法
     */
	public static void main(String[] args) {
		Sentry.init("http://0641be2867664b0daed5c615b7a398a6@sentry.itheima.net/16");
		SpringApplication.run(MyAuditApplication.class, args);
	}

	@Bean
	public HandlerExceptionResolver sentryExceptionResolver() {
		return new io.sentry.spring.SentryExceptionResolver();
	}

	@Bean
	public ServletContextInitializer sentryServletContextInitializer() {
		return new io.sentry.spring.SentryServletContextInitializer();
	}

    @Bean
    public IdWorker idWorker() {
        return new IdWorker();
    }

    @Bean
    public JwtUtils jwtUtils() {
        return new JwtUtils();
    }

    //解决no session
    @Bean
    public OpenEntityManagerInViewFilter openEntityManagerInViewFilter() {
        return new OpenEntityManagerInViewFilter();
    }

}

ActivitiService

package com.ihrm.myaudit.service;

import com.baomidou.mybatisplus.extension.service.IService;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;

public interface ActivitiService extends IService<Object> {
    public void deployProcessDefinition(MultipartFile file, String companyId) throws IOException;
}

ActivitiServiceImpl

package com.ihrm.myaudit.service.impl;

import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.ihrm.myaudit.mapper.ActivitiMapper;
import com.ihrm.myaudit.service.ActivitiService;
import org.activiti.engine.RepositoryService;
import org.activiti.engine.RuntimeService;
import org.activiti.engine.repository.Deployment;
import org.activiti.engine.repository.DeploymentBuilder;
import org.activiti.engine.repository.ProcessDefinition;
import org.activiti.engine.runtime.ProcessInstance;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

@Service
public class ActivitiServiceImpl extends ServiceImpl<ActivitiMapper,Object> implements ActivitiService {
    @Autowired
    private RepositoryService repositoryService;
    @Autowired
    private RuntimeService runtimeService;
    /**
     * 查询所有的流程实例
     * 1、根据companyId查询出对应的流程定义
     * 2、根据流程定义查询出所有的流程实例
     */
    public void getAllProInstance(String companyId){
        List<ProcessDefinition> list = repositoryService.createProcessDefinitionQuery().processDefinitionTenantId(companyId).latestVersion().list();
        List<ProcessInstance> proList=new ArrayList<>();
        for(ProcessDefinition processDefinition:list){
//            runtimeService.createProcessInstanceQuery().
        }
    }

    /**
     * 部署一个流程
     */
    public void deployProcessDefinition(MultipartFile file,String companyId) throws IOException {
        //获取文件名称
        String fileName=file.getOriginalFilename();
        //部署流程定义
        DeploymentBuilder builder = repositoryService.createDeployment().addBytes(fileName, file.getBytes()).tenantId(companyId);
        Deployment deploy=builder.deploy();
        System.out.println(deploy);
    }
}

目录结构

在这里插入图片描述

遇到的BUG

1、配置双数据源的时候,出现Autowired required a single bean, but 2 were found的异常,意思是只需要一个bean但是找到了两个
2、启动项目的时候遇到org.springframework.beans.factory.BeanCreationException,不知道什么原因
3、启动网关时报错:Failed to configure a DataSource: ‘url’ attribute is not specified and no embedded datasource could be configured.
4、运行登录子模块时,出现和3相同的报错。
5、postman通过网关或者直接访问端口出现404错误
6、访问端口报500错误
7、访问端口报空指针异常

解决方案

1、ngix文件的所在目录包含了中文,将目录换成英文目录
2、后台没有将查询到的数据返回给前端,添加数据即可
3、网关报错的原因可能是由于依赖了另一个子模块,而另一个子模块使用了mybatis-plus依赖,需要配置数据库信息,所以出现这个问题。因为我知道这个项目使用的时jpa,所以这两个子模块应该用不到mybatis,尝试取消子模块的mybatis-plus依赖,但还是出现这个报错。仔细检查发现这个子模块又引用了另一个子模块,另一个子模块中也包含mybatis-plus依赖,尝试将两个子模块的依赖全部注释,问题解决。
4、本来以为也是依赖或者子模块引用的问题,但是这个模块以来的两个子模块的mybatis-plus-boot-stater已经被注释掉了,其本身的pom文件中也没有mybatis-plus依赖,所以推测可能是模块本身的数据库连接配置出的问题。因为我的mysql用的是8.0版本,springboot默认的版本是5.1.47的,尝试修改默认版本为8.0.24,并且修改yml文件中的driver-class-name和时区,没有效果。后来仔细看了一下报错,提示的是url没有值,之前我把url改成了jdbc-url,所以就想是不是这个问题,重新改成url之后,运行项目,问题解决。但是什么时候需要用到jdbc-url什么时候用url?为什么另外的模块可以使用jdbc-url,这个模块就不行呢?
5、在controller中添加一个测试接口,测试是否进入了controller,发现测试接口也无法访问,再写了一个方法,使用@ModelAttribute注解,发现没有被调用。检查端口号,确定为9007,请求地址也没有写错,请求方式正确。Application的位置放置正确,Controller上加了RestController注解。之前请求路径前缀是直接放在@RestContoller中的,参考其他子模块发现,RestController中没有请求路径,而是使用@RequestMapping来定义前缀。修改之后不报404,报500了。
6、自己之前为了方便测试,把shiroConfiguration中的@Configuration注解给注释掉了。
7、debug发现,RepositoryService中的processEngineConfiguration为null,因为我的RepositoryService是在application中添加了一个bean,所以可能是没有配置正确。将自己在application中写的@Bean注解删除,更换Activiti的依赖,之前导入的依赖为activiti-engine,现在更换成activiti-spring-boot-starter,重新运行,访问接口,发现不报错了,但是没进断点,响应也没有任何内容。访问测试接口,报Unauthorized


  • 0
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值