MyBatis-框架使用和分析
一、基础知识
1.1 名词术语
名称
|
说明
|
SqlSession
|
作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能 。
|
Executor
|
MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护。
|
StatementHandler
|
封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
|
ParameterHandler
|
封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。
|
ResultSetHandler
|
负责将JDBC返回的ResultSet结果集对象转换成List类型的集合
|
TypeHandler
|
负责java数据类型和jdbc数据类型之间的映射和转换
|
MappedStatement
|
MappedStatement维护了一条<select|update|delete|insert>节点的封装
|
SqlSource
|
负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回
|
BoundSql
|
表示动态生成的SQL语句以及相应的参数信息
|
Configuration
|
MyBatis所有的配置信息都维持在Configuration对象之中
|
MetaObject
|
MetaObject是MyBatis提供的工具类,它可以有效的获取或修改一些重要对象的属性。
|
1.2 流程原理分析
二、插件使用
2.1 插件原理
// 插件声明格式
@Intercepts({
@Signature(type =StatementHandler.class,
method="prepare" ,
args={Connection.class})})
public class MyPlugin implements Interceptor{
......
}
支持拦截的方法
- 执行器Executor(update、query、commit、rollback等方法);
- 参数处理器ParameterHandler(getParameterObject、setParameters方法);
- 结果集处理器ResultSetHandler(handleResultSets、handleOutputParameters等方法);
- SQL语法构建器StatementHandler(prepare、parameterize、batch、update、query等方法);
- @Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class}),编写逻辑,基于StatementHandler接口,查看里面对应方法,配置的参数,args为StatementHandler.query()方法的入参。参考代码:
流程说明
2.2 应用场景
场景
|
描述
|
分页
|
mybatis的分页默认是基于内存分页的(查出所有,再截取),数据量大的情况下效率较低,不过使用mybatis插件可以改变该行为,只需要拦截StatementHandler类的prepare方法,改变要执行的SQL语句为分页语句即可。
|
公共字段统一赋值
|
一般业务系统都会有创建者,修改者等基础字段字段,对于基础字段的赋值,可以在DAO层统一拦截处理,可以用mybatis插件拦截Executor类的update方法,对相关参数进行统一赋值即可。
|
性能监控
|
对于SQL语句执行的性能监控,可以通过拦截Executor类的update, query等方法,用日志记录每个方法执行的时间。
|
其他
|
其实mybatis扩展性还是很强的,基于插件机制,基本上可以控制SQL执行的各个阶段,如执行阶段,参数处理阶段,语法构建阶段,结果集处理阶段,具体可以根据项目业务来实现对应业务逻辑。
|
三、框架使用
3.1 基于Java API模式
基础配置信息


**
* @author wl
* @description 单据生命流程日志
* @date 2019/10/9 21:06
*/
@Data
public class PayLifeLog implements Serializable { /**
* 日志主键编码
*/
private Integer logId;
/**
* 单据包编码
*/
private String packageId;
/**
* 单据编码
*/
private String billCode;
/**
* 节点执行结果
*/
private Byte lifeStatus;
/**
* 节点
*/
private Byte lifeState;
/**
* 备注
*/
private String markMsg;
/**
* 更新时间
*/
private LocalDateTime updatetime;
}
构建DataBaseConfiguration配置类
package com.trace.base.tool.configuration; import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration; /**
* 数据库层配置类
*
* @author wl
* @date 2021-4-27
*/
@Configuration
@MapperScan(basePackages = {"com.trace.base.tool.mapper.**"}, sqlSessionFactoryRef = "sqlSessionFactory")
public class DataBaseConfiguration {
@Value("${hikaricp.dataSource.jdbc.driverClassName}")
private String driverClassName;
@Value("${hikaricp.dataSource.url}")
private String jdbcUrl;
@Value("${hikaricp.dataSource.username}")
private String username;
@Value("${hikaricp.dataSource.password}")
private String password;
@Value("${hikaricp.dataSource.connectionTestQuery}")
private String connectionTestQuery;
@Value("${hikaricp.dataSource.connectionTimeout}")
private long connectionTimeout;
@Value("${hikaricp.dataSource.idleTimeout}")
private long idleTimeout;
@Value("${hikaricp.dataSource.maxLifetime}")
private long maxLifetime;
@Value("${hikaricp.dataSource.maximumPoolSize}")
private int maximumPoolSize;
@Value("${hikaricp.dataSource.minimumIdle}")
private int minimumIdle; /**
* 注入一个hikaricp dataSource
*/
@Bean(value = "dataSource", destroyMethod = "close")
public HikariDataSource hikariDataSource() {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setDriverClassName(driverClassName);
hikariConfig.setJdbcUrl(jdbcUrl);
hikariConfig.setUsername(username);
hikariConfig.setPassword(password);
hikariConfig.setConnectionTestQuery(connectionTestQuery);
hikariConfig.setConnectionTimeout(connectionTimeout);
hikariConfig.setIdleTimeout(idleTimeout);
hikariConfig.setMaxLifetime(maxLifetime);
hikariConfig.setMaximumPoolSize(maximumPoolSize);
hikariConfig.setMinimumIdle(minimumIdle);
return new HikariDataSource(hikariConfig);
}
}
- 读取application.properties配置文件的信息,并生成一个hikariDataSource对象。
- 通过注解@MapperScan限定数据库应用的包,支持项目中多数据源应用。
构建Mapper接口
package com.trace.base.tool.mapper; import com.trace.base.tool.domain.base.PayLifeLog;
import com.trace.base.tool.sqlprovider.LifeLogSqlProvider;
import com.trace.base.tool.web.Page;
import org.apache.ibatis.annotations.*;
import org.springframework.stereotype.Repository; import java.util.List; /**
* @author wl
* @description 支付请求日志-数据接口
* @date 2019/10/10 18:42
*/
@Mapper
@Repository
public interface LifeLogMapper {
/**
* 获取批次支付失败日志
*
* @param billCode 单号
* @return 结果
*/
@SelectProvider(type = LifeLogSqlProvider.class, method = "getPayLifeLogByIdSql")
PayLifeLog getPayLifeLogById(@Param("billCode") String billCode);
}
- 参考mybatis 3.4版本,带上@param注解,保证参数传递正常。
- @SelectProvider注解,指定基于Java API模式,提供具体sql信息的配置类信息。
构建SqlProvider配置类
package com.trace.base.tool.sqlprovider; import com.trace.base.tool.domain.base.PayLifeLog;
import com.trace.base.tool.util.sql.SQL;
import com.trace.base.tool.web.Page;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.annotations.Param; /**
* @author wl
* @description 请求日志sql生成器
* @date 2019/10/8 18:43
*/
public class LifeLogSqlProvider { public String getPayLifeLogByIdSql(@Param("billCode") String billCode) {
String sql = " select log_id as logId, package_id as packageId,bill_code as billCode,life_state as lifeState,life_status as lifeStatus,mark_msg as markMsg,updatetime " +
"from caiwu_pay_life_log where " +
"bill_code = #{billCode}" +
"limit 0,1";
return sql;
}
}
- 编写一个基本类,类似xml文件功能,生成对应执行的sql。
- sql可参考网上或者Mybatis自身的一sql生成工具类 SQL(),扩展开发,方便生成sql语句。
构建RestController业务控制器
package com.trace.base.tool.controller;
import com.trace.base.tool.domain.base.PayLifeLog;
import com.trace.base.tool.mapper.LifeLogMapper;
import com.trace.base.tool.web.Page;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import java.util.List; /**
* mybatis测试demo
*
* @author wl
* @date 2020-12-01
*/
@RestController
@RequestMapping("mybatis")
@Validated
public class MyBatisController { @Autowired
private LifeLogMapper lifeLogMapper; /**
* 获取当前日志信息
*
* @return 返回存储数据
*/
@GetMapping("/log")
public PayLifeLog getTraceService() {
return lifeLogMapper.getPayLifeLogById("5");
}
}
3.2 编写慢sql监控日志插件
- 基于环境配置变量,控制该插件是否需要启用(如测试环境启用,线上关闭)。
- 当sql运行时长超过1秒时,记录运行的sql信息,写入到慢sql日志中。
基础配置信息
构建DataBaseConfiguration配置类
package com.trace.base.tool.configuration; import com.trace.base.tool.mybatis.study.StudySqlSessionFactoryBean;
import com.zaxxer.hikari.HikariConfig;
import com.zaxxer.hikari.HikariDataSource;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.DataSourceTransactionManager; /**
* 数据库层配置类
*
* @author wl
* @date 2021-4-27
*/
@Configuration
@MapperScan(basePackages = {"com.trace.base.tool.mapper.**"}, sqlSessionFactoryRef = "sqlSessionFactory")
public class DataBaseConfiguration {
@Value("${hikaricp.dataSource.jdbc.driverClassName}")
private String driverClassName;
@Value("${hikaricp.dataSource.url}")
private String jdbcUrl;
@Value("${hikaricp.dataSource.username}")
private String username;
@Value("${hikaricp.dataSource.password}")
private String password;
@Value("${hikaricp.dataSource.connectionTestQuery}")
private String connectionTestQuery;
@Value("${hikaricp.dataSource.connectionTimeout}")
private long connectionTimeout;
@Value("${hikaricp.dataSource.idleTimeout}")
private long idleTimeout;
@Value("${hikaricp.dataSource.maxLifetime}")
private long maxLifetime;
@Value("${hikaricp.dataSource.maximumPoolSize}")
private int maximumPoolSize;
@Value("${hikaricp.dataSource.minimumIdle}")
private int minimumIdle; /**
* 注入一个hikaricp dataSource
*/
@Bean(value = "dataSource", destroyMethod = "close")
public HikariDataSource hikariDataSource() {
HikariConfig hikariConfig = new HikariConfig();
hikariConfig.setDriverClassName(driverClassName);
hikariConfig.setJdbcUrl(jdbcUrl);
hikariConfig.setUsername(username);
hikariConfig.setPassword(password);
hikariConfig.setConnectionTestQuery(connectionTestQuery);
hikariConfig.setConnectionTimeout(connectionTimeout);
hikariConfig.setIdleTimeout(idleTimeout);
hikariConfig.setMaxLifetime(maxLifetime);
hikariConfig.setMaximumPoolSize(maximumPoolSize);
hikariConfig.setMinimumIdle(minimumIdle);
return new HikariDataSource(hikariConfig);
} /**
* 注入一个sqlSessionFactory
*/
@Bean(value = "sqlSessionFactory")
public StudySqlSessionFactoryBean sqlSessionFactory(HikariDataSource dataSource) {
StudySqlSessionFactoryBean sessionFactoryBean = new StudySqlSessionFactoryBean();
sessionFactoryBean.setDataSource(dataSource);
return sessionFactoryBean;
} /**
* 主动注入一个transactionManger,适用多数据库事务管理器环境
*/
@Bean(value = "transactionManager")
public DataSourceTransactionManager dataSourceTransactionManager(HikariDataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
- 读取application.properties配置文件的信息,并生成一个hikariDataSource对象。
- 自定义一个StudySqlSessionFactoryBean继承SqlSessionFactoryBean,便于扩展默认的SqlSessionFactoryBean功能,并基于MyBatis Java API功能,做插件或其他配置。
慢sql监控插件编写


package com.trace.base.tool.mybatis.study.plugin; import com.mysql.jdbc.PreparedStatement;
import com.trace.base.tool.logging.BaseLog;
import com.trace.base.tool.logging.Channel;
import com.trace.base.tool.logging.LevelEnum;
import com.trace.base.tool.mybatis.monitor.SlowSqlEnum;
import com.trace.base.tool.mybatis.monitor.SlowSqlLog;
import com.trace.base.tool.util.LocalDateTimeUtil;
import com.zaxxer.hikari.pool.ProxyStatement;
import org.apache.ibatis.executor.statement.StatementHandler;
import org.apache.ibatis.logging.jdbc.PreparedStatementLogger;
import org.apache.ibatis.plugin.*;
import org.apache.ibatis.session.ResultHandler;
import org.postgresql.jdbc.PgStatement; import java.lang.reflect.Field;
import java.lang.reflect.Proxy;
import java.sql.Statement;
import java.util.Properties; /**
* 监控慢SQL插件
*
* @author wl
*/
@Intercepts({
@Signature(type = StatementHandler.class, method = "update", args = {Statement.class}),
@Signature(type = StatementHandler.class, method = "query", args = {Statement.class, ResultHandler.class})
})
public class SlowSqlMonitorPlugin implements Interceptor {
public static final String SLOW_SQL_ENABLE = "sql.slow.enable";
private static boolean POSTGRESQL_DRIVER_AVAILABLE;
private static boolean MYSQL_DRIVER_AVAILABLE;
private static boolean HIKARICP_AVAILABLE;
private static Field DELEGATE_FIELD; static {
try {
Class.forName("org.postgresql.jdbc.PgPreparedStatement");
POSTGRESQL_DRIVER_AVAILABLE = true;
} catch (ClassNotFoundException e) {
// ignore
POSTGRESQL_DRIVER_AVAILABLE = false;
}
try {
Class.forName("com.mysql.jdbc.PreparedStatement");
MYSQL_DRIVER_AVAILABLE = true;
} catch (ClassNotFoundException e) {
// ignore
MYSQL_DRIVER_AVAILABLE = false;
}
try {
Class.forName("com.zaxxer.hikari.pool.HikariProxyPreparedStatement");
DELEGATE_FIELD = ProxyStatement.class.getDeclaredField("delegate");
DELEGATE_FIELD.setAccessible(true);
HIKARICP_AVAILABLE = true;
} catch (ClassNotFoundException | NoSuchFieldException e) {
// ignore
HIKARICP_AVAILABLE = false;
}
} @Override
public Object intercept(Invocation invocation) throws Throwable {
long start = System.currentTimeMillis();
try {
Object obj = invocation.proceed();
return obj;
} finally {
long end = System.currentTimeMillis();
long used = end - start;
// >= 1s
final long max = 1000L;
if (used >= max) {
try {
Object target = invocation.getTarget();
String sql = "unknown";
if (target instanceof StatementHandler) {
sql = ((StatementHandler) target).getBoundSql().getSql();
}
// 外部提前做一次猜测是否为预处理语句,只用instanceof PreparedStatement有可能没有?,这种情况不需要执行下面逻辑
boolean mightPreparedSql = sql.contains("?");
Object statementArg = invocation.getArgs()[0];
// 可能是预处理语句才处理
if (mightPreparedSql) {
// 这里还要区分是否为debug模式,debug模式下,生成的connection和statement都是被mybatis logger类代理
if (Proxy.isProxyClass(statementArg.getClass())) {
// 获取到真实被代理的statement
statementArg = ((PreparedStatementLogger) Proxy.getInvocationHandler(statementArg)).getPreparedStatement();
}
// 被HikariProxyPreparedStatement代理,通过反射才能获取到真实的PreparedStatement
if (HIKARICP_AVAILABLE && statementArg instanceof ProxyStatement) {
java.sql.PreparedStatement preparedStatement = (java.sql.PreparedStatement) DELEGATE_FIELD.get(statementArg);
// postgresql,前提是SQL为预处理语句,避免非预处理语句也执行了toString()造成拿到内存地址
if (POSTGRESQL_DRIVER_AVAILABLE && preparedStatement instanceof PgStatement) {
// 因为PgPreparedStatement是保护类,只能使用PgStatement转换,实际是执行子类的toString()
sql = preparedStatement.toString();
}
// mysql
else if (MYSQL_DRIVER_AVAILABLE && preparedStatement instanceof PreparedStatement) {
sql = ((PreparedStatement) preparedStatement).asSql();
}
} }
// 记录日志信息
SlowSqlLog slowSqlLog = new SlowSqlLog();
slowSqlLog.setTraceId("idnum-0001");
slowSqlLog.setType(SlowSqlEnum.DML);
slowSqlLog.setMessage("执行DML[" + sql + "]超时1秒");
slowSqlLog.setStart(LocalDateTimeUtil.formatMilliPlus8(start));
slowSqlLog.setEnd(LocalDateTimeUtil.formatMilliPlus8(end));
slowSqlLog.setUsed(used);
BaseLog<SlowSqlLog> baseLog = new BaseLog<>();
baseLog.setContext(slowSqlLog);
baseLog.setLevel(LevelEnum.WARNING.getLevel());
baseLog.setLevelName(LevelEnum.WARNING.getLevelName());
baseLog.setChannel(Channel.SYSTEM);
baseLog.setMessage("slowsql log");
baseLog.setDatetime(LocalDateTimeUtil.getMicroSecondFormattedNow());
String sqlLog=slowSqlLog.toString();
// todo 记录日志信息
} catch (Throwable ex) {
// ignore
}
}
}
} @Override
public Object plugin(Object target) {
return Plugin.wrap(target, this);
} @Override
public void setProperties(Properties properties) {
}
}
- 参考插件的使用说明文档,指定@Intercepts,设置产生拦截的触发条件。
- 重新实现intercept方法,监听invocation.proceed()方法执行前后的时间,记录sql运行的相关日志信息。
- 插件中获取的原生sql的方法,在不同的驱动,数据库版本下,方法不一样,仅供参考。
配置插件


package com.trace.base.tool.mybatis.study; import com.trace.base.tool.mybatis.monitor.MonitorSpringManagedTransactionFactory;
import com.trace.base.tool.mybatis.study.plugin.PagePlugin;
import com.trace.base.tool.mybatis.study.plugin.SlowSqlMonitorPlugin;
import org.apache.commons.lang3.ArrayUtils;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.AutoMappingBehavior;
import org.apache.ibatis.session.Configuration;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.springframework.context.EnvironmentAware;
import org.springframework.core.env.Environment; /**
* 自定义的sqlSessionFactoryBean
*
* @author wl
* @date 2021-3-9
*/
public class StudySqlSessionFactoryBean extends SqlSessionFactoryBean implements EnvironmentAware {
private Interceptor[] plugins;
public static Configuration CONFIGURATION;
private boolean slowSqlEnabled = false; public StudySqlSessionFactoryBean() {
this(null);
} public StudySqlSessionFactoryBean(Configuration configuration) {
super();
if (configuration == null) {
configuration = new Configuration();
configuration.setMapUnderscoreToCamelCase(true);
configuration.setAutoMappingBehavior(AutoMappingBehavior.FULL);
}
CONFIGURATION = configuration;
setConfiguration(configuration);
} @Override
public void setPlugins(Interceptor[] plugins) {
this.plugins = plugins;
} /**
* 真实执行设置插件,setPlugins只用于记录客户端自定义的plugin,便于后续拷贝
*/
private void actualSetPlugins() {
if (slowSqlEnabled) {
// 使用自定义监控功能的事务管理器工厂类
setTransactionFactory(new MonitorSpringManagedTransactionFactory());
this.plugins = ArrayUtils.add(plugins == null ? new Interceptor[0] : plugins, new SlowSqlMonitorPlugin());
}
super.setPlugins(plugins);
} @Override
public void setEnvironment(Environment environment) {
slowSqlEnabled = environment.getProperty(SlowSqlMonitorPlugin.SLOW_SQL_ENABLE, boolean.class, true);
actualSetPlugins();
}
}
- setConfiguration只是设置Mybatis的全局配置信息,如设置统一的下划线转驼峰功能。
- 重新实现setEnvironment方法,可以获取配置信息,用于环境变量标识,在指定的环境下,设置插件是否运行。
- 重新实现setPlugins方法,添加自定义的插件。
3.3 Mybatis应用的一些思考
- 到底是xml模式设置sql好,还是Java API模式更好?
- 若基于Java API模式,MyBatis提供的动态sql工具类SQL()如何,有无局限性?
- 若扩展SQL()或者自实现,有哪些方向和注意点?
- 插件开发便于项目做监控管理,一般需要考虑哪些自定义的插件?
- 是否存在一些开源优秀的插件(自定义插件可以作为参考的方向)?
- Mapper配置一些关联查询如何处理,如一个Mapper查询集成其他mapper中的查询功能?
- 多个插件先后顺序如何定义?
四、Mybatis扩展
MyBatis-框架使用和分析的更多相关文章
- MyBatis框架及原理分析
MyBatis 是支持定制化 SQL.存储过程以及高级映射的优秀的持久层框架,其主要就完成2件事情: 封装JDBC操作 利用反射打通Java类与SQL语句之间的相互转换 MyBatis的主要设计目的就 ...
- MyBatis框架的使用及源码分析(十一) StatementHandler
我们回忆一下<MyBatis框架的使用及源码分析(十) CacheExecutor,SimpleExecutor,BatchExecutor ,ReuseExecutor> , 这4个Ex ...
- MyBatis框架的使用及源码分析(九) Executor
从<MyBatis框架的使用及源码分析(八) MapperMethod>文中我们知道执行Mapper的每一个接口方法,最后调用的是MapperMethod.execute方法.而当执行Ma ...
- MyBatis框架的使用及源码分析(八) MapperMethod
从 <MyBatis框架中Mapper映射配置的使用及原理解析(七) MapperProxy,MapperProxyFactory> 文中,我们知道Mapper,通过MapperProxy ...
- MyBatis框架的使用及源码分析(七) MapperProxy,MapperProxyFactory
从上文<MyBatis框架中Mapper映射配置的使用及原理解析(六) MapperRegistry> 中我们知道DefaultSqlSession的getMapper方法,最后是通过Ma ...
- MyBatis框架的使用及源码分析(六) MapperRegistry
我们先Mapper接口的调用方式,见<MyBatis框架中Mapper映射配置的使用及原理解析(一) 配置与使用>的示例: public void findUserById() { Sql ...
- MyBatis框架的使用及源码分析(五) DefaultSqlSessionFactory和DefaultSqlSession
我们回顾<MyBatis框架中Mapper映射配置的使用及原理解析(一) 配置与使用> 一文的示例 private static SqlSessionFactory getSessionF ...
- MyBatis框架的使用及源码分析(四) 解析Mapper接口映射xml文件
在<MyBatis框架中Mapper映射配置的使用及原理解析(二) 配置篇 SqlSessionFactoryBuilder,XMLConfigBuilder> 一文中,我们知道mybat ...
- MyBatis框架的使用及源码分析(三) 配置篇 Configuration
从上文<MyBatis框架中Mapper映射配置的使用及原理解析(二) 配置篇 SqlSessionFactoryBuilder,XMLConfigBuilder> 我们知道XMLConf ...
- MyBatis框架的使用及源码分析(二) 配置篇 SqlSessionFactoryBuilder,XMLConfigBuilder
在 <MyBatis框架中Mapper映射配置的使用及原理解析(一) 配置与使用> 的demo中看到了SessionFactory的创建过程: SqlSessionFactory sess ...
随机推荐
- 记一次 .NET医疗布草API程序 内存暴涨分析
一:背景 1. 讲故事 我在年前写过一篇关于CPU爆高的分析文章 再记一次 应用服务器 CPU 暴高事故分析 ,当时是给同济做项目升级,看过那篇文章的朋友应该知道,最后的结论是运维人员错误的将 IIS ...
- 解决github不能访问的问题
亲测有效,授之以鱼不如授之以渔,网上看了很多方法,也试着做了,很多都是治标不治本,最后找到个靠谱的方式:利用DNS查询工具,找到最快的IP地址,然后把host地址换成查询到的结果,方法如下: 在系统的 ...
- 【SpringBoot】SpringBoot 处理后端返回的小数(全局配置 + 定制化配置)
一.抛出问题: 现在的项目中,存在这样的几个问题: 问题一.数据库存的数据类型是BigDecimal,或者代码中计算需要返回BigDecimal的值,由于BigDecimal返回给前端可能存在精度丢失 ...
- 技术面试问题汇总第001篇:猎豹移动反病毒工程师part1
我在2014年7月1日参加了猎豹移动(原金山网络)反病毒工程师的电话面试,但是很遗憾,由于我当时准备不足,加上自身水平不够,面试官向我提出的很多技术问题我都没能答出来(这里面既有基础类的问题,也有比较 ...
- NT 内核函数原型大全
NTSYSAPINTSTATUSNTAPINtAcceptConnectPort(OUT PHANDLE PortHandle,IN PVOID PortIdentifier,IN PPORT_MES ...
- 修改wordpress版权信息
修改页脚版权信息位置:找到C:\wamp64\www\wordpress\wp-content\themes\travelify\library\structure\footer-extensions ...
- 【译】.NET 的新的动态检测分析
随着 Visual Studio 16.9 的发布,Visual Studio 中的检测分析变得更好用了.本文介绍我们新的动态分析工具.这个工具显示了函数被调用的确切次数,并且比我们以前的静态检测工具 ...
- Nebula Graph 的 Ansible 实践
本文首发于 Nebula Graph 公众号 NebulaGraphCommunity,Follow & 看大厂图数据库技术实践 背景 在 Nebula-Graph 的日常测试中,我们会经常在 ...
- 附近的人?你zao吗?
前几天收到一个新的需求,需要实现类似"附近的人"的功能:根据自己当前的定位,获取距离范围内的所有任务地点.刚看到这个需求时有点懵逼,第一想到的就是要利用地球的半径公式去计算距离,也 ...
- win10 下安卓源码同步小技巧
win10下,通过 清华镜像源 AOSP 可以快速拿到 100G 的 .repo 备份 然后 用 repo sync 就可以得到 安卓源码,爽不爽! 下载到win10 e盘下,用powershell ...