关于 MySQL 和 MyBatis 易错的几个点

由于某些不可抗拒力原因,自从开博以来断更了一个月,昨天晚上突然发现竟然解封了,今天立即写一篇小文章感谢党感谢政府感谢人民。话说,这一周有一个实习的同学,在写一个小东西的时候,发现一个问题,排序没有生效,刚好之前我也看过另外一个问题,现在算是总结一下。 ORDER BY 不生效 代码大概就是: SELECT * FROM t ORDER BY "id DESC"; 我们其实可以很明显的看出来这个 SQL 有问题,ORDER BY 的后面多了引号,关键是这个 SQL 报错吗?如果 id 是随便写的一个不存在的列报错吗?答案是都不报错,排序也不生效,大家可以测试一些,这有时候就比较坑了,具体为什么会出现这个错误,后面再说,先说第二个。 分页问题 代码如下: /** * 开始页码 */ private String pageSize; /** * 分页量 */ private String offset; <if test="pageSize != null"> <if test="offset != null"> limit #{offset}, #{pageSize} </if> <if test="offset == null"> limit #{pageSize} </if> </if> 这个错误就比较明显了,一般人也不会犯,为什么要单独说一下呢?因为某同事说,分页用 # 会出错,但是我记得我一直用 # 没问题啊,刚好在遇到上面这个问题的时候,顺便测试了一些,还真有错,奇怪啊,其实原因很简单,就是分页的对象的属性有误,我们知道分页的属性,一般都是用 Integer,这个地方却写了 String,真是没事找事啊,一般谁会这么写?个人认为除非脑子有病。下面看第三个问题,也是一个比较诡异的问题。 UPDATE UPDATE t SET username = "BridgeLi" AND passwd = "BridgeLi" WHERE id = 1; 这个 SQL 有问题吗?会报错吗?这个问题有时候真不太看得出来,其实也很明显,SET 后面的各个列应该是 “,” 相连,而这个用的是 AND,有时候脑子一抽,还真有可能写错。但是会报错吗?这个问题还真不好答复,因为这个 SQL 这么写,相当于: ...

July 8, 2019 · 1 min · 186 words · Bridge Li

org.apache.ibatis.builder.IncompleteElementException: Could not find parameter map

上周和同事一块开发一个功能模块,在开发中拉下来同事代码,在测试的时候,突然跑不通了,报错信息如下: org.apache.ibatis.builder.IncompleteElementException: Could not find parameter map java.util.Map at org.apache.ibatis.builder.MapperBuilderAssistant.getStatementParameterMap(MapperBuilderAssistant.java:320) at org.apache.ibatis.builder.MapperBuilderAssistant.addMappedStatement(MapperBuilderAssistant.java:296) at org.apache.ibatis.builder.xml.XMLStatementBuilder.parseStatementNode(XMLStatementBuilder.java:109) at org.apache.ibatis.session.Configuration.buildAllStatements(Configuration.java:753) at org.apache.ibatis.session.Configuration.hasStatement(Configuration.java:723) at org.apache.ibatis.session.Configuration.hasStatement(Configuration.java:718) at org.apache.ibatis.binding.MapperMethod$SqlCommand.<init>(MapperMethod.java:201) at org.apache.ibatis.binding.MapperMethod.<init>(MapperMethod.java:48) at org.apache.ibatis.binding.MapperProxy.cachedMapperMethod(MapperProxy.java:59) at org.apache.ibatis.binding.MapperProxy.invoke(MapperProxy.java:52) at com.sun.proxy.$Proxy40.insertSelective(Unknown Source) at com.weidai.urge.service.disposition.BidDispositionOrderServiceTest.testInsertSelective(BidDispositionOrderServiceTest.java:48) at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:57) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:606) at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50) at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12) at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47) at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17) at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75) at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86) at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84) at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:254) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:89) at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290) at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71) at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288) at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58) at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268) at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61) at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70) at org.junit.runners.ParentRunner.run(ParentRunner.java:363) at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:193) at org.junit.runner.JUnitCore.run(JUnitCore.java:137) at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68) at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51) at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:237) at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70) Caused by: java.lang.IllegalArgumentException: Parameter Maps collection does not contain value for java.util.Map at org.apache.ibatis.session.Configuration$StrictMap.get(Configuration.java:853) at org.apache.ibatis.session.Configuration.getParameterMap(Configuration.java:625) at org.apache.ibatis.builder.MapperBuilderAssistant.getStatementParameterMap(MapperBuilderAssistant.java:318) &#8230; 39 more 看报错信息,也就是自己跑一个 junit test,测试批量插入,突然他就不行了,再看看自己写的批量插入没问题啊,我也没用 map,感觉好奇怪,就网上搜了一下这个问题,原来确实有问题,也确实在这个配置文件中,提示的错误也对,就是提示的位置不对。mybatis 中只要有任何一个地方报错,都无法通过。最后搜了一下发现是刚刚来下来的代码,同事新增了一个方法上将 parameterType 写成了 parameterMap 了 ...

June 2, 2018 · 1 min · 153 words · Bridge Li

介绍一个 Mybatis 插件:mybatis-generator-plugin

在实际开发中,我们都是先建表,然后根据表生成对应的 Java 类,现在很流行的 ORMapping 框架是: Mybatis,所以我们需要生成 entity、mapper 和 xml,我们都知道有一个插件是:mybatis-generator,使用它就可以很方便的生成这些结构化的重复性基础性的代码,但是他有一个问题,生成的查询没有分页,所以很烦。然后我搜索了一些资料,重新封装了一下,重新命名为:mybatis-generator-plugin,具体源码放在了 GitHub 上: https://github.com/bridgeli/mybatis-generator-plugin 大家可以 clone 下来,deploy 到自己公司的私服,或者 install 到本地使用。具体就是在 pom 文件中。具体用法 在 pom 文件中添加如下内容: <properties> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> </properties> <build> <plugins> <plugin> <groupId>org.mybatis.generator</groupId> <artifactId>mybatis-generator-maven-plugin</artifactId> <version>1.3.5</version> <dependencies> <dependency> <groupId>cn.bridgeli</groupId> <artifactId>mybatis-generator-plugin</artifactId> <version>0.0.1-SNAPSHOT</version> </dependency> </dependencies> <configuration> <verbose>true</verbose> <overwrite>true</overwrite> <configurationFile>src/main/resources/generatorConfig.xml</configurationFile> </configuration> </plugin> </plugins> </build> 其中:configurationFile 配置 generatorConfig.xml 文件的位置,我们生成的对应的文件,都是根据这个文件来的。所以第二步就是: 在对应的文件下添加该文件,它的模板如下: <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE generatorConfiguration PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN" "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd"> <generatorConfiguration> <!&#8211; 请替换本地jar路径 此文件不要上传 &#8211;> <classPathEntry location="/Users/bridgeli/.m2/repository/mysql/mysql-connector-java/5.1.33/mysql-connector-java-5.1.33.jar" /> <!&#8211; mvn mybatis-generator:generate &#8211;> <context id="oneHour" targetRuntime="cn.bridgeli.plugin.IntrospectedTableMyBatis3ImplExt"> <property name="suppressAllComments" value="true" /> <property name="useActualColumnNames" value="false" /> <!&#8211; 格式化java代码 &#8211;> <property name="javaFormatter" value="org.mybatis.generator.api.dom.DefaultJavaFormatter" /> <!&#8211; 格式化XML代码 &#8211;> <property name="xmlFormatter" value="org.mybatis.generator.api.dom.DefaultXmlFormatter" /> <!&#8211; 配置插件 &#8211;> <plugin type="cn.bridgeli.mybatis.MySQLLimitPlugin"></plugin> <commentGenerator> <!&#8211; 是否去除自动生成的注释 true:是 : false:否 &#8211;> <property name="suppressAllComments" value="true" /> </commentGenerator> <!&#8211;数据库连接的信息:驱动类、连接地址、用户名、密码 &#8211;> <jdbcConnection driverClass="com.mysql.jdbc.Driver" connectionURL="jdbc:mysql://ip:port/databasename" userId="username" password="password"> </jdbcConnection> <javaTypeResolver> <property name="forceBigDecimals" value="false" /> </javaTypeResolver> <!&#8211; 配置model生成位置 &#8211;> <javaModelGenerator targetPackage="cn.bridgeli.entity" targetProject="src/main/java"> <property name="enableSubPackages" value="true" /> <property name="trimStrings" value="true" /> </javaModelGenerator> <!&#8211; 配置sqlmap生成位置 &#8211;> <sqlMapGenerator targetPackage="cn/bridgeli/mapper" targetProject="src/main/resources"> <property name="enableSubPackages" value="true" /> </sqlMapGenerator> <!&#8211; 配置mapper接口生成位置 &#8211;> <javaClientGenerator type="XMLMAPPER" targetPackage="cn.bridgeli.mapper" targetProject="src/main/java"> <property name="enableSubPackages" value="true" /> </javaClientGenerator> <!&#8211; 配置表信息 &#8211;> <table tableName="table_name" enableCountByExample="true" domainObjectName="DomainObjectName" enableDeleteByExample="false" enableSelectByExample="true" enableUpdateByExample="true"> <generatedKey column="ID" sqlStatement="MySQL" identity="true" /> </table> </context> </generatorConfiguration> 这里面需要说明就是: ...

April 29, 2018 · 2 min · 260 words · Bridge Li

Spring加Mybatis实现MySQL数据库主从读写分离

上周在一个同事的指点下,实现了Spring加Mybatis实现了MySQL的主从读写分离,今天记一下笔记,以供自己今后参考,下面是配置文件的写法。 数据源也就是jdbc.properties,因为是主从读写分离,那么肯定有两个数据源了 jdbc.driver=org.mariadb.jdbc.Driver \# 从库,只读 slave.jdbc.url=jdbc:mariadb://xxx.xxx.xxx.xxx:3306/xxx?characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&noAccessToProcedureBodies=true&autoReconnect=true slave.jdbc.username=xxx slave.jdbc.password=xxx \# 主库,需要写 master.jdbc.url=jdbc:mariadb://xxx.xxx.xxx.xxx:3306/xxx?characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&noAccessToProcedureBodies=true&autoReconnect=true master.jdbc.username=xxx master.jdbc.password=xxx 这个非常简单和普通的区别不是很大,另外数据库的驱动是:mariadb,动下面就是第二个配置文件spring.xml spring.xml <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd "> <!&#8211; Import properties file &#8211;> <context:property-placeholder location="classpath:jdbc.properties" /> <!&#8211; Auto Scan &#8211;> <context:component-scan base-package="cn.bridgeli.demo" /> </beans> 这个文件很简单,有两个作用,①. 引入数据源配置文件;②. spring扫描的文件的路径 spring-mybatis.xml配置文件 <?xml version="1.0" encoding="UTF-8"?> <beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:tx="http://www.springframework.org/schema/tx" xmlns:aop="http://www.springframework.org/schema/aop" xsi:schemaLocation=" http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd "> <bean id="slaveDataSourceImpl" class="com.jolbox.bonecp.BoneCPDataSource" destroy-method="close"> <property name="driverClass" value="${jdbc.driver}" /> <property name="jdbcUrl" value="${slave.jdbc.url}" /> <property name="username" value="${slave.jdbc.username}" /> <property name="password" value="${slave.jdbc.password}" /> <!&#8211; 检查数据库连接池中空闲连接的间隔时间,单位是分,默认值:240,如果要取消则设置为0 &#8211;> <property name="idleConnectionTestPeriodInMinutes" value="10" /> <!&#8211; 连接池中未使用的链接最大存活时间,单位是分,默认值:60,如果要永远存活设置为0 &#8211;> <property name="idleMaxAgeInMinutes" value="10" /> <!&#8211; 每个分区最大的连接数 &#8211;> <property name="maxConnectionsPerPartition" value="20" /> <!&#8211; 每个分区最小的连接数 &#8211;> <property name="minConnectionsPerPartition" value="10" /> <!&#8211; 分区数 ,默认值2,最小1,推荐3-4,视应用而定 &#8211;> <property name="partitionCount" value="3" /> <!&#8211; 每次去拿数据库连接的时候一次性要拿几个,默认值:2 &#8211;> <property name="acquireIncrement" value="3" /> <!&#8211; 缓存prepared statements的大小,默认值:0 &#8211;> <property name="statementsCacheSize" value="50" /> <!&#8211; 在做keep-alive的时候的SQL语句 &#8211;> <property name="connectionTestStatement" value="select 1 from dual" /> <!&#8211; 在每次到数据库取连接的时候执行的SQL语句,只执行一次 &#8211;> <property name="initSQL" value="select 1 from dual" /> <property name="closeConnectionWatch" value="false" /> <property name="logStatementsEnabled" value="true" /> <property name="transactionRecoveryEnabled" value="true" /> </bean> <bean id="masterDataSourceImpl" class="com.jolbox.bonecp.BoneCPDataSource" destroy-method="close"> <property name="driverClass" value="${jdbc.driver}" /> <property name="jdbcUrl" value="${master.jdbc.url}" /> <property name="username" value="${master.jdbc.username}" /> <property name="password" value="${master.jdbc.password}" /> <!&#8211; 检查数据库连接池中空闲连接的间隔时间,单位是分,默认值:240,如果要取消则设置为0 &#8211;> <property name="idleConnectionTestPeriodInMinutes" value="10" /> <!&#8211; 连接池中未使用的链接最大存活时间,单位是分,默认值:60,如果要永远存活设置为0 &#8211;> <property name="idleMaxAgeInMinutes" value="10" /> <!&#8211; 每个分区最大的连接数 &#8211;> <property name="maxConnectionsPerPartition" value="20" /> <!&#8211; 每个分区最小的连接数 &#8211;> <property name="minConnectionsPerPartition" value="10" /> <!&#8211; 分区数 ,默认值2,最小1,推荐3-4,视应用而定 &#8211;> <property name="partitionCount" value="3" /> <!&#8211; 每次去拿数据库连接的时候一次性要拿几个,默认值:2 &#8211;> <property name="acquireIncrement" value="3" /> <!&#8211; 缓存prepared statements的大小,默认值:0 &#8211;> <property name="statementsCacheSize" value="50" /> <!&#8211; 在做keep-alive的时候的SQL语句 &#8211;> <property name="connectionTestStatement" value="select 1 from dual" /> <!&#8211; 在每次到数据库取连接的时候执行的SQL语句,只执行一次 &#8211;> <property name="initSQL" value="select 1 from dual" /> <property name="closeConnectionWatch" value="false" /> <property name="logStatementsEnabled" value="true" /> <property name="transactionRecoveryEnabled" value="true" /> </bean> <!&#8211; DataSource/Master &#8211;> <bean id="masterDataSource" class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy"> <property name="targetDataSource" ref="masterDataSourceImpl" /> </bean> <bean id="masterTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="masterDataSource" /> </bean> <bean id="masterTransactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="masterTransactionManager" /> </bean> <!&#8211; DataSource/Slave &#8211;> <bean id="slaveDataSource" class="org.springframework.jdbc.datasource.TransactionAwareDataSourceProxy"> <property name="targetDataSource" ref="slaveDataSourceImpl" /> </bean> <bean id="slaveTransactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> <property name="dataSource" ref="slaveDataSource" /> </bean> <bean id="slaveTransactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> <property name="transactionManager" ref="slaveTransactionManager" /> </bean> <!&#8211; Mybatis/Master &#8211;> <bean id="masterSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="masterDataSource"></property> <property name="configLocation" value="classpath:mybatis.xml" /> <property name="typeAliasesPackage" value="cn.bridgeli.demo.entity" /> <property name="mapperLocations"> <list> <value>classpath:cn/bridgeli/demo/mapper/master/*.xml</value> </list> </property> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="cn.bridgeli.demo.mapper.master" /> <property name="sqlSessionFactoryBeanName" value="masterSqlSessionFactory" /> </bean> <!&#8211; Mybatis/Slave &#8211;> <bean id="slaveSqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="slaveDataSource"></property> <property name="configLocation" value="classpath:mybatis.xml" /> <property name="typeAliasesPackage" value="cn.bridgeli.demo.entity" /> <property name="mapperLocations"> <list> <value>classpath:cn/bridgeli/demo/mapper/slave/*.xml</value> </list> </property> </bean> <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> <property name="basePackage" value="cn.bridgeli.demo.mapper.slave" /> <property name="sqlSessionFactoryBeanName" value="slaveSqlSessionFactory" /> </bean> <!&#8211; Configuration transaction advice &#8211;> <tx:advice id="txAdvice" transaction-manager="masterTransactionManager"> <tx:attributes> <tx:method name="add*" propagation="REQUIRED" /> <tx:method name="update*" propagation="REQUIRED" /> <tx:method name="delete*" propagation="REQUIRED" /> <tx:method name="get*" read-only="true" propagation="SUPPORTS" /> <tx:method name="list*" read-only="true" propagation="SUPPORTS" /> </tx:attributes> </tx:advice> <!&#8211; Configuration transaction aspect &#8211;> <aop:config> <aop:pointcut id="systemServicePointcut" expression="execution(\* cn.bridgeli.demo.service.\*.*(..))" /> <aop:advisor advice-ref="txAdvice" pointcut-ref="systemServicePointcut" /> </aop:config> </beans> 这个配置文件老夫是完整的copy了下来,看起来也比较易懂,就不做解释了,需要说明的mybatis下那些dao的接口,分别对应cn.bridgeli.demo.mapper.master、cn.bridgeli.demo.mapper.slave,cn.bridgeli.demo.mapper.master下的这些dao接口是要写的,另一个是读的,这些接口对应的配置文件肯定就是他们对应的文件夹下面的xml文件了,在将来的项目中几乎可以照抄 ...

May 3, 2015 · 3 min · 502 words · Bridge Li

MyBatis下最好的分页实现:mybatis-paginator使用入门

前两天写一个项目,发现在MyBatis下一个最好的分页实现类库mybatis-paginator,今天就写一篇其入门教程供大家参考。 先引入maven依赖 <dependency> <groupId>com.github.miemiedev</groupId> <artifactId>mybatis-paginator</artifactId> <version>1.2.15</version> </dependency> 从这个依赖中,我们可以看到 1 他是mybatis的一个插件,2 其开源在GitHub上,感兴趣的可以去GitHub上搜源码看看,既然是mybatis的插件,下一步肯定就是和mybatis的集成了. 和mybatis集成 ①. 新建一个mybatis.xml的配置文件,其内容如下: <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <settings> <setting name="cacheEnabled" value="true" /> <setting name="lazyLoadingEnabled" value="true" /> </settings> <plugins> <plugin interceptor="com.github.miemiedev.mybatis.paginator.OffsetLimitInterceptor"> <property name="dialectClass" value="com.github.miemiedev.mybatis.paginator.dialect.MySQLDialect" /> <property name="asyncTotalCount" value="true" /> </plugin> </plugins> </configuration> ②. 和spring集成,即在spring-mybatis.xml中引入该文件 &#8230;&#8230; <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> <property name="dataSource" ref="dataSource"></property> <property name="configLocation" value="classpath:mybatis.xml" /> <property name="typeAliasesPackage" value="cn.bridgeli.demo.entity" /> <property name="mapperLocations" value="classpath:cn/bridgeli/demo/mapper/*.xml" /> </bean> &#8230;&#8230; 因为这个文件太长,就不贴全文了,仅把位置这一块贴出来,当我们把这些配置工作做好之后,下一步就是如何使用了。 ...

April 26, 2015 · 2 min · 219 words · Bridge Li