1、 Mybatis的工作流程
- 1、读取配置文件,根据XML配置文件创建Conifuration
- 2、根据配置信息 创建SqlSessionFactory,SqlSessionFactory的生命周期是程序级,程序运行的时候建立起来,程序结束的时候消亡
- 3、SqlSessionFactory 创建 SqlSession,SqlSession是过程级,一个方法开始时建立,方法结束应该关闭
- 4、获取mapper接口的代理对象(DefaultSqlSession.getMapper()拿到Mapper接口对应的MapperProxy)
- 5、执行增删改查方法
5.1 调用DefaultSqlSession的增删改查(Executor);
5.2 会创建一个StatementHandler对象(同时也会创建出ParameterHandler和ResultSetHandler);
5.3 调用StatementHandler预编译参数以及设置参数值(使用ParameterHandler来给sql设置参数);
5.4 调用StatementHandler的增删改查方法;
5.5 ResultSetHandler封装结果;
2、Mybatis 核心对象
- Configuration: MyBatis所有的配置信息都维持在Configuration对象之中
- SqlSession: 负责和数据库进行交互,实现完成常对数据操作的增删改查功能
- Executor: Mybatis执行器,调度的核心,负责SQL语句的生成和维护
- StatementHandler :封装并操作Jdbc statement,例如设置参数,将statement结果集转化为List集合
- ParameterHandler: 负责将用户传递的参数转换为Jdbc statement所需要的参数进行传递
- ResultSetHandler: 负责将Jdbc返回的ResultSet结果集对象转换为List类型的集合
- TypeHandler: 负责将java数据类型和Jdbc数据类型之间的转换和映射
- MappedStatement: MappedStatement维护了一条mapper.xml文件里面 select 、update、delete、insert节点的封装
- BoundSql: 表示动态生成的SQL语句以及相应的参数信息
3、Mybatis拦截器
在很多业务场景下我们需要去拦截sql,达到不入侵原有代码业务处理一些东西,比如:分页操作,数据权限过滤操作,SQL执行时间性能监控等等,Mybatis拦截器设计的思路是为了供用户灵活的实现自己的逻辑,而不动mybatis固有的逻辑。通过Mybatis拦截器我们能拦截某些方法的调用,我们可以选择在这些被拦截方法执行前后加上我们自己的逻辑,达到丰富方法的效果;也可以在执行这些方法的时候拦截住,转而实现自己设计的方法,而最后不执行被拦截的方法。
在mybatis中可被拦截的类型有四种(按照拦截顺序):
- Executor:拦截执行器的方法;
- ParameterHandler:拦截参数的处理;
- ResultHandler:拦截结果集的处理;
- StatementHandler:拦截Sql语法构建的处理;
拦截器一般在业务处理中用于:
- 1、分页查询
- 2、多租户添加条件过滤,即数据权限过滤操作
- 3、对返回结果,过滤掉审计字段,敏感字段(比如手机号)
- 4、对返回结果中的加密数据进行解密
- 5、对新增数据自动添加创建人,创建时间,更新时间,更新人 ,对更新数据自动新增更新时间,更新人
- 6、打印SQL执行日志、执行时间
3.1 分页拦截器
mybatisplus 分页拦截器 PaginationInnerInterceptor
// 使用
Page producePage = new Page<>(1,1);
Page page = produceService.selectList(producePage);
拦截器的核心如下图,当我们的select方法的入参 属于(instanceof) IPage时(如上面的 producePage ),
才会执行分页;或者是当入参为Map时,查询map中是否存在属于(instanceof) IPage 的对象。
优点:通过Page<>泛型,使用简单,性能好;
缺点:如果项目中有很多以Map
对于在Mapper 入参中使用 @Param 的查询方法,也会被拦截到,这会导致性能问题。
github分页拦截器 PageInterceptor
Page
PageHelper 在startPage时,把分页信息写入 ThreadLocal,然后在拦截器中从ThreadLocal获取Page信息,如果存在,则是分页查询;不存在,则是普通查询
优点:不会校验普通查询;
缺点:使用ThreadLocal作为存储,需要先 set,然后get,最后remove;对于高并发接口,会有性能问题。
总结:对比上面两种分页的优缺点,还是使用github分页拦截器(使用PageHelper)比较好,但是对于高并发接口,最好是手动写分页,即手动写 select count(*)
3.2 MetaObjectHandler 自定义字段自动填充处理类
MetaObjectHandler接口是mybatisPlus为我们提供的的一个扩展接口,我们可以利用这个接口在我们插入或者更新数据的时候,为一些字段指定默认值。常见的比如createTm modifyTm creator modifier
@Configuration
public class MyMetaObjectHandler implements MetaObjectHandler {
private static final Logger log = LoggerFactory.getLogger(MyMetaObjectHandler.class);
private static final Integer NOT_DELETED = 0;
public MyMetaObjectHandler() {
}
public void insertFill(MetaObject metaObject) {
String createDate = "createDate";
boolean hasCreateDate = this.hasProperty(metaObject, createDate);
if (hasCreateDate) {
this.setFieldValByName(createDate, new Date(), metaObject);
}
String createTm = "createTm";
boolean hasCreateTm = this.hasProperty(metaObject, createTm);
if (hasCreateTm) {
this.setFieldValByName(createTm, new Date(), metaObject);
}
String updateDate = "updateDate";
boolean hasUpdateDate = this.hasProperty(metaObject, updateDate);
if (hasUpdateDate) {
this.setFieldValByName(updateDate, new Date(), metaObject);
}
String modifyTm = "modifyTm";
boolean hasModifyTm = this.hasProperty(metaObject, modifyTm);
if (hasModifyTm) {
this.setFieldValByName(modifyTm, new Date(), metaObject);
}
String deleteFlag = "deleteFlag";
boolean hasDeleteFlag = this.hasProperty(metaObject, deleteFlag);
if (hasDeleteFlag) {
this.setFieldValByName(deleteFlag, NOT_DELETED, metaObject);
}
String empNum = ContextHoldUtil.getEmpNum();
if (SfStrUtil.isNotBlank(empNum)) {
String createUser = "createUser";
boolean hasCreateUser = this.hasProperty(metaObject, createUser);
if (hasCreateUser) {
this.setFieldValByName(createUser, empNum, metaObject);
}
String creator = "creator";
boolean hasCreator = this.hasProperty(metaObject, creator);
if (hasCreator) {
this.setFieldValByName(creator, empNum, metaObject);
}
String updateUser = "updateUser";
boolean hasUpdateUser = this.hasProperty(metaObject, updateUser);
if (hasUpdateUser) {
this.setFieldValByName(updateUser, empNum, metaObject);
}
String modifier = "modifier";
boolean hasModifier = this.hasProperty(metaObject, modifier);
if (hasModifier) {
this.setFieldValByName(modifier, empNum, metaObject);
}
}
}
public void updateFill(MetaObject metaObject) {
String updateDate = "updateDate";
boolean hasUpdateDate = this.hasProperty(metaObject, updateDate);
if (hasUpdateDate) {
this.setFieldValByName(updateDate, new Date(), metaObject);
}
String modifyTm = "modifyTm";
boolean hasModifyTm = this.hasProperty(metaObject, modifyTm);
if (hasModifyTm) {
this.setFieldValByName(modifyTm, new Date(), metaObject);
}
String empNum = ContextHoldUtil.getEmpNum();
if (SfStrUtil.isNotBlank(empNum)) {
String updateUser = "updateUser";
boolean hasUpdateUser = this.hasProperty(metaObject, updateUser);
if (hasUpdateUser) {
this.setFieldValByName(updateUser, empNum, metaObject);
}
String modifier = "modifier";
boolean hasModifier = this.hasProperty(metaObject, modifier);
if (hasModifier) {
this.setFieldValByName(modifier, empNum, metaObject);
}
}
}
private boolean hasProperty(MetaObject metaObject, String fieldName) {
return metaObject.hasGetter(fieldName) || metaObject.hasGetter("et." + fieldName);
}
}
3.3 1、日志拦截器 —— 用于测试环境,sql美化,sql执行时间
在测试环境,通常我们会配置SQL日志打印,用于调试。
1、原生 org.apache.ibatis.logging.stdout.StdOutImpl
mybatis-plus.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
Creating a new SqlSession
SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@38cd0f91] was not registered for synchronization because synchronization is not active
JDBC Connection [com.alibaba.druid.proxy.jdbc.ConnectionProxyImpl@7182d8bf] will not be managed by Spring
==> Preparing: select g.name as goodsName, g.goods_type as goodsType, g.goods_no as goodsNo, g.current_store as currentStore, gi.img_url as goodsImage, g.point_price as pointPrice, g.discount_price as discountPrice, g.rise_date as exchangeBeginTime, g.original_price as originPrice, g.category_code as categoryCode, g.sub_category_code as subCategoryCode, g.serial_num as serialNum, g.purchase_price as purchasePrice, g.exchange_mem_level as exchangeLevelLimit, g.sale_channel as saleChannel, g.label as label, g.like_amount as likeAmount, g.state as state from mall_goods g left join mall_goods_img gi on g.goods_no = gi.goods_no and gi.img_type = 1 where g.category_code = ? and g.state = 1 and g.mall = 'Point_Mall' and g.if_life_privilege in (0,2) and (g.current_store>0 or g.current_store=-1) order by g.serial_num asc,g.id desc limit ?,?
==> Parameters: XPZQ(String), 1(Integer), 10(Integer)
<== Columns: goodsName, goodsType, goodsNo, currentStore, goodsImage, pointPrice, discountPrice, exchangeBeginTime, originPrice, categoryCode, subCategoryCode, serialNum, purchasePrice, exchangeLevelLimit, saleChannel, label, likeAmount, state
<== Row: 唯品会券对接_0803 , SFSJ, GOODS20220803185335009, 499996, https://fff, 2, 0, 2022-08-03 18:53:37.0, 2000, XPZQ, null, 1, 0, 0, 0, [{"type":3}], 0, 1
<== Row: 井1, SFM, GOODS20220713112243913, 17, https://aaa, 1, 0, 2022-08-03 19:04:20.0, 100, XPZQ, null, 1, 0, 0, 0, [{"type":3}], 4, 1
<== Row: #14, SFM, GOODS20220707181501415, 19, bbb, 10, 0, 2022-07-30 15:17:04.0, 10000, XPZQ, null, 1, 0, 0, 0, [{"type":3}], 1, 1
<== Row: 0601版本券码导入10W张验证, SFW, GOODS20220713181357698, 500000, https:/ccc, 1, 0, 2022-07-29 15:07:13.0, 12, XPZQ, null, 2, 0, 0, 0, [{"type":3}], 0, 1
<== Row: 礼包名称-实物xy, SFM, GOODS20220713075614668, 20, https://ddd, 1, 0, 2022-07-29 15:34:31.0, 100, XPZQ, null, 2, 0, 0, 0, [{"type":3}], 0, 1
<== Row: #15, SFM, GOODS20220705173709700, 14, https://eee, 1, 0, 2022-07-30 11:42:00.0, 10000, XPZQ, null, 3, 0, 0, 0, [{"type":3}], 6, 1
<== Total: 6
Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@38cd0f91]
优点:配置简单
缺点:SQL语句和参数分开,且结果直接打印,不直观
2、mybatisplus 支持p6spy
优点:配置复杂
缺点:SQL语句没有美化
3、手动开发 MybatisLogInterceptor
@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}
), @Signature(
type = Executor.class,
method = "query",
args = {MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class}
)})
@Component
public class MybatisLogInterceptor implements Interceptor {
private static final Logger log = LoggerFactory.getLogger(MybatisLogInterceptor.class);
private Properties properties;
private static final SimpleDateFormat SIMPLE_DATE_FORMAT = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
private static final String DEFAULT_SQL_LOG_SHOW_SWITCH = "false";
@Value("${sqlLogShowSwitch}")
private String sqlLogShowSwitch;
public MybatisLogInterceptor() {
}
public Object intercept(Invocation invocation) throws Throwable {
if (StrUtil.equals(this.sqlLogShowSwitch, "false")) {
return invocation.proceed();
} else {
try {
Object[] args = invocation.getArgs();
MappedStatement mappedStatement = (MappedStatement)args[0];
Object parameter = null;
if (args.length > 1) {
parameter = args[1];
}
String sqlId = mappedStatement.getId();
BoundSql boundSql = null;
if (args.length == 6) {
boundSql = (BoundSql)args[5];
}
if (boundSql == null) {
boundSql = mappedStatement.getBoundSql(parameter);
}
Configuration configuration = mappedStatement.getConfiguration();
long start = System.currentTimeMillis();
Object returnValue = invocation.proceed();
long end = System.currentTimeMillis();
long time = end - start;
String sql = getSql(configuration, boundSql, sqlId, time);
log.info("开始执行sql==> " + sql.split("(\\^ZHIKE\\^)")[0] + "\n" + "执行SQL==>" + (new PrettyFormatterSqlUtil()).getPrettySql(sql.split("(\\^ZHIKE\\^)")[1]) + "\n" + "<==执行总耗时【 " + time + " ms】\n");
String returnStr = "";
if (returnValue != null) {
returnStr = SfJsonUtil.toJsonStr(returnValue);
}
log.info("执行sql结果:{}", returnStr.length() >= 2000 ? returnStr.substring(0, 2000) : returnStr);
return returnValue;
} catch (Throwable var17) {
log.error("执行sql报错!errMsg:{}", var17.getMessage(), var17);
return invocation.proceed();
}
}
}
public Object plugin(Object target) {
return Plugin.wrap(target, this);
}
public void setProperties(Properties properties) {
this.properties = properties;
}
public static String getSql(Configuration configuration, BoundSql boundSql, String sqlId, long time) {
String sql = showSql(configuration, boundSql);
StringBuilder builder = new StringBuilder(100);
builder.append(sqlId);
builder.append("^ZHIKE^");
builder.append(sql);
builder.append("^ZHIKE^");
builder.append(time);
builder.append("ms");
return builder.toString();
}
private static String getParameterValue(Object obj) {
String value;
if (obj instanceof String) {
value = "'" + obj.toString() + "'";
} else if (obj instanceof Date) {
value = "'" + SIMPLE_DATE_FORMAT.format(obj) + "'";
} else if (obj != null) {
value = obj.toString();
} else {
value = "";
}
return value;
}
public static String showSql(Configuration configuration, BoundSql boundSql) {
try {
Object parameterObject = boundSql.getParameterObject();
List 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);
Iterator var7 = parameterMappings.iterator();
while(var7.hasNext()) {
ParameterMapping parameterMapping = (ParameterMapping)var7.next();
String propertyName = parameterMapping.getProperty();
Object obj;
if (metaObject.hasGetter(propertyName)) {
obj = metaObject.getValue(propertyName);
sql = sql.replaceFirst("\\?", getParameterValue(obj));
} else if (boundSql.hasAdditionalParameter(propertyName)) {
obj = boundSql.getAdditionalParameter(propertyName);
sql = sql.replaceFirst("\\?", getParameterValue(obj));
}
}
}
}
return sql;
} catch (Exception var11) {
return "";
}
}
}
SQL美化工具类
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
import java.util.StringTokenizer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class PrettyFormatterSqlUtil {
private static final Logger log = LoggerFactory.getLogger(PrettyFormatterSqlUtil.class);
private static final Set BEGIN_CLAUSES = new HashSet();
private static final Set END_CLAUSES = new HashSet();
private static final Set LOGICAL = new HashSet();
private static final Set QUANTIFIERS = new HashSet();
private static final Set DML = new HashSet();
private static final Set MISC = new HashSet();
public static final String WHITESPACE = " \n\r\f\t";
static final String INDENT_STRING = " ";
static final String INITIAL = "\n ";
public PrettyFormatterSqlUtil() {
}
public String getPrettySql(String source) {
return (new PrettyFormatterSqlUtil.FormatProcess(source)).perform();
}
static {
BEGIN_CLAUSES.add("left");
BEGIN_CLAUSES.add("right");
BEGIN_CLAUSES.add("inner");
BEGIN_CLAUSES.add("outer");
BEGIN_CLAUSES.add("group");
BEGIN_CLAUSES.add("order");
END_CLAUSES.add("where");
END_CLAUSES.add("set");
END_CLAUSES.add("having");
END_CLAUSES.add("join");
END_CLAUSES.add("from");
END_CLAUSES.add("by");
END_CLAUSES.add("join");
END_CLAUSES.add("into");
END_CLAUSES.add("union");
LOGICAL.add("and");
LOGICAL.add("or");
LOGICAL.add("when");
LOGICAL.add("else");
LOGICAL.add("end");
QUANTIFIERS.add("in");
QUANTIFIERS.add("all");
QUANTIFIERS.add("exists");
QUANTIFIERS.add("some");
QUANTIFIERS.add("any");
DML.add("insert");
DML.add("update");
DML.add("delete");
MISC.add("select");
MISC.add("on");
}
private static class FormatProcess {
boolean beginLine = true;
boolean afterBeginBeforeEnd = false;
boolean afterByOrSetOrFromOrSelect = false;
boolean afterValues = false;
boolean afterOn = false;
boolean afterBetween = false;
boolean afterInsert = false;
int inFunction = 0;
int parensSinceSelect = 0;
private LinkedList parenCounts = new LinkedList();
private LinkedList afterByOrFromOrSelects = new LinkedList();
int indent = 1;
StringBuilder result = new StringBuilder();
StringTokenizer tokens;
String lastToken;
String token;
String lcToken;
public FormatProcess(String sql) {
this.tokens = new StringTokenizer(sql, "()+*/-=<>'`\"[], \n\r\f\t", true);
}
public String perform() {
this.result.append("\n ");
while(this.tokens.hasMoreTokens()) {
this.token = this.tokens.nextToken();
this.lcToken = this.token.toLowerCase();
String t;
if ("'".equals(this.token)) {
do {
t = this.tokens.nextToken();
this.token = this.token + t;
} while(!"'".equals(t) && this.tokens.hasMoreTokens());
} else if ("\"".equals(this.token)) {
do {
t = this.tokens.nextToken();
this.token = this.token + t;
} while(!"\"".equals(t));
}
if (this.afterByOrSetOrFromOrSelect && ",".equals(this.token)) {
this.commaAfterByOrFromOrSelect();
} else if (this.afterOn && ",".equals(this.token)) {
this.commaAfterOn();
} else if ("(".equals(this.token)) {
this.openParen();
} else if (")".equals(this.token)) {
this.closeParen();
} else if (PrettyFormatterSqlUtil.BEGIN_CLAUSES.contains(this.lcToken)) {
this.beginNewClause();
} else if (PrettyFormatterSqlUtil.END_CLAUSES.contains(this.lcToken)) {
this.endNewClause();
} else if ("select".equals(this.lcToken)) {
this.select();
} else if (PrettyFormatterSqlUtil.DML.contains(this.lcToken)) {
this.updateOrInsertOrDelete();
} else if ("values".equals(this.lcToken)) {
this.values();
} else if ("on".equals(this.lcToken)) {
this.on();
} else if (this.afterBetween && "and".equals(this.lcToken)) {
this.misc();
this.afterBetween = false;
} else if (PrettyFormatterSqlUtil.LOGICAL.contains(this.lcToken)) {
this.logical();
} else if (isWhitespace(this.token)) {
this.white();
} else {
this.misc();
}
if (!isWhitespace(this.token)) {
this.lastToken = this.lcToken;
}
}
return this.result.toString();
}
private void commaAfterOn() {
this.out();
--this.indent;
this.newline();
this.afterOn = false;
this.afterByOrSetOrFromOrSelect = true;
}
private void commaAfterByOrFromOrSelect() {
this.out();
this.newline();
}
private void logical() {
if ("end".equals(this.lcToken)) {
--this.indent;
}
this.newline();
this.out();
this.beginLine = false;
}
private void on() {
++this.indent;
this.afterOn = true;
this.newline();
this.out();
this.beginLine = false;
}
private void misc() {
this.out();
if ("between".equals(this.lcToken)) {
this.afterBetween = true;
}
if (this.afterInsert) {
this.newline();
this.afterInsert = false;
} else {
this.beginLine = false;
if ("case".equals(this.lcToken)) {
++this.indent;
}
}
}
private void white() {
if (!this.beginLine) {
this.result.append(" ");
}
}
private void updateOrInsertOrDelete() {
this.out();
++this.indent;
this.beginLine = false;
if ("update".equals(this.lcToken)) {
this.newline();
}
if ("insert".equals(this.lcToken)) {
this.afterInsert = true;
}
}
private void select() {
this.out();
++this.indent;
this.newline();
this.parenCounts.addLast(this.parensSinceSelect);
this.afterByOrFromOrSelects.addLast(this.afterByOrSetOrFromOrSelect);
this.parensSinceSelect = 0;
this.afterByOrSetOrFromOrSelect = true;
}
private void out() {
this.result.append(this.token);
}
private void endNewClause() {
if (!this.afterBeginBeforeEnd) {
--this.indent;
if (this.afterOn) {
--this.indent;
this.afterOn = false;
}
this.newline();
}
this.out();
if (!"union".equals(this.lcToken)) {
++this.indent;
}
this.newline();
this.afterBeginBeforeEnd = false;
this.afterByOrSetOrFromOrSelect = "by".equals(this.lcToken) || "set".equals(this.lcToken) || "from".equals(this.lcToken);
}
private void beginNewClause() {
if (!this.afterBeginBeforeEnd) {
if (this.afterOn) {
--this.indent;
this.afterOn = false;
}
--this.indent;
this.newline();
}
this.out();
this.beginLine = false;
this.afterBeginBeforeEnd = true;
}
private void values() {
--this.indent;
this.newline();
this.out();
++this.indent;
this.newline();
this.afterValues = true;
}
private void closeParen() {
--this.parensSinceSelect;
if (this.parensSinceSelect < 0) {
--this.indent;
this.parensSinceSelect = (Integer)this.parenCounts.removeLast();
this.afterByOrSetOrFromOrSelect = (Boolean)this.afterByOrFromOrSelects.removeLast();
}
if (this.inFunction > 0) {
--this.inFunction;
this.out();
} else {
if (!this.afterByOrSetOrFromOrSelect) {
--this.indent;
this.newline();
}
this.out();
}
this.beginLine = false;
}
private void openParen() {
if (isFunctionName(this.lastToken) || this.inFunction > 0) {
++this.inFunction;
}
this.beginLine = false;
if (this.inFunction > 0) {
this.out();
} else {
this.out();
if (!this.afterByOrSetOrFromOrSelect) {
++this.indent;
this.newline();
this.beginLine = true;
}
}
++this.parensSinceSelect;
}
private static boolean isFunctionName(String tok) {
char begin = tok.charAt(0);
boolean isIdentifier = Character.isJavaIdentifierStart(begin) || '"' == begin;
return isIdentifier && !PrettyFormatterSqlUtil.LOGICAL.contains(tok) && !PrettyFormatterSqlUtil.END_CLAUSES.contains(tok) && !PrettyFormatterSqlUtil.QUANTIFIERS.contains(tok) && !PrettyFormatterSqlUtil.DML.contains(tok) && !PrettyFormatterSqlUtil.MISC.contains(tok);
}
private static boolean isWhitespace(String token) {
return " \n\r\f\t".indexOf(token) >= 0;
}
private void newline() {
this.result.append("\n");
for(int i = 0; i < this.indent; ++i) {
this.result.append(" ");
}
this.beginLine = true;
}
}
}
打印结果:
2022-09-09 20:49:47.768[http-nio-9026-exec-1] MybatisLogInterceptor.intercept(81) INFO [c3c83832b58f49579561e92bceafdec1] :开始执行sql==> com.sf.cemp.goods.mallgoods.repository.mapper.MallGoodsMapper.selectPointMallListForApp
执行SQL==>
select
g.name as goodsName,
g.goods_type as goodsType,
g.goods_no as goodsNo,
g.current_store as currentStore,
gi.img_url as goodsImage,
g.point_price as pointPrice,
g.discount_price as discountPrice,
g.rise_date as exchangeBeginTime,
g.original_price as originPrice,
g.category_code as categoryCode,
g.sub_category_code as subCategoryCode,
g.serial_num as serialNum,
g.purchase_price as purchasePrice,
g.exchange_mem_level as exchangeLevelLimit,
g.sale_channel as saleChannel,
g.label as label,
g.like_amount as likeAmount,
g.state as state
from
mall_goods g
left join
mall_goods_img gi
on g.goods_no = gi.goods_no
and gi.img_type = 1
where
g.category_code = 'XPZQ'
and g.state = 1
and g.mall = 'Point_Mall'
and g.if_life_privilege in (
0,2
)
and (
g.current_store>0
or g.current_store=-1
)
order by
g.serial_num asc,
g.id desc limit 1,
10
<==执行总耗时【 281 ms】
2022-09-09 20:52:03.886[http-nio-9026-exec-3] MybatisLogInterceptor.intercept(88) INFO [1a97f40126b9410aad411dc4bc327eca] :执行sql结果:[{"categoryCode":"XPZQ","currentDate":1662727923886,"currentStore":499995,"discountPrice":0,"exchangeBeginTime":1661242860000,"exchangeLevelLimit":0,"goodsImage":"https://cemp.sit.sf-express.com/packetImg/853523/202208/20220823161800408.png","goodsName":"商品预热验证-cyr","goodsNo":"GOODS20220823161825360","goodsType":"SFW","label":"[{\"type\":3}]","likeAmount":0,"originPrice":3300,"pointPrice":33,"purchasePrice":0,"saleChannel":"0","serialNum":1,"state":1}]