一、项目重点有:
(1)、SpringBoot+Mybatis+Mysql+Durid整合
(2)、错误后跳转到指定页面
(3)、多数据源动态切换
(4)、mybatis分页
(5)、durid监控
(6)、集成log4j2日志
(7)、通过mybatis拦截器,在控制台打印完整的sql
二、项目截图:
三、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);
}
}
说明:
- 注入多个数据源
- 声明动态数据源,声明动态数据源和目标数据源(多个用于切换的数据源)
- 声明一些插件,比如分页插件和打印全文sql的插件
- 声明sql会话
- 声明事务管理器
- 声明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动态多数据源项目搭建的主要代码和过程。