mybatis mapper问题列表
id出现两次
2018-11-14 16:15:03.833 DEBUG 41432 --- [ main] c.a.i.o.d.mapper.DatvMapper.insert : ==> Preparing: INSERT INTO dwi_dav_env ( id,gmt_create,gmt_modified,is_deleted,id,day,count,unit,env_type ) VALUES( ?,?,?,?,?,?,?,?,? )
Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Column 'id' specified twice
at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance(Constructor.java:423)
at com.mysql.jdbc.Util.handleNewInstance(Util.java:425)
at com.mysql.jdbc.Util.getInstance(Util.java:408)
at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:943)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3970)
at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3906)
子类id使用@KeySql时, 父类不能有@GeneratedValue(generator = "JDBC")
子类同时有@GeneratedValue和@KeySql, 生效的是第二个?
mapper找不到
mybatis 指定mapper位置是为了扫描这些interface,并生成对应的proxy对象
而不是扫描有哪些PO, interface会引用PO,只要interface被扫描到了,再具体操作PO的CRUD时一般不会有问题。
有问题原因:1. interface没有被扫描到 2. 自定义了一些sql provider (比如逻辑删除), proxy生成后 对PO进行检查失败 抛异常了
PO可以继承统一的父类,也可以不继承。
mapper中的方法找不到
扫描后 所有mapper的statement都会解析到Configuration.class中
org.apache.ibatis.session.Configuration#hasStatement(java.lang.String, boolean)
原因 重构时①忘记copy xml文件, ②包名变更, namespace没有对应上
参考:https://blog.csdn.net/softwarehe/article/details/8889206
mybatis-plus中默认xml扫描位置/mapper/**.xml,如果自定义xml位置 ,要修改mybatis-plus.mapperLocation
mybatis-spring-boot-starter mybatis-plus-boot-starter 这两个starter最好不要放在一起
pagehelper-spring-boot-starter中默认引用了mybatis-spring-boot-starter,和mybatis-plus分页拦截器是否有冲突?
public class PageHelperAutoConfiguration {
@Autowired
private List<SqlSessionFactory> sqlSessionFactoryList;
}
注入了多个sqlSeesionfactory, 判断pagehelper是否生效,启动时在PageHelperAutoConfiguration中打断点,看看这个自动配置是否生效
mybatis.mapper-locations只配置xml位置
@MapperScan配置的是xxxmapper.java位置
java.lang.ArrayStoreException: sun.reflect.annotation.TypeNotPresentExceptionProxy
原因: PageHelper的autoConfig依赖MybatisAutoConfiguration.class, 但是因为集成mybatis-plus的原因 把mybatis-spring-boot-starter排除掉了, 导致MybatisAutoConfiguration.class实际在项目中不存在, 最终导致jvm类加载异常
解决方式: 把mybatis-spring-boot-starter加回来, mapper扫描使用mybatis-plus和mybatis-spring两种配置格式
save之后,id仍然是null
- 没有使用包装类
- mapper.xml中自定义了 insert方法, 没有走mybatis-plus的baseMapper方法
mapper中出现同名方法
因为是映射到同一个sqlId, 可能会出错,或者某个方法没有注册
继承多个baseDao时,容易出现这种情况
mybatis plugin
github page-helper 使用线程ThreadLocal绑定参数
bamidou mybatis-plus直接把参数写到mapper的方法内,不使用ThreadLocal, 充分利用mybatis plugin特性
分页问题
mybatis-plus的分页插件,默认没有启用, 需要自己加Configuration
https://mp.baomidou.com/guide/page.html
数据total为0时,不再加limit 日志中就会看不到limit, 通过PaginationInterceptor
debug查看是否启用
多参数绑定 xml中以 对象名.(点)对象属性名 的方式访问
结果映射:找不到field 值为null, 定位Column名字不对
createTime createdTime
根据db column name ---> set field
xml中的columnname可以是CREATED_TIME或者createdTime (官方示例), mybatis根据是否有大小写 自动判断是否要用驼峰命名
@TableField注解并不会处理xml中的result mapping, 列名和属性名对应不上,值就会是null, 这个注解只在使用MP的baseMapper相关方法中有效
使用@Select时, 如果有列名不一致,需要配合使用@Results
结果映射的其他规则
-
返回值是list, 数据库值为null, java中的结果是
new ArrayList()
, 和hibernate规则一致,不需要判断if(xx != null)
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets
-
返回值是单个Object, 数据库值为null, java中的结果也为null, 需要判断
xxx!=null
-
数据库值为多个, 直接抛异常TooManyResultsException
org.apache.ibatis.session.defaults.DefaultSqlSession#selectOne(java.lang.String, java.lang.Object)
-
返回基本类型int时 会报NPE异常
ifnull(max(xxx), 0)
-
同一个DB COLUMN 可以映射到多个java field
这个在项目重构过程中 很有用的一个特性。源代码:org.apache.ibatis.executor.resultset.DefaultResultSetHandler#applyPropertyMappings
-
自动结果映射
可以混用java field name和DB COLUMN_NAME, 但是有个条件是 resultMap中不能再嵌套<association property="xxx" resultMap="xxxMap"/>
!!!
association和自动映射有冲突, 因为auto-mapping可能会把一个COLLUM映射给两个不通entity的同名字段
https://mybatis.org/mybatis-3/sqlmap-xml.html#Auto-mapping
例子:
本来STATUS是自动映射的,没有这一句, 但是随着业务发展 加上了association, 这时auto-mapping失效了 造成status=null
BaseTypeHandler
对应hibernate中的@Type
注解,实现类型转换
如果需要Enum类型的数据 insert的时候插入code, select的时候返回text, 则可以自定义一个TypeHandler (同时在mapper中使用typeHandler引用这个类), 实现互转
但并不适用与PreparedStatement.setArray("1", connection.createArrayOf("aa", "bb", "cc"))实现一个?占位符 传递多个参数的情形。
setArray只在少数数据库支持,mysql, oracle都不支持这个JDBC特性
这里也可以用于JSON--> VARCHAR的自动互转
mybatis plus中的@TableField(typeHandler=xxx.class)
需要配合class上的 @TableName(autoResultMap=true)
使用,默认autoResultMap=false
对于带有DB function的column 不能用@TableField直接映射,
比如 case when , sum , ... , @TableField("NVL(xxx,yyy)")
在oracle中虽然可以映射成功 (表达式中间不能有空格),但是其他数据库就不一定了,不通用
正确的做法是
方法1:xxx, yyy各映射一个字段, 在应用层实现DB function的功能
方法2:在xml中使用<resultMap>
, 不使用BaseMapper中的方法
hibernate的做法是配置一个@Formula()
, 而不是@Column
https://stackoverflow.com/questions/2986318/how-to-map-calculated-properties-with-jpa-and-hibernate
Logging
org.apache.ibatis.logging.jdbc.ConnectionLogger
org.apache.ibatis.logging.jdbc.PreparedStatementLogger
SELECT SQL为什么要加事务
非常简单的GET当然可以不加,
但业务上通常很复杂,同一条sql要执行多次(多个方法要引用),不加事务的话就不能利用到mybatis的一级缓存,多次执行同一条sql,徒然增加了数据库的压力.
加上事务 就可以用到一级缓存了, 这也是hibernate必须加事务的原因(状态管理是另一方面)
transaction selectById
aaa = xxxMapper.selectById()
aaa = xxxMapper.selectById() # 此调用用到了缓存!!
xxxMapper.updateXXX()
bbb = xxxMapper.selectById() # 此调用没用缓存!!!, 因为上一步有updateXXX操作,mybatis自动更新策略不使用缓存
include 标签
<include>
是可以传静态参数的
<include refId='xxx'>
<property name='yyy1' value='yyyy2'
</include>
引用property时要用 ${}
, 不能用#
bind标签可以调用java的静态代码
ognl能力 @class@method(#{xxxx})
占位符设置默认值
${username:aa_user}
要用${:}
这个才用到OGNl, #{:}
这种好像不行
启用并修改成其他的分割符:
mybatis-plus.configuration-properties.org.apache.ibatis.parsing.PropertyParser.enable-default-value=true
mybatis-plus.configuration-properties.org.apache.ibatis.parsing.PropertyParser.default-value-separator=?:
@Param用还是不用
https://stackoverflow.com/questions/59668117/how-to-properly-use-the-param-annotation-of-mybatis
jdk8之后, 单个参数的时候才需要用
多数据源
@MapperScan()
中有个sqlSessionFactoryRef,专门用来加载多数据源的
根据不同的目录,创建对应的sessessionFactory就行了
interceptor plugin也是加载到sessionFactory的
MapperScannerConfigurer.postProcessBeanDefinitionRegistry()
mapper是绑定到sessionFactory的, mapper调用的时候就能找到对应datasource的sessionFactory.openSeesion()
https://developpaper.com/analyzing-the-session-mechanism-of-mybatis-from-the-perspective-of-source-code/
一段异常
Caused by: org.apache.ibatis.builder.BuilderException: Error evaluating expression ''. Cause: org.apache.ibatis.ognl.ExpressionSyntaxException: Malformed OGNL expression: [org.apache.ibatis.ognl.ParseException: Encountered "<EOF>" at line 1, column 0.
Was expecting one of:
":" ...
"not" ...
"+" ...
"-" ...
"~" ...
"!" ...
"(" ...
"true" ...
"false" ...
"null" ...
"#this" ...
"#root" ...
"#" ...
"[" ...
"{" ...
"@" ...
"new" ...
<IDENT> ...
<DYNAMIC_SUBSCRIPT> ...
"\'" ...
"`" ...
"\"" ...
<INT_LITERAL> ...
<FLT_LITERAL> ...
]
at org.apache.ibatis.scripting.xmltags.OgnlCache.getValue(OgnlCache.java:48)
at org.apache.ibatis.scripting.xmltags.ExpressionEvaluator.evaluateIterable(ExpressionEvaluator.java:43)
at org.apache.ibatis.scripting.xmltags.ForEachSqlNode.apply(ForEachSqlNode.java:54)
at org.apache.ibatis.scripting.xmltags.MixedSqlNode.lambda$apply$0(MixedSqlNode.java:32)
at java.util.ArrayList.forEach(ArrayList.java:1259)
at org.apache.ibatis.scripting.xmltags.MixedSqlNode.apply(MixedSqlNode.java:32)
at org.apache.ibatis.scripting.xmltags.DynamicSqlSource.getBoundSql(DynamicSqlSource.java:39)
at org.apache.ibatis.mapping.MappedStatement.getBoundSql(MappedStatement.java:305)
at com.baomidou.mybatisplus.extension.plugins.MybatisPlusInterceptor.intercept(MybatisPlusInterceptor.java:69)
at org.apache.ibatis.plugin.Plugin.invoke(Plugin.java:62)
at com.sun.proxy.$Proxy316.query(Unknown Source)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:151)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:145)
at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:140)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.mybatis.spring.SqlSessionTemplate$SqlSessionInterceptor.invoke(SqlSessionTemplate.java:427)
... 137 common frames omitted
Caused by: org.apache.ibatis.ognl.ExpressionSyntaxException: Malformed OGNL expression:
at org.apache.ibatis.ognl.Ognl.parseExpression(Ognl.java:181)
at org.apache.ibatis.scripting.xmltags.OgnlCache.parseExpression(OgnlCache.java:55)
at org.apache.ibatis.scripting.xmltags.OgnlCache.getValue(OgnlCache.java:46)
... 155 common frames omitted
Caused by: org.apache.ibatis.ognl.ParseException: Encountered "<EOF>" at line 1, column 0.
Was expecting one of:
这里解析失败,直接导致ms.getBoundSql返回了null
if test中的String比较
string方法是可以直接调用的
<if test='xxx.indexOf("xxx")>0' > ...
<if test='xxx.contains("xxx")' > ...
对于单个char的比较,需要加toString() 或者用双引号比较。 否则有NumberFormatException
<if test='xxx == "Y"' > ...
<if test="xxx == 'Y'.toString()" > ...
查找param.method()时 大小写不敏感, 但是param.field大小写是敏感的
In MyBatis, the if
statement in XML mapping files is case-insensitive when checking against Java methods. This means that param.isXxxMethod()
is case-insensitive, and it will match both isXxxMethod
and isxxxmethod
in Java.
For example, if you have the following if
statement in your MyBatis XML mapping file:
<if test="param.isXxxMethod()">
...
</if>
It would match both param.isXxxMethod()
and param.isxxxmethod()
methods in your Java code, regardless of the case of the Xxx
part.
However, it is worth mentioning that this case-insensitive behavior only applies when matching against Java methods. If you are using other variables or properties in the if
statement, the comparison may be case-sensitive.