前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
圈层
工具
发布
首页
学习
活动
专区
圈层
工具
MCP广场
社区首页 >专栏 >Mybatis通过Interceptor来简单实现影子表进行动态sql读取和写入

Mybatis通过Interceptor来简单实现影子表进行动态sql读取和写入

作者头像
chinotan
发布于 2019-04-03 07:53:27
发布于 2019-04-03 07:53:27
7.5K00
代码可运行
举报
运行总次数:0
代码可运行

首先进行Mybatis 拦截器介绍

    拦截器的一个作用就是我们可以拦截某些方法的调用,我们可以选择在这些被拦截的方法执行前后加上某些逻辑,也可以在执行这些被拦截的方法时执行自己的逻辑而不再执行被拦截的方法。Mybatis拦截器设计的一个初衷就是为了供用户在某些时候可以实现自己的逻辑而不必去动Mybatis固有的逻辑。打个比方,对于Executor,Mybatis中有几种实现:BatchExecutor、ReuseExecutor、SimpleExecutor和CachingExecutor。这个时候如果你觉得这几种实现对于Executor接口的query方法都不能满足你的要求,那怎么办呢?是要去改源码吗?当然不。我们可以建立一个Mybatis拦截器用于拦截Executor接口的query方法,在拦截之后实现自己的query方法逻辑,之后可以选择是否继续执行原来的query方法。 对于拦截器Mybatis为我们提供了一个Interceptor接口,通过实现该接口就可以定义我们自己的拦截器。我们先来看一下这个接口的定义:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package org.apache.ibatis.plugin;
 
import java.util.Properties;
 
public interface Interceptor {
 
  Object intercept(Invocation invocation) throws Throwable;
 
  Object plugin(Object target);
 
  void setProperties(Properties properties);
 
}
 org.apache.ibatis.plugin;
 
import java.util.Properties;
 
public interface Interceptor {
 
  Object intercept(Invocation invocation) throws Throwable;
 
  Object plugin(Object target);
 
  void setProperties(Properties properties);
 
}

我们可以看到在该接口中一共定义有三个方法,intercept、plugin和setProperties。plugin方法是拦截器用于封装目标对象的,通过该方法我们可以返回目标对象本身,也可以返回一个它的代理。当返回的是代理的时候我们可以对其中的方法进行拦截来调用intercept方法,当然也可以调用其他方法,这点将在后文讲解。setProperties方法是用于在Mybatis配置文件中指定一些属性的。

       定义自己的Interceptor最重要的是要实现plugin方法和intercept方法,在plugin方法中我们可以决定是否要进行拦截进而决定要返回一个什么样的目标对象。而intercept方法就是要进行拦截的时候要执行的方法。

       对于plugin方法而言,其实Mybatis已经为我们提供了一个实现。Mybatis中有一个叫做Plugin的类,里面有一个静态方法wrap(Object target,Interceptor interceptor),通过该方法可以决定要返回的对象是目标对象还是对应的代理。

    我们先看一下Plugin的wrap方法,它根据当前的Interceptor上面的注解定义哪些接口需要拦截,然后判断当前目标对象是否有实现对应需要拦截的接口,如果没有则返回目标对象本身,如果有则返回一个代理对象。而这个代理对象的InvocationHandler正是一个Plugin。所以当目标对象在执行接口方法时,如果是通过代理对象执行的,则会调用对应InvocationHandler的invoke方法,也就是Plugin的invoke方法。所以接着我们来看一下该invoke方法的内容。这里invoke方法的逻辑是:如果当前执行的方法是定义好的需要拦截的方法,则把目标对象、要执行的方法以及方法参数封装成一个Invocation对象,再把封装好的Invocation作为参数传递给当前拦截器的intercept方法。如果不需要拦截,则直接调用当前的方法。Invocation中定义了定义了一个proceed方法,其逻辑就是调用当前方法,所以如果在intercept中需要继续调用当前方法的话可以调用invocation的procced方法。

       这就是Mybatis中实现Interceptor拦截的一个思想,如果用户觉得这个思想有问题或者不能完全满足你的要求的话可以通过实现自己的Plugin来决定什么时候需要代理什么时候需要拦截。以下讲解的内容都是基于Mybatis的默认实现即通过Plugin来管理Interceptor来讲解的。

       对于实现自己的Interceptor而言有两个很重要的注解,一个是@Intercepts,其值是一个@Signature数组。@Intercepts用于表明当前的对象是一个Interceptor,而@Signature则表明要拦截的接口、方法以及对应的参数类型。来看一个自定义的简单Interceptor:        首先看setProperties方法,这个方法在Configuration初始化当前的Interceptor时就会执行,这里只是简单的取两个属性进行打印。

       其次看plugin方法中我们是用的Plugin的逻辑来实现Mybatis的逻辑的。

       接着看MyInterceptor类上我们用@Intercepts标记了这是一个Interceptor,然后在@Intercepts中定义了两个@Signature,即两个拦截点。第一个@Signature我们定义了该Interceptor将拦截Executor接口中参数类型为MappedStatement、Object、RowBounds和ResultHandler的query方法;第二个@Signature我们定义了该Interceptor将拦截StatementHandler中参数类型为Connection的prepare方法。

       最后再来看一下intercept方法,这里我们只是简单的打印了一句话,然后调用invocation的proceed方法,使当前方法正常的调用。

       对于这个拦截器,Mybatis在注册该拦截器的时候就会利用定义好的n个property作为参数调用该拦截器的setProperties方法。之后在新建可拦截对象的时候会调用该拦截器的plugin方法来决定是返回目标对象本身还是代理对象。对于这个拦截器而言,当Mybatis是要Executor或StatementHandler对象的时候就会返回一个代理对象,其他都是原目标对象本身。然后当Executor代理对象在执行参数类型为MappedStatement、Object、RowBounds和ResultHandler的query方法或StatementHandler代理对象在执行参数类型为Connection的prepare方法时就会触发当前的拦截器的intercept方法进行拦截,而执行这两个接口对象的其他方法时都只是做一个简单的代理。     册拦截器是通过在Mybatis配置文件中plugins元素下的plugin元素来进行的。一个plugin对应着一个拦截器,在plugin元素下面我们可以指定若干个property子元素。Mybatis在注册定义的拦截器时会先把对应拦截器下面的所有property通过Interceptor的setProperties方法注入给对应的拦截器。所以,我们可以这样来注册我们在前面定义的MyInterceptor:

    Mybatis拦截器只能拦截四种类型的接口:Executor、StatementHandler、ParameterHandler和ResultSetHandler。这是在Mybatis的Configuration中写死了的,如果要支持拦截其他接口就需要我们重写Mybatis的Configuration。Mybatis可以对这四个接口中所有的方法进行拦截。        下面将介绍一个Mybatis拦截器的实际应用。Mybatis拦截器常常会被用来进行分页处理。我们知道要利用JDBC对数据库进行操作就必须要有一个对应的Statement对象,Mybatis在执行Sql语句前也会产生一个包含Sql语句的Statement对象,而且对应的Sql语句是在Statement之前产生的,所以我们就可以在它成Statement之前对用来生成Statement的Sql语句下手。在Mybatis中Statement语句是通过RoutingStatementHandler对象的prepare方法生成的。所以利用拦截器实现Mybatis分页的一个思路就是拦截StatementHandler接口的prepare方法,然后在拦截器方法中把Sql语句改成对应的分页查询Sql语句,之后再调用StatementHandler对象的prepare方法,即调用invocation.proceed()。更改Sql语句这个看起来很简单,而事实上来说的话就没那么直观,因为包括sql等其他属性在内的多个属性都没有对应的方法可以直接取到,它们对外部都是封闭的,是对象的私有属性,所以这里就需要引入反射机制来获取或者更改对象的私有属性的值了。对于分页而言,在拦截器里面我们常常还需要做的一个操作就是统计满足当前条件的记录一共有多少,这是通过获取到了原始的Sql语句后,把它改为对应的统计语句再利用Mybatis封装好的参数和设置参数的功能把Sql语句中的参数进行替换,之后再执行查询记录数的Sql语句进行总记录数的统计。

    下面是一个简单的实现影子表切换的功能:数据库mysql8.0.12,数据库连接池:Druid1.1.10,mybatis版本3.4.6,springboot版本2.0.3,使用mybatis-plus插件版本3.0.7.1

    主要数据库相关pom文件如下:    

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
       <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.12</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.2</version>
            <exclusions>
                <exclusion>
                    <groupId>org.mybatis</groupId>
                    <artifactId>mybatis</artifactId>
                </exclusion>
                <exclusion>
                    <groupId>org.mybatis</groupId>
                    <artifactId>mybatis-spring</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
        
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.10</version>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatisplus-spring-boot-starter</artifactId>
            <version>1.0.5</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generator</artifactId>
            <version>3.0.7.1</version>
        </dependency>

        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-autoconfigure</artifactId>
            <version>1.3.2</version>
        </dependency>

        <!-- velocity 模板引擎, 默认 -->
        <dependency>
            <groupId>org.apache.velocity</groupId>
            <artifactId>velocity-engine-core</artifactId>
            <version>2.0</version>
        </dependency>

        <!-- freemarker 模板引擎 -->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.28</version>
        </dependency>


        <!-- beetl 模板引擎 -->
        <dependency>
            <groupId>com.ibeetl</groupId>
            <artifactId>beetl</artifactId>
            <version>2.9.8</version>
        </dependency>

    mybatis配置如下:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
##########################  mysql   ##########################
spring.datasource.url: "jdbc:mysql://localhost:3306/chinotan?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true"
spring.datasource.username: root
spring.datasource.password: 
spring.datasource.driver-class-name: "com.mysql.cj.jdbc.Driver"

logging.level.com.shyroke.mapper: debug


##########################  mybatis   ##########################
mybatis.mapper-locations: classpath:mybatis/*.xml

##########################  druid配置   ##########################
spring.datasource.type: com.alibaba.druid.pool.DruidDataSource

# 初始化大小,最小,最大  
spring.datasource.initialSize: 5
spring.datasource.minIdle: 5
spring.datasource.maxActive: 20
# 配置获取连接等待超时的时间  
spring.datasource.maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒  
spring.datasource.timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒  
spring.datasource.minEvictableIdleTimeMillis: 300000
# 校验SQL,Oracle配置 spring.datasource.validationQuery: SELECT 1 FROM DUAL,如果不配validationQuery项,则下面三项配置无用  
spring.datasource.validationQuery: SELECT 'x'
spring.datasource.testWhileIdle: true
spring.datasource.testOnBorrow: false
spring.datasource.testOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的大小  
spring.datasource.poolPreparedStatements: true
spring.datasource.maxPoolPreparedStatementPerConnectionSize: 20
# 配置监控统计拦截的filters,去掉后监控界面sql无法统计,'wall'用于防火墙  
spring.datasource.filters: stat,wall,log4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录  
spring.datasource.connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合并多个DruidDataSource的监控数据  
spring.datasource.useGlobalDataSourceStat: true

# 配置mybatis-plus
mybatis-plus:
  mapper-locations: classpath:/mapper/*.xml
  #实体扫描,多个package用逗号或者分号分隔
  typeAliasesPackage: cn.chinotan.entity
  global-config:
    #主键类型  0:"数据库ID自增", 1:"用户输入ID",2:"全局唯一ID (数字类型唯一ID)", 3:"全局唯一ID UUID";
    id-type: 0
    #字段策略 0:"忽略判断",1:"非 NULL 判断"),2:"非空判断"
    field-strategy: 2
    #驼峰下划线转换
    db-column-underline: true
    #刷新mapper 调试神器
    refresh-mapper: true
    #数据库大写下划线转换
    #capital-mode: true
    #序列接口实现类配置
    #key-generator: com.baomidou.springboot.xxx
    #逻辑删除配置(下面3个配置)
    logic-delete-value: 0
    logic-not-delete-value: 1
    #自定义SQL注入器
    #sql-injector: com.baomidou.mybatisplus.mapper.LogicSqlInjector
    #自定义填充策略接口实现
    #meta-object-handler: com.baomidou.springboot.xxx
  configuration:
    map-underscore-to-camel-case: true
    cache-enabled: false

    Druid和mybatis的配置文件:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package cn.chinotan.config;

import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.PropertySource;

import javax.sql.DataSource;
import java.sql.SQLException;

/**
 * @program: test
 * @description: 导入增加的jdbc配置文件
 * @author: xingcheng
 * @create: 2019-02-16 17:43
 **/
@Configuration
@PropertySource(value = "classpath:application.yml")
public class DataSourceConfiguration {

    @Value("${spring.datasource.url}")
    private String dbUrl;
    @Value("${spring.datasource.username}")
    private String username;
    @Value("${spring.datasource.password}")
    private String password;
    @Value("${spring.datasource.driver-class-name}")
    private String driverClassName;
    @Value("${spring.datasource.initialSize}")
    private int initialSize;
    @Value("${spring.datasource.minIdle}")
    private int minIdle;
    @Value("${spring.datasource.maxActive}")
    private int maxActive;
    @Value("${spring.datasource.maxWait}")
    private int maxWait;
    @Value("${spring.datasource.timeBetweenEvictionRunsMillis}")
    private int timeBetweenEvictionRunsMillis;
    @Value("${spring.datasource.minEvictableIdleTimeMillis}")
    private int minEvictableIdleTimeMillis;
    @Value("${spring.datasource.validationQuery}")
    private String validationQuery;
    @Value("${spring.datasource.testWhileIdle}")
    private boolean testWhileIdle;
    @Value("${spring.datasource.testOnBorrow}")
    private boolean testOnBorrow;
    @Value("${spring.datasource.testOnReturn}")
    private boolean testOnReturn;
    @Value("${spring.datasource.poolPreparedStatements}")
    private boolean poolPreparedStatements;
    @Value("${spring.datasource.maxPoolPreparedStatementPerConnectionSize}")
    private int maxPoolPreparedStatementPerConnectionSize;
    @Value("${spring.datasource.filters}")
    private String filters;
    @Value("${spring.datasource.connectionProperties}")
    private String connectionProperties;
    @Value("${spring.datasource.useGlobalDataSourceStat}")
    private boolean useGlobalDataSourceStat;

    @Bean     //声明其为Bean实例  
    @Primary  //在同样的DataSource中,首先使用被标注的DataSource  
    public DataSource druidDataSource() {
        DruidDataSource datasource = new DruidDataSource();
        datasource.setUrl(this.dbUrl);
        datasource.setUsername(username);
        datasource.setPassword(password);
        datasource.setDriverClassName(driverClassName);

        //configuration  
        datasource.setInitialSize(initialSize);
        datasource.setMinIdle(minIdle);
        datasource.setMaxActive(maxActive);
        datasource.setMaxWait(maxWait);
        datasource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
        datasource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
        datasource.setValidationQuery(validationQuery);
        datasource.setTestWhileIdle(testWhileIdle);
        datasource.setTestOnBorrow(testOnBorrow);
        datasource.setTestOnReturn(testOnReturn);
        datasource.setPoolPreparedStatements(poolPreparedStatements);
        datasource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
        datasource.setUseGlobalDataSourceStat(useGlobalDataSourceStat);
        try {
            datasource.setFilters(filters);
        } catch (SQLException e) {
            System.err.println("druid configuration initialization filter: "+ e);
        }
        datasource.setConnectionProperties(connectionProperties);
        return datasource;
    }

    /**
     * 注册一个StatViewServlet
     * @return
     */
    @Bean
    public ServletRegistrationBean druidStatViewServlet(){
        //org.springframework.boot.context.embedded.ServletRegistrationBean提供类的进行注册.
        ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(),"/druid/*");

        //添加初始化参数:initParams
        //白名单:
        servletRegistrationBean.addInitParameter("allow","127.0.0.1");
        //IP黑名单 (存在共同时,deny优先于allow) : 如果满足deny的话提示:Sorry, you are not permitted to view this page.
        servletRegistrationBean.addInitParameter("deny","192.168.1.73");
        //登录查看信息的账号密码.
        servletRegistrationBean.addInitParameter("loginUsername","admin");
        servletRegistrationBean.addInitParameter("loginPassword","123456");
        //是否能够重置数据.
        servletRegistrationBean.addInitParameter("resetEnable","false");
        return servletRegistrationBean;
    }

    /**
     * 注册一个:filterRegistrationBean
     * @return
     */
    @Bean
    public FilterRegistrationBean druidStatFilter(){

        FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());

        //添加过滤规则.
        filterRegistrationBean.addUrlPatterns("/*");

        //添加不需要忽略的格式信息.
        filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
        return filterRegistrationBean;
    }
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package cn.chinotan.config;

import cn.chinotan.interceptor.ShareStatementPlugin;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.core.MybatisXMLLanguageDriver;
import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
import com.baomidou.mybatisplus.extension.plugins.OptimisticLockerInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.plugins.PerformanceInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.mapper.MapperScannerConfigurer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.ResourceLoader;

import javax.sql.DataSource;

/**
 * @program: test
 * @description:
 * @author: xingcheng
 * @create: 2019-02-16 20:39
 **/
@Configuration
public class MybatisPlusConfig {

    @Bean
    public MapperScannerConfigurer mapperScannerConfigurer() {
        MapperScannerConfigurer scannerConfigurer = new MapperScannerConfigurer();
        //可以通过环境变量获取你的mapper路径,这样mapper扫描可以通过配置文件配置了
        scannerConfigurer.setBasePackage("cn.chinotan.dao");
        return scannerConfigurer;
    }

    @Bean("mybatisSqlSession")
    public SqlSessionFactory sqlSessionFactory(DataSource dataSource, ResourceLoader resourceLoader) throws Exception {
        MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(dataSource);
        sqlSessionFactory.setTypeAliasesPackage("cn.chinotan.entity");
        MybatisConfiguration configuration = new MybatisConfiguration();
        configuration.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class);
        configuration.setJdbcTypeForNull(JdbcType.NULL);
        sqlSessionFactory.setConfiguration(configuration);
        sqlSessionFactory.setPlugins(new Interceptor[]{
                new PaginationInterceptor(),
                new PerformanceInterceptor(),
                new OptimisticLockerInterceptor()
        });
        return sqlSessionFactory.getObject();
    }

    /***
     * plus 的性能优化
     * @return
     */
    @Bean
    public PerformanceInterceptor performanceInterceptor() {
        PerformanceInterceptor performanceInterceptor = new PerformanceInterceptor();
        /*<!-- SQL 执行性能分析,开发环境使用,线上不推荐。 maxTime 指的是 sql 最大执行时长 -->*/
        performanceInterceptor.setMaxTime(500000000);
        /*<!--SQL是否格式化 默认false-->*/
        performanceInterceptor.setFormat(true);
        return performanceInterceptor;
    }

    /**
     * @Description : mybatis-plus分页插件
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        return new PaginationInterceptor();
    }

    /**
     * @Description : mybatis插件
     */
    @Bean
    public ShareStatementPlugin examplePlugin(SqlSessionFactory sqlSessionFactory) {
        ShareStatementPlugin plugin = new ShareStatementPlugin();
        sqlSessionFactory.getConfiguration().addInterceptor(plugin);
        return plugin;
    }

}

    自动文件生成:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package cn.chinotan.codeGenerator;

import com.baomidou.mybatisplus.annotation.DbType;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.po.TableInfo;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
import org.apache.commons.lang3.StringUtils;

import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;

/**
 * @program: test
 * @description: 代码生成
 * @author: xingcheng
 * @create: 2019-02-16 19:52
 **/
public class CodeGenerator {

    /**
     *
     * @Title: main
     * @Description: 生成
     * @param args
     */
    public static void main(String[] args) {
        AutoGenerator mpg = new AutoGenerator();

        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        gc.setOutputDir("/Users/xingcheng/Documents/IdeaProjects/test/start_test/out"); //输出文件路径
        gc.setFileOverride(true);
        gc.setActiveRecord(false);// 不需要ActiveRecord特性的请改为false
        gc.setEnableCache(false);// XML 二级缓存
        gc.setBaseResultMap(true);// XML ResultMap
        gc.setBaseColumnList(false);// XML columList
        gc.setAuthor("xingcheng");// 作者

        // 自定义文件命名,注意 %s 会自动填充表实体属性!
        gc.setControllerName("%sController");
        gc.setServiceName("%sService");
        gc.setServiceImplName("%sServiceImpl");
        gc.setMapperName("%sMapper");
        gc.setXmlName("%sMapper");
        mpg.setGlobalConfig(gc);

        // 数据源配置
        DataSourceConfig dsc = new DataSourceConfig();
        dsc.setDbType(DbType.MYSQL);
        dsc.setDriverName("com.mysql.cj.jdbc.Driver");
        dsc.setUsername("root");
        dsc.setPassword("");
        dsc.setUrl("jdbc:mysql://localhost:3306/chinotan?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true");
        mpg.setDataSource(dsc);

        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        // strategy.setTablePrefix(new String[] { "sys_" });// 此处可以修改为您的表前缀
        strategy.setNaming(NamingStrategy.underline_to_camel);// 表名生成策略
        strategy.setInclude(new String[] { "user", "user_bak" }); // 需要生成的表

        strategy.setSuperServiceClass(null);
        strategy.setSuperServiceImplClass(null);
        strategy.setSuperMapperClass(null);

        mpg.setStrategy(strategy);

        // 包配置
        PackageConfig pc = new PackageConfig();
        pc.setParent("cn.chinotan");
        pc.setController("controller");
        pc.setService("service");
        pc.setServiceImpl("service.impl");
        pc.setMapper("dao");
        pc.setEntity("entity");
        pc.setXml("xml");
        mpg.setPackageInfo(pc);

        // 执行生成
        mpg.execute();

    }

}

    数据实体:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package cn.chinotan.entity;

import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;

import java.time.LocalDateTime;
import java.io.Serializable;

/**
 * <p>
 * 用户表
 * </p>
 *
 * @author xingcheng
 * @since 2019-02-16
 */
public class User {

    /**
     * 主键
     */
    @TableId(value = "id", type = IdType.AUTO)
    private Long id;

    /**
     * 姓名
     */
    private String name;

    /**
     * 创建时间
     */
    private LocalDateTime createTime;

    /**
     * 更新时间
     */
    private LocalDateTime updateTime;


    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public LocalDateTime getCreateTime() {
        return createTime;
    }

    public void setCreateTime(LocalDateTime createTime) {
        this.createTime = createTime;
    }

    public LocalDateTime getUpdateTime() {
        return updateTime;
    }

    public void setUpdateTime(LocalDateTime updateTime) {
        this.updateTime = updateTime;
    }

    @Override
    public String toString() {
        return "User{" +
        "id=" + id +
        ", name=" + name +
        ", createTime=" + createTime +
        ", updateTime=" + updateTime +
        "}";
    }
}

    建立一个注解,用来配置路由策略    

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package cn.chinotan.aop;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @program: test
 * @description: 路由策略
 * @author: xingcheng
 * @create: 2019-02-23 16:44
 **/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE})
public @interface TableConfig {
    //是否影子表
    boolean split() default true;

    //表名
    String value() default "";

    //获取分表策略
    String strategy();
}

    mapper接口:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package cn.chinotan.dao;

import cn.chinotan.aop.TableConfig;
import cn.chinotan.entity.User;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;

/**
 * <p>
 * 用户表 Mapper 接口
 * </p>
 *
 * @author xingcheng
 * @since 2019-02-16
 */
@TableConfig(strategy="bak")
public interface UserMapper extends BaseMapper<User> {
    
}

    mapperXML路径:

    Controller层控制:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package cn.chinotan.controller;


import cn.chinotan.entity.User;
import cn.chinotan.service.UserService;
import com.baomidou.mybatisplus.core.conditions.update.UpdateWrapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.time.LocalDateTime;
import java.util.Date;
import java.util.List;

/**
 * <p>
 * 用户表 前端控制器
 * </p>
 *
 * @author xingcheng
 * @since 2019-02-16
 */
@RestController
@RequestMapping("/user")
public class UserController {

    @Autowired
    UserService userService;


    @GetMapping("/list")
    public Object list() {
        List<User> list = userService.list();
        return list;
    }

    @GetMapping("/save/{name}")
    public Object save(@PathVariable("name") String name) {
        User user = new User();
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        user.setName(name);
        boolean save = userService.save(user);
        return save;
    }

    @GetMapping("/update/{name}")
    public Object update(@PathVariable("name") String name) {
        User user = new User();
        user.setId(1L);
        user.setCreateTime(LocalDateTime.now());
        user.setUpdateTime(LocalDateTime.now());
        user.setName(name);
        boolean update = userService.updateById(user);
        return update;
    }
}

    路由策略接口和实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package cn.chinotan.service;

/**
 * @program: test
 * @description:
 * @author: xingcheng
 * @create: 2019-02-23 17:52
 **/
public interface Strategy {
    /**
     * 传入一个需要分表的表名,返回一个处理后的表名 
     * Strategy必须包含一个无参构造器
     * @param tableName
     * @return
     */
    String convert(String tableName);
}
代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package cn.chinotan.service.impl;

import cn.chinotan.service.Strategy;
import org.springframework.stereotype.Component;

import java.text.SimpleDateFormat;
import java.util.Date;

/**
 * @program: test
 * @description:
 * @author: xingcheng
 * @create: 2019-02-23 17:52
 **/
@Component("bak")
public class BakStrategy implements Strategy {

    @Override
    public String convert(String tableName) {
        StringBuilder sb=new StringBuilder(tableName);
        sb.append("_bak");
        return sb.toString();
    }
}

    接下来最重要的Interceptor实现:

代码语言:javascript
代码运行次数:0
运行
AI代码解释
复制
package cn.chinotan.interceptor;

import cn.chinotan.aop.TableConfig;
import cn.chinotan.service.Strategy;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.reflection.MetaObject;
import org.apache.ibatis.reflection.SystemMetaObject;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Proxy;
import java.lang.reflect.Type;
import java.sql.Connection;
import java.util.Map;
import java.util.Properties;

/**
 * 完成插件签名:
 * 告诉MyBatis当前插件用来拦截哪个对象的哪个方法
 * type  指四大对象拦截哪个对象,
 * method : 代表拦截哪个方法  ,在StatementHandler 中查看,需要拦截的方法
 * args  :代表参数
 */
@Component
@Intercepts({
        @Signature(type = StatementHandler.class, method = "prepare", args = {
                Connection.class, Integer.class})})
public class ShareStatementPlugin implements Interceptor {

    private static final Logger LOG = LoggerFactory.getLogger(ShareStatementPlugin.class);

    @Autowired
    private Map<String, Strategy> strategyMap;

    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        StatementHandler statementHandler = realTarget(invocation.getTarget());
        MetaObject metaObject = SystemMetaObject.forObject(statementHandler);
        doTable(statementHandler, metaObject);
        return invocation.proceed();
    }

    private void doTable(StatementHandler handler, MetaObject metaStatementHandler) throws ClassNotFoundException {
        BoundSql boundSql = handler.getBoundSql();
        String originalSql = boundSql.getSql();

        if (originalSql != null && !originalSql.equals("")) {
            LOG.info("分表前的SQL:{}", originalSql);
            MappedStatement mappedStatement = (MappedStatement) metaStatementHandler
                    .getValue("delegate.mappedStatement");
            String id = mappedStatement.getId();
            String className = id.substring(0, id.lastIndexOf("."));
            Class<?> classObj = Class.forName(className);
            Class baseEntity = null;
            Type[] interfacesTypes = classObj.getGenericInterfaces();
            for (Type type : interfacesTypes) {
                if (type instanceof ParameterizedType) {
                    ParameterizedType interfacesType = (ParameterizedType) interfacesTypes[0];
                    Type t = interfacesType.getActualTypeArguments()[0];
                    baseEntity = (Class) t;
                }
            }
            // 根据配置自动生成分表SQL
            TableConfig tableConfig = classObj.getAnnotation(TableConfig.class);
            // 获取表名 并进行相应转化
            String tableName = baseEntity.getSimpleName().toLowerCase();
            if (StringUtils.isNotBlank(tableConfig.value())) {
                tableName = tableConfig.value();
            }

            if (tableConfig != null && tableConfig.split()) {
                // 获取策略来处理
                Strategy strategy = strategyMap.get(tableConfig.strategy());
                String convertedSql = originalSql.replaceAll(tableName, strategy.convert(tableName));
                metaStatementHandler.setValue("delegate.boundSql.sql", convertedSql);
                LOG.info("分表后的SQL:{}", convertedSql);
            }
        }
    }

    @Override
    public Object plugin(Object target) {
        if (target instanceof StatementHandler) {
            return Plugin.wrap(target, this);
        }
        return target;
    }

    @Override
    public void setProperties(Properties properties) {
    }

    /**
     * 获得真正的处理对象,可能多层代理
     *
     * @param target
     * @param <T>
     * @return
     */
    public static <T> T realTarget(Object target) {
        if (Proxy.isProxyClass(target.getClass())) {
            MetaObject metaObject = SystemMetaObject.forObject(target);
            return realTarget(metaObject.getValue("h.target"));
        }
        return (T) target;
    }
}

    效果:变更前两张表数据

执行save接口后:

可以看到只查询影子表,简单效果实现

下一步优化内容:

能够根据控制层传输过来的是否采用影子表标识来动态的进行影子表的读取和写入,而不是写死在代码中

(adsbygoogle = window.adsbygoogle || []).push({});

本文参与 腾讯云自媒体同步曝光计划,分享自作者个人站点/博客。
如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 作者个人站点/博客 前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
1 条评论
热度
最新
作者 您好请问 2、镜像制作net use z: \\服务端IP\share "123" / 我输入回车后显示找不到网络路径 然后在请问net use z:中的这个z: 是什么作用
作者 您好请问 2、镜像制作net use z: \\服务端IP\share "123" / 我输入回车后显示找不到网络路径 然后在请问net use z:中的这个z: 是什么作用
回复回复点赞举报
推荐阅读
harbor搭建详解(仓库阁楼搭建效果图)
Docker容器应用的开发和运行离不开可靠的镜像管理,虽然Docker官方也提供了公共的镜像仓库,但是从安全和效率等方面考虑,部署私有环境内的Registry也是非常必要的。Harbor是由VMware公司开源的企业级的Docker Registry管理项目,它包括权限管理(RBAC)、LDAP、日志审核、管理界面、自我注册、镜像复制和中文支持等功能
全栈程序员站长
2022/08/02
6450
harbor搭建详解(仓库阁楼搭建效果图)
基于gitlab ci构建devops平台
devops的概念很多,理解也很多。我的理解,它属于软件工程范畴。它定义了一种理念,基于这种理念,能够快速的开发,交付软件及成果物。各个团队直接在这个体系中,高效的沟通,协作等。
暮雨
2018/10/11
4.7K2
基于gitlab ci构建devops平台
企业级Docker镜像仓库Harbor部署与使用
在实际生产运维中,往往需要把镜像发布到几十、上百台或更多的节点上。这时单台Docker主机上镜像已无法满足,项目越来越多,镜像就越来越多,都放到一台Docker主机上是不行的,我们需要一个像Git仓库一样系统来统一管理镜像。这里介绍的是一个企业级镜像仓库Harbor,将作为我们容器云平台的镜像仓库中心。
星哥玩云
2022/07/28
8840
企业级Docker镜像仓库Harbor部署与使用
Docker镜像仓库Harbor之搭建及配置
哎_小羊
2018/01/02
6.6K0
Docker镜像仓库Harbor之搭建及配置
企业实战(6)修改Harbor镜像仓库默认存储路径
Docker与Docker Engine部署:https://blog.csdn.net/qq_44895681/article/details/105540702
非著名运维
2022/06/22
1.2K0
企业实战(6)修改Harbor镜像仓库默认存储路径
搭建 Harbor 私有镜像仓库
什么是 Harbor? ---- harbor 是 VMware 公司开源的企业级 DockerRegistry 项目,项目地址为 https://github.com/vmware/harbor。其目标是帮助用户迅速搭建一个企业级的 Docker registry 服务。它以 Docker 公司开源的 registry 为基础,提供了管理UI,基于角色的访问控制(Role Based Access Control),AD/LDAP集成、以及审计日志(Auditlogging) 等企业用户需求的功能,同时还
keepyan
2018/12/13
1.8K0
搭建 Harbor 私有镜像仓库
这就是你日日夜夜想要的docker!!!---------Harbor私有仓库
是多个容器同时跑起来的服务 所以必须要装docker compose Harbor是VMware公司的开源级的企业级DockerRegistry(仓库)项目,项目地址为 https://github.com/vmware/harbor. Harbor的目标是帮助用户迅速搭建一个企业级的DockerRegistry服务。 Harbor以docker公司开源的registry为基础,提供了管理UI,基于角色的访问控制(Role Based Access Control),AD/LDAP集成,以及审计日志(Auditlogging)等企业用户需求的功能,同时还原生支持中文。 Harbor的每个组件都是以Docker容器的形式构建的,使用docker-compose来对它进行部署。用于部署Harbor的docker-compose模板位于/usr/local/bin/harbor/docker-compose.yml(自定义)
不吃小白菜
2020/09/25
1.4K0
这就是你日日夜夜想要的docker!!!---------Harbor私有仓库
docker-企业级镜像仓库harbor
 Habor是由VMWare公司开源的容器镜像仓库。事实上,Habor是在Docker Registry上进行了相应的企业级扩展,从而获得了更加广泛的应用,这些新的企业级特性包括:管理用户界面,基于角色的访问控制,AD/LDAP集成以及审计日志等,足以满足基本企业需求。
yuezhimi
2020/09/30
5730
docker-企业级镜像仓库harbor
基于 Distribution / Harbor 部署 Docker 私有镜像仓库
Docker 在 2015 年推出了 Distribution 项目,即 Docker Registry 2。相比于 Old Registry,Registry 2 使用 Go 实现,在安全性、性能方面均有大幅改进。Registry 设计了全新的 Rest API,并且在 Image 存储格式等方面不再兼容于 Old Registry。如果你要与Registry2 交互,你的 Docker 版本至少要是 Docker 1.6。
iMike
2019/06/02
2.9K0
docker基础:私库系列:再探Harbor:(4) https方式的私库管理
在前面的介绍中,缺省使用了http的方式,而考虑安全的角度,容器的仓库在生产环境中往往被设定为https的方式,而harbor将这些证书的创建和设定都进行了简单的集成,这篇文章来看一下在harbor下如何使用https的方式。
全栈程序员站长
2022/09/09
3100
企业级Docker Registry开源工具Harbor的介绍以及使用指南
#Harbor 的简介以及基本架构 Harbor简介 基本架构 Harbor安装和配置指导 Harbor 可以使用以下三种方式进行安装部署: 在线安装: 使用者可以直接从docker hub上下载harbor的官方镜像。 离线安装: 使用者需要下载源码包,并进行自己构建images。源码包比较大 Virtual Appliance: 一般使用这种方式在第三方平台搭建一个私有仓库作为平台的组建比如vsphere等,需要下载OVA 版本的Harbor. 官方下载页面 请点击我 . 需要安装Ha
BGBiao
2018/02/26
8710
第一章 Docker入门基础
MySQL使用过程中的环境变量 Num|Env Variable| Description —-|—-|—- 1|MYSQL_ROOT_PASSWORD|root用户的密码 2|MYSQL_DATABASE|创建一个数据库 3|MYSQL_USER,MYSQL_PASSWORD|创建一个用户以及用户密码 4|MYSQL_ALLOW_EMPTY_PASSWORD|允许空密码
公众号: 云原生生态圈
2021/11/15
6820
Docker 私有仓库搭建
在 Docker 中,当我们执行 docker pull xxx 的时候 ,它实际上是从 registry.hub.docker.com 这个地址去查找,这就是Docker公司为我们提供的公共仓库。在工作中,我们不可能把企业项目push到公有仓库进行管理。所以为了更好的管理镜像,Docker不仅提供了一个中央仓库,同时也允许我们搭建本地私有仓库。这一篇介绍registry、harbor两种私有仓库搭建。
程序员果果
2019/05/28
1.9K0
[原创]harbor自建网桥和办公网段冲突一例
https://github.com/vmware/harbor/issues/1403
追马
2020/07/02
1.4K0
搭建个私有docker镜像仓库
但是这个私有仓不能满足我们的需求,生产线上万一该私有仓服务器故障,其他服务器也无法接管。再者,也没有页面可以便于管理。
我的小碗汤
2018/10/18
2.9K0
搭建个私有docker镜像仓库
Docker login Harbor报错解决:Error response from daemon: Get https:..
 docker-compose down -v:停止并移除整个project的所有services
非著名运维
2022/06/22
4.2K0
手把手带你部署Docker私有镜像仓库Harbor v2.3.2
 Harbor是一个用于存储和分发Docker镜像的企业级Registry服务器,通过添加一些企业必需的功能特性,例如安全、标识和管理等,扩展了开源Docker Distribution。作为一个企业级私有Registry服务器,Harbor提供了更好的性能和安全。提升用户使用Registry构建和运行环境传输镜像的效率。Harbor支持安装在多个Registry节点的镜像资源复制,镜像全部保存在私有Registry中, 确保数据和知识产权在公司内部网络中管控。另外,Harbor也提供了高级的安全特性,诸如用户管理,访问控制和活动审计等。
非著名运维
2022/06/22
1.4K0
手把手带你部署Docker私有镜像仓库Harbor v2.3.2
企业级Docker私有仓库之Harbor部署(http)
部署环境 Centos7.3 x64 docker-ce-17.06.0 docker-compose-1.15.0 Python-2.7.5(系统默认) Docker及Docker-compose安装 yum install -y yum-utils device-mapper-persistent-data lvm2 yum-config-manager \ --add-repo \ https://download.docker.com/linux/centos/docker-ce
程序员同行者
2018/06/22
1K0
性能环境之docker操作指南7(全网最全)
Harbor 的所有组件都在 Dcoker 中部署,所以 Harbor 可使用 Docker Compose 快速部署。 harbor共有六个容器组成:
高楼Zee
2019/07/17
5930
性能环境之docker操作指南7(全网最全)
DockerHub访问慢怎么破?自建个企业级镜像仓库试试!
Harbor是一款开源的Docker镜像仓库服务,在Github上目前有13.4k+Star。提供了基于角色的镜像访问机制,可以保护你的镜像安全。
macrozheng
2020/12/21
1.3K0
DockerHub访问慢怎么破?自建个企业级镜像仓库试试!
推荐阅读
相关推荐
harbor搭建详解(仓库阁楼搭建效果图)
更多 >
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档