Skip to content
Toggle navigation
P
Projects
G
Groups
S
Snippets
Help
huang.tao
/
jmai-platform
This project
Loading...
Sign in
Toggle navigation
Go to a project
Project
Repository
Issues
0
Merge Requests
0
Pipelines
Wiki
Snippets
Settings
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Commit
969c28f6
authored
Jan 09, 2026
by
zhu.zewen
Browse files
Options
_('Browse Files')
Download
Email Patches
Plain Diff
优化数据库相关错误处理
parent
13706a12
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
234 additions
and
41 deletions
jmai-sys/src/main/java/com/jmai/sys/config/db/MybatisInterceptorConfig.java
jmai-sys/src/main/java/com/jmai/sys/config/properties/InfynovaProperties.java
jmai-sys/src/main/java/com/jmai/sys/config/web/WebConfig.java
jmai-sys/src/main/java/com/jmai/sys/exception/ServiceExceptionHandler.java
jmai-sys/src/main/java/com/jmai/sys/exception/SqlContextHolder.java
jmai-sys/src/main/java/com/jmai/sys/exception/SqlExecutionInterceptor.java
jmai-sys/src/main/java/com/jmai/sys/config/db/MybatisInterceptorConfig.java
0 → 100644
View file @
969c28f6
package
com
.
jmai
.
sys
.
config
.
db
;
import
com.jmai.sys.exception.SqlExecutionInterceptor
;
import
org.apache.ibatis.session.SqlSessionFactory
;
import
org.springframework.context.annotation.Configuration
;
import
javax.annotation.PostConstruct
;
import
javax.annotation.Resource
;
import
java.util.Collection
;
/**
* MyBatis拦截器配置类,用于注册自定义SQL执行拦截器
*/
@Configuration
public
class
MybatisInterceptorConfig
{
@Resource
private
Collection
<
SqlSessionFactory
>
sqlSessionFactoryList
;
@PostConstruct
public
void
addMybatisInterceptor
()
{
SqlExecutionInterceptor
sqlExecutionInterceptor
=
new
SqlExecutionInterceptor
();
sqlSessionFactoryList
.
forEach
(
sqlSessionFactory
->
sqlSessionFactory
.
getConfiguration
().
addInterceptor
(
sqlExecutionInterceptor
));
}
}
\ No newline at end of file
jmai-sys/src/main/java/com/jmai/sys/config/properties/InfynovaProperties.java
View file @
969c28f6
...
...
@@ -47,9 +47,8 @@ public class InfynovaProperties {
@ApiModelProperty
(
"每个路由最大连接数:100(默认)"
)
private
Integer
restTemplateMaxConnectionPerRoute
=
20
;
/********************************** 页面配置 **********************************/
@ApiModelProperty
(
"页面配置-选项查询器类型:0 - 前端调用(默认),1 - 后端调用"
)
private
Integer
pageConfigOptionFetcherType
=
1
;
@ApiModelProperty
(
"是否开启数据库异常处理:true - 开启(默认),false - 关闭"
)
private
Boolean
handleDatabaseExceptionCurrentSqlDispEnabled
=
true
;
/********************************** 文件上传校验 **********************************/
/**
...
...
@@ -84,42 +83,4 @@ public class InfynovaProperties {
*/
private
Integer
maxExport
=
20000
;
/********************************** IVS **********************************/
@ApiModelProperty
(
value
=
"业务数据存储是否开启:true - 开启,false - 关闭(默认)"
)
private
Boolean
bizDataStoreEnabled
=
false
;
@ApiModelProperty
(
value
=
"业务数据存储类型:空 - 不存储(默认),snapshot - 快照(直接恢复),view - 视图(按逻辑还原)"
)
private
String
bizDataStoreType
=
""
;
@ApiModelProperty
(
"业务数据存储源:空 - 无需存储(默认),database - 数据库存储,thirdParty - 第三方存储"
)
private
String
bizDataStoreSource
=
""
;
@ApiModelProperty
(
"允许删除本地数据库的业务单相关业务数据:true - 允许(仅测试用),false - 不允许(默认)"
)
private
Boolean
deleteOrderInLocalEnabled
=
false
;
/********************************** OCR **********************************/
@ApiModelProperty
(
"镭射码识别置信区间下限:0.90(默认)"
)
private
Double
minLaserOcrScore
=
90
D
;
@ApiModelProperty
(
"无镭射码(无钉):true - 开启(默认),false - 关闭"
)
private
Boolean
noLaserStatusEnabled
=
true
;
@ApiModelProperty
(
"镭射码识别消费者数量:1个消费者(默认)"
)
private
Integer
laserRecognizeConsumerPoolSize
=
2
;
@ApiModelProperty
(
"镭射码识别任务重试判断表达式:空 - 不重试(默认)"
)
private
String
laserRecognizeTaskRetryFilter
=
""
;
@ApiModelProperty
(
"镭射码识别任务重试次数:0 - 不重试(默认)"
)
private
Integer
laserRecognizeTaskRetryCount
=
1
;
@ApiModelProperty
(
"镭射码识别任务重试触发条件:5 - 积累待处理任务超5个就直接使用重试识别(默认)"
)
private
Integer
laserRecognizeTaskRetryCondition
=
5
;
@ApiModelProperty
(
"镭射码识别丢弃时间(毫秒):60秒(默认)"
)
private
Long
laserRecognizeTaskDiscardInMillis
=
TimeUnit
.
SECONDS
.
toMillis
(
60
);
@ApiModelProperty
(
"镭射码识别超时时间(毫秒):90秒(默认)"
)
private
Long
laserRecognizeTimeoutInMillis
=
TimeUnit
.
SECONDS
.
toMillis
(
90
);
/********************************** OPEN **********************************/
@ApiModelProperty
(
"开启OpenAPI:true - 开启,false - 关闭(默认)"
)
private
Boolean
openapiEnabled
=
true
;
/********************************** SYNC **********************************/
}
jmai-sys/src/main/java/com/jmai/sys/config/web/WebConfig.java
View file @
969c28f6
package
com
.
jmai
.
sys
.
config
.
web
;
import
com.jmai.sys.exception.SqlContextHolder
;
import
org.springframework.context.annotation.Bean
;
import
org.springframework.context.annotation.Configuration
;
import
org.springframework.web.filter.OncePerRequestFilter
;
import
org.springframework.web.multipart.MultipartResolver
;
import
org.springframework.web.multipart.support.StandardServletMultipartResolver
;
import
javax.servlet.Filter
;
import
javax.servlet.FilterChain
;
import
javax.servlet.ServletException
;
import
javax.servlet.http.HttpServletRequest
;
import
javax.servlet.http.HttpServletResponse
;
import
java.io.IOException
;
@Configuration
public
class
WebConfig
{
@Bean
public
MultipartResolver
multipartResolver
()
{
return
new
StandardServletMultipartResolver
();
}
/**
* SQL上下文清理过滤器,确保每次请求结束后清理SQL上下文
*/
@Bean
public
Filter
sqlContextCleanupFilter
()
{
return
new
OncePerRequestFilter
()
{
@Override
protected
void
doFilterInternal
(
HttpServletRequest
request
,
HttpServletResponse
response
,
FilterChain
filterChain
)
throws
ServletException
,
IOException
{
try
{
filterChain
.
doFilter
(
request
,
response
);
}
finally
{
// 请求处理完成后清理SQL上下文,防止内存泄漏
SqlContextHolder
.
clearCurrentSql
();
}
}
};
}
}
\ No newline at end of file
jmai-sys/src/main/java/com/jmai/sys/exception/ServiceExceptionHandler.java
View file @
969c28f6
...
...
@@ -4,6 +4,7 @@ package com.jmai.sys.exception;
import
cn.hutool.core.util.ObjectUtil
;
import
com.jmai.api.base.ServiceCode
;
import
com.jmai.api.exception.ServiceException
;
import
com.jmai.sys.config.properties.InfynovaProperties
;
import
com.jmai.sys.dto.ResponseData
;
import
lombok.extern.slf4j.Slf4j
;
import
org.springframework.web.HttpRequestMethodNotSupportedException
;
...
...
@@ -14,8 +15,15 @@ import org.springframework.web.bind.annotation.RestControllerAdvice;
import
org.springframework.web.multipart.MultipartException
;
import
org.springframework.web.servlet.NoHandlerFoundException
;
import
javax.annotation.Resource
;
import
javax.servlet.http.HttpServletResponse
;
import
java.io.FileNotFoundException
;
import
java.sql.SQLException
;
import
java.util.regex.Matcher
;
import
java.util.regex.Pattern
;
import
org.springframework.dao.DataAccessException
;
import
org.mybatis.spring.MyBatisSystemException
;
import
static
com
.
jmai
.
sys
.
exception
.
ErrorCode
.*;
...
...
@@ -28,6 +36,8 @@ import static com.jmai.sys.exception.ErrorCode.*;
@Slf4j
@RestControllerAdvice
public
class
ServiceExceptionHandler
{
@Resource
private
InfynovaProperties
infynovaProperties
;
private
ResponseData
buildResponse
(
ServiceCode
serviceCode
,
Throwable
cause
)
{
ServiceCode
code
=
ObjectUtil
.
isEmpty
(
cause
)
?
...
...
@@ -76,11 +86,94 @@ public class ServiceExceptionHandler {
@ExceptionHandler
(
MethodArgumentNotValidException
.
class
)
public
ResponseData
handleException
(
MethodArgumentNotValidException
e
,
HttpServletResponse
response
)
{
log
.
error
(
"Request params error: {}"
,
e
.
getMessage
(),
e
);
response
.
setStatus
(
SYSTEM_501
.
getCode
());
return
ResponseData
.
error
(
SYSTEM_501
.
getCode
(),
e
.
getBindingResult
().
getFieldError
().
getDefaultMessage
());
}
/**
* 数据库相关异常处理
*
* @param e
* @param response
* @return
*/
@ExceptionHandler
({
SQLException
.
class
,
DataAccessException
.
class
,
MyBatisSystemException
.
class
})
public
ResponseData
handleDatabaseException
(
Exception
e
,
HttpServletResponse
response
)
{
log
.
error
(
"Database error: {}"
,
e
.
getMessage
(),
e
);
response
.
setStatus
(
SYSTEM_500
.
getCode
());
String
errorMessage
=
e
.
getMessage
();
// 获取当前执行的SQL语句
String
currentSql
=
infynovaProperties
.
getHandleDatabaseExceptionCurrentSqlDispEnabled
()
?
SqlContextHolder
.
getCurrentSql
()
:
""
;
if
(
errorMessage
!=
null
&&
errorMessage
.
contains
(
"Unknown column"
))
{
// 处理 "Unknown column 'xxx' in 'field list'" 错误
String
detailedMessage
=
extractColumnInfo
(
errorMessage
);
String
fullMessage
=
"数据库字段错误:"
+
detailedMessage
;
if
(
ObjectUtil
.
isNotEmpty
(
currentSql
))
{
fullMessage
+=
",执行的SQL语句:"
+
currentSql
;
}
return
ResponseData
.
error
(
500
,
fullMessage
);
}
else
if
(
errorMessage
!=
null
&&
errorMessage
.
contains
(
"doesn't exist"
))
{
// 处理表或数据库不存在的错误
String
detailedMessage
=
extractTableInfo
(
errorMessage
);
String
fullMessage
=
"数据库对象不存在:"
+
detailedMessage
;
if
(
ObjectUtil
.
isNotEmpty
(
currentSql
))
{
fullMessage
+=
",执行的SQL语句:"
+
currentSql
;
}
return
ResponseData
.
error
(
500
,
fullMessage
);
}
else
{
String
fullMessage
=
e
.
getMessage
();
if
(
ObjectUtil
.
isNotEmpty
(
currentSql
))
{
fullMessage
=
"数据库错误:"
+
fullMessage
+
",执行的SQL语句:"
+
currentSql
;
}
return
ResponseData
.
error
(
500
,
fullMessage
);
}
}
/**
* 从错误信息中提取字段相关信息
* @param errorMessage 原始错误信息
* @return 包含字段详情的错误信息
*/
private
String
extractColumnInfo
(
String
errorMessage
)
{
// 匹配 "Unknown column 'column_name' in 'field list'" 或类似格式
Pattern
pattern
=
Pattern
.
compile
(
"Unknown column '([^']+)' in '([^']+)'"
);
Matcher
matcher
=
pattern
.
matcher
(
errorMessage
);
if
(
matcher
.
find
())
{
String
columnName
=
matcher
.
group
(
1
);
String
context
=
matcher
.
group
(
2
);
// 如 'field list', 'where clause', 'order clause' 等
return
String
.
format
(
"未知字段 '%s' 在 %s 中:%s"
,
columnName
,
context
,
errorMessage
);
}
// 处理其他可能的格式,例如 "Unknown column 'column_name'"
Pattern
simplePattern
=
Pattern
.
compile
(
"Unknown column '([^']+)'"
);
Matcher
simpleMatcher
=
simplePattern
.
matcher
(
errorMessage
);
if
(
simpleMatcher
.
find
())
{
String
columnName
=
simpleMatcher
.
group
(
1
);
return
String
.
format
(
"未知字段 '%s':%s"
,
columnName
,
errorMessage
);
}
return
errorMessage
;
}
/**
* 从错误信息中提取表相关信息
* @param errorMessage 原始错误信息
* @return 包含表详情的错误信息
*/
private
String
extractTableInfo
(
String
errorMessage
)
{
// 匹配 "Table 'database.table_name' doesn't exist" 或类似格式
Pattern
pattern
=
Pattern
.
compile
(
"Table '([^']+)' doesn't exist"
);
Matcher
matcher
=
pattern
.
matcher
(
errorMessage
);
if
(
matcher
.
find
())
{
String
fullTableName
=
matcher
.
group
(
1
);
return
String
.
format
(
"表 '%s' 不存在:%s"
,
fullTableName
,
errorMessage
);
}
return
errorMessage
;
}
/**
* 系统异常
*
* @param e
...
...
jmai-sys/src/main/java/com/jmai/sys/exception/SqlContextHolder.java
0 → 100644
View file @
969c28f6
package
com
.
jmai
.
sys
.
exception
;
/**
* SQL上下文持有者,用于在线程范围内存储当前执行的SQL语句
*/
public
class
SqlContextHolder
{
private
static
final
ThreadLocal
<
String
>
SQL_HOLDER
=
new
ThreadLocal
<>();
/**
* 设置当前线程的SQL语句
* @param sql SQL语句
*/
public
static
void
setCurrentSql
(
String
sql
)
{
SQL_HOLDER
.
set
(
sql
);
}
/**
* 获取当前线程的SQL语句
* @return SQL语句
*/
public
static
String
getCurrentSql
()
{
return
SQL_HOLDER
.
get
();
}
/**
* 清除当前线程的SQL语句
*/
public
static
void
clearCurrentSql
()
{
SQL_HOLDER
.
remove
();
}
}
\ No newline at end of file
jmai-sys/src/main/java/com/jmai/sys/exception/SqlExecutionInterceptor.java
0 → 100644
View file @
969c28f6
package
com
.
jmai
.
sys
.
exception
;
import
org.apache.ibatis.executor.statement.StatementHandler
;
import
org.apache.ibatis.mapping.BoundSql
;
import
org.apache.ibatis.plugin.*
;
import
java.sql.Connection
;
import
java.util.Properties
;
/**
* SQL执行拦截器,用于捕获执行的SQL语句
*/
@Intercepts
({
@Signature
(
type
=
StatementHandler
.
class
,
method
=
"prepare"
,
args
=
{
Connection
.
class
,
Integer
.
class
})
})
public
class
SqlExecutionInterceptor
implements
Interceptor
{
@Override
public
Object
intercept
(
Invocation
invocation
)
throws
Throwable
{
StatementHandler
statementHandler
=
(
StatementHandler
)
invocation
.
getTarget
();
BoundSql
boundSql
=
statementHandler
.
getBoundSql
();
// 获取实际的SQL语句
String
sql
=
boundSql
.
getSql
();
// 存储到ThreadLocal中,以便异常处理时使用
SqlContextHolder
.
setCurrentSql
(
sql
);
try
{
// 执行原方法
return
invocation
.
proceed
();
}
finally
{
// 清除ThreadLocal中的SQL,防止内存泄漏
// 注意:这里不立即清除,因为异常可能在后续处理中发生
// 我们将在适当的时候清理,比如请求结束时
}
}
@Override
public
Object
plugin
(
Object
target
)
{
if
(
target
instanceof
StatementHandler
)
{
return
Plugin
.
wrap
(
target
,
this
);
}
return
target
;
}
@Override
public
void
setProperties
(
Properties
properties
)
{
// 不需要设置属性
}
}
\ No newline at end of file
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment