SpringBoot+MyBatis+Mysql+Durid实现动态多数据源

亲测有效,网上其他不是报错就是唠海嗑,所以自用在别人基础上改进报错,封装一套。

一、项目重点有:

(1)、SpringBoot+Mybatis+Mysql+Durid整合
(2)、错误后跳转到指定页面
(3)、多数据源动态切换
(4)、mybatis分页
(5)、durid监控
(6)、集成log4j2日志
(7)、通过mybatis拦截器,在控制台打印完整的sql

二、项目截图:

SpringBoot+MyBatis+Mysql+Durid实现动态多数据源

 

SpringBoot+MyBatis+Mysql+Durid实现动态多数据源

 

SpringBoot+MyBatis+Mysql+Durid实现动态多数据源

 

 SpringBoot+MyBatis+Mysql+Durid实现动态多数据源

 

 三、SpringBoot+Mybatis+Mysql+Durid整合

(1)、application.yml:

spring:
  dynamic-datasource:
    druid:
      # 连接池的配置信息
      # 初始化大小,最小,最大
      initial-size: 5
      min-idle: 5
      maxActive: 20
      # 配置获取连接等待超时的时间
      maxWait: 60000
      # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
      timeBetweenEvictionRunsMillis: 60000
      # 配置一个连接在池中最小生存的时间,单位是毫秒
      minEvictableIdleTimeMillis: 300000
      validationQuery: SELECT 1
      testWhileIdle: true
      testOnBorrow: false
      testOnReturn: false
      # 打开PSCache,并且指定每个连接上PSCache的大小
      poolPreparedStatements: true
      maxPoolPreparedStatementPerConnectionSize: 20
      # 配置监控统计拦截的filters,去掉后监控界面sql无法统计,‘wall‘用于防火墙
      filters: stat,wall,slf4j
      # 通过connectProperties属性来打开mergeSql功能;慢SQL记录
      connectionProperties: druid.stat.mergeSql\=true;druid.stat.slowSqlMillis\=5000
      # 配置DruidStatFilter
      web-stat-filter:
        enabled: true
        url-pattern: "/*"
        exclusions: "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*"
      # 配置DruidStatViewServlet
      stat-view-servlet:
        url-pattern: "/druid/*"
        # IP白名单(没有配置或者为空,则允许所有访问)
        allow: 127.0.0.1
        # IP黑名单 (存在共同时,deny优先于allow)
        deny: 192.168.1.73
        #  禁用HTML页面上的“Reset All”功能
        reset-enable: false
        # 登录名
        login-username: admin
        # 登录密码
        login-password: 123456
      filter:
        stat:
          log-slow-sql: true
          slow-sql-millis: 1000
          merge-sql: true
        wall:
          config:
            multi-statement-allow: true
    druid-datasources:
      jwpd:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
        username: root
        password: 137972zc
      lkj:
        driver-class-name: com.mysql.cj.jdbc.Driver
        url: jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf-8&useSSL=true&serverTimezone=UTC
        username: root
        password: 137972zc
  mvc:
    view:
      prefix: /WEB-INF/page/
      suffix: .jsp
server:
  port: 9090
logging:
  config: classpath:log4j2-spring-dev.xml
mybatis:
  type-aliases-package: com.base.springboot.entity

 说明:配置druid数据连接池,配置jdbc连接(两个数据源)

(2)、配置数据源(DataSourceProperties.class)

@Configuration
public class DataSourceProperties {

    @ConfigurationProperties(prefix = "spring.dynamic-datasource.druid-datasources.jwpd")
    @Bean(name = "JWPDDataSource")
    public DataSource JWPDDataSource(StandardEnvironment env){
        DruidDataSource druidDataSource = DruidDataSourceBuilder.create().build();
        return common(env,druidDataSource);
    }



    @ConfigurationProperties(prefix = "spring.dynamic-datasource.druid-datasources.lkj")
    @Bean(name = "LKJDataSource")
    public DataSource LKJDataSource(StandardEnvironment env){
        DruidDataSource druidDataSource = DruidDataSourceBuilder.create().build();
        return common(env,druidDataSource);
    }

    public DataSource common(StandardEnvironment env, DruidDataSource druidDataSource){
        Properties properties = new Properties();
        PropertySource<?> appProperties =  env.getPropertySources().get("applicationConfig: [classpath:/application.yml]");
        Map<String,Object> source = (Map<String, Object>) appProperties.getSource();
        properties.putAll(source);
        druidDataSource.configFromPropety(properties);
        return druidDataSource;
    }
}

 

说明:配置数据源,common(env,druidDataSource)方法,是为了继续设置为null的属性(durid配置属性,最大最小连接数、监控地址等等)

(3)、Spring和Mybatis的整合配置文件(MybatisConfig.class)

@Configuration
public class MybatisConfig {

    //注入数据源JWPDDataSource
    @Autowired
    @Qualifier("JWPDDataSource")
    public DataSource JWPDDataSource;

    //注入数据源LKJDataSource
    @Autowired
    @Qualifier("LKJDataSource")
    public DataSource LKJDataSource;


    //声明动态数据源,默认值为JWPDDataSource
    @Bean("dynamicDataSource")
    @Primary
    public DynamicDataSource dynamicDataSource(){
        //动态数据源集合
        Map<Object, Object> targetDataSourcesMap = new HashMap<>(2);
        targetDataSourcesMap.put(DataSourceEnum.jwpd.name(),JWPDDataSource);
        targetDataSourcesMap.put(DataSourceEnum.lkj.name(),LKJDataSource);
        DynamicDataSource dynamicDataSource = new DynamicDataSource(targetDataSourcesMap,JWPDDataSource);

        return dynamicDataSource;
    }


    @Bean(name="pageHelper")
    public PageHelper pageHelper() {
        PageHelper pageHelper = new PageHelper();
        Properties p = new Properties();
        p.setProperty("offsetAsPageNum", "true");
        p.setProperty("rowBoundsWithCount", "true");
        p.setProperty("reasonable", "true");
        p.setProperty("dialect", "mysql");
        pageHelper.setProperties(p);

        return pageHelper;
    }


    //sql打印插件
    @Bean(name="fullSqlInterceptor")
    public FullSqlInterceptor fullSqlInterceptor(){
        return new FullSqlInterceptor();
    }

    /**
     * 声明sql会话
     * @return
     */
    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("pageHelper")PageHelper pageHelper,@Qualifier("fullSqlInterceptor")FullSqlInterceptor fullSqlInterceptor) throws Exception{
        //声明sql会话工厂
        SqlSessionFactoryBean factoryBean=new SqlSessionFactoryBean();
        //设置数据源
        factoryBean.setDataSource(dynamicDataSource());
        //设置扫描mybatisXml的路径
        factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:**/dao/*/*.xml"));
        factoryBean.setPlugins(new Interceptor[] {pageHelper(), fullSqlInterceptor});//添加分页插件
        //返回sql会话
        return factoryBean.getObject();
    }


    /**
     * 声明事务管理器
     * @return PlatformTransactionManager
     */
    @Bean(name = "transactionManager")
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dynamicDataSource());
    }

    /**
     * 声明sqlSession模板
     * @param sqlSessionFactory
     * @return
     */
    @Bean(name = "sqlSessionTemplate")
    public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory){
        return new  SqlSessionTemplate(sqlSessionFactory);
    }
}

说明:

  1. 注入多个数据源
  2. 声明动态数据源,声明动态数据源和目标数据源(多个用于切换的数据源)
  3. 声明一些插件,比如分页插件和打印全文sql的插件
  4. 声明sql会话
  5. 声明事务管理器
  6. 声明sqlSession模板

(4)、创建动态数据源对象(DynamicDataSource.class)

public class DynamicDataSource extends AbstractRoutingDataSource {


    /**
     * 有参构造方法,声明对象的时候执行,调用父类AbstractRoutingDataSource的方法
     * @param targetDataSources  数据源Map集合
     * @param defaultTargetDataSource  默认数据源
     */
    public DynamicDataSource(Map<Object,Object> targetDataSources, DataSource defaultTargetDataSource) {
        //将数据源的 key 放到数据源上下文的 key 集合中,用于切换时判断数据源是否有效
        DynamicDataSourceContextHolder.addDataSourceKeys(targetDataSources.keySet());
        //设置数据源集合
        super.setTargetDataSources(targetDataSources);
        //设置默认数据源
        super.setDefaultTargetDataSource(defaultTargetDataSource);
    }

    /**
     * 重写determineCurrentLookupKey方法,这个方法返回一个key值,
     * 通过这个key值执行determineTargetDataSource方法,获取当前的数据源
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceKey();
    }
}

说明:继承AbstractRoutingDataSource抽象类,实现setTargetDataSources(设置目标数据源集合)、setDefaultTargetDataSource(设置默认数据源)、determineCurrentLookupKey(返回当前数据源)等方法。

(5)、动态数据源操作上下文类(DynamicDataSourceContextHolder.class)

public class DynamicDataSourceContextHolder {

    /**
     * 静态ThreadLocal常量contextHolder,用来装当前线程的数据源key
     */
    public static final ThreadLocal<String> contextHolder=new ThreadLocal<>();



    /**
     * 数据源的 key集合,用于切换时判断数据源是否存在
     */
    public static List<Object> dataSourceKeys = new ArrayList<>();


    /**
     * 获取contextHolder值(数据源key)的方法(获取当前数据源)
     */
    public static String getDataSourceKey(){
        return contextHolder.get();
    }


    /**
     * 写入contextHolder值(数据源key)的方法(写入当前数据源)
     */
    public static void setDataSourceKey(String key){
        contextHolder.set(key);
    }


    /**
     * 清除contextHolder值(数据源key)的方法(写入当前数据源)
     */
    public static void clearDataSourceKey(){
        contextHolder.remove();
    }


    /**
     * 判断是否包含数据源
     * @param key 数据源key
     * @return boolean
     */
    public static boolean containDataSourceKey(String key) {
        return dataSourceKeys.contains(key);
    }

    /**
     * 添加数据源keys
     * @param keys
     * @return boolean
     */
    public static boolean addDataSourceKeys(Collection<? extends Object> keys) {
        return dataSourceKeys.addAll(keys);
    }
}

说明:声明set、get当前数据源key、判断是否包含当前数据源、添加数据源key的方法

(6)、设置当前数据源注解(TargetDs.class)

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface TargetDs {

    /**
     * 数据源key值
     * @return
     */
    String value();
}

(7)、设置动态数据源切换类(DynamicDataSourceAspect.class)

@Aspect
@Order(-1)  // 该切面应当先于 @Transactional 执行
@Component
public class DynamicDataSourceAspect {


    /**
     * 前置通知,进入切点之前,先切换数据源
     * @param point
     * @param targetDs
     */
    @Before("@annotation(targetDs)")
    public void switchDataSource(JoinPoint point, TargetDs targetDs) {
        //判断,如果没有此数据源
        if (!DynamicDataSourceContextHolder.containDataSourceKey(targetDs.value())){
            System.out.println("没有找到key为[{}]的数据源,所以当前还是使用默认数据源!"+targetDs.value());
        }else {
            DynamicDataSourceContextHolder.setDataSourceKey(targetDs.value());
            System.out.println("方法"+point.getSignature().getName()+"上发现@TargetDs注解,"+"当前数据源已经切换为[{}]!"+targetDs.value());
        }
    }


    /**
     * 后置通知,切合方法执行完成之后,重置数据源
     * @param point
     * @param targetDs
     */
    @After("@annotation(targetDs)")
    public void restoreDataSource(JoinPoint point, TargetDs targetDs) {
        System.out.println("重置数据源 [" + DynamicDataSourceContextHolder.getDataSourceKey()
                + "] in Method [" + point.getSignature() + "]");
        // 将数据源置为默认数据源
        DynamicDataSourceContextHolder.clearDataSourceKey();
    }
}

说明:设置切面类,切点是带有@targetDs注解的方法,当遇到这种方法,执行前将数据源切换到对应的key对应的数据源,执行完成后还原到默认数据源。

(8)、事务配置(TransactionAdviceConfig.class)

@Aspect
@Configuration
public class TransactionAdviceConfig {

    //声明切面
    private static final String AOP_POINTCUT_EXPRESSION = "execution (* com.base.springboot.service.*.impl.*.*(..))";


    //事务管理器
    @Autowired
    @Qualifier("transactionManager")
    private PlatformTransactionManager transactionManager;

    //声明通知
    @Bean(name = "txInterceptor")
    public TransactionInterceptor txInterceptor(){

        Properties attributes = new Properties();
        attributes.setProperty("insert*",   "PROPAGATION_REQUIRED");
        attributes.setProperty("add*",      "PROPAGATION_REQUIRED");
        attributes.setProperty("update*",   "PROPAGATION_REQUIRED");
        attributes.setProperty("delete*",   "PROPAGATION_REQUIRED");
        attributes.setProperty("deploy*",   "PROPAGATION_REQUIRED");
        attributes.setProperty("select*",   "PROPAGATION_REQUIRED,readOnly");
        attributes.setProperty("get*",  	"PROPAGATION_REQUIRED,readOnly");
        attributes.setProperty("query*",    "PROPAGATION_REQUIRED,readOnly");
        return new TransactionInterceptor(transactionManager, attributes);
    }

    @Bean
    public AspectJExpressionPointcutAdvisor pointcutAdvisor(@Qualifier("txInterceptor") TransactionInterceptor txInterceptor){
        AspectJExpressionPointcutAdvisor pointcutAdvisor = new AspectJExpressionPointcutAdvisor();
        pointcutAdvisor.setAdvice(txInterceptor);
        pointcutAdvisor.setExpression(AOP_POINTCUT_EXPRESSION);
        return pointcutAdvisor;
    }
}

说明:注入事务管理器,声明切面(控制的范围),声明事务拦截器(设置不同的方法对应的事务策略),声明AspectJExpressionPointcutAdvisor,传入切面和事务拦截通知,完成aop切入。

以上8个步骤,就完成了SpringBoot+MyBatis+Mysql+Durid的配置,多数据源通过自定义注解,动态切换,没有数据源都被事务管控,实现多数据源动态切换核心点就是AbstractRoutingDataSource。

四、集成log4j2日志

(1)、log4j2-spring-dev.xml

<?xml version="1.0" encoding="UTF-8"?>
<configuration status="OFF" monitorInterval="30">
    <Properties>
        <!-- 日志默认存放的位置,这里设置为项目根路径下,也可指定绝对路径 -->
        <property name="basePath">D://log4j2Logs</property>
        <!-- 控制台默认输出格式,"%-5level":日志级别,"%l":输出完整的错误位置,是小写的L,因为有行号显示,所以影响日志输出的性能 -->
        <property name="console_log_pattern">%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %l - %m%n</property>
        <!-- 日志文件默认输出格式,不带行号输出(行号显示会影响日志输出性能);%C:大写,类名;%M:方法名;%m:错误信息;%n:换行 -->
        <property name="log_pattern">%d{yyyy-MM-dd HH:mm:ss.SSS} [%-5level] %C.%M - %m%n</property>
    </Properties>

    <appenders>
        <Console name="Console" target="SYSTEM_OUT">
            <!--只接受程序中INFO级别的日志进行处理-->
            <ThresholdFilter level="DEBUG" onMatch="ACCEPT" onMismatch="DENY"/>
            <PatternLayout pattern="${console_log_pattern}"/>
        </Console>

        <!--处理DEBUG级别的日志,并把该日志放到logs/debug.log文件中-->
        <!--打印出DEBUG级别日志,每次大小超过size,则这size大小的日志会自动存入按年份-月份建立的文件夹下面并进行压缩,作为存档-->
        <RollingFile name="RollingFileDebug" fileName="${basePath}/logs/debug.log"
                     filePattern="${basePath}/logs/$${date:yyyy-MM}/debug-%d{yyyy-MM-dd}-%i.log.gz">
            <Filters>
                <ThresholdFilter level="DEBUG"/>
                <ThresholdFilter level="INFO" onMatch="DENY" onMismatch="NEUTRAL"/>
            </Filters>
            <PatternLayout pattern="${log_pattern}"/>
            <Policies>
                <SizeBasedTriggeringPolicy size="500 MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>
        </RollingFile>

        <!--处理INFO级别的日志,并把该日志放到logs/info.log文件中-->
        <RollingFile name="RollingFileInfo" fileName="${basePath}/logs/info.log"
                     filePattern="${basePath}/logs/$${date:yyyy-MM}/info-%d{yyyy-MM-dd}-%i.log.gz">
            <Filters>
                <!--只接受INFO级别的日志,其余的全部拒绝处理-->
                <ThresholdFilter level="INFO"/>
                <ThresholdFilter level="WARN" onMatch="DENY" onMismatch="NEUTRAL"/>
            </Filters>
            <PatternLayout pattern="${log_pattern}"/>
            <Policies>
                <SizeBasedTriggeringPolicy size="500 MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>
        </RollingFile>

        <!--处理WARN级别的日志,并把该日志放到logs/warn.log文件中-->
        <RollingFile name="RollingFileWarn" fileName="${basePath}/logs/warn.log"
                     filePattern="${basePath}/logs/$${date:yyyy-MM}/warn-%d{yyyy-MM-dd}-%i.log.gz">
            <Filters>
                <ThresholdFilter level="WARN"/>
                <ThresholdFilter level="ERROR" onMatch="DENY" onMismatch="NEUTRAL"/>
            </Filters>
            <PatternLayout pattern="${log_pattern}"/>
            <Policies>
                <SizeBasedTriggeringPolicy size="500 MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>
        </RollingFile>

        <!--处理error级别的日志,并把该日志放到logs/error.log文件中-->
        <RollingFile name="RollingFileError" fileName="${basePath}/logs/error.log"
                     filePattern="${basePath}/logs/$${date:yyyy-MM}/error-%d{yyyy-MM-dd}-%i.log.gz">
            <ThresholdFilter level="ERROR"/>
            <PatternLayout pattern="${log_pattern}"/>
            <Policies>
                <SizeBasedTriggeringPolicy size="500 MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>
        </RollingFile>

        <!--druid的日志记录追加器-->
        <RollingFile name="druidSqlRollingFile" fileName="${basePath}/logs/druid-sql.log"
                     filePattern="${basePath}/logs/$${date:yyyy-MM}/api-%d{yyyy-MM-dd}-%i.log.gz">
            <PatternLayout pattern="${log_pattern}"/>
            <Policies>
                <SizeBasedTriggeringPolicy size="500 MB"/>
                <TimeBasedTriggeringPolicy/>
            </Policies>
        </RollingFile>
    </appenders>

    <loggers>
        <root level="DEBUG">
            <appender-ref ref="Console"/>
            <appender-ref ref="RollingFileInfo"/>
            <appender-ref ref="RollingFileWarn"/>
            <appender-ref ref="RollingFileError"/>
            <appender-ref ref="RollingFileDebug"/>
        </root>

        <!--记录druid-sql的记录-->
        <logger name="druid.sql.Statement" level="info" additivity="false">
            <appender-ref ref="druidSqlRollingFile"/>
        </logger>

        <!--过滤掉spring和hibernate的一些无用的debug信息-->
        <logger name="org.springframework" level="INFO"></logger>
        <!--输出sql语句-->
        <logger name="com.base.springboot.dao" level="info" additivity="false">
            <appender-ref ref="Console" />
        </logger>
    </loggers>
</configuration>

 (2)、application.yml:

logging:
  config: classpath:log4j2-spring-dev.xml

说明:配置log4j2日志,记录不同的级别的日志到不同的文件。

五、不同的错误跳转到错误页面

1、创建错误配置类(ErrorPageConfig.class),继承HandlerInterceptorAdapter。

@Component
public class ErrorPageConfig extends HandlerInterceptorAdapter {

    private List<Integer> errorList= Arrays.asList(404, 405, 500);


    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        if (errorList.contains(response.getStatus())){
            response.sendRedirect("/error/"+response.getStatus());
            return false;
        }
        return super.preHandle(request, response, handler);
    }
}

 2、在Controller里面写错误页面跳转方法(BaseController.class)

@Controller
public class BaseController {

    @RequestMapping("/error/{code}")
    public String errorController(@PathVariable("code") String code, Model model){
        String errorStr="";
        switch (code){
            case "404":
                errorStr="找不到页面!";
                break;
            case "405":
                errorStr="405错误!";
                break;
            case "500":
                errorStr="服务器错误!";
                break;
        }
        model.addAttribute("errorStr",errorStr);
        return "errorPage";
    }
}

说明:继承拦截器,重写preHandle方法(执行之前拦截),当response.getStatus()状态码为错误码时,重定向到"/error/"+response.getStatus()方法,这个方法里面做不同的处理。

六、mybatis分页

(1)、声明列表视图类ListVo.class

public class ListVo<T> {
    //数据量
    private int totalSize = 0;
    //数据列表
    private List<T> list = new ArrayList();

    public int getTotalSize() {
        return totalSize;
    }

    public void setTotalSize(int totalSize) {
        this.totalSize = totalSize;
    }

    public List<T> getList() {
        return list;
    }

    public void setList(List<T> list) {
        this.list = list;
    }
}

说明:属性有数量和list列表

(2)、MybatisConfig.class配置类里面声明pageHelper对象,并且在sql会话里面插入插件。

@Bean(name="pageHelper")
public PageHelper pageHelper() {
    PageHelper pageHelper = new PageHelper();
    Properties p = new Properties();
    p.setProperty("offsetAsPageNum", "true");
    p.setProperty("rowBoundsWithCount", "true");
    p.setProperty("reasonable", "true");
    p.setProperty("dialect", "mysql");
    pageHelper.setProperties(p);

    return pageHelper;
}

	/**
     * 声明sql会话
     * @return
     */
    @Bean(name = "sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory(@Qualifier("pageHelper")PageHelper pageHelper,@Qualifier("fullSqlInterceptor")FullSqlInterceptor fullSqlInterceptor) throws Exception{
        //声明sql会话工厂
        SqlSessionFactoryBean factoryBean=new SqlSessionFactoryBean();
        //设置数据源
        factoryBean.setDataSource(dynamicDataSource());
        //设置扫描mybatisXml的路径
        factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:**/dao/*/*.xml"));
        factoryBean.setPlugins(new Interceptor[] {pageHelper(), fullSqlInterceptor});//添加分页插件
        //返回sql会话
        return factoryBean.getObject();
    }

 (3)、Dao层的base操作类里面(BaseDaoImpl.class)

@Repository("baseDao")
public class BaseDaoImpl implements IBaseDao {

    @Autowired
    public SqlSessionTemplate sqlSessionTemplate;


    @Override
    public List<Object> getObjectList(String statement, Map<String, Object> paramMap) {

        List<Object> list=null;
        try {
            list=sqlSessionTemplate.selectList(statement,paramMap);
        }catch (Exception e){
            e.printStackTrace();
        }
        return list;
    }


    @Override
    public <T> ListVo<T> getObjectPage(String start,String limit,String statement, Map<String, Object> paramMap) {
        try {
//            RowBounds rowBounds=new RowBounds(Integer.parseInt(start),Integer.parseInt(limit));
//            List<T> list=sqlSessionTemplate.selectList(statement,paramMap,rowBounds);
            ListVo<T> listVo = new ListVo<T>();
            PageHelper.startPage(Integer.parseInt(start),Integer.parseInt(limit),true);
            List<T> list=sqlSessionTemplate.selectList(statement,paramMap);
            listVo.setList(list);
            Page<T> page = (Page<T>)list;
            listVo.setTotalSize((int)page.getTotal());
            return listVo;
        }catch (Exception e){
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public void insertObject(String statement, Object object) {
        try{
            sqlSessionTemplate.insert(statement,object);
        }catch (Exception e){
            e.printStackTrace();
        }
    }
}

 说明:

ListVo<T> listVo = new ListVo<T>();
PageHelper.startPage(Integer.parseInt(start),Integer.parseInt(limit),true);
List<T> list=sqlSessionTemplate.selectList(statement,paramMap);
listVo.setList(list);
Page<T> page = (Page<T>)list;
listVo.setTotalSize((int)page.getTotal());
return listVo;
  • PageHelper.startPage:进行物理分页
  • listVo.setList(list);:将list设置进listVo类的list属性
  • Page page = (Page)list;
  • listVo.setTotalSize((int)page.getTotal());:设置进listVo类的totalSize属性。

七、通过mybatis拦截器,在控制台打印完整的sql(FullSqlInterceptor.class)

 

@Intercepts({
        @Signature(type = Executor.class, method = "update", args = { MappedStatement.class, Object.class }),
        @Signature(type = Executor.class, method = "query", args = { MappedStatement.class, Object.class,
                RowBounds.class, ResultHandler.class }) })
public class FullSqlInterceptor implements Interceptor {


    public Object intercept(Invocation invocation) throws Throwable {
        //获取<select> or <update> or <delete>节点
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        Object parameter = null;
        if (invocation.getArgs().length > 1) {
            //获取参数
            parameter = invocation.getArgs()[1];
        }
        //获取方法id
        String sqlId = mappedStatement.getId();
        //获取sql语句
        BoundSql boundSql = mappedStatement.getBoundSql(parameter);
        //获取连接信息
        Configuration configuration = mappedStatement.getConfiguration();
        Object returnValue = null;
        //获取系统时间
        long start = System.currentTimeMillis();
        //获取返回数据
        returnValue = invocation.proceed();
        long end = System.currentTimeMillis();
        long time = (end - start);
        if (time > 1) {
            //调用getSql方法,获取处理后的sql语句
            String sql = getSql(configuration, boundSql, sqlId, time);
            System.err.println(sql);
        }
        return returnValue;
    }

    public static String getSql(Configuration configuration, BoundSql boundSql, String sqlId, long time) {
        String sql = showSql(configuration, boundSql);
        StringBuilder str = new StringBuilder(100);
        str.append("执行方法: ");
        str.append(sqlId);
        str.append("\n");
        str.append("执行语句:  ");
        str.append(sql);
        str.append("   :耗时");
        str.append(time);
        str.append("ms");
        return str.toString();
    }

    //这个方法是用来转换参数值的
    private static String getParameterValue(Object obj) {
        String value = null;
        if (obj instanceof String) {
            value = "‘" + obj.toString() + "‘";
        } else if (obj instanceof Date) {
            DateFormat formatter = DateFormat.getDateTimeInstance(DateFormat.DEFAULT, DateFormat.DEFAULT, Locale.CHINA);
            value = "‘" + formatter.format(new Date()) + "‘";
        } else {
            if (obj != null) {
                value = obj.toString();
            } else {
                value = "";
            }

        }
        return value;
    }

    public static String showSql(Configuration configuration, BoundSql boundSql) {
        Object parameterObject = boundSql.getParameterObject();
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        String sql = boundSql.getSql().replaceAll("[\\s]+", " ");
        //如果有参数
        if (parameterMappings.size() > 0 && parameterObject != null) {
            TypeHandlerRegistry typeHandlerRegistry = configuration.getTypeHandlerRegistry();
            //如果是基础类型
            if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
                sql = sql.replaceFirst("\\?", getParameterValue(parameterObject));
            } else {
                //如果参数不是基础类型
                MetaObject metaObject = configuration.newMetaObject(parameterObject);
                for (ParameterMapping parameterMapping : parameterMappings) {
                    String propertyName = parameterMapping.getProperty();
                    if (metaObject.hasGetter(propertyName)) {
                        Object obj = metaObject.getValue(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
                    } else if (boundSql.hasAdditionalParameter(propertyName)) {
                        Object obj = boundSql.getAdditionalParameter(propertyName);
                        sql = sql.replaceFirst("\\?", getParameterValue(obj));
                    }
                }
            }
        }
        return sql;
    }

}

说明:主要是在执行mybatis方法的时候,替换掉原生语句中的参数#{},替换成具体的值,方便在控制台直接复制到数据库查看。

以上就是SpringBoot+MyBatis+Mysql+Durid动态多数据源项目搭建的主要代码和过程。

 

SpringBoot+MyBatis+Mysql+Durid实现动态多数据源

上一篇:sql 语句的bug


下一篇:MySQL和B+树的那些事&mysql 索引原理