最近面試常常被問到關於數據庫的事務的問題,可能平時我就知道加個註解@Transactional以後就一臉懵逼的。如今發現這一塊真的是經常被忽略了,然而面試官就是最喜歡這種看是不經常使用,可是很是重要的問題,進而達到出其不意趁火打劫。不吹水了,開始正文。css
此方案主要是經過環繞切面的方式將mapper包下的接口方法,而後先後計算時間差便可。這就是典型的AOP知識,不過這種計算比較粗糙,可是也是個辦法。具體方法以下:java
@Aspect
@Component
@Slf4j
public class MapperAspect {
@AfterReturning("execution(* cn.xbmchina.mybatissqltime.mapper.*Mapper.*(..))")
public void logServiceAccess(JoinPoint joinPoint) {
log.info("Completed: " + joinPoint);
}
/** * 監控cn.xbmchina.mybatissqltime.mapper..*Mapper包及其子包的全部public方法 */
@Pointcut("execution(* cn.xbmchina.mybatissqltime.mapper.*Mapper.*(..))")
private void pointCutMethod() {
}
/** * 聲明環繞通知 * * @param pjp * @return * @throws Throwable */
@Around("pointCutMethod()")
public Object doAround(ProceedingJoinPoint pjp) throws Throwable {
long begin = System.nanoTime();
Object obj = pjp.proceed();
long end = System.nanoTime();
log.info("調用Mapper方法:{},參數:{},執行耗時:{}納秒,耗時:{}毫秒",
pjp.getSignature().toString(), Arrays.toString(pjp.getArgs()),
(end - begin), (end - begin) / 1000000);
return obj;
}
}
複製代碼
MyBatis在四大對象的建立過程當中,都會有插件進行介入。插件能夠利用動態代理機制一層層的包裝目標對象,而實如今目標對象執行目標方法以前進行攔截的效果。 MyBatis 容許在已映射語句執行過程當中的某一點進行攔截調用。 默認狀況下,MyBatis 容許使用插件來攔截的方法調用包括: ①Executor(update, query, flushStatements, commit, rollback, getTransaction, close, isClosed) ②ParameterHandler(getParameterObject, setParameters) ③ResultSetHandler(handleResultSets, handleOutputParameters) ④StatementHandler(prepare, parameterize, batch, update, query)mysql
下面是代碼:web
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.mapping.BoundSql;
import org.apache.ibatis.mapping.ParameterMapping;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.plugin.Intercepts;
import org.apache.ibatis.plugin.Invocation;
import org.apache.ibatis.plugin.Plugin;
import org.apache.ibatis.plugin.Signature;
import org.apache.ibatis.session.ResultHandler;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.stereotype.Component;
import java.sql.Statement;
import java.util.List;
import java.util.Properties;
/** * Sql執行時間記錄攔截器 * * @author zero * 2019年12月13日17:05:28 */
@Intercepts({@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),
@Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
@Signature(type = StatementHandler.class, method = "batch", args = {Statement.class})})
@Component
public class SqlExecuteTimeCountInterceptor implements Interceptor {
private static Logger logger = LoggerFactory.getLogger(SqlExecuteTimeCountInterceptor.class);
/** * 打印的參數字符串的最大長度 */
private final static int MAX_PARAM_LENGTH = 50;
/** * 記錄的最大SQL長度 */
private final static int MAX_SQL_LENGTH = 200;
@Override
public Object intercept(Invocation invocation) throws Throwable {
Object target = invocation.getTarget();
long startTime = System.currentTimeMillis();
StatementHandler statementHandler = (StatementHandler) target;
try {
return invocation.proceed();
} finally {
long endTime = System.currentTimeMillis();
long timeCount = endTime - startTime;
BoundSql boundSql = statementHandler.getBoundSql();
String sql = boundSql.getSql();
Object parameterObject = boundSql.getParameterObject();
List<ParameterMapping> parameterMappingList = boundSql.getParameterMappings();
// 格式化Sql語句,去除換行符,替換參數
sql = formatSQL(sql, parameterObject, parameterMappingList);
logger.info("執行 SQL:[ , {} ]執行耗時[ {} ms]", sql, timeCount);
}
}
/** * 格式化/美化 SQL語句 * * @param sql sql 語句 * @param parameterObject 參數的Map * @param parameterMappingList 參數的List * @return 格式化以後的SQL */
private String formatSQL(String sql, Object parameterObject, List<ParameterMapping> parameterMappingList) {
// 輸入sql字符串空判斷
if (sql == null || sql.length() == 0) {
return "";
}
// 美化sql
sql = beautifySql(sql);
// 不傳參數的場景,直接把sql美化一下返回出去
if (parameterObject == null || parameterMappingList == null || parameterMappingList.size() == 0) {
return sql;
}
return LimitSQLLength(sql);
}
/** * 返回限制長度以後的SQL語句 * * * @param sql 原始SQL語句 */
private String LimitSQLLength(String sql) {
if (sql == null || sql.length() == 0) {
return "";
}
if (sql.length() > MAX_SQL_LENGTH) {
return sql.substring(0, MAX_SQL_LENGTH);
} else {
return sql;
}
}
@Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
@Override
public void setProperties(Properties properties) {
}
/** * 替換SQL 中? 所對應的值, 只保留前50個字符 * * @param sql sql語句 * @param valueOf ?對應的值 */
private String replaceValue(String sql, String valueOf) {
//超過50個字符只取前50個
if (valueOf != null && valueOf.length() > MAX_PARAM_LENGTH) {
valueOf = valueOf.substring(0, MAX_PARAM_LENGTH);
}
sql = sql.replaceFirst("\\?", valueOf);
return sql;
}
/** * 美化sql * * @param sql sql語句 */
private String beautifySql(String sql) {
sql = sql.replaceAll("[\\s\n ]+", " ");
return sql;
}
}
複製代碼
這種就是咱們平時用的最多的,可是面試的話說一下就得了,估計也沒有怎麼好問的了。面試
Springboot+druid的配置application.yml文件以下:spring
spring:
datasource:
url: jdbc:mysql://localhost:3306/testdb1?characterEncoding=utf-8&useUnicode=true&useSSL=false&serverTimezone=UTC
driver-class-name: com.mysql.jdbc.Driver # mysql8.0之前使用com.mysql.jdbc.Driver
username: root
password: root
platform: mysql
#經過這句配置將druid鏈接池引入到咱們的配置中,spring會盡量判斷類型是什麼,而後根據狀況去匹配驅動類。
type: com.alibaba.druid.pool.DruidDataSource
druid:
initial-size: 5 # 初始化大小
min-idle: 5 # 最小
max-active: 100 # 最大
max-wait: 60000 # 配置獲取鏈接等待超時的時間
time-between-eviction-runs-millis: 60000 # 配置間隔多久才進行一次檢測,檢測須要關閉的空閒鏈接,單位是毫秒
min-evictable-idle-time-millis: 300000 # 指定一個空閒鏈接最少空閒多久後可被清除,單位是毫秒
validationQuery: select 'x'
test-while-idle: true # 當鏈接空閒時,是否執行鏈接測試
test-on-borrow: false # 當從鏈接池借用鏈接時,是否測試該鏈接
test-on-return: false # 在鏈接歸還到鏈接池時是否測試該鏈接
filters: config,wall,stat # 配置監控統計攔截的filters,去掉後監控界面sql沒法統計,'wall'用於防火牆
poolPreparedStatements: true # 打開PSCache,而且指定每一個鏈接上PSCache的大小
maxPoolPreparedStatementPerConnectionSize: 20
maxOpenPreparedStatements: 20
# 經過connectProperties屬性來打開mergeSql功能;慢SQL記錄
connectionProperties: druid.stat.slowSqlMillis=200;druid.stat.logSlowSql=true;config.decrypt=false
# 合併多個DruidDataSource的監控數據
#use-global-data-source-stat: true
#WebStatFilter配置,說明請參考Druid Wiki,配置_配置WebStatFilter
web-stat-filter:
enabled: true #是否啓用StatFilter默認值true
url-pattern: /*
exclusions: /druid/*,*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico
session-stat-enable: true
session-stat-max-count: 10
#StatViewServlet配置,說明請參考Druid Wiki,配置_StatViewServlet配置
stat-view-servlet:
enabled: true #是否啓用StatViewServlet默認值true
url-pattern: /druid/*
reset-enable: true
login-username: admin
login-password: admin
複製代碼
你們若是還有什麼好的建議,歡迎留言告訴我哈。道路艱難且長,還請各位多多提點。sql