目录
1、编写mybatis插件,实现字段自动填充
注意
2、ThreadLocal的简单使用
3、问题:添加员工语句执行成功,但数据库中未添加新员工
4、问题:foreach
1、编写mybatis插件,实现字段自动填充
如何编写插件
@Intercepts({@Signature(type = Executor.class, method = "update",
args = {MappedStatement.class, Object.class})})
public class MybatisMetaInterceptor implements Interceptor {
/**
* invocation存有捕获dao方法的参数,以及sql语句等相关信息<br>
* 实现自动填充功能
* @param invocation
* @return
* @throws Throwable
*/
@Override
public Object intercept(Invocation invocation) throws Throwable {
//获取语句信息
MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
//获取参数
Object param = invocation.getArgs()[1];
//如果dao方法上参数为空,则不进行填充
if (param == null)
return invocation.proceed();
/**
* 判断参数类型是否为ParamMap
* 当dao参数为Collection类或者有两个及以上参数时,Mybatis会把参数封装进一个Map中
*否则,Param的类型就是dao方法上的参数
*/
if (param instanceof MapperMethod.ParamMap) {
MapperMethod.ParamMap paramMap = (MapperMethod.ParamMap) param;
List list = (List) paramMap.get("list");
List<Field[]> fields = getAllFields(list.get(0));
list.stream().forEach(item -> {
try {
setValue(mappedStatement, fields, item);
} catch (Exception e) {
e.printStackTrace();
}
});
} else {
//单个参数
List<Field[]> fields = getAllFields(param);
setValue(mappedStatement, fields, param);
}
return invocation.proceed();
}
/**
* 获取参数字段
* @param param
* @return
*/
private List<Field[]> getAllFields(Object param) {
if (param == null)
return null;
ArrayList<Field[]> list = new ArrayList<>();
Class<?> paramClass = param.getClass();
while (paramClass != null) {
list.add(paramClass.getDeclaredFields());
paramClass = paramClass.getSuperclass();
}
return list;
}
/**
* 对对象的字段进行赋值
* @param mappedStatement
* @param fields
* @param param
* @throws Exception
*/
private void setValue(MappedStatement mappedStatement, List<Field[]> fields, Object param) throws Exception {
Long user = BaseThreadLocal.get();
LocalDateTime time = LocalDateTime.now();
if (SqlCommandType.INSERT == mappedStatement.getSqlCommandType()) {
for (Field[] f : fields) {
for (Field field : f) {
field.setAccessible(true);
if (field.getName().equals("id")) {
field.set(param, YitIdHelper.nextId());
log.info(Long.toString((Long) field.get(param)));
}
if (field.getName().matches("(create|update)User")) {
field.set(param, user);
}
if (field.getName().matches("(create|update)Time")) {
field.set(param, time);
}
field.setAccessible(false);
}
}
}else if (SqlCommandType.UPDATE == mappedStatement.getSqlCommandType()) {
for (Field[] f : fields) {
for (Field field : f) {
field.setAccessible(true);
if (field.getName().matches("updateUser")) {
field.set(param, user);
}
if (field.getName().matches("updateTime")) {
field.set(param, time);
}
field.setAccessible(false);
}
}
}
}
}
注意
1、 Invocation.getArgs();会得到一个Object数组,其中的args[0]是MappedStatement对象,存放了被拦截SQL的类型及相关信息。args[1]存放了被拦截方法的参数,首先来看args[1]的类型。
args[1]的类型有两种ParamMap与参数的自身类型,或者null。
当参数个数只有一个且没有@param,并且不是集合类与数组时,我们可以直接强转。
当args[1]返回了ParamMap,我们需要从该Map中获取值,map的key已在图中标出
注意:在Java8及以上,可在JVM启动时添加参数-parameters,这样mybatis可以直接获取到参数名作为key。
2、将自定义拦截器加入mybatis
yaml:
mybatis:
mapper-locations: classpath:/mapper/*.xml
configuration:
interceptors: //全类名
xml:
<plugins>
<plugin interceptor="org.example.Utils.MybatisMetaInterceptor"/>
</plugins>
yml的Configuration与xml定制配置,只能用一个
配置类方式加入
@Configuration
public class MyBatisConfig {
@Bean
ConfigurationCustomizer mybatisConfigurationCustomizer() {
return new ConfigurationCustomizer() {
@Override
public void customize(Configuration configuration) {
// customize ...
}
};
}
}
相关文章:MyBatis运行原理(三) : 多参数封装Map的流程、查询实现原理 (源码分析)
启用 -parameters 编译选项简化 mybatis @Param 注解重复问题
2、ThreadLocal的简单使用
在看了许多文章后,ThreadLocal的原理应该是这样的:每个Thread内部都有一个Map,当使用threadLocal.set()方法时,会先获取当前线程的Map,在把(threadLocal, value)存入map中。
Threadlocal.get(),则是从当前线程的Map中,获取与threadLocal对应的value
ThreadLocal相当于一个套壳的工具类,本质上还是使用Thread中的Map进行存储。
相关文章
public class BaseThreadLocal {
private static ThreadLocal<Long> local = new ThreadLocal<>();
public static Long get() {
return local.get();
}
public static void set(Long v) {
local.set(v);
}
}
3、问题:添加员工语句执行成功,但数据库中未添加新员工
表面原因:传给后端的Json对象的Id与数据库中的不一致
根本原因:这个id是Long型,前端解析Long的精度比后端小,当后端传给前端id时,前端会有精度丢失问题
解决:传给前端时,把Long转给String在转给前端
@Configuration
public class WebConfig extends WebMvcConfigurationSupport {
@Override
protected void extendMessageConverters(List<HttpMessageConverter<?>> converters) {
log.info("扩展消息转换器...");
//创建消息转换器对象
MappingJackson2HttpMessageConverter messageConverter = new MappingJackson2HttpMessageConverter();
//设置对象转换器,底层使用Jackson将Java对象转为json
messageConverter.setObjectMapper(objectMapper());
//将上面的消息转换器对象追加到mvc框架的转换器集合中
converters.add(0,messageConverter);
}
private ObjectMapper objectMapper() {
final String DATE_TIME_FORMAT = "yyyy-MM-dd HH:mm:ss";
ObjectMapper mapper = new ObjectMapper();
JavaTimeModule javaTimeModule = new JavaTimeModule();
//添加序列化器
javaTimeModule.addSerializer(Long.class, ToStringSerializer.instance);
mapper.registerModule(javaTimeModule);
return mapper;
}
}
注意,以上写法只针对Json对象,当java对象需要转成Json对象时,才会执行上述消息转换器的序列化方法。
4、问题:foreach
相关文章:Java中foreach循环两种实现原理
原理:集合类或者实现了Iterator接口的类的foreach本质上利用了Iterator迭代器
数组的foreach只是把for(var i : arr) ==> for (int i = 0; i < arr.length; i++)的模式
注意点:
foreach循环不可以改变集合,原因:相关文章
public class Main{
public static void main(String[] args) throws IOException {
List<String> list = new ArrayList<>();
list.add("1");
list.add("2");
list.add("3");
list.add("4");
for (String s : list) {
list.remove(s);
}
}
}
但是可以改变集合中的对象的属性。