Mybatis通过Interceptor来简单实现影⼦表进⾏动态sql读取和
写⼊
⾸先进⾏Mybatis 介绍
的⼀个作⽤就是我们可以拦截某些⽅法的调⽤,我们可以选择在这些被拦截的⽅法执⾏前后加上某些逻辑,也可以在执⾏这些被拦截的⽅法时执⾏⾃⼰的逻辑⽽不再执⾏被拦截的⽅法。Mybatis设计的⼀个初衷就是为了供⽤户在某些时候可以实现⾃⼰的逻辑⽽不必去动Mybatis固有的逻辑。打个⽐⽅,对于Executor,Mybatis中有⼏种实现:BatchExecutor、ReuseExecutor、SimpleExecutor 和CachingExecutor。这个时候如果你觉得这⼏种实现对于Executor接⼝的query⽅法都不能满⾜你的要求,那怎么办呢?是要去改源码吗?当然不。我们可以建⽴⼀个Mybatis⽤于拦截Executor接⼝的query⽅法,在拦截之后实现⾃⼰的query⽅法逻辑,之后可以选择是否继续执⾏原来的query⽅法。
对于Mybatis为我们提供了⼀个Interceptor接⼝,通过实现该接⼝就可以定义我们⾃⼰的。我们先来看⼀下这个接⼝的定义:
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⽂件如下:
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.12</version>
</dependency>
<dependency>
<groupId&batis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
<exclusions>
<exclusion>
<groupId&batis</groupId>
<artifactId>mybatis</artifactId>
</exclusion>
<exclusion>
<groupId&batis</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&batis.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配置如下:
>>>>>#  mysql  >>>>>#
>>>>>#  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: "sql.cj.jdbc.Driver"
logging.level.shyroke.mapper: debug
>>>>>#  mybatis  >>>>>#
mybatis.mapper-locations: classpath:mybatis/*.xml
>>>>>#  druid配置  >>>>>#
pe: com.alibaba.druid.pool.DruidDataSource
# 初始化⼤⼩,最⼩,最⼤
spring.datasource.initialSize: 5
spring.datasource.minIdle: 5
spring.datasource.maxActive: 20
# 配置获取连接等待超时的时间
spring.datasource.maxWait: 60000
# 配置间隔多久才进⾏⼀次检测,检测需要关闭的空闲连接,单位是毫秒sql语句替换表中内容
spring.datasource.timeBetweenEvictionRunsMillis: 60000
# 配置⼀个连接在池中最⼩⽣存的时间,单位是毫秒
spring.datasource.minEvictableIdleTimeMillis: 300000
# 校验SQL,Oracle配置 spring.datasource.validationQuery: SELECT 1 FROM DUAL,如果不配validationQuery项,则下⾯三项配置⽆⽤
spring.datasource.validationQuery: SELECT 'x'
stWhileIdle: true
stOnBorrow: false
stOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的⼤⼩
spring.datasource.poolPreparedStatements: true
spring.datasource.maxPoolPreparedStatementPerConnectionSize: 20
# 配置监控统计拦截的filters,去掉后监控界⾯sql⽆法统计,'wall'⽤于防⽕墙
spring.datasource.filters: stat,wall,log4j
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
tionProperties: Sql=true;druid.stat.slowSqlMillis=5000
# 合并多个DruidDataSource的监控数据
spring.datasource.useGlobalDataSourceStat: true
# 配置mybatis-plus
mybatis-plus:
mapper-locations: classpath:/mapper/*.xml
#实体扫描,多个package⽤逗号或者分号分隔
typeAliasesPackage: ity
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.
#逻辑删除配置(下⾯3个配置)
logic-delete-value: 0
logic-not-delete-value: 1
#⾃定义SQL注⼊器
#sql-injector: batisplus.mapper.LogicSqlInjector
#⾃定义填充策略接⼝实现
#meta-object-handler: com.
configuration:
configuration:
map-underscore-to-camel-case: true
cache-enabled: false
Druid和mybatis的配置⽂件:
package fig;
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.t.properties.ConfigurationProperties; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletRegistrationBean; import t.annotation.Bean;
import t.annotation.Configuration;
import t.annotation.Primary;
import t.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 = "l")
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("${stWhileIdle}")
private boolean testWhileIdle;
@Value("${stOnBorrow}")
private boolean testOnBorrow;
@Value("${stOnReturn}")
private boolean testOnReturn;
@Value("${spring.datasource.poolPreparedStatements}")
private boolean poolPreparedStatements;

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。

发表评论