MyBatis 是一个开源的、基于 Java 的持久层框架,它简化了数据库操作的复杂性,同时又提供了灵活的 SQL 映射机制。
数据库中有一张 User
表,下面将使用 MyBatis 对其进行查询操作:
x1create database test01 charset utf8;
2
3create table if not exists user(
4 id int(11) unsigned not null auto_increment comment '用户编号'
5 ,username varchar(32) not null comment '用户名称'
6 ,birthday varchar(38) not null comment '用户生日'
7 ,sex char(2) not null comment '性别,0男1女'
8 ,address varchar(255) null comment '用户地址'
9 ,primary key(id)
10 )engine=innodb default charset=utf8 comment='用户表';
11
12insert into user(username, birthday, sex, address) values('hyx', '1999-09-13', '0', '深圳市南山区');
13commit;
14
该表对应的实体类和 DAO 接口如下:
211// 实体类
2package org.example.model;
3import java.io.Serializable;
4import java.sql.Date;
5
6public class User implements Serializable {
7 private Integer id;
8 private String username;
9 private Date birthday;
10 private String sex;
11 private String address;
12}
13
14// DAO 接口
15package org.example.dao;
16import org.example.model.User;
17import java.util.List;
18public interface UserDao {
19 List<User> findAll();
20}
21
如果使用传统的 JDBC 或 JdbcTemplate ,那么下一步应该实现 DAO 接口,编写具体的 CURD 方法。
但是,使用 MyBatis 后,你无需自己手写这些繁琐的实现类,这些 MyBatis 都会使用动态代理技术帮你生成。
引入 MyBatis 相关的依赖如下:
361
2<project xmlns="http://maven.apache.org/POM/4.0.0"
3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
4 xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
5 <modelVersion>4.0.0</modelVersion>
6
7 <groupId>org.example</groupId>
8 <artifactId>demo01</artifactId>
9 <version>1.0-SNAPSHOT</version>
10
11 <dependencies>
12 <!-- MyBatis 依赖-->
13 <dependency>
14 <groupId>org.mybatis</groupId>
15 <artifactId>mybatis</artifactId>
16 <version>3.4.5</version>
17 </dependency>
18
19 <!-- MySql JDBC 驱动-->
20 <dependency>
21 <groupId>mysql</groupId>
22 <artifactId>mysql-connector-java</artifactId>
23 <version>5.1.6</version>
24 <scope>runtime</scope>
25 </dependency>
26
27 <!-- Junit 用于测试-->
28 <dependency>
29 <groupId>junit</groupId>
30 <artifactId>junit</artifactId>
31 <version>4.10</version>
32 <scope>test</scope>
33 </dependency>
34 </dependencies>
35
36</project>
如果你使用的是Oracle或SqlServer数据库,应该切换如下驱动:
121<dependency>
2 <groupId>com.microsoft.sqlserver</groupId>
3 <artifactId>sqljdbc4</artifactId>
4 <version>4.0</version>
5</dependency>
6
7<dependency>
8 <groupId>com.oracle</groupId>
9 <artifactId>ojdbc14</artifactId>
10 <version>10.2.0.5.0</version>
11</dependency>
12
在类路径下新建src\main\resources\mybatis-config.xml
配置文件,用来配置 MyBatis 的相关属性和行为:
241
2
3
4
5<configuration>
6 <!-- 环境配置 -->
7 <environments default="dev">
8 <environment id="dev">
9 <transactionManager type="JDBC"></transactionManager>
10 <!-- 数据源配置 -->
11 <dataSource type="POOLED">
12 <property name="driver" value="com.mysql.jdbc.Driver"/>
13 <property name="url" value="jdbc:mysql://42.192.223.129:3306/test01"/>
14 <property name="username" value="root"/>
15 <property name="password" value="root"/>
16 </dataSource>
17 </environment>
18 </environments>
19
20 <!-- 映射文件扫描 -->
21 <mappers>
22 <mapper resource="org/example/dao/UserDao.xml"/>
23 </mappers>
24</configuration>
如果你使用的是Oracle或SqlServer数据库,JDBC连接四要素应修改如下:
91 <property name="driver" value="oracle.jdbc.driver.OracleDriver"/>
2 <property name="url" value="jdbc:oracle:thin:@127.0.0.1:1521:ORCLONE"/>
3 <property name="username" value="kbssfms"/>
4 <property name="password" value="kbssfms"/>
5
6 <property name ="driverClassName" value ="com.microsoft.sqlserver.jdbc.SQLServerDriver"></property>
7 <property name ="url" value ="jdbc:sqlserver://127.0.0.1:1433;DatabaseName=kbssfms"></property>
8 <property name ="username" value ="sa"></property>
9 <property name ="password" value ="Sa147741"></property>
在类路径下新建src\main\resources\org\example\dao\UserDao.xml
配置文件,用于配置执行的SQL语句和结果集映射方式。
111
2
3
4
5<mapper namespace="org.example.dao.UserDao">
6
7 <!-- 查询所有用户 -->
8 <select id="findAll" resultType="org.example.model.User">
9 SELECT * FROM user
10 </select>
11</mapper>
编写一个简单的测试类进行测试:
461package org.example.dao.test;
2
3import org.apache.ibatis.io.Resources;
4import org.apache.ibatis.session.SqlSession;
5import org.apache.ibatis.session.SqlSessionFactory;
6import org.apache.ibatis.session.SqlSessionFactoryBuilder;
7import org.example.dao.UserDao;
8import org.example.model.User;
9
10import java.io.InputStream;
11import java.util.List;
12
13public class MybatisTest {
14
15 public static void main(String[] args) throws Exception {
16 //1.读取主配置文件
17 InputStream in = Resources.getResourceAsStream("mybatis-config.xml");
18
19 //2.创建 SqlSessionFactoryBuilder
20 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
21
22 //3. 构建 SqlSessionFactory
23 SqlSessionFactory factory = builder.build(in);
24
25 //4. 打开会话
26 SqlSession session = factory.openSession();
27
28 //5. 获取代理对象
29 UserDao userDao = session.getMapper(UserDao.class);
30
31 //6. 使用代理对象执行查询所有方法
32 List<User> users = userDao.findAll();
33 for (User user : users) {
34 System.out.println(user);
35 }
36
37 //7. 释放资源
38 if (session != null) {
39 session.close();
40 }
41 if (in != null) {
42 in.close();
43 }
44
45 }
46}
运行结果如下:
注意:
上述案例的完整工程代码可参考附件 mybatis-demo/demo01 工程!
SqlSessionFactoryBuilder
使用了构建者模式,用于解析配置文件和构建SqlSessionFactory
,支持的构建方法如下:
81// 方式1:基于配置文件构建(mybatis-config.xml及引用的 Mapper 配置)
2SqlSessionFactory build(InputStream inputStream)
3SqlSessionFactory build(InputStream inputStream, String environment)
4SqlSessionFactory build(InputStream inputStream, Properties properties)
5SqlSessionFactory build(InputStream inputStream, String env, Properties props)
6
7// 方式2:基于Java配置构建
8SqlSessionFactory build(Configuration config)
方式1已经在入门案例中介绍,方式2基于Java配置构建方式示例如下:
181// 获取数据源
2DataSource dataSource = BaseDataTest.createBlogDataSource();
3
4// 创建一个环境(使用JDBC事务管理器)
5Environment environment = new Environment("development", new JdbcTransactionFactory(), dataSource);
6
7// 创建一个配置
8Configuration configuration = new Configuration(environment);
9configuration.setLazyLoadingEnabled(true);
10configuration.setEnhancementEnabled(true);
11configuration.getTypeAliasRegistry().registerAlias(Blog.class);
12configuration.getTypeAliasRegistry().registerAlias(Post.class);
13configuration.getTypeAliasRegistry().registerAlias(Author.class);
14configuration.addMapper(BoundBlogMapper.class);
15configuration.addMapper(BoundAuthorMapper.class);
16
17// 构建SqlSessionFactory
18SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(configuration);
SqlSessionFactory
使用了工厂模式,用于管理配置信息和创建SqlSession
,支持的创建方法如下:
191// 默认创建方法
2// 事务不自动提交、使用默认事务隔离级别、使用SIMPLE执行器、从当前环境配置的DataSource 实例中获取 Connection 对象
3SqlSession openSession()
4
5// 支持设置是否自动提交、事务隔离级别、连接等
6SqlSession openSession(boolean autoCommit)
7SqlSession openSession(Connection connection)
8SqlSession openSession(TransactionIsolationLevel level)
9
10// 支持选择不同的执行器
11// SIMPLE:每个语句的执行创建一个新的预处理语句
12// REUSE: 复用预处理语句
13// BATCH: 批量执行更新语句
14SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level)
15SqlSession openSession(ExecutorType execType)
16SqlSession openSession(ExecutorType execType, boolean autoCommit)
17SqlSession openSession(ExecutorType execType, Connection connection)
18Configuration getConfiguration();
19
SqlSession
是 MyBatis 的核心组件之一,用于执行 SQL 语句、管理事务以及获取映射器对象(Mapper)等,主要方法如下:
481// 1. 语句执行方法
2<T> T selectOne(String statement, Object parameter)
3<E> List<E> selectList(String statement, Object parameter)
4<T> Cursor<T> selectCursor(String statement, Object parameter) // 游标形式:可以借助迭代器实现数据的惰性加载
5<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey)
6int insert(String statement, Object parameter) // insert、update 以及 delete 方法返回的值表示受该语句影响的行数
7int update(String statement, Object parameter)
8int delete(String statement, Object parameter)
9
10// 2. 语句执行方法(不需要参数的重载形式)
11<T> T selectOne(String statement)
12<E> List<E> selectList(String statement)
13<T> Cursor<T> selectCursor(String statement)
14<K,V> Map<K,V> selectMap(String statement, String mapKey)
15int insert(String statement)
16int update(String statement)
17int delete(String statement)
18
19// 3. select方法的高级版本:允许你限制返回行数的范围,或是提供自定义结果处理逻辑,通常在数据集非常庞大的情形下使用。
20<E> List<E> selectList (String statement, Object parameter, RowBounds rowBounds)
21<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds)
22<K,V> Map<K,V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowbounds)
23void select (String statement, Object parameter, ResultHandler<T> handler)
24void select (String statement, Object parameter, RowBounds rowBounds, ResultHandler<T> handler)
25
26// 4. 批量更新方法:当使用 BATCH 执行器时,可以使用这个方法清除(执行)缓存在 JDBC 驱动类中的批量更新语句。
27List<BatchResult> flushStatements()
28
29// 5. 事务控制方法:在使用由 Connection 实例控制的 JDBC 事务管理器时,用于控制事务作用域。
30// 注意:这些方法不适用于外部事务管理器。
31void commit()
32void commit(boolean force)
33void rollback()
34void rollback(boolean force)
35
36// 6. 清空本地缓存(会话缓存)
37void clearCache()
38
39// 7. 关闭会话
40void close()
41
42// 8. 获取 Configuration 实例
43Configuration getConfiguration()
44
45// 9. 获取映射器
46// 映射器相对上述CURD方法,最大的优点是符合类型安全,并且对IDE和单元测试也更加友好。
47<T> T getMapper(Class<T> type)
48
映射器通过SqlSession
的getMapper
方法获取,本质上是一个 DAO 接口的动态代理类,内部保存了当前SqlSession
对象的引用,主要用于执行SQL语句,并通过返回值获得执行结果。
注意:
用于创建映射器的 DAO 接口,不能出现方法重载,否则会出现命名空间冲突。
映射器方法的参数名默认按位置命名为
param1、param2...
等,可以使用@Param
注解进行修改。
MyBatis支持在映射器上通过注解进行映射配置,示例如下:
41public interface UserDao {
2 "SELECT * FROM USER") (
3 List<User> findAll();
4}
此时需修改核心配置mybatis-config.xml
,通过注解进行扫描:
41<!-- 通过注解进行扫描 -->
2<mappers>
3 <mapper class="org.example.dao.UserDao"/>
4</mappers>
注意
注意删除XML中该方法对应的映射配置,同一个映射器方法,不能同时存在两种配置方式。
MyBatis按照SLF4J -> COMMONS_LOGGING -> LOG4J2 -> LOG4J -> JDK_LOGGING
的顺序查找日志实现,可在配置文件中进行修改:
61<!-- mybatis-config.xml -->
2<configuration>
3 <settings>
4 <setting name="logImpl" value="LOG4J"/>
5 </settings>
6</configuration>
此外,还可以配置为STDOUT_LOGGING
和NO_LOGGING
,或者其它实现了 org.apache.ibatis.logging.Log
接口的日志适配器。
MyBatis-Spring是一个适配模块,可以将 MyBatis 无缝集成到 Spring 框架中,它可以:
从容器中查找数据源创建SqlSessionFactory
。
创建线程安全的SqlSession
或Mapper
,并将其注册到容器中,支持直接注入使用。
使用 Mapper 接口执行SQL语句时,从Spring上下文获取SqlSession,从而参与Spring事务管理。
将 MyBatis 抛出的持久化异常PersistenceException
统一转换为 Spring 的数据访问异常DataAccessException
。
引入相关依赖如下:
51<dependency>
2 <groupId>org.mybatis</groupId>
3 <artifactId>mybatis-spring</artifactId>
4 <version>2.0.7</version>
5</dependency>
Spring需要一个SqlSessionFactory来保存配置信息和创建SqlSession,可以通过SqlSessionFactoryBean
来创建并注册到Spring容器。
51<!-- 创建SqlSessionFactory -->
2<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
3 <!-- 需要一个dataSource -->
4 <property name="dataSource" ref="dataSource" />
5</bean>
等效的 Java 配置如下:
91
2public class MyBatisConfig {
3
4 public SqlSessionFactory sqlSessionFactory() throws Exception {
5 SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
6 factoryBean.setDataSource(dataSource());
7 return factoryBean.getObject();
8 }
9}
注意:
可以直接通过依赖注入或继承 SqlSessionDaoSupport 的方式来获得容器中的 SqlSessionFactory 实例。
不建议直接调用该实例的
openSession()
方法获取SQL会话,因为通过该会话执行SQL时,不会参与到Spring管理的事务中,并且需要手动关闭该会话。
SqlSessionTemplate是SqlSession的实现类,与其它 Spring 模板类似,它将会话数据保存在线程变量中,是线程安全的。
可以通过如下配置来创建SqlSessionTemplate
,以代替 MyBatis 原生的DefaultSqlSession
,将其注入到新创建的 Mapper 中,从而让 Spring 介入到 MyBatis 的会话管理,控制事务的提交和异常转换等相关操作。
51<!-- 创建SqlSession -->
2<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
3 <!-- 需要一个sqlSessionFactory -->
4 <constructor-arg index="0" ref="sqlSessionFactory" />
5</bean>
等效的Java配置如下:
71
2public class MyBatisConfig {
3
4 public SqlSessionTemplate sqlSession() throws Exception {
5 return new SqlSessionTemplate(sqlSessionFactory());
6 }
7}
SqlSessionTemplate创建完毕并注册到Spring容器后,可以通过注入的方式,来使用它,如下所示。
101
2public class UserDaoImpl implements UserDao {
3
4 private SqlSession sqlSession;
5
6 public User getUser(String userId) {
7 return sqlSession.selectOne("org.mybatis.spring.sample.mapper.UserMapper.getUser", userId);
8 }
9}
10
注意:
SqlSessionTemplate 是通过实现+组合方式对 DefaultSqlSession 的装饰,通过它执行SQL语句时,会获取 Spring 的事务管理器创建或获取当前线程会话,从而参与到Spring管理的事务中。
请勿直接调用 SqlSessionTemplate 的 commit()、rollback()、close() 方法,这将会抛出 UnsupportedOperationException 异常。
SqlSessionTemplate是线程安全的,但是数据源等配置信息是各线程共享的,如果存在多个数据源,则需要创建多个实例。
一般来说,实际开发中,都会使用 MyBatis 的映射器来访问数据库,可以通过MapperFactoryBean
来创建并注册到Spring容器。
71<bean id="userMapper" class="org.mybatis.spring.mapper.MapperFactoryBean">
2 <!--为UserMapper接口创建线程安全的映射器-->
3 <property name="mapperInterface" value="org.mybatis.spring.sample.mapper.UserMapper" />
4 <!--通过sqlSessionFactory传入配置信息-->
5 <property name="sqlSessionFactory" ref="sqlSessionFactory" />
6</bean>
7
等效的java配置如下:
91
2public class MyBatisConfig {
3
4 public UserMapper userMapper() throws Exception {
5 SqlSessionTemplate sqlSessionTemplate = new SqlSessionTemplate(sqlSessionFactory());
6 return sqlSessionTemplate.getMapper(UserMapper.class);
7 }
8}
9
然后就可以将映射器注入到你的业务对象中进行使用了。
91
2public class FooServiceImpl implements FooService {
3
4 private final UserMapper userMapper;
5
6 public User doSomeBusinessStuff(String userId) {
7 return this.userMapper.getUser(userId);
8 }
9}
提示:
你也可以为 MapperFactoryBean 传入一个 SqlSessionTemplate,这样将会使用其内部的 SqlSessionFactory,而忽略传入的那个。
在 MyBatis-Spring 整合工程中,一般通过 SqlSessionFactoryBean 来创建 SqlSessionFactory 并注册到 Spring 容器,以便 MapperFactoryBean或继承 SqlSessionDaoSupport 的类注入使用,其主要配置说明如下:
configLocation
用来指定MyBatis配置文件路径。但这个配置文件并不是一个完整的MyBatis配置,它会忽略配置中的<environments>
、<dataSource>
和<transactionManager>
等信息,但仍会解析<settings>
和<typeAliases>
等配置。
61<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
2 <property name="dataSource" ref="dataSource"/>
3
4 <!-- 引用mybatis配置文件中的配置 -->
5 <property name="configLocation" value="classpath:mybatis-config.xml"/>
6</bean>
mapperLocations
用来指定映射配置文件路径。很多时候,映射配置文件和映射器类不在同一资源路径下,这时可以通过该属性来指定,并且可以同时指定多个路径和递归搜索。
51<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
2 <property name="dataSource" ref="dataSource" />
3 <!-- 加载在sample.config.mappers包和子包中的映射器配置文件 -->
4 <property name="mapperLocations" value="classpath*:sample/config/mappers/**/*.xml" />
5</bean>
提示:
你也可以通过配置上面的 configLocation 属性,指定 MyBatis 配置文件,在其
<mappers>
标签内指定映射器配置文件。
如果你使用了多种数据库,那么需要设置 databaseIdProvider
属性。
201<!-- 配置数据库厂商的databaseId -->
2<bean id="databaseIdProvider" class="org.apache.ibatis.mapping.VendorDatabaseIdProvider">
3 <property name="properties">
4 <props>
5 <prop key="SQL Server">mssql</prop>
6 <prop key="DB2">db2</prop>
7 <prop key="Oracle">oracle</prop>
8 <prop key="MySQL">mysql</prop>
9 </props>
10 </property>
11</bean>
12
13<!-- 创建SqlSessionFactory -->
14<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
15 <property name="dataSource" ref="dataSource" />
16
17 <!--指定数据库厂商ID配置-->
18 <property name="databaseIdProvider" ref="databaseIdProvider"/>
19</bean>
20
configuration
属性能够在没有对应的 MyBatis 配置文件的情况下,直接设置 Configuration 实例。例如:
81<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
2 <property name="dataSource" ref="dataSource" />
3 <property name="configuration">
4 <bean class="org.apache.ibatis.session.Configuration">
5 <property name="mapUnderscoreToCamelCase" value="true"/>
6 </bean>
7 </property>
8</bean>
transactionFactoryClass
属性用于自定义事务工厂,一般不做配置。
MyBatis-Spring 工程提供的 SqlSessionTemplate,将会替代原 MyBatis 的 DefaultSqlSession 注入到映射器中,并且在注入前经过动态代理,在调用其方法时被拦截,用于控制事务和转换异常等操作。下面是创建时可以使用的一些配置参数:
ExecutorType
用于指定 SqlSessionTemplate 的执行器类型,如下配置创建一个用于批处理的SqlSessionTemplate。
71<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate">
2 <constructor-arg index="0" ref="sqlSessionFactory" />
3
4 <!--指定执行器类型为批处理执行器(编译1次,设置参数N次,执行1次)-->
5 <constructor-arg index="1" value="BATCH" />
6</bean>
7
等效的java配置如下:
71
2public class MyBatisConfig {
3
4 public SqlSessionTemplate sqlSession() throws Exception {
5 return new SqlSessionTemplate(sqlSessionFactory(), ExecutorType.BATCH);
6 }
7}
现在所有的映射语句可以进行批量操作了,可以在 DAO 中编写如下的代码。
121
2public class UserService {
3
4 private final SqlSession sqlSession;
5
6 public void insertUsers(List<User> users) {
7 for (User user : users) {
8 sqlSession.insert("org.mybatis.spring.sample.mapper.UserMapper.insertUser", user);
9 }
10 }
11}
12
注意:
同一个事务只能使用一种执行器,如果你需要不同的执行器执行SQL,请拆分为多个事务或完全不使用事务。
SqlSessionDaoSupport 是一个抽象的支持类,用来为你提供 SqlSession。业务类继承它后,调用 getSqlSession() 方法就会得到一个 SqlSessionTemplate,之后可以用于执行 SQL 方法,并参与 Spring 事务管理。
51public class UserDaoImpl extends SqlSessionDaoSupport implements UserDao {
2 public User getUser(String userId) {
3 return getSqlSession().selectOne("org.mybatis.spring.sample.mapper.UserMapper.getUser", userId);
4 }
5}
前提是你为业务对象注入了 SqlSessionFactory 或 SqlSessionTemplate,如果两个属性都被注入,那么SqlSessionFactory 将会被忽略。
31<bean id="userDao" class="org.mybatis.spring.sample.dao.UserDaoImpl">
2 <property name="sqlSessionFactory" ref="sqlSessionFactory" />
3</bean>
<mybatis:scan>
标签与组件扫描所使用的<context:component-scan/>
标签类似,通过base-package
属性指定多个以逗号分割的包名,递归扫描发现的映射器,然后创建对应的 MapperFactoryBean
并注册到Spring中。
111<beans xmlns="http://www.springframework.org/schema/beans"
2 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
3 xmlns:mybatis="http://mybatis.org/schema/mybatis-spring"
4 xsi:schemaLocation="
5 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
6 http://mybatis.org/schema/mybatis-spring http://mybatis.org/schema/mybatis-spring.xsd">
7
8 <!-- 扫描 org.example.dao 及其子包下的映射器 -->
9 <mybatis:scan base-package="org.example.dao" factory-ref="sqlSessionFactory"/>
10
11</beans>
下面是<mybatis:scan>
标签的一些配置参数:
factory-ref/template-ref:指定创建映射器时使用的 SqlSessionFactory 或 SqlSessionTemplate的Bean名称。
一般只在有多数据源时使用,因为默认情况下MapperFactoryBean会被注入默认的 SqlSessionFactory 或 SqlSessionTemplate。
annotation: 指定映射器应该具有的注解(如@Repository),如果扫描到的接口未包含该注解,则跳过解析。
marker-interface:指定映射器应该继承的接口,如果扫描到的接口未继承该接口,则跳过解析。
默认情况下,注册的映射器都以映射器接口的首字母小写非全限定类名作为名称,你可以通过 @Component 或 JSR-330 标准中 @Named 注解来进行修改。
注意:
<context:component-scan/>
标签无法发现并注册映射器。因为映射器是一个接口,组建扫描时无法实例化。映射器接口的Bean名称默认为首字母小写类名,可通过 @Component 或 @Named 注解修改。
@MapperScan
注解也可以用于批量创建Mapper,它会自动扫描basePackageClasses
或 basePackages
包及其子包的映射器。
51
2"org.mybatis.spring.sample.mapper") (
3public class AppConfig {
4 // ...
5}
注意:
如果未定义
basePackageClasses
或basePackages
,则会从声明@MapperScan
注解类的包中进行扫描。同样的,@MapperScan 也可配置 sqlSessionFactory、sqlSessionTemplate、markerInterface、annotationClass 等属性。
MapperScannerConfigurer
是一个 BeanDefinitionRegistryPostProcessor
,可以在容器创建的时候扫描映射器。
71<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
2 <property name="basePackage" value="org.mybatis.spring.sample.mapper" />
3
4 <!-- 可指定 SqlSessionFactory 的BeanName -->
5 <!-- 可指定 SqlSessionFactory 的BeanName -->
6 <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
7</bean>
如果有多个数据源,可以使用sqlSessionFactoryBeanName
或 sqlSessionTemplateBeanName
属性指定 sqlSessionFactory 或 sqlSessionTemplate 的 bean 名称(注意使用 vlaue 指定BenaName,而不是 ref 引用对象)。
为了支持 SpringBoot 2.2 的延迟初始化控制功能,新增了lazyInitialization
属性,用于控制映射器的延迟初始化,默认为false。如果使用延迟初始化功能,开发人员需要了解以下限制,否则延迟初始化功能无法在您的应用程序上使用。
不能使用 <association>(@One) 和 <collection>(@Many) 引用其他映射器的语句
不能使用<include> 包含到其他映射器的片段
不能使用 <cache-ref>(@CacheNamespaceRef) 引用其他映射器的缓存
不能使用 <select resultMap="...">(@ResultMap) 引用其他映射器的结果映射
但是,你可以通过使用 @DependsOn
同时初始化依赖 bean 来使用它,如下所示:
41"vendorMapper") (
2public GoodsMapper {
3 // ...
4}
MyBatis 与 Spring 整合后,使用同一个事务管理器(默认为DataSourceTransactionManage
),在事务开启时,会创建一个全新的 DefaultSqlSession
,当事务完成后,这个 session 会以合适的方式提交或回滚。
MyBatis使用与 JDBC 相同的事务管理器 DataSourceTransactionManager,并且配置方式一致,详情可以参考 Spring 事务相关讲解。
51<!-- 配置一个事务管理器 -->
2<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
3 <!-- 需要一个数据源 -->
4 <constructor-arg ref="dataSource" />
5</bean>
等效的java配置如下:
71
2public class DataSourceConfig {
3
4 public DataSourceTransactionManager transactionManager() {
5 return new DataSourceTransactionManager(dataSource());
6 }
7}
注意:
为事务管理器指定的 DataSource 必须和用来创建 SqlSessionFactoryBean 的是同一个数据源,否则事务管理器就无法工作了。
你可以使用 @Transaction
注解或 AOP 方式来配置哪些方法应该进行事务控制,这和 Spring 事务章节所讲述的方式相同。也可以使用Spring 提供的编程式API来精细控制事务,如下案例所示。
191
2public class UserService {
3
4 private final PlatformTransactionManager transactionManager;
5
6 public void createUser() {
7 // 获取当前活动事务或创建新事物(由TransactionDefinition决定事务传播行为)
8 TransactionStatus txStatus = transactionManager.getTransaction(new DefaultTransactionDefinition());
9 try {
10 userMapper.insertUser(user);
11 } catch (Exception e) {
12 // 出异常回滚
13 transactionManager.rollback(txStatus);
14 throw e;
15 }
16 // 业务成功,提交事务
17 transactionManager.commit(txStatus);
18 }
19}
MyBatis-Spring-Boot-Starter用于快速构建基于SpringBoot 和 Mybatis 的应用程序。
首先从官方文档查找合适的依赖版本。
如果你使用 Maven来管理项目,则可以通过下面配置来引入依赖。
51<dependency>
2 <groupId>org.mybatis.spring.boot</groupId>
3 <artifactId>mybatis-spring-boot-starter</artifactId>
4 <version>2.2.2</version>
5</dependency>
当容器中只有一个DataSource
时(或存在主候选对象),MyBatis-Spring-Boot-Starter 会进行自动配置。自动配置包括以下一些内容:
使用默认 DataSource 配置SqlSessionFactory
,使用该 SqlSessionFactory 配置SqlSessionTemplate
。
配置MapperScannerConfigurer
,用于批量创建Mapper
。
配置Interceptor、TypeHandler、DatabaseIdProvider、LanguageDriver
。
只需要将映射器接口加上@Mapper
注解即可,容器启动时会自动扫描类路径下带@Mapper
注解的映射器并注册到容器中。
51
2public interface CityMapper {
3 "SELECT * FROM CITY WHERE state = #{state}") (
4 City findByState( ("state") String state);
5}
然后就可以通过注入的方式使用该映射器了。
181
2public class SampleMybatisApplication implements CommandLineRunner {
3 private final CityMapper cityMapper;
4
5 public SampleMybatisApplication(CityMapper cityMapper) {
6 this.cityMapper = cityMapper;
7 }
8
9 public static void main(String[] args) {
10 SpringApplication.run(SampleMybatisApplication.class, args);
11 }
12
13
14 public void run(String... args) throws Exception {
15 System.out.println(this.cityMapper.findByState("CA"));
16 }
17}
18
同样,也可以通过注入的方式使用已配置好的SqlSessionFactory和SqlSessionTemplate。
121
2public class CityDao {
3 private final SqlSession sqlSession;
4
5 public CityDao(SqlSession sqlSession) {
6 this.sqlSession = sqlSession;
7 }
8
9 public City selectCityById(long id) {
10 return this.sqlSession.selectOne("selectCityById", id);
11 }
12}
注意:
如果需要修改扫描路径或扫描指定的注解/接口,可以结合 MyBatis-Spring 提供的
@MapperScan
注解。
MyBatis-Spring-Boot-Starter还会检测容器中存在的Interceptor
、TypeHandler
、DatabaseIdProvider
、LanguageDriver
接口实现类,并将它们添加到 Configuration 中。
601// 添加 Interceptor、TypeHandler、DatabaseIdProvider
2
3public class MyBatisConfig {
4
5 MyInterceptor myInterceptor() {
6 return MyInterceptor();
7 }
8
9 MyTypeHandler myTypeHandler() {
10 return MyTypeHandler();
11 }
12
13 MyLanguageDriver myLanguageDriver() {
14 return MyLanguageDriver();
15 }
16
17 VendorDatabaseIdProvider databaseIdProvider() {
18 VendorDatabaseIdProvider databaseIdProvider = new VendorDatabaseIdProvider();
19 Properties properties = new Properties();
20 properties.put("SQL Server", "sqlserver");
21 properties.put("DB2", "db2");
22 properties.put("H2", "h2");
23 databaseIdProvider.setProperties(properties);
24 return databaseIdProvider;
25 }
26}
27
28// 添加 ThymeleafLanguageDriver
29
30public class MyBatisConfig {
31
32 ThymeleafLanguageDriverConfig thymeleafLanguageDriverConfig() {
33 return ThymeleafLanguageDriverConfig.newInstance(c -> {
34 // ... customization code
35 });
36 }
37}
38
39// 添加 FreeMarkerLanguageDriverConfig
40
41public class MyBatisConfig {
42
43 FreeMarkerLanguageDriverConfig freeMarkerLanguageDriverConfig() {
44 return FreeMarkerLanguageDriverConfig.newInstance(c -> {
45 // ... customization code
46 });
47 }
48}
49
50// 添加 Velocity语言驱动程序
51
52public class MyBatisConfig {
53
54 VelocityLanguageDriverConfig velocityLanguageDriverConfig() {
55 return VelocityLanguageDriverConfig.newInstance(c -> {
56 // ... customization code
57 });
58 }
59}
60
MyBatis-Spring-Boot-Starter提供的SpringBootVFS
继承自MyBatis框架的VFS(虚拟文件系统),用于从应用程序或应用服务器加载指定的类,如别名对应的类、类型处理器等。
在自动配置 SqlSessionFactory 时,将会默认使用,但在自定义 SqlSessionFactory 时,需要通过下面示例代码显式调用。
111
2public class MyBatisConfig {
3
4 public SqlSessionFactory masterSqlSessionFactory() throws Exception {
5 SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
6 factoryBean.setDataSource(masterDataSource());
7 factoryBean.setVfs(SpringBootVFS.class);
8 // ...
9 return factoryBean.getObject();
10 }
11}
使用mybatis
前缀来配置MyBatis的相关属性,部分属性说明如下,完整的属性列表及说明可参考MybatisProperties
。
属性 | 说明 |
---|---|
config-location | mybatis.xml配置文件的位置 |
mapper-locations | 映射器xml配置文件的位置 |
type-aliases-package | 需要注册类型别名的包,可用, 、; 、\t 、\n 做分隔 |
check-config-location | 是否对mybatis.xml配置文件执行状态检查 |
type-aliases-super-type | 注册别名时,仅当实现了该类时才注册 |
type-handlers-package | 类型处理器所在包,用, 、; 、\t 、'\n'做分隔 |
executor-type | 默认的执行器类型,可选SIMPLE``REUSE``BATCH |
default-scripting-language-driver | 默认脚本语言驱动程序类(mybatis-spring 2.0.2+) |
configuration-properties | MyBatis配置的外部化属性,指定的属性可以用作MyBatis配置文件和映射器文件的占位符 |
lazy-initialization | 是否启用映射器bean的延迟初始化(mybatis-spring 2.0.2+) |
mapper-default-scope | 通过自动配置扫描的映射器Bean的默认范围(mybatis-spring 2.0.6+) |
mybatis.inject-sql-session-on-mapper-scan | 是否注入SqlSessionTemplate,2.2.1+版本默认为true(否则注入SqlSessionFactory) |
configuration.* | mybatis.xml的setting属性,注意不可与config-location属性一起使用 |
scripting-language-driver.thymeleaf.* | Thymeleaf语言驱动特定属性 |
scripting-language-driver.freemarker.* | FreeMarker语言驱动特定属性 |
scripting-language-driver.velocity.* | Velocit语言驱动特定属性 |
下面是一个简单的使用案例。
81## application.yml
2mybatis
3 type-aliases-package com.example.domain.model
4 type-handlers-package com.example.typehandler
5 configuration
6 map-underscore-to-camel-casetrue
7 default-fetch-size100
8 default-statement-timeout30
实现ConfigurationCustomizer
接口可对 Configuration 进行全面的 Java 配置。
131
2public class MyBatisConfig {
3
4 ConfigurationCustomizer mybatisConfigurationCustomizer() {
5 return new ConfigurationCustomizer() {
6
7 public void customize(Configuration configuration) {
8 // customize ...
9 }
10 };
11 }
12}
13
实现SqlSessionFactoryBeanCustomizer
接口可对SqlSessionFactoryBean进行全面的Java配置(2.2.2+版本)。
131
2public class MyBatisConfig {
3
4 SqlSessionFactoryBeanCustomizer sqlSessionFactoryBeanCustomizer() {
5 return new SqlSessionFactoryBeanCustomizer() {
6
7 public void customize(SqlSessionFactoryBean factoryBean) {
8 // customize ...
9 }
10 };
11 }
12}
13
通过 properties
标签定义属性名和属性值:
151<!-- 方式1:内部标签 -->
2<!-- 方式2:通过 resource 属性引入类路径下的 properties 文件 -->
3<!-- 方式3:通过 url 属性引入网络或本地 properties 文件 -->
4<properties resource="org/mybatis/example/config.properties">
5 <!-- 内部标签定义属性 -->
6 <property name="username" value="dev_user"/>
7 <property name="password" value="F2Fa3!33TYyg"/>
8
9 <!-- 开启属性默认值特性 -->
10 <property name="org.apache.ibatis.parsing.PropertyParser.enable-default-value" value="true"/>
11 <!-- 设置默认值分隔符(默认为冒号) -->
12 <property name="org.apache.ibatis.parsing.PropertyParser.default-value-separator" value=":"/>
13</properties>
14
15
注意:
不同位置配置的属性优先级如下:直接通过 build() 方法传入的属性 > 引入文件中配置的属性 > 内部property标签定义的属性。
在 mybatis-config.xml
中使用属性:
61<dataSource type="POOLED">
2 <property name="driver" value="${driver}"/>
3 <property name="url" value="${url}"/>
4 <property name="username" value="${username:root}"/> <!-- 属性默认值 -->
5 <property name="password" value="${password}"/>
6</dataSource>
注意:
使用属性时可以设置默认值,但需要启用
enable-default-value
开关
通过settings
标签可设置各项参数,以调整 MyBatis 的默认行为:
1041<settings>
2 <!-- 全局缓存开关 -->
3 <setting name="cacheEnabled" value="true"/>
4
5 <!-- 全局延迟加载开关,可通过 fetchType 属性覆盖 -->
6 <setting name="lazyLoadingEnabled" value="false"/>
7
8 <!-- 任一方法的调用都会加载该对象的所有延迟加载属性? -->
9 <setting name="aggressiveLazyLoading" value="false"/>
10
11 <!-- 允许单个语句返回多结果集(需要数据库驱动支持) -->
12 <setting name="multipleResultSetsEnabled" value="true"/>
13
14 <!-- 使用列标签代替列名 -->
15 <setting name="useColumnLabel" value="true"/>
16
17 <!-- 使用 JDBC 自动生成的主键(需要数据库驱动支持) -->
18 <setting name="useGeneratedKeys" value="false"/>
19
20 <!-- 自动映射行为 -->
21 <!-- NONE:关闭自动映射 -->
22 <!-- PARTIAL:只会自动映射没有定义嵌套结果映射的字段 -->
23 <!-- FULL:会自动映射任何复杂的结果集(无论是否嵌套) -->
24 <setting name="autoMappingBehavior" value="PARTIAL"/>
25
26 <!-- 自动映射未知列行为 -->
27 <!-- NONE:不做任何反应 -->
28 <!-- WARNING:输出警告日志 -->
29 <!-- FAILING:映射失败,抛出 SqlSessionException -->
30 <setting name="autoMappingUnknownColumnBehavior" value="NONE"/>
31
32 <!-- 默认执行器类型 -->
33 <!-- SIMPLE:普通的执行器 -->
34 <!-- REUSE:重用预处理语句(PreparedStatement) -->
35 <!-- BATCH:不仅重用语句还会执行批量更新 -->
36 <setting name="defaultExecutorType" value="SIMPLE"/>
37
38 <!-- 语句执行超时时间 -->
39 <setting name="defaultStatementTimeout" value="25"/>
40
41 <!-- 结果集提取批次数(fetchSize) -->
42 <setting name="defaultFetchSize" value="100"/>
43
44 <!-- 结果集滚动策略,可选:DEFAULT(未设置)、FORWARD_ONLY、SCROLL_SENSITIVE、SCROLL_INSENSITIVE -->
45 <setting name="defaultResultSetType" value="DEFAULT"/>
46
47 <!-- “禁止”在嵌套语句中使用分页(RowBounds) -->
48 <setting name="safeRowBoundsEnabled" value="false"/>
49
50 <!-- “禁止”在嵌套语句中使用结果处理器(ResultHandler) -->
51 <setting name="safeResultHandlerEnabled" value="false"/>
52
53 <!-- 下划线列名映射小驼峰属性名 -->
54 <setting name="mapUnderscoreToCamelCase" value="false"/>
55
56 <!-- 本地缓存(一级缓存)范围,可选:SESSION、STATEMENT -->
57 <!-- 注意:本地缓存用于防止循环引用和加速重复的嵌套查询 -->
58 <setting name="localCacheScope" value="SESSION"/>
59
60 <!-- 当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型,可选:NULL、VARCHAR、OTHER -->
61 <setting name="jdbcTypeForNull" value="OTHER"/>
62
63 <!-- 指定对象的哪些方法触发一次延迟加载 -->
64 <setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
65
66 <!-- 指定动态 SQL 生成使用的默认脚本语言 -->
67 <setting name="defaultScriptingLanguage" value="org.apache.ibatis.scripting.xmltags.XMLLanguageDriver"/>
68
69 <!-- 指定 Enum 使用的默认 TypeHandler -->
70 <setting name="defaultEnumTypeHandler" value="org.apache.ibatis.type.EnumTypeHandler"/>
71
72 <!-- 当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法 -->
73 <setting name="callSettersOnNulls" value="false"/>
74
75 <!-- 当返回行的所有列都是空时,是否返回一个空实例,默认是返回 null -->
76 <setting name="returnInstanceForEmptyRow" value="false"/>
77
78 <!-- MyBatis 增加到日志名称的前缀 -->
79 <setting name="logPrefix" value="mylog_"/>
80
81 <!-- MyBatis 所用日志的具体实现,未指定时将自动查找 -->
82 <!-- 可选:SLF4J 、 LOG4J 、 LOG4J2 、 JDK_LOGGING 、 COMMONS_LOGGING 、 STDOUT_LOGGING 、 NO_LOGGING -->
83 <setting name="logImpl" value="SLF4J"/>
84
85 <!-- Mybatis 创建可延迟加载对象所用到的代理工具,可选:CGLIB、JAVASSIST -->
86 <setting name="proxyFactory" value="JAVASSIST"/>
87
88 <!-- 指定 VFS 的实现 -->
89 <setting name="vfsImpl" value="com.huangyuanxin.mybatis.HyxVfs"/>
90
91 <!-- 允许使用方法签名中的名称作为语句参数名称 -->
92 <!-- 注意:项目必须采用 Java 8 编译,并且加上 -parameters 选项 -->
93 <setting name="useActualParamName" value="true"/>
94
95 <!-- 指定一个提供 Configuration 实例的类,用来加载被反序列化对象的延迟加载属性值 -->
96 <!-- 注意: 这个类必须包含一个签名为 static Configuration getConfiguration() 的方法 -->
97 <setting name="configurationFactory" value="com.huangyuanxin.mybatis.MyConfigurationFactory"/>
98
99 <!-- 从SQL中删除多余的空格字符 -->
100 <setting name="shrinkWhitespacesInSql" value="false"/>
101
102 <!-- 默认的数据库厂商类型 -->
103 <setting name="defaultSqlProviderType" value="com.huangyuanxin.mybatis.MySqlProvider"/>
104</settings>
MyBatis默认定义了许多别名,其中基本类型一般为下划线+类型名,包装类型一般为类名小写。
别名(不区分大小写) | 映射的类型 |
---|---|
_byte | byte |
_long | long |
_short | short |
_int | int |
_integer | int |
_double | double |
_float | float |
_boolean | boolean |
string | String |
byte | Byte |
long | Long |
short | Short |
int | Integer |
integer | Integer |
double | Double |
float | Float |
boolean | Boolean |
date | Date |
decimal | BigDecimal |
bigdecimal | BigDecimal |
object | Object |
map | Map |
hashmap | HashMap |
list | List |
arraylist | ArrayList |
collection | Collection |
iterator | Iterator |
111<!-- 方式1:指定类 -->
2<typeAliases>
3 <typeAlias alias="Author" type="domain.blog.Author"/>
4 <typeAlias alias="Blog" type="domain.blog.Blog"/>
5</typeAliases>
6
7<!-- 方式2:指定包 -->
8<!-- 注意:别名默认为全小写的类名,可通过在类上加上 @Alias("author") 注解修改 -->
9<typeAliases>
10 <package name="domain.blog"/>
11</typeAliases>
在MyBatis配置使用全类名的地方,都可以使用别名代替:
41<!-- 在 resultType 属性使用别名 -->
2<select id="findById" parameterType="int" resultType="user">
3 SELECT * FROM user WHERE id = #{id}
4</select>
在设置SQL参数或从结果集取值时,需要类型处理器进行 JDBC 类型和 Java 类型的转换,MyBatis内置的类型处理器如下:
类型处理器 | Java 类型 | JDBC 类型 |
---|---|---|
BooleanTypeHandler | java.lang.Boolean、boolean | 数据库兼容的 BOOLEAN |
ByteTypeHandler | java.lang.Byte、byte | 数据库兼容的 NUMERIC 或 BYTE |
ShortTypeHandler | java.lang.Short、short | 数据库兼容的 NUMERIC 或 SMALLINT |
IntegerTypeHandler | java.lang.Integer、int | 数据库兼容的 NUMERIC 或 INTEGER |
LongTypeHandler | java.lang.Long、long | 数据库兼容的 NUMERIC 或 BIGINT |
FloatTypeHandler | java.lang.Float、float | 数据库兼容的 NUMERIC 或 FLOAT |
DoubleTypeHandler | java.lang.Double、double | 数据库兼容的 NUMERIC 或 DOUBLE |
BigDecimalTypeHandler | java.math.BigDecimal | 数据库兼容的 NUMERIC 或 DECIMAL |
StringTypeHandler | java.lang.String | CHAR, VARCHAR |
ClobReaderTypeHandler | java.io.Reader | - |
ClobTypeHandler | java.lang.String | CLOB, LONGVARCHAR |
NStringTypeHandler | java.lang.String | NVARCHAR, NCHAR |
NClobTypeHandler | java.lang.String | NCLOB |
BlobInputStreamTypeHandler | java.io.InputStream | - |
ByteArrayTypeHandler | byte[] | 数据库兼容的字节流类型 |
BlobTypeHandler | byte[] | BLOB, LONGVARBINARY |
DateTypeHandler | java.util.Date | TIMESTAMP |
DateOnlyTypeHandler | java.util.Date | DATE |
TimeOnlyTypeHandler | java.util.Date | TIME |
SqlTimestampTypeHandler | java.sql.Timestamp | TIMESTAMP |
SqlDateTypeHandler | java.sql.Date | DATE |
SqlTimeTypeHandler | java.sql.Time | TIME |
ObjectTypeHandler | Any | OTHER 或未指定类型 |
EnumTypeHandler | Enumeration Type | VARCHAR 或任何兼容的字符串类型,用来存储枚举的名称(而不是索引序数值) |
EnumOrdinalTypeHandler | Enumeration Type | 任何兼容的 NUMERIC 或 DOUBLE 类型,用来存储枚举的序数值(而不是名称)。 |
SqlxmlTypeHandler | java.lang.String | SQLXML |
InstantTypeHandler | java.time.Instant | TIMESTAMP |
LocalDateTimeTypeHandler | java.time.LocalDateTime | TIMESTAMP |
LocalDateTypeHandler | java.time.LocalDate | DATE |
LocalTimeTypeHandler | java.time.LocalTime | TIME |
OffsetDateTimeTypeHandler | java.time.OffsetDateTime | TIMESTAMP |
OffsetTimeTypeHandler | java.time.OffsetTime | TIME |
ZonedDateTimeTypeHandler | java.time.ZonedDateTime | TIMESTAMP |
YearTypeHandler | java.time.Year | INTEGER |
MonthTypeHandler | java.time.Month | INTEGER |
YearMonthTypeHandler | java.time.YearMonth | VARCHAR 或 LONGVARCHAR |
JapaneseDateTypeHandler | java.time.chrono.JapaneseDate | DATE |
可以通过实现 org.apache.ibatis.type.TypeHandler
接口, 或继承org.apache.ibatis.type.BaseTypeHandler
抽象类(推荐),来自定义类型处理器:
261// ExampleTypeHandler.java
2// 将java类型 String 映射到JDBC类型 VARCHAR
3JdbcType.VARCHAR) (
4public class ExampleTypeHandler extends BaseTypeHandler<String> {
5
6
7 public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType) throws SQLException {
8 ps.setString(i, parameter);
9 }
10
11
12 public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
13 return rs.getString(columnName);
14 }
15
16
17 public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
18 return rs.getString(columnIndex);
19 }
20
21
22 public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
23 return cs.getString(columnIndex);
24 }
25}
26
自定义类型处理器需要在typehandlers
标签进行配置:
81<!-- mybatis-config.xml -->
2<typeHandlers>
3 <!-- 注册自定义类型处理器 -->
4 <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
5
6 <!-- 也可以使用自动查找类型处理器(注意:此时JDBC 的类型仅能通过注解方式来指定) -->
7 <!-- <package name="org.mybatis.example"/> -->
8</typeHandlers>
注意:
可以通过
@MappedTypes
注解或typeHandler
标签的javaType="String"
属性指定 Java 类型(优先)。可以通过
@MappedJdbcTypes
注解或typeHandler
标签的jdbcType="VARCHAR"
属性指定 JDBC 类型(优先)。
@MappedJdbcTypes
注解的includeNullJdbcType=true
参数可以开启对 NULL 值的映射处理(默认关闭)。
泛型类型处理器可以处理某一类 java 类型和 JDBC 类型之间的转换:
141// GenericTypeHandler.java
2public class GenericTypeHandler<E extends MyObject> extends BaseTypeHandler<E> {
3
4 // 处理的具体类型
5 private Class<E> type;
6
7 // 构造函数:接收MyBatis传入的具体类型
8 public GenericTypeHandler(Class<E> type) {
9 if (type == null) throw new IllegalArgumentException("Type argument cannot be null");
10 this.type = type;
11 }
12
13 ...
14}
EnumTypeHandler
和 EnumOrdinalTypeHandler
都是泛型类型处理器,它会处理任意继承了 Enum
的类,用于枚举值和枚举值名称或枚举值顺序之间的转换。
181<!-- 方式1:全局指定枚举类型处理器(mybatis-config.xml),以自动处理 枚举类属性 的映射 -->
2<typeHandlers>
3 <typeHandler handler="org.apache.ibatis.type.EnumOrdinalTypeHandler" javaType="java.math.RoundingMode"/>
4</typeHandlers>
5
6<!-- 方式2:在 resultMap 中单独指定枚举类型处理器,用于获取结果集数据 -->
7<resultMap id="selectUser" type="org.apache.ibatis.submitted.rounding.User" >
8 <id column="id" property="id"/>
9 <result column="name" property="name"/>
10 <result column="roundingMode" property="roundingMode" typeHandler="org.apache.ibatis.type.EnumTypeHandler"/>
11</resultMap>
12
13<!-- 方式2:在 行内参数映射 指定枚举类型处理器,用于设置SQL参数 -->
14<insert id="insert2">
15 insert into users2 (id, name, funkyNumber, roundingMode) values (
16 #{id}, #{name}, #{funkyNumber}, #{roundingMode, typeHandler=org.apache.ibatis.type.EnumTypeHandler}
17 )
18</insert>
ObjectFactory用于实例化结果对象,一般通过无参构造来完成,如果配置了构造参数,则调用对应的有参构造。
如需修改 ObjectFactory 的默认行为,可以对其进行自定义:
231// ExampleObjectFactory.java
2public class ExampleObjectFactory extends DefaultObjectFactory {
3
4 // 处理无参构造
5 public Object create(Class type) {
6 return super.create(type);
7 }
8
9 // 处理有参构造
10 public Object create(Class type, List<Class> constructorArgTypes, List<Object> constructorArgs) {
11 return super.create(type, constructorArgTypes, constructorArgs);
12 }
13
14 // 配置ObjectFactory
15 public void setProperties(Properties properties) {
16 super.setProperties(properties);
17 }
18
19 // 是否集合?
20 public <T> boolean isCollection(Class<T> type) {
21 return Collection.class.isAssignableFrom(type);
22 }
23}
自定义 ObjectFactory 在 mybatis-config.xml 的objectFactory
标签中进行配置:
31<objectFactory type="org.mybatis.example.ExampleObjectFactory">
2 <property name="someProperty" value="100"/>
3</objectFactory>
MyBatis 的插件可对如下一些方法进行拦截,并在其前后植入自定义代码:
StatementHandler (prepare, parameterize, batch, update, query)
ParameterHandler (getParameterObject, setParameters)
Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ResultSetHandler (handleResultSets, handleOutputParameters)
制作插件需要实现 Interceptor
接口,并指定想要拦截的方法签名:
241// ExamplePlugin.java
2// 拦截 Executor 类中参数列表为 MappedStatement.class,Object.class 的 update 方法
3 ({
4 type= Executor.class, method = "update", args = {MappedStatement.class,Object.class}) (
5})
6public class ExamplePlugin implements Interceptor {
7 // 插件属性
8 private Properties properties = new Properties();
9
10 // 拦截逻辑
11 public Object intercept(Invocation invocation) throws Throwable {
12 // 放行
13 Object returnObject = invocation.proceed();
14
15 // 返回
16 return returnObject;
17 }
18
19 // 设置插件属性
20 public void setProperties(Properties properties) {
21 this.properties = properties;
22 }
23}
24
自定义插件在 mybatis-config.xml 的plugins
标签中进行配置:
51<plugins>
2 <plugin interceptor="org.mybatis.example.ExamplePlugin">
3 <property name="someProperty" value="100"/>
4 </plugin>
5</plugins>
531/**
2 * Mybatis插件:统计SQL执行时间
3 *
4 * @author huangyuanxin
5 * @date 2021-07-30
6 */
7value = { (
8 type = Executor.class, method = "update", args = {MappedStatement.class, Object.class}), (
9 type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, (
10 RowBounds.class, ResultHandler.class}),
11 type = Executor.class, method = "query", args = {MappedStatement.class, Object.class, (
12 RowBounds.class, ResultHandler.class, CacheKey.class, BoundSql.class})})
13public class MyBatisExcutorInterceptor implements Interceptor {
14
15 public Object intercept(Invocation invocation) {
16 // 获取参数传入的MappedStatement
17 MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
18
19 // 获取MappedStatementId ==> com.**.dao.**Dao.selectByPrimaryKey
20 String sqlId = mappedStatement.getId();
21
22 // 记录开始时间
23 long start = System.currentTimeMillis();
24
25 try {
26 // 放行
27 return invocation.proceed();
28 } catch (Exception e) {
29 // SQL执行出现异常
30 System.out.println(sqlId + "执行失败!");
31 return null;
32 } finally {
33 // 结束时间
34 long time = System.currentTimeMillis() - start;
35
36 // 输出到控制台 或 打印日志
37 System.out.println(sqlId + ":cost time " + time + " ms");
38 }
39 }
40
41
42 public Object plugin(Object arg0) {
43 // JDK动态代理,InvocationHandler为Plugin
44 // Plugin拦截行为为调用被代理对象的intercept方法
45 // 多个MyBatis拦截器则会被多次动态代理,包装多次!
46 return Plugin.wrap(arg0, this);
47 }
48
49
50 public void setProperties(Properties arg0) {
51 // nothing 该拦截器无需配置属性
52 }
53}
environments 标签定义了 SQL 执行环境相关的配置,并且支持多个 environment 子标签的定义。
201<environments default="development">
2
3 <!--开发环境:development-->
4 <environment id="development">
5 <!-- 事务管理器 -->
6 <transactionManager type="JDBC">
7 <property name="..." value="..."/>
8 </transactionManager>
9 <!-- 数据库 -->
10 <dataSource type="POOLED">
11 <property name="driver" value="${driver}"/>
12 <property name="url" value="${url}"/>
13 <property name="username" value="${username}"/>
14 <property name="password" value="${password}"/>
15 </dataSource>
16 </environment>
17
18 <!-- XXX环境-->
19
20</environments>
注意:
尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择其中一个,默认环境通过
default
属性指定。
在 MyBatis 中有两种类型的事务管理器:
JDBC:直接使用 JDBC 的提交和回滚设施,它依赖从数据源获得的连接来管理事务作用域。
MANAGED:使用容器来管理事务的整个生命周期。从不提交或回滚一个连接,但默认情况下它会关闭连接。
注意:
在使用 MANAGED 事务管理器的时候, 一些容器并不希望连接被关闭,因此需要将
closeConnection
属性设置为 false 。如果你正在使用 Spring + MyBatis,则没有必要配置事务管理器,因为 Spring 模块会使用自带的管理器来覆盖前面的配置。
如需自定义事务管理器,需要实现TransactionFactory
和Transaction
接口:
151// 事务管理器
2public interface TransactionFactory {
3 void setProperties(Properties props);
4 Transaction newTransaction(Connection conn);
5 Transaction newTransaction(DataSource dataSource, TransactionIsolationLevel level, boolean autoCommit);
6}
7
8// 事务
9public interface Transaction {
10 Connection getConnection() throws SQLException;
11 void commit() throws SQLException;
12 void rollback() throws SQLException;
13 void close() throws SQLException;
14 Integer getTimeout() throws SQLException;
15}
dataSource 标签使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。有三种内建的数据源类型:
UNPOOLED:这个数据源的实现会每次请求时打开和关闭连接。
driver
这是 JDBC 驱动的 Java 类全限定名(并不是 JDBC 驱动中可能包含的数据源类)。
url
这是数据库的 JDBC URL 地址。
username
登录数据库的用户名。
password
登录数据库的密码。
defaultTransactionIsolationLevel 默认的连接事务隔离级别。
defaultNetworkTimeout 等待数据库操作完成的默认网络超时时间(单位:毫秒)。
POOLED:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来,避免了创建新的连接实例时所必需的初始化和认证时间。 除了上述提到 UNPOOLED 下的属性外,还有更多属性用来配置 POOLED 的数据源:
poolMaximumActiveConnections
最大活动连接数,默认值:10。
poolMaximumIdleConnections
最大空闲连接数。
poolMaximumCheckoutTime
在被强制返回之前,池中连接被检出时间,默认值:20000 毫秒。
poolTimeToWait
获取连接超时时间,超时后打印错误日志并尝试重新获取连接,默认值:20000 毫秒。
poolPingEnabled
是否启用侦测查询,默认值:false。
poolPingQuery
侦测查询语句,用来检验连接是否正常工作并准备接受请求,默认“NO PING QUERY SET”。
poolPingConnectionsNotUsedFor
侦测查询的频率,默认值:0(所有连接每一时刻都被侦测,一般与 poolTimeToWait 一致)。
poolMaximumLocalBadConnectionTolerance
坏连接容忍度底层设置,默认值:3(新增于 3.4.5)。
JNDI:这个数据源实现是为了能在如 EJB 或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个 JNDI 上下文的数据源引用。这种数据源配置只需要两个属性:
initial_context
这个属性用来在 InitialContext 中寻找上下文(即,initialContext.lookup(initial_context))。
data_source
这是引用数据源实例位置的上下文路径。
注意:
可通过
driver.
前缀对数据库驱动进行配置,如:driver.encoding=UTF8
,将会通过 DriverManager.getConnection(url, driverProperties) 方法进行传递。数据源配置是可选的,但如果要启用延迟加载特性,就必须配置数据源。
可通过实现 org.apache.ibatis.datasource.DataSourceFactory
接口或继承UnpooledDataSourceFactory
抽象类来使用第三方数据源实现:
91import org.apache.ibatis.datasource.unpooled.UnpooledDataSourceFactory;
2import com.mchange.v2.c3p0.ComboPooledDataSource;
3
4// 定义C3P0数据源
5public class C3P0DataSourceFactory extends UnpooledDataSourceFactory {
6 public C3P0DataSourceFactory() {
7 this.dataSource = new ComboPooledDataSource();
8 }
9}
使用自定义的C3P0数据源如下:
61<dataSource type="org.myproject.C3P0DataSourceFactory">
2 <property name="driver" value="org.postgresql.Driver"/>
3 <property name="url" value="jdbc:postgresql:mydb"/>
4 <property name="username" value="postgres"/>
5 <property name="password" value="root"/>
6</dataSource>
为了方便后续使用,可在 mybatis-config.xml
中对各数据库厂商配置别名:
91<!-- 默认使用 VendorDatabaseIdProvider 识别数据库厂商 -->
2<!-- 左侧用于匹配 DatabaseMetaData#getDatabaseProductName() 返回的长字符串, 右侧是配置的别名 -->
3<databaseIdProvider type="DB_VENDOR">
4 <property name="MySQL" value="mysql"/>
5 <property name="Oracle" value="oracle"/>
6 <property name="SQL Server" value="sqlserver"/>
7 <property name="PostgreSQL" value="postgresql"/>
8 <property name="H2" value="h2"/>
9</databaseIdProvider>
可以根据数据库厂商标识,针对不同数据库执行不同SQL:
221<!-- 默认SQL -->
2<select id="selectUser" resultType="User">
3 SELECT * FROM users WHERE id = #{id}
4</select>
5
6<!-- Mysql数据库特有SQL -->
7<!-- 方式1:在select/update/insert/delete标签中使用 -->
8<select id="selectUser" resultType="User" databaseId="mysql">
9 SELECT * FROM users WHERE user_id = #{id}
10</select>
11<!-- 方式2:在if/when标签中使用 -->
12<select id="selectUserById" resultType="User">
13 SELECT * FROM users WHERE
14 <choose>
15 <when test="_databaseId == 'mysql'">
16 user_id = #{id}
17 </when>
18 <otherwise>
19 id = #{id}
20 </otherwise>
21 </choose>
22</select>
注意:
databaseId配置只在同一资源文件生效,在不同资源文件,可能会先把通用的加载,再加载指定库的就会报错。
可以通过实现接口 org.apache.ibatis.mapping.DatabaseIdProvider
来自定义数据库厂商别名配置行为(替换DB_VENDOR):
151public class MyDatabaseIdProvider implements DatabaseIdProvider {
2 private Properties properties;
3
4
5 public void setProperties(Properties properties) {
6 this.properties = properties;
7 }
8
9
10 public String getDatabaseId(DataSource dataSource) throws SQLException {
11 // 自定义逻辑,根据数据源获取 databaseId
12 String productName = dataSource.getConnection().getMetaData().getDatabaseProductName();
13 return properties.getProperty(productName);
14 }
15}
251<!-- 使用相对于类路径的资源引用 -->
2<mappers>
3 <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
4 <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
5 <mapper resource="org/mybatis/builder/PostMapper.xml"/>
6</mappers>
7
8<!-- 使用完全限定资源定位符(URL) -->
9<mappers>
10 <mapper url="file:///var/mappers/AuthorMapper.xml"/>
11 <mapper url="file:///var/mappers/BlogMapper.xml"/>
12 <mapper url="file:///var/mappers/PostMapper.xml"/>
13</mappers>
14
15<!-- 使用映射器接口实现类的完全限定类名 -->
16<mappers>
17 <mapper class="org.mybatis.builder.AuthorMapper"/>
18 <mapper class="org.mybatis.builder.BlogMapper"/>
19 <mapper class="org.mybatis.builder.PostMapper"/>
20</mappers>
21
22<!-- 将包内的映射器接口实现全部注册为映射器 -->
23<mappers>
24 <package name="org.mybatis.builder"/>
25</mappers>
这些配置会告诉 MyBatis 去哪里找映射文件,剩下的细节就应该是每个 SQL 映射文件了,也就是接下来我们要讨论的。
select
标签用于配置查询语句:
91<!-- 根据ID查询用户 -->
2<select id="findById" resultType="org.example.model.User">
3 SELECT * FROM user WHERE id = #{id}
4</select>
5
6<!-- 调用存储过程 -->
7<select id="selectBlog" resultType="org.example.model.Blog" statementType="CALLABLE">
8 {call getBlogs(#{id,jdbcType=INTEGER,mode=IN})}
9</select>
其可选的属性配置如下:
属性 | 描述 |
---|---|
parameterType | 参数类型,可自动推断,一般不填 |
parameterMap | 参数映射,已废弃,推荐使用行内参数映射 |
resultType | 结果类型 |
resultMap | 结果映射 |
flushCache | 语句执行前,是否清空本地缓存和二级缓存,默认为false |
useCache | 是否启用二级缓存,默认为true |
timeout | 查询超时时间,默认为空,取决于驱动配置 |
fetchSize | 结果集提取批次数,默认为空,取决于驱动配置 |
statementType | 语句类型,可选:STATEMENT、PREPARED、CALLABLE |
resultSetType | 结果集类型,可选:FORWARD_ONLY、SCROLL_SENSITIVE、SCROLL_INSENSITIVE,默认为空(DEFAULT) |
databaseId | 适用的数据库厂商 |
resultOrdered | 结果是否被排序,默认为false,可能会用于优化性能 |
resultSets | 设置多结果集名称列表,以逗号分隔 |
insert
、update
和delete
标签用于配置更新语句:
141<!-- 插入 -->
2<insert id="insertAuthor">
3 insert into Author (id,username,password,email) values (#{id},#{username},#{password},#{email})
4</insert>
5
6<!-- 更新 -->
7<update id="updateAuthor">
8 update Author set password = #{password} where id = #{id}
9</update>
10
11<!-- 删除 -->
12<delete id="deleteAuthor">
13 delete from Author where id = #{id}
14</delete>
注意:
insert、update和 delete 标签仅有语义上的区别,且都可以用来执行 DDL 语句。
flushCache
属性对更新标签的默认值为true
,而对查询标签的默认值为false
。
此外,更新标签还有几个额外的属性配置,用于自增列回写:
属性 | 描述 |
---|---|
useGeneratedKeys | 在执行insert和update语句时,是否回写数据库自动生成的主键(getGeneratedKeys),默认为false |
keyColumn | 主键列名,默认为第一列,复合主键以逗号分隔 |
keyProperty | 主键列对应属性名,复合主键以逗号分隔,会被设置为getGeneratedKeys的返回值或通过selectKey标签设置 |
141<!-- 在插入时回写自增列(假设id列为自增列) -->
2<insert id="insertAuthor" useGeneratedKeys="true" keyProperty="id">
3 insert into Author (username,password,email) values (#{username},#{password},#{email})
4</insert>
5
6<!-- 在批量插入时回写自增列 -->
7<insert id="insertAuthor" useGeneratedKeys="true" keyProperty="id">
8 insert into Author (username, password, email) values
9
10 <!-- 传入List<Author>类型参数进行批量插入,自动生成的主键将会依次回写到对应的id属性中 -->
11 <foreach item="item" collection="list" separator=",">
12 (#{item.username}, #{item.password}, #{item.email})
13 </foreach>
14</insert>
selectKey
标签可用于生成主键,并设置到 insert 语句的参数中:
101<!-- 在插入前生成主键 -->
2<insert id="insertAuthor">
3 <!-- 生成主键 -->
4 <selectKey keyProperty="id" resultType="int" order="BEFORE">
5 select cast(random()*1000000 as integer)
6 </selectKey>
7
8 <!-- 带主键插入 -->
9 insert into Author (id, username, password, email) values (#{id}, #{username}, #{password}, #{email})
10</insert>
注意:
selectKey 标签支持 keyProperty、keyColumn、resultType、statementType等属性,用法与之前类似。
此外,selectKey 标签还可以通过
order
属性选择在 insert 语句之前或之后执行(BEFORE
或AFTER
)。
sql
标签用于定义可重用的SQL片段,以便在其它语句中使用:
111<!-- 定义用户表的列名 -->
2<sql id="userColumns">
3 ${alias}.id,${alias}.username,${alias}.password
4</sql>
5
6<!-- 在selct/insert/update/delete/sql等标签中引用 -->
7<select id="selectUsers" resultType="map">
8 select <include refid="userColumns"> <property name="alias" value="t1"/> </include>
9 from Author t1
10</select>
11
注意:
引用可嵌套,但注意必须要能够在加载时推断出来(而非运行时)。
151-- 1. 仅指定参数名称(大多数情形使用)
2and id = #{id}
3and sex = #{user.sex}
4
5-- 2. 指定JDBC类型(一般在参数值可能为空时使用,避免因数据库驱动程序的差异导致的问题)
6and email = #{email,jdbcType=VARCHAR}
7
8-- 3. 指定类型处理器(在需要自定义类型转换逻辑时使用)
9and age = #{age,typeHandler=MyTypeHandler}
10
11-- 4. 指定全部属性(不常用)
12-- "mode=IN" 可以指定参数为输入参数(IN)、输出参数(OUT)或输入输出参数(INOUT)
13-- “numericScale=2” 可以指定数值类型的精度,如未指定,则使用数据库默认的精度
14-- “javaType=NUMERIC” 可以显示指定Java类型,但一般无需指定,MyBatis可以根据参数对象的类型推断(使用HashMap作为参数除外)
15and age = #{age, mode=OUT,javaType=int,jdbcType=NUMERIC,typeHandler=MyTypeHandler,numericScale=2}
注意:
默认情况下,MyBatis会按
parm1、parm2、parm3....
的顺序对参数进行命名。如果采用 Java 8 及以上版本编译,并且加上
-parameters
选项,则可直接使用形参名作为参数名。此外,也可以直接通过
@Parm("xxx")
注解显示指定参数名称。如果只存在一个POJO参数,则可省略前缀,如参数列表为
xxx(User user)
,则#{user.sex}
可简写为#{sex}
。如果参数是SQL语句的一部分,无法作为预编译参数
?
,则可使用${xxx}
进行字符串替换,如:ORDER BY ${columnName}
,但需注意SQL注入问题。
使用select
标签的resultType
属性,可以进行简单结果映射:
141<!-- 1. 映射到包装类(适用于SQL返回单列结果集) -->
2<select id="findTotal" resultType="int">
3 select count(id) from user;
4</select>
5
6<!-- 2. 映射到Map类型(key为列名,value为列值) -->
7<select id="selectUsers" resultType="map">
8 select id, username, hashedPassword from some_table where id = #{id}
9</select>
10
11<!-- 3. 映射到JavaBean类型(同名属性映射,可支持下划线转驼峰) -->
12<select id="selectUsers" resultType="com.someapp.model.User">
13 select user_id as "id", username, hashedPassword from some_table where user_id = #{id}
14</select>
如果查询数据返回多行,需要将 Mapper 方法的返回类型修改为集合类型,但resultType
属性配置无需修改:
61
2public interface UserMapper {
3 List<Integer> findIds(UserQueryReq req);
4 List<Map<String,Object> selectUsers(UserQueryReq req);
5 List<User> selectUsers(UserQueryReq req);
6}
注意:
可以在
mybatis-config.xml
中为JavaBean类型配置类型别名,这样resultType
中就不用输入全限定类名了。在映射到JavaBean类型时,如果列名和属性名无法匹配,则可以通过
user_id as "id"
方式为列起别名(SQL语法)。
使用select
标签的resultMap
属性,可以进行自定义结果映射:
151<!-- step1:定义一个自定义结果映射配置 -->
2<resultMap id="userResultMap" type="User">
3 <!-- 映射主键列 -->
4 <!-- 主键列可用于快速比较对象,优化缓存和嵌套映射的性能 -->
5 <id property="id" column="user_id" />
6
7 <!-- 映射普通列 -->
8 <result property="username" column="user_name"/>
9 <result property="password" column="hashed_password"/>
10</resultMap>
11
12<!-- step2:使用 userResultMap 进行自定义结果映射 -->
13<select id="selectUsers" resultMap="userResultMap">
14 select user_id, user_name, hashed_password from some_table where id = #{id}
15</select>
注意:
默认情况下,MyBatis会自动映射未配置的列,可通过
resultMap
标签的autoMapping="false"
属性关闭自动映射。
resultMap
标签支持指定extends="parentResultMap"
属性,对另一个映射配置进行继承,然后再覆盖和扩展。子标签
id
和result
可以指定jdbcType
和typeHandler
,用于声明Java类型(映射到Map时)和自定义类型转换过程。
嵌套结果映射指对复杂的关联查询的结果集进行嵌套映射,下面以一对一关联查询映射为例:
451<!-- “博客+作者”一对一关联查询SQL -->
2<select id="selectBlog" resultMap="blogResult">
3 select B.id as blog_id,
4 B.title as blog_title,
5 B.author_id as blog_author_id,
6 A.id as author_id,
7 A.username as author_username,
8 A.password as author_password,
9 A.email as author_email,
10 A.bio as author_bio
11 from Blog B left outer join Author A on B.author_id = A.id
12 where B.id = #{id}
13</select>
14
15<!-- 方式1:内联方式 -->
16<resultMap id="blogResult" type="Blog">
17 <id property="id" column="blog_id" />
18 <result property="title" column="blog_title"/>
19
20 <!-- 一对一映射 -->
21 <association property="author" javaType="Author">
22 <id property="id" column="author_id"/>
23 <result property="username" column="author_username"/>
24 <result property="password" column="author_password"/>
25 <result property="email" column="author_email"/>
26 <result property="bio" column="author_bio"/>
27 </association>
28</resultMap>
29
30<!-- 方式2:引用方式 -->
31<resultMap id="blogResult" type="Blog">
32 <id property="id" column="blog_id" />
33 <result property="title" column="blog_title"/>
34
35 <!-- 引用其它映射配置,进行一对一映射 -->
36 <association property="author" column="blog_author_id" javaType="Author" resultMap="authorResult"/>
37</resultMap>
38<!-- 被引用的映射配置 -->
39<resultMap id="authorResult" type="Author">
40 <id property="id" column="author_id"/>
41 <result property="username" column="author_username"/>
42 <result property="password" column="author_password"/>
43 <result property="email" column="author_email"/>
44 <result property="bio" column="author_bio"/>
45</resultMap>
一对多查询基本类似,只是多了一个ofType
属性,用于指明集合元素的类型:
71<!-- 内联方式 -->
2<collection property="posts" ofType="Post">
3 <!-- 其它部分和一对一完全一致 -->
4</collection>
5
6<!-- 引用方式 -->
7<collection property="posts" ofType="Post" resultMap="blogPostResult" columnPrefix="post_"/>
注意:
引用外部映射配置时,可以添加列别名前缀,如
columnPrefix="co_"
。可以通过
notNullColumn
属性配置非空列(以逗号分隔),只有在这些列非空时才会创建子对象。
嵌套Select查询指先查询主要字段,再通过N+1查询子对象字段,下面以一对一嵌套Select查询为例:
141<!-- “博客”单独查询 -->
2<select id="selectBlog" resultMap="blogResult">
3 SELECT * FROM BLOG WHERE ID = #{id}
4</select>
5
6<!-- 通过嵌套Select映射再查询“作者” -->
7<resultMap id="blogResult" type="Blog">
8 <association property="author" column="author_id" javaType="Author" select="selectAuthor" fetchType="lazy"/>
9</resultMap>
10
11<!-- 被引用的“作者”单独查询 -->
12<select id="selectAuthor" resultType="Author">
13 SELECT * FROM AUTHOR WHERE ID = #{id}
14</select>
一对多查询基本类似,只是多了一个ofType
属性,用于指明集合元素的类型:
21<!-- 一对多嵌套Select查询 -->
2<collection property="posts" column="id" ofType="Post" select="selectPostsForBlog"/>
注意:
column
标签可通过column="{prop1=col1,prop2=col2}
方式给嵌套Select查询传递多个参数。嵌套Select查询一般通过
fetchType="lazy"
方式设置懒加载,以防止过多的N+1查询。
某些数据库允许存储过程返回多个结果集,或一次性执行多个语句,每个语句返回一个结果集:
51CREATE PROCEDURE getBlogAndAuthor(IN id INT)
2BEGIN
3 SELECT * FROM BLOG WHERE ID = id;
4 SELECT * FROM AUTHOR WHERE ID = id;
5END;
在MyBatis中,可以对多结果集进行映射,下面以一对一多结果集映射为例:
231<!-- 调用存储过程,返回多个结果集 -->
2<select id="selectBlog" resultSets="blogs,authors" resultMap="blogResult" statementType="CALLABLE">
3 {call getBlogsAndAuthors(#{id,jdbcType=INTEGER,mode=IN})}
4</select>
5
6<!-- 多结果集映射配置 -->
7<resultMap id="blogResult" type="Blog">
8 <!-- 映射第1结果集 -->
9 <id property="id" column="id" />
10 <result property="title" column="title"/>
11
12 <!-- 映射第2结果集 -->
13 <!-- resultSet:从哪个结果集查询数据 -->
14 <!-- column:使用第1结果集的哪个字段进行查询(多个字段以逗号分隔) -->
15 <!-- foreignColumn:使用当前结果集的哪个字段进行匹配(多个字段以逗号分隔) -->
16 <association property="author" javaType="Author" resultSet="authors" column="author_id" foreignColumn="id">
17 <id property="id" column="id"/>
18 <result property="username" column="username"/>
19 <result property="password" column="password"/>
20 <result property="email" column="email"/>
21 <result property="bio" column="bio"/>
22 </association>
23</resultMap>
一对多查询基本类似,只是多了一个ofType
属性,用于指明集合元素的类型:
41<!-- 一对多多结果集映射 -->
2<collection property="posts" ofType="Post" resultSet="posts" column="id" foreignColumn="blog_id">
3 <!-- 其它部分和一对一完全一致 -->
4</collection>
一般情况下,MyBatis通过无参构造创建结果对象,然后再通过 getter/setter 方法进行赋值,如果需要指定有参构造方法,则可通过<constructor>
标签实现。
71<!-- 指定构造方法进行映射 -->
2<!-- public User(Integer id, String username, int age){...} -->
3<constructor>
4 <idArg column="id" javaType="int" name="id" />
5 <arg column="username" javaType="String" name="username" />
6 <arg column="age" javaType="_int" name="age" />
7</constructor>
注意:
如果映射配置顺序和构造方法字段顺序一致,则
name
属性可以省略。此外,
idArg
和arg
标签还支持jdbcType
、typeHandler
、select
、resultMap
等一些属性。
鉴别器可根据某个列值进行不同的映射,类似于switch语句:
271<!-- 通过鉴别器映射不同类型的交通工具 -->
2<resultMap id="vehicleResult" type="Vehicle">
3 <!-- 交通工具的通用字段映射 -->
4 <id property="id" column="id" />
5 <result property="vin" column="vin"/>
6 <result property="year" column="year"/>
7 <result property="make" column="make"/>
8 <result property="model" column="model"/>
9 <result property="color" column="color"/>
10
11 <!-- 使用 vehicle_type 值鉴别为不同交通工具 -->
12 <discriminator javaType="int" column="vehicle_type">
13 <!-- 交通工具1映射 -->
14 <case value="1" resultType="carResult">
15 <result property="doorCount" column="door_count" />
16 </case>
17 <!-- 交通工具2映射 -->
18 <case value="2" resultType="truckResult">
19 <result property="boxSize" column="box_size" />
20 <result property="extendedCab" column="extended_cab" />
21 </case>
22 <!-- 交通工具3映射 -->
23 <case value="3" resultType="vanResult">
24 <result property="powerSlidingDoor" column="power_sliding_door" />
25 </case>
26 </discriminator>
27</resultMap>
注意:
case
标签还支持通过resultMap
属性引用自定义映射,并且引用的自定义映射可以配置extends="vehicleResult"
属性。
下面是一个非常复杂的查询映射案例,可以作为学习参考。
651<!-- 把这个查询结果映射到一个复杂的Java对象。
2 这个对象表示了一篇博客,它由某位作者所写,有很多的博文,每篇博文有零或多条的评论和标签。-->
3<select id="selectBlogDetails" resultMap="detailedBlogResultMap">
4 select
5 B.id as blog_id,
6 B.title as blog_title,
7 B.author_id as blog_author_id,
8 A.id as author_id,
9 A.username as author_username,
10 A.password as author_password,
11 A.email as author_email,
12 A.bio as author_bio,
13 A.favourite_section as author_favourite_section,
14 P.id as post_id,
15 P.blog_id as post_blog_id,
16 P.author_id as post_author_id,
17 P.created_on as post_created_on,
18 P.section as post_section,
19 P.subject as post_subject,
20 P.draft as draft,
21 P.body as post_body,
22 C.id as comment_id,
23 C.post_id as comment_post_id,
24 C.name as comment_name,
25 C.comment as comment_text,
26 T.id as tag_id,
27 T.name as tag_name
28 from Blog B
29 left outer join Author A on B.author_id = A.id
30 left outer join Post P on B.id = P.blog_id
31 left outer join Comment C on P.id = C.post_id
32 left outer join Post_Tag PT on PT.post_id = P.id
33 left outer join Tag T on PT.tag_id = T.id
34 where B.id = #{id}
35</select>
36
37<!-- 结果映射写法参考 -->
38<resultMap id="detailedBlogResultMap" type="Blog">
39 <constructor>
40 <idArg column="blog_id" javaType="int"/>
41 </constructor>
42 <result property="title" column="blog_title"/>
43 <association property="author" javaType="Author">
44 <id property="id" column="author_id"/>
45 <result property="username" column="author_username"/>
46 <result property="password" column="author_password"/>
47 <result property="email" column="author_email"/>
48 <result property="bio" column="author_bio"/>
49 <result property="favouriteSection" column="author_favourite_section"/>
50 </association>
51 <collection property="posts" ofType="Post">
52 <id property="id" column="post_id"/>
53 <result property="subject" column="post_subject"/>
54 <association property="author" javaType="Author"/>
55 <collection property="comments" ofType="Comment">
56 <id property="id" column="comment_id"/>
57 </collection>
58 <collection property="tags" ofType="Tag" >
59 <id property="id" column="tag_id"/>
60 </collection>
61 <discriminator javaType="int" column="draft">
62 <case value="1" resultType="DraftPost"/>
63 </discriminator>
64 </collection>
65</resultMap>
一级缓存也叫会话缓存或本地缓存(local cache),默认的缓存作用域为SESSION
级别,可以在主配置文件的setting
标签修改。
41<settings>
2 <!-- 修改一级缓存作用域为STATEMENT级别(仅用于解决关联查询死循环问题)-->
3 <setting name="localCacheScope" value="STATEMENT"/>
4</settings>
如果想要命中一级缓存,必须要满足下面两个条件:
同一个会话。
同一个CacheKey。即EnvironmentId、MappedStatementId、SQL语句、运行时参数和RowBounds都需一致。
同时需要注意,在下面一些情形会直接清空一级缓存。
会话提交、回滚和执行 DML 语句始终会清空一级缓存。
在主查询前,如果MS配置了flushCache=true
也会刷新一级缓存。
在主查询后,如果缓存作用域为SATEMENT
也会清空一级缓存。
另外需注意:
缓存作用域建议为SESSION级别, STATEMENT级别仅关联查询的子查询可用缓存。
如果开启了二级缓存,则会优先使用二级缓存,二级缓存未命中才会查询一级缓存。
1031/**
2 * 一级缓存测试验证。
3 * 注意:
4 * 1. setting 中一级缓存的作用域(localCacheScope属性)为 SESSION(默认值),而非STATEMENT。
5 * 2. 确保在 setting 和 mapper 中没有都开启二级缓存(默认关闭)。
6 */
7public class FirstCacheTest {
8 SqlSessionFactory factory;
9 SqlSession sqlSession;
10
11
12 public void init() throws SQLException {
13 factory = new SqlSessionFactoryBuilder().build(ExecutorTest.class.getResourceAsStream("/mybatis-config.xml"));
14 sqlSession = factory.openSession();
15
16 // 在此插入id为2和3的数据
17 }
18
19 // 1. 参数必须相同
20
21 public void test01() {
22 UserDao userMapper = sqlSession.getMapper(UserDao.class);
23 User user01 = userMapper.findById(2);
24 User user02 = userMapper.findById(2); // 从缓存取值
25 System.out.println(user01 == user02); // true
26
27 User user03 = userMapper.findById(3);
28 System.out.println(user01 == user03); // false (注意是否有数据,否则null==null。下面例子不再提及,自行注意!)
29 }
30
31 // 2. MappedStatementId(类名.方法名)必须相同
32
33 public void test02() {
34 UserDao userMapper = sqlSession.getMapper(UserDao.class);
35 User user01 = userMapper.findById(2);
36 User user02 = userMapper.findByIdCopy(2);
37 System.out.println(user01 == user02); // false
38 }
39
40 // 3. 同一个会话。这也是一级缓存被称为会话级缓存的原因之一。
41
42 public void test03() {
43 UserDao userMapper = sqlSession.getMapper(UserDao.class);
44 User user01 = userMapper.findById(2);
45
46 // 创建一个新的 SqlSession
47 SqlSession sqlSessionAnother = factory.openSession();
48 UserDao userMapperAnother = sqlSessionAnother.getMapper(UserDao.class);
49 User user02 = userMapperAnother.findById(2);
50 System.out.println(user01 == user02); // false
51
52 // 调用方式可以多样,确保为同一个sqlSession即可。
53 List user03 = sqlSession.selectList("org.example.dao.UserDao.findById", 2);
54 System.out.println(user01 == user03.get(0)); // true
55 }
56
57 // 4. RowBounds 必返回行范围必须相同。默认为 RowBounds.DEFAULT。
58
59 public void test04() {
60 UserDao userMapper = sqlSession.getMapper(UserDao.class);
61 User user01 = userMapper.findById(2);
62
63 // 设置分页
64 RowBounds rowBounds = new RowBounds(0, 10);
65 List user02 = sqlSession.selectList("org.example.dao.UserDao.findById", 2, rowBounds);
66 System.out.println(user01 == user02.get(0)); // false
67
68 // 默认分页条件
69 RowBounds rowBoundsDefault = RowBounds.DEFAULT;
70 List user03 = sqlSession.selectList("org.example.dao.UserDao.findById", 2, rowBoundsDefault);
71 System.out.println(user01 == user03.get(0)); // true
72 }
73
74 // 5. 会话未进行提交、回滚和清除缓存等操作。
75
76 public void test05() {
77 UserDao userMapper = sqlSession.getMapper(UserDao.class);
78 User user01 = userMapper.findById(2);
79 // sqlSession.commit();
80 // sqlSession.rollback();
81 sqlSession.clearCache();
82 User user02 = userMapper.findById(2);
83 System.out.println(user01 == user02); // false
84 }
85
86 // 6. 未调用 flushCache=true 的方法。增删改的flushCache默认为true,查默认为false。
87
88 public void test06() {
89 UserDao userMapper = sqlSession.getMapper(UserDao.class);
90 User user01 = userMapper.findById(2);
91
92 // 调用 flushCache 为true的方法,刷新缓存
93 // userMapper.setName(3, "test06");
94 User userFlush = userMapper.findByIdOptionsFlushCacheIsTrue(2);
95 System.out.println(user01 == userFlush); // false 先刷新,再执行
96
97 // 再次查询
98 User user02 = userMapper.findById(2);
99 System.out.println(user01 == user02); // false
100 }
101
102}
103
MyBatis在查询一级缓存之前,还会优先查找二级缓存,二级缓存是应用级别的缓存,适用于不同会话之间。要启用全局的二级缓存,只需要在你的 SQL 映射文件中添加一行配置即可。
81<!-- 开启当前Mapper的二级缓存
2 - 当前Mapper的所有select语句的结果将会被缓存
3 - 当前Mapper的所有 insert、update 和 delete 语句会刷新缓存
4 - 使用最近最少使用的淘汰策略(LRU, Least Recently Used),size默认为1024。
5 - 不会进行过期清理,即缓存未被淘汰将永久存在。
6 - 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
7-->
8<cache/>
使用二级缓存时必须注意一些相关事项:
请勿关闭全局缓存开关cacheEnabled
,以及单个语句的缓存开关useCache
。
缓存的数据只有在会话提交后才能被使用,防止脏读问题(注意:设置自动提交并不能使二级缓存立即生效)。
执行配置了flushCache=true的语句会在执行查询前刷新缓存(flushCache=true也会在DML语句和主查询前刷新一级缓存)。
注解缓存配置和XML缓存配置必须通过缓存引用才能共享(这是MyBatis的一个bug,两者不能同时配置)。
不能使用自定义的ResultHandler,否则将无法使用二级缓存。
如果二级缓存仍无法命中,请检查CacheKey是否一致。
下面是一个二级缓存的简单测试案例。
231// 提交或关闭会话后,二级缓存才能被其他会话命中
2// 即使在打开会话时设置了自动提交,也需要手动提交后才会生效
3// 其他命中条件:环境ID一致、StatementId一致(Sql一致)、执行参数一致、RowBounds一致。
4
5public void cacheTest02() {
6 System.out.println("================ 第一次执行 ================");
7 SqlSession sqlSession01 = factory.openSession(true); // 设置自动提交并不能使二级缓存立即生效
8 UserDao userMapper01 = sqlSession01.getMapper(UserDao.class);
9 User user01 = userMapper01.findById(2); // 查询数据库
10
11 System.out.println("================ 第二次执行 ================");
12 SqlSession sqlSession02 = factory.openSession();
13 UserDao userMapper02 = sqlSession02.getMapper(UserDao.class);
14 User user02 = userMapper02.findById(2); // 也是查询数据库(看日志),因为 sqlSession01 还未提交
15
16 sqlSession02.commit(); // 提交会话,sqlSession02 设置的二级缓存生效
17 // sqlSession02.close(); // close()方法也会提交会话
18
19 System.out.println("================ 第三次执行 ================");
20 SqlSession sqlSession03 = factory.openSession();
21 UserDao userMapper03 = sqlSession03.getMapper(UserDao.class);
22 User user03 = userMapper03.findById(2); // 查询缓存
23}
可以使用 select/update/insert/delete 标签的属性进行更细粒度的缓存控制。
useCache:为 true 时表示配置的 SQL 语句使用二级缓存,否则表示不进行缓存。默认值为true
。
flushCache:为true时表示执行该语句前进行缓存刷新,否则表示无需刷新缓存。select 标签的默认值为false,其他为true。
二级缓存的行为可以通过 cache
标签的属性来修改。比如:
11<cache eviction="FIFO" flushInterval="60000" size="512" readOnly="true"/>
这个更高级的配置创建了一个 FIFO 缓存,每隔 60000 毫秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。
对某一命名空间的语句,只会使用该命名空间的缓存进行缓存或刷新。 但你可能会想要在多个命名空间中共享相同的缓存配置和实例。要实现这种需求,你可以使用 cache-ref
元素来引用另一个缓存。
101<!-- Mapper1.xml -->
2<mapper namespace="xyz.coolblog.dao.Mapper1">
3 <!-- Mapper1 与 Mapper2 共用一个二级缓存 -->
4 <cache-ref namespace="xyz.coolblog.dao.Mapper2"/>
5</mapper>
6
7<!-- Mapper2.xml -->
8<mapper namespace="xyz.coolblog.dao.Mapper2">
9 <cache/>
10</mapper>
注意:
即使在同一个命名空间,如果是通过注解和XML分别配置,则两者不会共享,需要通过缓存引用进行关联。
通过cache
标签的type
属性,可以配置其他第三方缓存的适配器,来代替MyBatis内置的二级缓存模块。
11<cache type="com.domain.something.MyCustomCache"/>
第三方缓存的适配器需要实现org.apache.ibatis.cache.Cache
接口,并提供一个接受 String 参数作为 id 的构造器。
91public interface Cache {
2 String getId();
3 int getSize();
4 void putObject(Object key, Object value);
5 Object getObject(Object key);
6 boolean hasKey(Object key);
7 Object removeObject(Object key);
8 void clear();
9}
如需对缓存适配器进行配置,可直接通过如下方式,MyBatis将会自动匹配和调用对应的 set 方法:
51<!-- 使用第三方缓存的适配器 -->
2<cache type="com.domain.something.MyCustomCache">
3 <!-- 缓存适配器属性配置,将会调用 setCacheFile(String file) 方法-->
4 <property name="cacheFile" value="/tmp/my-custom-cache.tmp"/>
5</cache>
如需对缓存适配器进行初始化,可实现 org.apache.ibatis.builder.InitializingObject
接口,将会在属性设置完毕后调用:
31public interface InitializingObject {
2 void initialize() throws Exception;
3}
注意:
上一节中对缓存的配置(如清除策略、可读或可读写等),不能应用于自定义缓存。
<if>
标签用于在动态 SQL 中根据条件动态拼接 SQL 语句:
141<select id="findUsers" parameterType="map" resultType="User">
2 SELECT * FROM user WHERE 1=1
3
4 <!-- 字符类型 -->
5 <if test="name != null and name != ''">
6 AND name LIKE CONCAT('%', #{name}, '%')
7 </if>
8
9 <!-- 数值类型 -->
10 <if test="age != null and age > 0">
11 AND age = #{age}
12 </if>
13
14</select>
<choose>
标签允许根据多个条件动态选择 SQL 片段,类似 Java 中的 switch
或 if-else if-else
的逻辑:
191<select id="findUsers" parameterType="map" resultType="User">
2 SELECT * FROM user WHERE 1=1
3 <where>
4 <choose>
5 <when test="name != null and name != ''">
6 AND name LIKE CONCAT('%', #{name}, '%')
7 </when>
8 <when test="age != null">
9 AND age = #{age}
10 </when>
11 <when test="email != null and email != ''">
12 AND email = #{email}
13 </when>
14 <otherwise>
15 AND 1 = 1 <!-- 默认条件,可以省略 -->
16 </otherwise>
17 </choose>
18 </where>
19</select>
注意:
<choose>
标签中的<when>
条件是按顺序检查的,一旦某个条件满足,后续的<when>
和<otherwise>
将被忽略。
trim
标签用于移除指定前后缀,并在标签内容不为空时,添加新的前后缀。
141<!-- trim标签模板 -->
2<trim prefixOverrides="要移除的前缀" suffixOverrides="要移除的后缀" prefix="新的前缀" suffix="新的后缀" >
3 <!-- 标签内容 -->
4</trim>
5
6<!-- 基于trim标签实现where标签 -->
7<trim prefix="WHERE" prefixOverrides="AND |OR ">
8 <!-- 标签内容 -->
9</trim>
10
11<!-- 基于trim标签实现set标签 -->
12<trim prefix="SET" suffixOverrides=",">
13 <!-- 标签内容 -->
14</trim>
也可直接使用内置的where
标签和set
标签:
251<!-- where标签:移除开头的AND或OR,如果标签内容不为空,则在开头拼接WHERE -->
2<select id="findUsers" parameterType="map" resultType="User">
3 SELECT * FROM user
4 <where>
5 <!-- 字符类型 -->
6 <if test="name != null and name != ''">
7 AND name LIKE CONCAT('%', #{name}, '%')
8 </if>
9
10 <!-- 数值类型 -->
11 <if test="age != null and age > 0">
12 AND age = #{age}
13 </if>
14 </where>
15</select>
16
17<!-- where标签:移除末尾的逗号,如果标签内容不为空,则在开头拼接SET -->
18<update id="updateAuthorIfNecessary">
19 update Author
20 <set>
21 <if test="username != null">username=#{username},</if>
22 <if test="password != null">password=#{password},</if>
23 </set>
24 where id=#{id}
25</update>
<foreach>
标签用于处理集合或数组类型的参数,适合在批量操作或动态生成 IN
子句等场景中使用。
321<!-- foreach标签模板 -->
2<foreach collection="集合或数组" item="当前项的变量名" index="当前项的索引变量名"
3 open="开始字符" separator="分隔符" close="结束字符">
4 <!-- SQL 片段 -->
5</foreach>
6
7<!-- 批量插入 -->
8<insert id="insertUsers" parameterType="list">
9 INSERT INTO user (id, name, age) VALUES
10 <foreach collection="list" item="user" separator=",">
11 (#{user.id}, #{user.name}, #{user.age})
12 </foreach>
13</insert>
14
15<!-- 动态生成IN子句 -->
16<select id="findUsersByIds" parameterType="list" resultType="User">
17 SELECT * FROM user WHERE id IN
18 <foreach collection="list" item="id" open="(" separator="," close=")">
19 #{id}
20 </foreach>
21</select>
22
23<!-- MAP类型参数更新 -->
24<update id="updateUsers" parameterType="map">
25 UPDATE user
26 <set>
27 <foreach collection="map" item="value" index="key" separator=",">
28 ${key} = #{value}
29 </foreach>
30 </set>
31 WHERE id = #{id}
32</update>
注意:
<foreach>
标签的collection
属性可以是任何可迭代对象,如:集合、数组或 Map 类型。当使用可迭代对象或者数组时,index 是当前迭代的序号,item 的值是本次迭代获取到的元素。
当使用 Map 对象(或者 Map.Entry 对象的集合)时,index 是键,item 是值。
bind
标签允许你在动态 SQL 中定义一个变量,并将其绑定到当前的上下文。
161<select id="findUsers" parameterType="map" resultType="User">
2 SELECT * FROM user
3 <where>
4 <!-- 定义变量 -->
5 <bind name="namePattern" value="'%' + name + '%'" />
6 <bind name="emailPattern" value="'%' + email + '%'" />
7
8 <!-- 使用变量 -->
9 <if test="name != null and name != ''">
10 AND name LIKE #{namePattern}
11 </if>
12 <if test="email != null and email != ''">
13 AND email LIKE #{emailPattern}
14 </if>
15 </where>
16</select>
要在带注解的映射器接口类中使用动态 SQL,可以使用 script
标签。
91"<script>", ({
2 "update Author",
3 " <set>",
4 " <if test='username != null'>username=#{username},</if>",
5 " <if test='password != null'>password=#{password},</if>",
6 " </set>",
7 "where id=#{id}",
8 "</script>"})
9void updateAuthorValues(Author author);
前面的动态SQL标签由语言驱动 org.apache.ibatis.scripting.xmltags.XmlLanguageDriver
(别名为 xml
)提供支持。
如需自定义语言驱动,可以实现LanguageDriver
接口:
51public interface LanguageDriver {
2 ParameterHandler createParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql);
3 SqlSource createSqlSource(Configuration configuration, XNode script, Class<?> parameterType);
4 SqlSource createSqlSource(Configuration configuration, String script, Class<?> parameterType);
5}
并在 mybatis-config.xml
文件中将它设置为默认语言:
61<typeAliases>
2 <typeAlias type="org.sample.MyLanguageDriver" alias="myLanguage"/>
3</typeAliases>
4<settings>
5 <setting name="defaultScriptingLanguage" value="myLanguage"/>
6</settings>
或者,使用 lang
属性为特定的语句指定语言:
31<select id="selectBlog" lang="myLanguage">
2 SELECT * FROM BLOG
3</select>
或者,在 mapper 接口上添加 @Lang
注解:
51public interface Mapper {
2 MyLanguageDriver.class) (
3 "SELECT * FROM BLOG") (
4 List<Blog> selectBlog();
5}
注意:
可以使用 Apache Velocity 作为动态语言,更多细节请参考 MyBatis-Velocity 项目。
映射器注解也可用于映射配置,下面是所有映射器注解列表:
注解 | XML等价形式 | 注解 | XML等价形式 |
---|---|---|---|
@Insert | <insert> | @CacheNamespace | <cache> |
@Update | <update> | @CacheNamespaceRef | <cacheRef> |
@Delete | <delete> | @Flush | N/A |
@Select | <delete> | @InsertProvider | <insert> |
@Param | N/A | @UpdateProvider | <update> |
@Results | <resultMap> | @DeleteProvider | <delete> |
@Result | <result>、<id> | @SelectProvider | <delete> |
@ConstructorArgs | <constructor> | @SelectKey | <selectKey> |
@Arg | <arg>、<idArg> | @Options | N/A |
@One | <association> | @Property | <property> |
@Many | <collection> | @MapKey | N/A |
@ResultType | N/A | @TypeDiscriminator | <discriminator> |
@ResultMap | N/A | @Case | <case> |
531// 查询用户,返回对象
2id = "userResult", value = { (
3 property = "id", column = "uid", id = true), (
4 property = "firstName", column = "first_name"), (
5 property = "lastName", column = "last_name") (
6})
7"select * from users where id = #{id}") (
8User getUserById(Integer id);
9
10// 查询用户,包含一对一的 Profile 和一对多的 Orders
11"SELECT * FROM user WHERE id = #{id}") (
12 ({
13 property = "id", column = "id"), (
14 property = "name", column = "name"), (
15 property = "age", column = "age"), (
16 property = "profile", column = "id", (
17 one = (select = "com.example.mapper.ProfileMapper.getProfileByUserId")),
18 property = "orders", column = "id", (
19 many = (select = "com.example.mapper.OrderMapper.getOrdersByUserId"))
20})
21User getUserById( ("id") int id);
22
23// 查询所有用户,返回MAP(KEY为用户id)
24"SELECT * FROM user") (
25"id") (
26Map<Integer, User> getAllUsersMap();
27
28// 插入之前生成主键
29"insert into table3 (id, name) values(#{nameId}, #{name})") (
30statement="call next value for TestSequence", keyProperty="nameId", (
31 before=true, resultType=int.class)
32int insertTable3(Name name);
33
34// 插入之后查询主键
35"INSERT INTO user (name, age) VALUES (#{name}, #{age})") (
36statement = "SELECT LAST_INSERT_ID()", keyProperty = "id", before = false) (
37int insertUser( ("name") String name, ("age") int age);
38
39// 使用结果构造器
40id = "companyResults") (
41 ({
42 column = "cid", javaType = Integer.class, id = true), (
43 column = "name", javaType = String.class) (
44})
45"select * from company where id = #{id}") (
46Company getCompanyById(Integer id);
47
48// 多数据支持
49value = "SELECT SYS_GUID() FROM dual", databaseId = "oracle") // oracle (
50value = "SELECT uuid_generate_v4()", databaseId = "postgres") //postgres (
51"SELECT RANDOM_UUID()") //others (
52String generateId();
53
MyBatis-Plus(简称 MP)是一个 MyBatis的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
211<!-- 在 SpringBoot 项目中 -->
2<dependency>
3 <groupId>com.baomidou</groupId>
4 <artifactId>mybatis-plus-boot-starter</artifactId>
5 <version>3.5.2</version>
6</dependency>
7
8<!-- 在 SpringBoot3 项目中 -->
9<dependency>
10 <groupId>com.baomidou</groupId>
11 <artifactId>mybatis-plus-spring-boot3-starter</artifactId>
12 <version>最新版本</version>
13</dependency>
14
15<!-- 在 Spring 项目中 -->
16<dependency>
17 <groupId>com.baomidou</groupId>
18 <artifactId>mybatis-plus</artifactId>
19 <version>最新版本</version>
20</dependency>
21
注意:
使用 mybatis-plus 后,配置要使用 mybatis-plus 前缀开头:
41#mybatis:
2# mapper-locations: classpath*:com/huangyuanxin/mapper/common/*.xml,classpath*:com/huangyuanxin/mapper/${database.type}/*.xml
3mybatis-plus
4mapper-locations classpath* com/huangyuanxin/mapper/common/*.xml,classpath* com/huangyuanxin/mapper/$ database.type /*.xml
91"sys_user") (
2public class User {
3
4 private Long id;
5 "nickname") (
6 private String name;
7 private Integer age;
8 private String email;
9}
41// 继承BaseMapper
2
3public interface UserMapper extends BaseMapper<User> {
4}
在 SpringBoot 项目中
91
2"com.xxx.mapper") (
3public class Application {
4
5 public static void main(String[] args) {
6 SpringApplication.run(Application.class, args);
7 }
8
9}
在 Spring 项目中
111
2<!-- 1. 配置 MapperScan -->
3<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
4 <property name="basePackage" value="com.xxx.mapper"/>
5</bean>
6
7<!-- 2. 调整 SqlSessionFactory 为 MyBatis-Plus 的 SqlSessionFactory -->
8<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
9 <property name="dataSource" ref="dataSource"/>
10</bean>
11
161
2"org.example.mapper") (
3public class Application {
4
5 public static void main(String[] args) {
6 // 启动
7 ConfigurableApplicationContext ctx = SpringApplication.run(Application.class, args);
8
9 // 获取Mapper
10 UserMapper userMapper = ctx.getBean(UserMapper.class);
11
12 // 测试
13 List<User> users = userMapper.selectList(null);
14 System.out.println("users = " + users);
15 }
16}
41public interface BaseMapper<T> extends Mapper<T> {
2
3 // 增删改查...
4}
381// 测试查询
2public static void test(UserMapper userMapper) {
3 // 1. 通过ID查询(必须标注@Tableid注解)
4 userMapper.selectById(1L);
5
6 // 2. 通过ID批量查询(必须标注@Tableid注解)
7 userMapper.selectBatchIds(Arrays.asList(1, 2, 3));
8
9 // 3. 查询所有数据
10 userMapper.selectList(null);
11
12 // 4. 通过Map条件查询
13 userMapper.selectByMap(new HashMap<String, Object>() {{
14 put("age", 23);
15 }});
16}
17
18// 测试增删改
19public static void test(UserMapper userMapper) {
20 // 需增删改的用户
21 User user = new User(21L, "21客户", 23, "2030201403@qq.com");
22
23 // 1. 插入
24 userMapper.insert(user);
25
26 // 2. 修改
27 user.setAge(30);
28 userMapper.updateById(user); // 通过ID修改(必须标注@Tableid注解)
29
30 // 3. 删除
31 userMapper.deleteById(user.getId()); // 通过ID删除(必须标注@Tableid注解)
32 userMapper.deleteById(user); // 通过实体中的ID删除(必须标注@Tableid注解)
33 userMapper.deleteBatchIds(Arrays.asList(1, 2, 3)); // 通过ID批量删除(必须标注@Tableid注解)
34 userMapper.deleteByMap(new HashMap<String, Object>() {{ // 通过Map条件删除(必须标注@Tableid注解)
35 put("age", 23);
36 }});
37}
38
51public interface IService<T> {
2
3 // 批量操作、合并操作...
4}
5
241// Service接口
2public interface UserService extends IService<User> {
3}
4
5// Service实现
6
7public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {
8}
9
10// Service测试
11public static void test(UserService userService) {
12 // 1. count查询
13 long count = userService.count();
14
15 // 2. 批量插入
16 boolean saveBatch = userService.saveBatch(new ArrayList<User>() {
17 {
18 add(new User(101L, "101客户", 23, "2030201403@qq.com"));
19 add(new User(102L, "102客户", 23, "2030201403@qq.com"));
20 add(new User(103L, "103客户", 23, "2030201403@qq.com"));
21 }
22 });
23}
24
231public static void test(UserMapper userMapper) {
2
3 // 1. 指定WHERE条件
4 String name = "Xxx";
5 List<User> userList = userMapper.selectList(new QueryWrapper<User>()
6 .like(StringUtils.hasText(name), "name", name) // 根据用户是否输入来构造
7 .between("age", 1, 30)
8 .orderByDesc("id"));
9
10 // 2. 指定查询列
11 List<Map<String, Object>> userMapList = userMapper.selectMaps(new QueryWrapper<User>()
12 .select("id", "name", "age"));
13
14 // 3. 支持子查询
15 List<User> userListByInSql = userMapper.selectList(new QueryWrapper<User>()
16 .inSql("id", "select id from user where id < 30"));
17
18 // 4. Lambda形式
19 List<User> userListByLambda = userMapper.selectList(new LambdaQueryWrapper<User>()
20 .like(StringUtils.hasText(name), User::getName, name)
21 .between(User::getAge, 1, 30)
22 .orderByDesc(User::getId));
23}
注意:
在 JDK8 版本,需要正确填写 QueryWrapper 的泛型参数,不可省略,否则编译会报错。
151public static void test(UserMapper userMapper) {
2
3 // 1. 复杂条件更新
4 int rows = userMapper.update(null, new UpdateWrapper<User>()
5 .set("name", "hyx01")
6 .eq("id", 1L)
7 .isNotNull("email"));
8
9
10 // 2. Lambda形式
11 int rowsByLambda = userMapper.update(null, new LambdaUpdateWrapper<User>()
12 .set(User::getName, "hyx01")
13 .eq(User::getId, 1L)
14 .isNotNull(User::getEmail));
15}
51
2"t_user") // 标注实体类对应的表名 (
3public class User {
4 // field...
5}
提示:
也可通过
mybatis-plus.global-confi.db-config.table-prefix=t_
来指定默认表前缀。
71
2public class User {
3 "id") // 标注表主键 (
4 private Long id;
5
6 // others field...
7}
注意:
MyBatis-Plus 默认将
id
作为主键列,并在插入数据时,基于雪花算法的策略生成 id 值。MyBatis-Plus 不支持联合主键,需通过二次增强框架 MyBatis-Plus-Plus 实现。
主键生成策略支持雪花算法:
IdType.ASSIGN_ID
和数据库自增:IdType.AUTO
等。
121
2public class User {
3 "id") (
4 private Long id;
5 "username") // 标注表字段 (
6 private String name;
7
8 // others field...
9 // 同名或可兼容字段无需标注
10 private Integer age;
11 private String email;
12}
注意:
驼峰与下划线风格的字段名与属性名可以相互兼容,例如属性 userName 可以兼容字段 user_name 。
81
2public class User {
3 // others field...
4
5 // 配置逻辑删除字段(0-正常记录 1-已删除记录)
6
7 private Integer isDeleted;
8}
81
2public class User {
3 // others field...
4
5 // 配置版本号字段,以支持乐观锁修改,防止并发问题
6
7 private Integer version;
8}
注意:
该功能需要注册乐观锁插件:
OptimisticLockerInnerInterceptor
。
121
2public class MyBatisPlusConfig {
3
4 public MybatisPlusInterceptor mybatisPlusInterceptor() {
5
6 // 注册MyBatisPlus分页拦截器
7 MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
8 interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
9
10 return interceptor;
11 }
12}
171public static void test(UserMapper userMapper) {
2
3 // 1. 简单分页查询
4 Page<User> page = new Page<>(1, 5);
5 userMapper.selectPage(page, null);
6
7 // 2. 获取分页数据
8 List<User> list = page.getRecords();
9 list.forEach(System.out::println);
10 System.out.println("当前页:" + page.getCurrent());
11 System.out.println("每页显示的条数:" + page.getSize());
12 System.out.println("总记录数:" + page.getTotal());
13 System.out.println("总页数:" + page.getPages());
14 System.out.println("是否有上一页:" + page.hasPrevious());
15 System.out.println("是否有下一页:" + page.hasNext());
16
17}
按 MyBatis 的方式编写 Mapper 接口,只需在第一个参数传递@Param("page") Page<User> page
:
141
2public interface UserMapper extends BaseMapper<User> {
3
4 /**
5 * 根据年龄查询用户列表,分页显示
6 *
7 * @param page 分页对象,xml中可以从里面进行取值,传递参数 Page 即自动分页,必须放在第一位
8 * @param age 年龄
9 * @return
10 */
11 Page<User> selectPageVo( ("page") Page<User> page, ("age") Integer age);
12
13}
14
XML配置无需特殊修改:
31<select id="selectPageVo" resultType="org.example.model.User">
2 SELECT * FROM user WHERE age > #{age}
3</select>
查询时会自动根据分页参数进行分页处理:
151public static void test2(UserMapper userMapper) {
2 // 1. XML分页查询
3 Page<User> page = new Page<>(1, 5);
4 userMapper.selectPageVo(page, 21);
5
6 // 2. 获取分页数据
7 List<User> list = page.getRecords();
8 list.forEach(System.out::println);
9 System.out.println("当前页:" + page.getCurrent());
10 System.out.println("每页显示的条数:" + page.getSize());
11 System.out.println("总记录数:" + page.getTotal());
12 System.out.println("总页数:" + page.getPages());
13 System.out.println("是否有上一页:" + page.hasPrevious());
14 System.out.println("是否有下一页:" + page.hasNext());
15}
一种通过 @EnumValue
注解标识枚举属性值,并在插入和查询时进行映射的方式,不推荐。