mybatis与spring整合详解
mybatis可以单独使用,也可以与spring进行整合。我们在自己的项目中,需要引入相关依赖,如你的项目管理工具使用的是maven,可以在pom.xml中添加以下配置
1 | <!--spring依赖--> |
在整合之后,框架的之间的依赖关系如下图所示:
整合一:SqlSessionFactoryBean
在单独使用mybatis时,我们通过SqlSessionFactoryBuilder 来创建SqlSessionFactory。当mybatis与spring进行整合时,我们使用mybatis-spring提供的SqlSessionFactoryBean 来替代,SqlSessionFactoryBean实现了 Spring 的 FactoryBean 接口,用于创建 SqlSessionFactory 对象实例。
SqlSessionFactoryBean的配置有2种风格:
- 保留mybatis的核心配置文件
- 不保留mybatis的核心配置文件
保留mybatis的核心配置文件
我们将绝大部分关于mybatis的配置依然保留在mybatis的核心配置文件mybatis-config文件中,以下是一个示例:
mybatis-config.xml
1 | <?xml version="1.0" encoding="UTF-8" ?> |
细心的读者注意到,在mybatis-config.xml文件中我们并没有通过
此时SqlSessionFactoryBean配置方式如下:
1 | <!—-SqlSessionFactoryBean-—> |
不保留mybatis的核心配置文件
从mybatis-spring 1.3.0之后,我们可以移除mybatis-config.xml文件,将所有关于myabtis的配置都通过SqlSessionFactoryBean来指定。
以下配置案例演示了与上述等价的配置:
1 | <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean"> |
在mybatis与spring整合后, 通常我们不会再直接使用SqlSessionFactory。mybatis-spring提供了其他更加易于操作的工具类,如SqlSessionTemplate、SqlSessionDaoSupport,当然还有其他更加高级的使用方式,如:MapperFactoryBean,MapperScannerConfigurer。
整合二:使用SqlSessionTemplate
SqlSessionTemplate 是 mybatis-spring 的核心,其实现了SqlSession接口。在使用了SqlSessionTemplate之后,我们不再需要通过SqlSessionFactory.openSession()方法来创建SqlSession实例;使用完成之后,也不要调用SqlSession.close()方法进行关闭。另外,对于事务,SqlSessionTemplate 将会保证使用的 SqlSession 是和当前 Spring 的事务相关的。
SqlSessionTemplate依赖于SqlSessionFactory,其配置方式如下所示:
1 | <bean id="sqlSessionTemplate" class="org.mybatis.spring.SqlSessionTemplate"> |
之后我们可以在UserD类中直接进行注入。SqlSessionTemplate 是线程安全的, 可以被多个 DAO 所共享使用,以下是一个示例:
1 | public class UserDao { |
SqlSessionTemplate本质上是一个代理,关于SqlSessionTemplate的源码分析,后期会抽空编写。
整合三:继承SqlSessionDaoSupport
mybatis提供了抽象类SqlSessionDaoSupport,调用其getSqlSession()方法你会得到一个 SqlSessionTemplate,之后可以用于执行 SQL 方法, 就像下面这样:
1 | public class UserDao extends SqlSessionDaoSupport{ |
SqlSessionDaoSupport 需要一个 sqlSessionFactory 或 sqlSessionTemplate 属性来设置 。如果两者都被设置了 , 那么SqlSessionFactory是被忽略的。由于我们的UserDao类继承了SqlSessionDaoSupport,所以你可以在UserDao类中进行设置:
1 | <bean id="userDao" class="com.tianshouzhi.zebracost.dao.UserDao"> |
或:
1 | <bean id="userDao" class="com.tianshouzhi.zebracost.dao.UserDao"> |
事实上,如果你提供的是一个SqlSessionFactory,SqlSessionDaoSupport内部也会使用其来构造一个SqlSessionTemplate实例。
整合四:MapperFactoryBean
无论是使用SqlSessionTemplate,还是继承SqlSessionDaoSupport,我们都需要手工编写DAO类的代码。熟悉mybatis同学知道,SqlSession有一个getMapper()方法,可以让我们通过映射器接口的方式来使用mybatis。
例如针对有以下UserMapper.xml
1 | <?xml version="1.0" encoding="UTF-8" ?> |
我们可以定义以下UserMapper接口:
1 | package com.tianshouzhi.mybatis.mapper; |
细心的读者注意到:
1、映射文件的namespace属性值”com.tianshouzhi.mybatis.mapper.UserMapper”就是UserMapper接口的全路径
2、映射文件中的
之后,我们调用UserMapper接口中的方法,就会执行UserMapper.xml中对应的sql。
你甚至可以通过注解的方式直接将sql写到UserMapper接口的方法上,此时你就不再需要UserMapper.xml,如:
1 | package com.tianshouzhi.mybatis.mapper; |
此时我们可以修改mybatis-config.xml中
1 | <mappers> |
之后,我们可以通过以下方式来使用UserMapper接口,更加直观,不容易出错。
1 | SqlSession session = sqlSessionFactory.openSession(); |
但是在与spring进行整合时,是否有更加简单的使用方法呢?能够在一个业务Bean中注入UserMapper接口,不需要通过SqlSession的getMapper来创建。我们期望的使用方式如下:
1 | public class UserService { |
直接这样操作显然是会报错的,因为UserMapper是一个接口,且不是spring管理的bean,因此无法直接注入。
这个时候,本节的主角MapperFactoryBean登场了,通过如下配置,MapperFactoryBean会针对UserMapper接口创建一个代理,并将其变成spring的一个bean。MapperFactoryBean继承了SqlSessionDaoSupport类,这也是为什么我们先介绍SqlSessionDaoSupport,再介绍MapperFactoryBean的原因。显然的,你可以想到,我们可以为MapperFactoryBean指定一个SqlSessionTemplate或者SqlSessionFactory,如果两个属性都设置了,那么 SqlSessionFactory 就会被忽略。
通过以下配置,我们就可以在一个业务bean中直接注入UserMapper接口了:
1 | <bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean"> |
你可能想知道MapperFactoryBean为什么具有这样的魔力,但是这不是本文的重点,本文讲解的是mybatis是如何spring进行整合的。笔者将其会在其他章节分析MapperFactoryBean的源码。
上述方式,已经是mybatis与spring进行时理想的方式了。但是如果你的业务很复杂,有许多的XxxMapper接口要配置,针对每个接口都配置一个MapperFactoryBean,会使得我们的配置文件很臃肿。关于这一点,mybatis团队提供了MapperScannerConfigurer来帮助你解决这个问题。
整合五:MapperScannerConfigurer
通常我们在开发时,会将同一类型的类放于同一个包下,例如现在我们com.tianshouzhi.mybatis.mappers包下,有两个接口:UserMapper、UserAccountMapper。
MapperScannerConfigurer可以指定扫描某个包,将这个包下的所有接口,自动的为每个映射器接口都注册一个MapperFactoryBean。配置如下:
1 | <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> |
其中:basePackage 属性是让你为映射器接口的包路径。如果的映射器接口位于不同的包下,可以使用分号”;”或者逗号”,”进行分割。如:
1 | <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> |
如果你指定的还包含子包,子包中的映射器接口递归地被搜索到。因此对于上述配置,我们可以通过公共的包名”org.tianshouzhi.mybatis”进行简化。如:
1 | <property name="basePackage" value="org.tianshouzhi.mybatis" /> |
你可能想到了,如果指定的公共的包名下面还包含了一些其他的接口,这些接口是你作为其他用途使用到的,并不能作为mybatis的映射器接口来使用。此时,你可以通过markerInterface属性或者annotationClass属性来进行过滤。
对于markerInterface,首先,你需要定义一个标记接口,接口名随意,这个接口中不需要定义任何方法,如:
1 | public interface MybatisMapperInterface{} |
接着,你需要将你的映射器接口继承MybatisMapperInterface,如:
1 | public interface UserMapper implements MybatisMapperInterface{ |
此时你可以指定MapperScannerConfigurer中指定,只有继承了MybatisMapperInterface接口的子接口,才为其自动注册MapperFactoryBean,如:
1 | <property name="markerInterface" value="com.tianshouzhi.mybatis.MybatisMapperInterface"/> |
对于annotationClass属性,作用是类似的,根据注解进行过滤。一般我们是映射器接口上添加mybatis提供的@Mapper注解进行过滤,你也可以自定义一个注解。配置如下:
1 | <property name="annotationClass" value="org.apache.ibatis.annotations.Mapper"/> |
如果同时指定了markerInterface和annotationClass属性,那么只有同时满足这两个条件的接口才会被注册为MapperFactoryBean。
细心的读者可能意识到了,到目前,我们还没有为MapperScannerConfigurer指定一个SqlSessionFactory,或者SqlSessionTemplate。前面配置MapperFactoryBean的时候,我们已经看到,我们至少需要为其提供一项。之所以不指定,是因为MapperScannerConfigurer将会spring上下文中自动进行寻找类型为SqlSessionFactory,或者SqlSessionTemplate的bean,然后利用其来创建MapperFactoryBean实例。
当然你也可以手工的进行指定任意一个,如:
1 | <property name="sqlSessionFactory" ref=“sqlSessionFactory"/> |
然而,sqlSessionFactory和sqlSessionTemplate属性已经不建议使用了。原因在于,这两个属性不支持你使用spring提供的PropertyPlaceholderConfigurer的属性替换。例如你配置了SqlSessionFactoryBean来创建SqlSessionFactory实例,前面已经看到必须为其指定一个dataSource属性。很多用户习惯将数据源的配置放到一个独立的配置文件,如jdbc.properties文件中,之后在spring配置中,通过占位符来替换,如:
1 | <bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource"> |
对于这样的配置,spring在初始化时会报错,因为MapperScannerConfigurer会在PropertyPlaceholderConfigurer初始化之前,就加载dataSource的配置,而此时PropertyPlaceholderConfigurer还未准备好替换的占位符的内容,所以抛出异常。
当然,这个问题并不是无解,我们可以使用sqlSessionFactoryBeanName、sqlSessionTemplateBeanName属性来替代sqlSessionFactory和sqlSessionTemplate属性。如下:
1 | <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/> |
此时,你依然可以放心大胆的在你的数据源配置中,使用占位符了。
事实上,笔者总是建议你,在MapperScannerConfigurer的配置中,显示的指定sqlSessionFactoryBeanName或sqlSessionTemplateBeanName。如果你不指定,MapperScannerConfigurer就会在spring上下文中自动的寻找类型为SqlSessionFactory或者SqlSessionTemplate的bean。如果你的数据源配置中使用了占位符,还是会报错。
最后,你可能想知道,为什么MapperScannerConfigurer指定一个basePackage属性,就可以为包下的每个接口都注册一个MapperFactoryBean?其内部是如何自动在spring上下文中寻找类型为SqlSessionFactory或者SqlSessionTemplate的bean实例的?以及为什么又做了如此多限制,只有指定sqlSessionFactoryBeanName或sqlSessionTemplateBeanName才能在数据源的配置中使用占位符?
关于这些问题,笔者将在MapperScannerConfigurer的源码分析中进行解答。
整合六:@MapperScan
如果读者习惯使用注解,而不是xml文件的方式进行配置,mybatis-spring提供了@MapperScan注解,其用于取代MapperScannerConfigurer。以下演示了如何通过注解的方式来配置mybatis。
1 | @Configuration |
整合七:事务
上述所有的配置,还没有涉及到mybatis与spring进行整合另一个核心要点,即事务。整合后,我们需要将事务委派给spring来管理。
spring使用PlatformTransactionManager接口来表示一个事务管理器。其有2个重要的实现类:
DataSourceTransactionManager:用于支持本地事务,简单理解,你可以认为就是操作单个数据库的事务,其内部也是通过操作java.sql.Connection来开启、提交(commit)和回滚(rollback)事务。
JtaTransactionManager:用于支持分布式事务,其实现了JTA规范,使用XA协议进行两阶段提交。
在本文中,我们主要介绍的是DataSourceTransactionManager,绝大部分情况下, 我们使用的都是这个事务管理器。其配置方式如下:
1 | <!--spring 事务管理器--> |
关于事务管理器使用方式有2种:声明式事务管理、编程式事务管理
声明式事务管理
1、基于注解
首先,你需要配置事务管理是基于注解驱动的,如下:
1 | <tx:annotation-driven transaction-manager="transactionManager"/> |
之后,在业务bean的方法上添加@Transactional注解,此时这个方法就自动具备了事务的功能,如果出现异常,会自动回滚,没有出现异常则自动交。
1 | @Transactional |
基于注解的形式的声明式事务管理器,是最为简单的,也是建议使用的方式。
2、基于切面
如果你有很多方法,都需要有事务管理,你觉得每个方法都添加@Transactional注解比较麻烦,此时你可以使用以下配置取代tx:annotation-driven元素。
1 | <!-- 1.配置事务通知:(事务的增强) --> |
事实上,基于切面的配置,太麻烦了,即使对于一些老鸟,长时间没有编写过类似的配置,可能也无法立即正确的进行配置,没有@Transactional注解来的直观。
编程式事务管理
如果上述两种声明式事务的配置都不是你要想要,那么你可以采取编程式事务。你可以在业务bean中注入事务管理器,然后进行编程式事务的管理,如:
1 | public class AccountService{ |
另外,Spring还提供了一个TransactionTemplate,用于简化编程式事务代码的编写,配置方式如下:
1 | <bean id="transactionTemplate" class="org.springframework.transaction.support.TransactionTemplate"> |
使用方式如下:
1 | public void transfer(final String out,final String in,final Double money) { |
总的来说,编程式事务管理还是显得略微麻烦。
整合八:springboot
mybatis开发团队为Spring Boot 提供了 mybatis-spring-boot-starter。你需要引入如下依赖:
1 | <dependency> |
使用了该starter之后,只需要定义一个DataSource即可,它会自动利用该DataSource创建需要使用到的SqlSessionFactoryBean、SqlSessionTemplate、以及ClassPathMapperScanner来自动扫描你的映射器接口,并针对每个接口都创建一个MapperFactoryBean,注册到Spring上下文中。
关于mybatis-spring-boot-starter如何实现自动配置的相关源码,参见:org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration类。
默认情况下,扫描的basePackage是spring boot的根目录(这里指的是应用启动类Application.java类所在的目录),且只会对添加了@Mapper注解的映射器接口进行注册。
因此,最简单的情况下,你只需要在application.yml中进行数据源的相关配置即可,以下配置依赖于spring-boot-starter-jdbc:
1 | spring: |
之后,你就可以直接在你的业务bean中注入映射器接口来使用了。
需要注意的是,一旦你自己提供了MapperScannerConfigurer,或者配置了MapperFactoryBean,那么mybatis-spring-boot-starter的自动配置功能将会失效。此时所有关于mybatis与spring进行整合的配置,都需要由你自行控制。
整合九:多数据源
有的时候,应用需要访问多个数据库,假设现在有两个mysql库db_user、db_acount。这个时候我们就需要配置2个数据源,来连接不同的库,如下:
1 | <bean id="ds_user" class="com.alibaba.druid.pool.DruidDataSource"> |
针对上述的数据源ds_user,ds_account,我们需要为每个都配置一个SqlSessionFactoryBean,如下:
1 | <bean id="ssf_user" class="org.mybatis.spring.SqlSessionFactoryBean"> |
细心的读者可能已经注意到,这两个SqlSessionFactoryBean分别操作数据源ds_user、ds_account,且mapperLocations属性值指向的是不同的目录:
在ssf_user中,指定关于ds_user的映射器xml文件都位于类路径的mybatis/mappers/db_user/目录下
在ssf_account中,指定关于ds_account的映射器xml文件都位于类路径的mybatis/mappers/db_account/目录下
如果你是使用映射接口的方式来操作mybatis,那么还应该针对ssf_user和ssf_account两个SqlSessionFactoryBean,各配置一个MapperScannerConfigurer。如下:
1 | <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> |
可以看到,两个MapperScannerConfigurer的basePackage属性是不同的,我们将操作db_user库下的映射器XxxMapper接口都放在com.tianshouzhi.mybatis.mappers.user包中,将我们将操作db_account库下的映射器XxxMapper接口都放在com.tianshouzhi.mybatis.mappers.account包中。
再次提醒,在使用多数据源时,将操作不同库的xml映射文件、以及对应的映射器接口放到不同的目录下很重要,如果不这样,在使用时你几乎100%会遇到问题。
最后,由于你现在有2个数据源,因此你应该配置两个事务管理器,如:
1 | <bean id="userTxManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"> |
如果你是使用@Transactional注解来进行声明式事务管理,应该要指定你使用的是哪一个事务管理器,如:
1 | @Transactional("accountTxManager") |
一个事务管理器,只能保证操作单个库的事务,例如在这里,accountTxManager只能保证db_account库的事务,证userTxManager只能保证db_user库的事务。
如果你在一个方法内,想同时操作两个库,并保证事务,使用普通的DataSourceTransactionManager已经无法满足这种需求,这属于分布式事务的范畴。在笔者的另一篇文章atomikos JTA/XA全局事务演示了如何使用atomikos与mybatis、spring进行整合,来进行分布式事务的管理。
整合十:zebra
你应该使用:
1、使用zebra-api中提供了GroupDataSource(读写分离)或者ShardDataSource(分库分表),而不是直接使用第三方的数据库连接池。
2、使用zebra-dao提供了ZebraMapperScannerConfigurer替代mybatis-spring原生的MapperScannerConfigurer
3、此外,你应该使用zebra-ds-monitor-client,只需引入依赖即可,会自动将连接池的监控信息上报到cat中
参考资料
mybatis常用注解

1 介绍
mybatis 最初配置信息是基于 XML ,映射语句(SQL)也是定义在 XML 中的。而到了 MyBatis 3提供了新的基于注解的配置。mybatis提供的注解有很多,大致可以分为以下几类:
增删改查:@Insert、@Update、@Delete、@Select、@MapKey、@Options、@SelelctKey、@Param、@InsertProvider、@UpdateProvider、@DeleteProvider、@SelectProvider
结果集映射:@Results、@Result、@ResultMap、@ResultType、@ConstructorArgs、@Arg、@One、@Many、@TypeDiscriminator、@Case
缓存:@CacheNamespace、@Property、@CacheNamespaceRef、@Flush
绝大部分注解,在xml映射文件中都有元素与之对应,但是不是所有。此外在mybatis-spring中提供了@Mapper注解和@MapperScan注解,用于和spring进行整合。
2 增删改查相关注解
| 注解 | 使用对象 | 相对应的 XML | 描述 |
|---|---|---|---|
| @Insert @Update @Delete @Select | 方法 | 这四个注解分别代表将会被执行的 SQL 语句。它们用字符串数组(或单个字符串)作为参数。如果传递的是字符串数组,字符串之间先会被填充一个空格再连接成单个完整的字符串。这有效避免了以 Java 代码构建 SQL 语句时的“丢失空格”的问题。然而,你也可以提前手动连接好字符串。属性有:value,填入的值是用来组成单个 SQL 语句的字符串数组。 | |
| @Options | 方法 | 映射语句的属性 | 这个注解提供访问大范围的交换和配置选项的入口,它们通常在映射语句上作为属性出现。Options 注解提供了通俗易懂的方式来访问它们,而不是让每条语句注解变复杂。属性有:useCache=true, flushCache=FlushCachePolicy.DEFAULT, resultSetType=FORWARD_ONLY, statementType=PREPARED, fetchSize=-1, timeout=-1, useGeneratedKeys=false, keyProperty=”id”, keyColumn=””, resultSets=””。值得一提的是, Java 注解无法指定 null 值。因此,一旦你使用了 Options 注解,你的语句就会被上述属性的默认值所影响。要注意避免默认值带来的预期以外的行为。 注意: keyColumn 属性只在某些数据库中有效(如 Oracle、PostgreSQL等)。请在插入语句一节查看更多关于 keyColumn 和 keyProperty 两者的有效值详情。 |
| @MapKey | 方法 | 这是一个用在返回值为 Map 的方法上的注解。它能够将存放对象的 List 转化为 key 值为对象的某一属性的 Map。属性有: value,填入的是对象的属性名,作为 Map 的 key 值。 | |
| @SelectKey | 方法 | 这个注解的功能与 |
|
| @Param | 参数 | N/A | 如果你的映射方法的形参有多个,这个注解使用在映射方法的参数上就能为它们取自定义名字。若不给出自定义名字,多参数(不包括 RowBounds 参数)则先以 “param” 作前缀,再加上它们的参数位置作为参数别名。例如 #{param1}, #{param2},这个是默认值。如果注解是 @Param(“person”),那么参数就会被命名为 #{person}。 |
| @InsertProvider @UpdateProvider @DeleteProvider @SelectProvider | 方法 | 允许构建动态 SQL。这些备选的 SQL 注解允许你指定类名和返回在运行时执行的 SQL 语句的方法。(自从MyBatis 3.4.6开始,你可以用 CharSequence 代替 String 来返回类型返回值了。)当执行映射语句的时候,MyBatis 会实例化类并执行方法,类和方法就是填入了注解的值。你可以把已经传递给映射方法了的对象作为参数,”Mapper interface type” 和 “Mapper method” 会经过 ProviderContext (仅在MyBatis 3.4.5及以上支持)作为参数值。(MyBatis 3.4及以上的版本,支持多参数传入)属性有: type, method。type 属性需填入类。method 需填入该类定义了的方法名。注意 接下来的小节将会讨论类,能帮助你更轻松地构建动态 SQL。 |
映射器接口示例,假设有以下UserMapper接口:
1 | public interface UserMapper { |
说明:
2.1 @Insert、@Update、@Delete、@Options、@SelectKey注解
mybatis会根据接口方法上的@Insert、@Update、@Delete注解,分别去调用SqlSession的insert、update、delete方法。这个几个方法返回的都是一个int,表示影响的记录行数。
特别的,在UserMapper接口的insert方法上,除了添加了@Insert注解,还添加了@Options注解。在上面的案例中,@Options注解用于获取自动生成主键,并设置到User实体中。此外,@SelectKey注解也可以用于获取自动生成的主键,使用方式如下:
1 | @Insert("INSERT INTO user(id,name) VALUES (#{id},#{name})") |
2.2 @Select注解
在上面的案例中,UserMapper的selectById、selectAll、selectMap、selectByIds、selectPage方法都添加了@Select注解。mybatis会根据方法的返回值类型User、List
2.3 @MapKey注解
特别的,对于返回值是Map的情况,UserMapper的selectMap方法上额外添加了一个@MapKey(“id”)注解,表示将User实例的id属性当做Map的key。
2.4 动态sql与,这是因为SQL中使用了动态sql标签。不管是@Insert、@Update、@Delete、@Select注解,只要SQL里使用了mybatis的动态sql标签(包括:if、choose …when …otherwise、trim 、where、 set、foreach、bind)等,都建议在sql前后分别加上,否则可能会出现一些参数找不到的情况。
2.5 @Param注解
@Param注解用于给方法参数起一个名字。以下是笔者总结的使用原则:
在方法只接受一个参数的情况下,可以不使用@Param。
在方法接受多个参数的情况下,建议一定要使用@Param注解给参数命名。
例如上述案例的selectPage方法接受2个参数,所以其两个参数都使用了@Param注解。@Param注解看起来配置最简单,实际上理解确实最复杂,下面进行详细的介绍。
前面已经提到,当映射器接口定义的方法被调用时,mybatis内部根据方法上注解:@Insert、@Update、@Delete、@Select来选择调用SqlSession的insert、update、delete、selectXXX方法。以下是SqlSession接口的相关方法定义(部分省略):
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface SqlSession extends Closeable {
...
<T> T selectOne(String statement);
<T> T selectOne(String statement, Object parameter);
<E> List<E> selectList(String statement);
<E> List<E> selectList(String statement, Object parameter);
<K, V> Map<K, V> selectMap(String statement, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
...
int insert(String statement);
int insert(String statement, Object parameter);
int update(String statement);
int update(String statement, Object parameter);
int delete(String statement);
int delete(String statement, Object parameter);
...
}
可以看到部分方法接受一个Object类型的parameter参数,另外一些方法则没有此参数。mybatis在执行前,除了会根据方法上注解:@Insert、@Update、@Delete、@Select来选择调用SqlSession的insert、update、delete、selectXXX方法;还会提前对传入映射器接口方法中的参数进行一些处理,然后再调用SqlSession的相应方法。逻辑如下:
1、如果映射器接口方法不接受参数,mybatis在执行时会调用相应无parameter参数的方法重载形式。例如,如UserMapper接口的selectAll方法,其不接受参数,返回值类型为List,因此调用SqlSession不接受paramter参数的selectList方法:
1
<E> List<E> selectList(String statement);
2、如果映射器方法只有一个参数,并且这个参数没有使用@Param注解,则直接用这个参数来调用SqlSession相应接受Object类型parameter方法参数的重载形式。例如,如UserMapper接口的selectByIds方法,其接受1个int[]数组类型参数作为查询条件,返回值类型为List,因此调用SqlSession接受paramter参数的selectList方法:
1
<E> List<E> selectList(String statement, Object parameter);
3、如果映射器方法只接受一个参数,但是使用了@Param注解,也会先封装到Map中;活着映射器方法总是接受多个参数,不管有没有使用@Param注解指定参数,总是会先封装到一个Map中。之后,调用SqlSession的相应方法把这个Map当做parameter参数传入。
例如:UserMapper接口的selectPage方法, 通过@Param(“offset”)和@Param(“limit”)为2个int参数指定了名字。
1
2
@Select("SELECT id,name FROM user LIMIT #{offset},#{limit}")
public List<User> selectPage(@Param("offset") int offset, @Param("limit") int limit);
假设我们传入0和10,那么参数封装后的Map结构如下:
key value
-——————-
offset 0 //1
limit 10 //2
param1 0 //3
param2 10 //4
关于Map中1、2两个key-value,比较好理解,是我们通过@Param注解指定的映射关系。而3、4两个key-value,实际上是参数位置(param1、param2…,下标从1开始)和参数值的映射关系,不管我们有没有使用@Param注解,存在多个参数的情况下,我们总是可以按照位置进行引用。
因此,将UserMapper的selectPage方法定义改成以下形式也是正确的:
1
2
3
4
5
6
7
//使用了@Param注解的情况下,依然根据参数位置进行引用(param1,param2…,下标从1开始)
@Select("SELECT id,name FROM user LIMIT #{param1},#{param2}")
public List<User> selectPage(@Param("offset") int offset, @Param("limit") int limit);
//不使用@Param注解,直接根据参数位置进行引用
@Select("SELECT id,name FROM user LIMIT #{param1},#{param2}")
public List<User> selectPage(int offset, int limit);
显然,根据参数位置进行引用不太直观,因此建议在存在多个参数的情况,总是通过@Param注解显式的指定参数名。
2.6 @InsertProvider、@UpdateProvider、@DeleteProvider、@SelectProvider注解
这几个注解主要用于动态sql构建。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
public interface UserBuilderMapper {
@SelectProvider(type = UserSqlBuilder.class, method = "buildSelectByIdSql")
public User selectById(@Param("id") int id);
@InsertProvider(type = UserSqlBuilder.class, method = "buildInsertSql")
@Options(useGeneratedKeys = true, keyColumn = "id", keyProperty = "id")
public int insert(User user);
@UpdateProvider(type = UserSqlBuilder.class, method = "buildUpdateSql")
public int update(User user);
@DeleteProvider(type = UserSqlBuilder.class, method = "buildDeleteSql")
public int delete(@Param("id") int id);
//建议将sql builder以映射器接口内部类的形式进行定义
public static class UserSqlBuilder {
public static String buildSelectByIdSql(@Param("id") int id) {
return new SQL() {
{
SELECT("id, name");
FROM("user");
WHERE("id=#{id}");
}
}.toString();
}
public static String buildInsertSql(User user) {
return new SQL() {
{
INSERT_INTO("user");
VALUES("name", "#{name}");
}
}.toString();
}
public static String buildUpdateSql(User user) {
return new SQL() {
{
UPDATE("user");
SET("name=#{name}");
WHERE("id=#{id}");
}
}.toString();
}
public static String buildDeleteSql(@Param("id") int id) {
return new SQL() {
{
DELETE_FROM("user");
WHERE("id=#{id}");
}
}.toString();
}
}
}
}
3 结果集映射相关注解
注解
使用对象
相对应的 XML
描述
@Results
方法
结果映射的列表,包含了一个特别结果列如何被映射到属性或字段的详情。属性有:value, id。value 属性是 Result 注解的数组。这个 id 的属性是结果映射的名称。
@Result
方法
在列和属性或字段之间的单独结果映射。属性有:id, column, javaType, jdbcType, typeHandler, one, many。id 属性是一个布尔值,来标识应该被用于比较(和在 XML 映射中的相似)的属性。one 属性是单独的联系,和 相似,而 many 属性是对集合而言的,和相似。它们这样命名是为了避免名称冲突。
@ResultMap
方法
N/A
这个注解给 @Select 或者 @SelectProvider 提供在 XML 映射中的 的id。这使得注解的 select 可以复用那些定义在 XML 中的 ResultMap。如果同一 select 注解中还存在 @Results 或者 @ConstructorArgs,那么这两个注解将被此注解覆盖。
@ResultType
方法
N/A
此注解在使用了结果处理器的情况下使用。在这种情况下,返回类型为 void,所以 Mybatis 必须有一种方式决定对象的类型,用于构造每行数据。如果有 XML 的结果映射,请使用 @ResultMap 注解。如果结果类型在 XML 的
@ConstructorArgs
方法
收集一组结果传递给一个结果对象的构造方法。属性有:value,它是形式参数数组。
@Arg
N/A
单参数构造方法,是 ConstructorArgs 集合的一部分。属性有:id, column, javaType, jdbcType, typeHandler, select和 resultMap。id 属性是布尔值,来标识用于比较的属性,和 XML 元素相似。
@One
N/A
复杂类型的单独属性值映射。属性有:select,已映射语句(也就是映射器方法)的全限定名,它可以加载合适类型的实例。fetchType会覆盖全局的配置参数 lazyLoadingEnabled。注意 联合映射在注解 API中是不支持的。这是因为 Java 注解的限制,不允许循环引用。
@Many
N/A
映射到复杂类型的集合属性。属性有:select,已映射语句(也就是映射器方法)的全限定名,它可以加载合适类型的实例的集合,fetchType 会覆盖全局的配置参数 lazyLoadingEnabled。注意 联合映射在注解 API中是不支持的。这是因为 Java 注解的限制,不允许循环引用
@TypeDiscriminator
方法
一组实例值被用来决定结果映射的表现。属性有:column, javaType, jdbcType, typeHandler 和 cases。cases 属性是实例数组。
@Case
N/A
单独实例的值和它对应的映射。属性有:value, type, results。results 属性是结果数组,因此这个注解和实际的 ResultMap 很相似,由下面的 Results 注解指定。
3.1 @ConstructorArgs、@Arg注解
如果实体类没有无参的构造方法,那么我们就必须通过@ConstructorArgs与@Arg注解来提供实体类的构造方法参数信息。例如上述UserMapper的selectById方法,其返回一个User对象。假设User对象只有以下构造方法:
1
2
3
4
public User(Integer id, String name) {
this.id = id;
this.name = name;
}
那么需要在selectById方法上,添加@ConstructorArgs注解,提供构造方法信息,如下:
1
2
3
4
5
@Select("SELECT id,name FROM user where id= #{id}")
@ConstructorArgs({
@Arg(column = "id",javaType = Integer.class,id = true),
@Arg(column = "name",javaType = String.class)})
public User selectById(int id);
注意,通常情况下,我们都建议为实体类提供无参的构造方法的,这是最佳实践的总结,因此@ConstructorArgs注解和@Arg注解基本使用不到。
3.2 @Results、@Result注解
如果实体字段的名称与数据库表字段名称不一致时,我们就需要显式的指定映射关系。这是通过@Results、@Result注解来指定的,例如为UserMapper的selectById指定映射关系:
1
2
3
4
5
@Select("SELECT id,name FROM user where id= #{id}")
@Results(id = "userMap", value = {
@Result(property = "id", column = "id", javaType = Integer.class,jdbcType = JdbcType.INTEGER,id = true),
@Result(property = "name", column = "name",javaType = String.class,jdbcType = JdbcType.VARCHAR)})
public User selectById(int id);
其中:
@Results注解:id属性用于给这个映射关系起一个名字(这里指定的为userMap),其内部还包含了一个@Result[]来表示实体属性和数据库表字段的映射关系
@Result注解:property属性是java实体属性的名称,column表示对应的数据库字段的名称。javaType和JdbcType属性可以不指定。
3.3 @ResultMap注解
上述selectById方法已经通过@Results注解指定了结果映射关系,通过@ConstructorArgs指定了构造方法(必要的情况下才使用),那么在其他的查询方法中,我们不需要重复定义,可以通过@ResultMap来引用@Results的id属性值进行复用。如我们在UserMapper的selectAll方法进行复用:
1
2
3
@Select("SELECT id,name FROM user")
@ResultMap("userMap")
public List<User> selectAll();
3.4 @TypeDiscriminator、@Case注解
鉴别器注解基本没用,有空的时候再补充
3.5 @Many、@One注解
主要用于关联关系映射
假设有作者(author)和文章(article)两张数据库表,一个author可以有多个article,一个article只能属于一个author。相关表结构以及初始数据如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
--author表
CREATE TABLE `author` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `author` (`id`, `name`)
VALUES
(1, 'tianshouzhi');
--article表
CREATE TABLE `article` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`title` varchar(255) NOT NULL,
`content` longtext NOT NULL,
`author_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;
INSERT INTO `article` (`id`, `title`, `content`, `author_id`)
VALUES
(1, 'title1', 'content1', 1),
(2, 'title2', 'content2', 1);
相应的Java实体类如下所示:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public class Article {
private Integer id;
private String title;
private String content;
private Author author;
//...setters getters and toString...
}
public class Author {
private Integer id;
private String name;
private List<Article> articles;
//...setters getters and toString...
}
映射器接口定义分别如下:
ArticleMapper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public interface ArticleMapper {
//1、根据文章id查询文章Article对象,同时通过One注解关联查询出作者Author信息
@Select("SELECT id,title,content,author_id FROM article where id= #{articleId}")
@Results(id = "articleWithAuthor", value = {
@Result(property = "id", column = "id"),
@Result(property = "title", column = "title"),
@Result(property = "content", column = "content"),
//property属性:指定将关联查询的结果封装到Article对象的author属性上
//column属性指定:指定在执行@One注解中定义的select语句时,把article表的author_id字段当做参数传入
//one属性:通过@One注解定义关联查询的语句是AuthorMapper中的findAuthorByAuthorId方法
@Result(property = "author",column = "author_id”,
one = @One(select = "com.tianshouzhi.mapper.AuthorMapper.findAuthorByAuthorId"))})
public Article findArticleWithAuthorByArticleId(@Param("articleId") int articleId);
//2、根据作者(Author)的id查询其所有的文章(Article)
@Select("SELECT id,title,content,author_id FROM article WHERE author_id=#{authorId}")
@Results(id = "articlesWithoutAuthor", value = {
@Result(property = "id", column = "id"),
@Result(property = "title", column = "title"),
@Result(property = "content", column = "content")})
List<Article> findArticlesByAuthorId(@Param("authorId") int authorId);
}
AuthorMapper
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface AuthorMapper {
//根据作者id查询Author信息
@Select("SELECT id,name FROM author WHERE id=#{authorId}")
Author findAuthorByAuthorId(int authorId);
//根据作者id查询Author信息,通过@Many注解关联查询出所有的文章信息
@Select("SELECT id,name FROM author WHERE id=#{authorId}")
@Results(id = "authorWithArticles", value = {
@Result(property = "id", column = "id"),
@Result(property = "name", column = "name”),
//property属性:指定将关联查询的结果封装到Author对象的articles属性上
//column属性指定:指定在执行@Many注解中定义的select语句时,把author表的id字段当做参数传入
//many属性:指定通过@Many注解定义关联查询的语句是ArticleMapper中的findArticlesByAuthorId方法
@Result(property = "articles",column = "id”,
many = @Many(select = "com.tianshouzhi.mapper.ArticleMapper.findArticlesByAuthorId"))})
Author findAuthorWithArticlesByAuthorId(int authorId);
}
测试如下:
1
2
3
4
5
6
7
8
9
10
@Test
public void testOneAndMany(){
System.out.println("===========通过@One注解查询出Article关联的Auhtor===========");
Article article = articleMapper.findArticleWithAuthorByArticleId(1);
System.out.println(article);
System.out.println("===========通过@Many注解查询出Auhtor关联的Article==========");
Author author = authorMapper.findAuthorWithArticlesByAuthorId(1);
System.out.println(author);
}
控制台输出结果为:
===========通过@One注解查询出Article关联的Auhtor===========
Article{id=1, title=’title1’, content=’content1’, author=Author{id=1, name=’tianshouzhi’, articles=null}}
===========通过@Many注解查询出Auhtor关联的Article==========
Author{id=1, name=’tianshouzhi’, articles=[Article{id=1, title=’title1’, content=’content1’, author=null}, Article{id=2, title=’title2’, content=’content2’, author=null}]}
4 缓存相关注解
注解
使用对象
相对应的 XML
描述
@CacheNamespace
类
为给定的命名空间(比如类)配置缓存。属性有:implemetation, eviction, flushInterval, size, readWrite, blocking 和properties。
@Property
N/A
指定参数值或占位值(placeholder)(能被 mybatis-config.xml内的配置属性覆盖)。属性有:name, value。(仅在MyBatis 3.4.2以上版本生效)
@CacheNamespaceRef
类
参照另外一个命名空间的缓存来使用。属性有:value, name。如果你使用了这个注解,你应设置 value 或者 name 属性的其中一个。value 属性用于指定 Java 类型而指定命名空间(命名空间名就是指定的 Java 类型的全限定名),name 属性(这个属性仅在MyBatis 3.4.2以上版本生效)直接指定了命名空间的名字。
@Flush
方法
N/A
如果使用了这个注解,定义在 Mapper 接口中的方法能够调用 SqlSession#flushStatements() 方法。(Mybatis 3.3及以上)
2.5 @Param注解
@Param注解用于给方法参数起一个名字。以下是笔者总结的使用原则:
在方法只接受一个参数的情况下,可以不使用@Param。
在方法接受多个参数的情况下,建议一定要使用@Param注解给参数命名。
例如上述案例的selectPage方法接受2个参数,所以其两个参数都使用了@Param注解。@Param注解看起来配置最简单,实际上理解确实最复杂,下面进行详细的介绍。
前面已经提到,当映射器接口定义的方法被调用时,mybatis内部根据方法上注解:@Insert、@Update、@Delete、@Select来选择调用SqlSession的insert、update、delete、selectXXX方法。以下是SqlSession接口的相关方法定义(部分省略):
1 | public interface SqlSession extends Closeable { |
可以看到部分方法接受一个Object类型的parameter参数,另外一些方法则没有此参数。mybatis在执行前,除了会根据方法上注解:@Insert、@Update、@Delete、@Select来选择调用SqlSession的insert、update、delete、selectXXX方法;还会提前对传入映射器接口方法中的参数进行一些处理,然后再调用SqlSession的相应方法。逻辑如下:
1、如果映射器接口方法不接受参数,mybatis在执行时会调用相应无parameter参数的方法重载形式。例如,如UserMapper接口的selectAll方法,其不接受参数,返回值类型为List,因此调用SqlSession不接受paramter参数的selectList方法:
1 | <E> List<E> selectList(String statement); |
2、如果映射器方法只有一个参数,并且这个参数没有使用@Param注解,则直接用这个参数来调用SqlSession相应接受Object类型parameter方法参数的重载形式。例如,如UserMapper接口的selectByIds方法,其接受1个int[]数组类型参数作为查询条件,返回值类型为List
1 | <E> List<E> selectList(String statement, Object parameter); |
3、如果映射器方法只接受一个参数,但是使用了@Param注解,也会先封装到Map中;活着映射器方法总是接受多个参数,不管有没有使用@Param注解指定参数,总是会先封装到一个Map中。之后,调用SqlSession的相应方法把这个Map当做parameter参数传入。
例如:UserMapper接口的selectPage方法, 通过@Param(“offset”)和@Param(“limit”)为2个int参数指定了名字。
1 | @Select("SELECT id,name FROM user LIMIT #{offset},#{limit}") |
假设我们传入0和10,那么参数封装后的Map结构如下:
key value
-——————-
offset 0 //1
limit 10 //2
param1 0 //3
param2 10 //4
关于Map中1、2两个key-value,比较好理解,是我们通过@Param注解指定的映射关系。而3、4两个key-value,实际上是参数位置(param1、param2…,下标从1开始)和参数值的映射关系,不管我们有没有使用@Param注解,存在多个参数的情况下,我们总是可以按照位置进行引用。
因此,将UserMapper的selectPage方法定义改成以下形式也是正确的:
1 | //使用了@Param注解的情况下,依然根据参数位置进行引用(param1,param2…,下标从1开始) |
显然,根据参数位置进行引用不太直观,因此建议在存在多个参数的情况,总是通过@Param注解显式的指定参数名。
2.6 @InsertProvider、@UpdateProvider、@DeleteProvider、@SelectProvider注解
这几个注解主要用于动态sql构建。
1 | public interface UserBuilderMapper { |
3 结果集映射相关注解
| 注解 | 使用对象 | 相对应的 XML | 描述 |
|---|---|---|---|
| @Results | 方法 | 结果映射的列表,包含了一个特别结果列如何被映射到属性或字段的详情。属性有:value, id。value 属性是 Result 注解的数组。这个 id 的属性是结果映射的名称。 | |
| @Result | 方法 | 在列和属性或字段之间的单独结果映射。属性有:id, column, javaType, jdbcType, typeHandler, one, many。id 属性是一个布尔值,来标识应该被用于比较(和在 XML 映射中的 |
|
| @ResultMap | 方法 | N/A | 这个注解给 @Select 或者 @SelectProvider 提供在 XML 映射中的 |
| @ResultType | 方法 | N/A | 此注解在使用了结果处理器的情况下使用。在这种情况下,返回类型为 void,所以 Mybatis 必须有一种方式决定对象的类型,用于构造每行数据。如果有 XML 的结果映射,请使用 @ResultMap 注解。如果结果类型在 XML 的 |
| @ConstructorArgs | 方法 | 收集一组结果传递给一个结果对象的构造方法。属性有:value,它是形式参数数组。 | |
| @Arg | N/A | 单参数构造方法,是 ConstructorArgs 集合的一部分。属性有:id, column, javaType, jdbcType, typeHandler, select和 resultMap。id 属性是布尔值,来标识用于比较的属性,和 |
|
| @One | N/A | 复杂类型的单独属性值映射。属性有:select,已映射语句(也就是映射器方法)的全限定名,它可以加载合适类型的实例。fetchType会覆盖全局的配置参数 lazyLoadingEnabled。注意 联合映射在注解 API中是不支持的。这是因为 Java 注解的限制,不允许循环引用。 | |
| @Many | N/A | 映射到复杂类型的集合属性。属性有:select,已映射语句(也就是映射器方法)的全限定名,它可以加载合适类型的实例的集合,fetchType 会覆盖全局的配置参数 lazyLoadingEnabled。注意 联合映射在注解 API中是不支持的。这是因为 Java 注解的限制,不允许循环引用 | |
| @TypeDiscriminator | 方法 | 一组实例值被用来决定结果映射的表现。属性有:column, javaType, jdbcType, typeHandler 和 cases。cases 属性是实例数组。 | |
| @Case | N/A | 单独实例的值和它对应的映射。属性有:value, type, results。results 属性是结果数组,因此这个注解和实际的 ResultMap 很相似,由下面的 Results 注解指定。 |
3.1 @ConstructorArgs、@Arg注解
如果实体类没有无参的构造方法,那么我们就必须通过@ConstructorArgs与@Arg注解来提供实体类的构造方法参数信息。例如上述UserMapper的selectById方法,其返回一个User对象。假设User对象只有以下构造方法:
1 | public User(Integer id, String name) { |
那么需要在selectById方法上,添加@ConstructorArgs注解,提供构造方法信息,如下:
1 | @Select("SELECT id,name FROM user where id= #{id}") |
注意,通常情况下,我们都建议为实体类提供无参的构造方法的,这是最佳实践的总结,因此@ConstructorArgs注解和@Arg注解基本使用不到。
3.2 @Results、@Result注解
如果实体字段的名称与数据库表字段名称不一致时,我们就需要显式的指定映射关系。这是通过@Results、@Result注解来指定的,例如为UserMapper的selectById指定映射关系:
1 | @Select("SELECT id,name FROM user where id= #{id}") |
其中:
@Results注解:id属性用于给这个映射关系起一个名字(这里指定的为userMap),其内部还包含了一个@Result[]来表示实体属性和数据库表字段的映射关系
@Result注解:property属性是java实体属性的名称,column表示对应的数据库字段的名称。javaType和JdbcType属性可以不指定。
3.3 @ResultMap注解
上述selectById方法已经通过@Results注解指定了结果映射关系,通过@ConstructorArgs指定了构造方法(必要的情况下才使用),那么在其他的查询方法中,我们不需要重复定义,可以通过@ResultMap来引用@Results的id属性值进行复用。如我们在UserMapper的selectAll方法进行复用:
1 | @Select("SELECT id,name FROM user") |
3.4 @TypeDiscriminator、@Case注解
鉴别器注解基本没用,有空的时候再补充
3.5 @Many、@One注解
主要用于关联关系映射
假设有作者(author)和文章(article)两张数据库表,一个author可以有多个article,一个article只能属于一个author。相关表结构以及初始数据如下所示:
1 | --author表 |
相应的Java实体类如下所示:
1 | public class Article { |
映射器接口定义分别如下:
ArticleMapper
1 | public interface ArticleMapper { |
AuthorMapper
1 | public interface AuthorMapper { |
测试如下:
1 | @Test |
控制台输出结果为:
===========通过@One注解查询出Article关联的Auhtor===========
Article{id=1, title=’title1’, content=’content1’, author=Author{id=1, name=’tianshouzhi’, articles=null}}
===========通过@Many注解查询出Auhtor关联的Article==========
Author{id=1, name=’tianshouzhi’, articles=[Article{id=1, title=’title1’, content=’content1’, author=null}, Article{id=2, title=’title2’, content=’content2’, author=null}]}
4 缓存相关注解
| 注解 | 使用对象 | 相对应的 XML | 描述 |
|---|---|---|---|
| @CacheNamespace | 类 | 为给定的命名空间(比如类)配置缓存。属性有:implemetation, eviction, flushInterval, size, readWrite, blocking 和properties。 | |
| @Property | N/A | 指定参数值或占位值(placeholder)(能被 mybatis-config.xml内的配置属性覆盖)。属性有:name, value。(仅在MyBatis 3.4.2以上版本生效) | |
| @CacheNamespaceRef | 类 | 参照另外一个命名空间的缓存来使用。属性有:value, name。如果你使用了这个注解,你应设置 value 或者 name 属性的其中一个。value 属性用于指定 Java 类型而指定命名空间(命名空间名就是指定的 Java 类型的全限定名),name 属性(这个属性仅在MyBatis 3.4.2以上版本生效)直接指定了命名空间的名字。 | |
| @Flush | 方法 | N/A | 如果使用了这个注解,定义在 Mapper 接口中的方法能够调用 SqlSession#flushStatements() 方法。(Mybatis 3.3及以上) |
mybatis技巧
Mybatis中判断集合的 size 不为空
1 | <if test="null != staffCodeList and staffCodeList.size > 0"> |
判断参数为空
1.判断Double类型:
2.判断Integer类型:
3.判断String类型:
4.判断Date类型:
因为String类型是符合的,但是如果是Integer类型的话,如果变量的值是0,即 faceValue = 0, mybatis在进行 faceValue != ‘’ 的时候会认为 faceValue 的值是空字符串, 即 faceValue == ‘’ 为true;
同理,Double,Date也是如此。所以如果是Integer类型,Double类型,Date类型只需要判断 != null 即可。如果String类型需要判断不等于0,则需要写name != ‘0’.toString(),否则会报错。
mybatis 中 foreach collection的三种用法
mybatis 中 foreach collection的三种用法
Mybatis使用IN语句查询
返回数据
注意事项
写入数据库的po,不要给字段设置默认值,除非能保证在update时,默认值不会覆盖错误。
NoSuchMethod错误排查
- 如果包含了不同版本的包,它的加载顺序是如何的?
- NoSuchMethod的报错是error级别的,exception是catch不住的。
mybatis用法总结
selectById
1 | @Select({ |
selectByCondition
1 | @Select({ |
分页查询的两个参数,分别是偏移量和size。偏移量需要通过页数和size计算出来。
insert
1 | @Insert({ |
batchInsert
1 | @Insert({ |
update
1 | @Update({ |
用法对比
jdbcType=”DATE” 和 jdbcType=”TIMESTAMP” 两种类型的区别
- DATE只有年月日,没有时分秒