目录
MyBatis 是一个开源、轻量级的数据持久化框架,是 JDBC 和 Hibernate 的替代方案。MyBatis 内部封装了 JDBC,简化了加载驱动、创建连接、创建 statement 等繁杂的过程,开发者只需要关注 SQL 语句本身。
优点
- MyBatis是免费且开源的。
- 与 JDBC 相比,减少了 50% 以上的代码量。
- MyBatis是最简单的持久化框架,体积小巧并且学习门槛低。
- MyBatis相当灵活,不会对应用程序或者数据库的现有设计强加任何影响,SQL 写在 XML 中,和程序逻辑代码分离,降低耦合度,便于同一管理和优化,提高了代码的可重用性。
- 提供 XML 标签,支持编写动态 SQL语句。
- 提供映射标签,支持
缺点
- 编写 SQL语句工作量较大,对开发人员编写 SQL 语句的功底有一定要求。
- SQL语句依赖于数据库,导致数据库移植性差,不能随意更换数据库。实体对象与数据库的表字段关系映射。
配置文件:
mybatis-config.xml需要注意的点:
映射要保持对应
重难点问题:
1.传递多个参数
现在需要根据 url 和 name来模糊查询网站信息,显然这涉及到了两个参数。给映射器传递多个参数分为以下三种方法。
- 使用Map传递参数
- 使用注解传递参数
- 使用JavaBean传递参数
(1).使用Map传递参数
使用 MyBatis提供的 Map接口作为参数。
在WebsiteMapper.xml中定义<select>节点
<!-- 根据name和url模糊查询网站信息 -->
<select id="selectWebsiteByMap"
resultType="com.apesource.entity.Website"
parameterType="map">
SELECT id,NAME,url FROM website
WHERE name LIKE CONCAT ('%',#{name},'%')
AND url LIKE CONCAT ('%',#{url},'%')
</select>
在 WebsiteMapper 接口中,定义方法selectWebsiteByMap:
public List<Website> selectWebsiteByMap(Map<String, String> params);
使用 Map 传递参数虽然简单易用,但是由于这样设置参数需要键值对应,业务关联性不强,开发人员需要深入到程序中看代码,造成可读性下降。
(2). 使用注解传递参数
使用 MyBatis 的注解 @Param() 传递参数
WebsiteMapper.xml
<!-- 根据name和url模糊查询网站信息 -->
<select id="selectWebsiteByAn" resultType="com.apesource.entity.Website">
SELECT id,NAME,url FROM website
WHERE name LIKE CONCAT ('%',#{name},'%')
AND url LIKE CONCAT ('%',#{url},'%')
</select>
在WebsiteMapper 接口中,定义方法selectWebsiteByAn():
public List<Website> selectWebsiteByAn(@Param("name") String name, @Param("url") String url);
当我们把参数传递给后台时,MyBatis 通过 @Param 提供的名称就会知道 #{name} 代表 name 参数,提高了参数可读性。但是如果这条 SQL 拥有 10 个参数的查询,就会造成可读性下降,增强了代码复杂性。
(3). 使用JavaBean传递参数
在参数过多的情况下,MyBatis 允许组织一个 JavaBean,通过简单的 setter和 getter方法设置参数,提高可读性。
WebsiteMapper.xml
<!-- 根据name和url模糊查询网站信息 -->
<select id="selectWebsiteByAn" resultType="com.apesource.entity.Website">
SELECT id,NAME,url FROM website
WHERE name LIKE CONCAT ('%',#{name},'%')
AND url LIKE CONCAT ('%',#{url},'%')
</select>
WebsiteMapper.java
public List<Website> selectWebsiteByAn(Website website);
以上 3 种方式的区别如下:
- 使用 Map 传递参数会导致业务可读性的丧失,继而导致后续扩展和维护的困难。
- 使用 @Param 注解传递参数会受到参数个数的影响。当 n≤5 时,它是最佳的传参方式,因为它更加直观;当 n>5 时,多个参数将给调用带来困难。
- 当参数个数大于 5 个时,建议使用 JavaBean 方式。
2. #{}与${}的区别
默认情况下,使用 #{}参数语法时,MyBatis会创建 PreparedStatement参数占位符,并通过?占位符安全地设置参数。 这样做更安全,更迅速,通常也是首选做法,不过有时你就是想直接在 SQL 语句中直接插入一个不转义的字符串。 比如 ORDER BY 子句,这时候你可以:ORDER BY ${columnName},这个时候${}将直接在SQL语句中进行字符串的拼接。
具体区别点总结如下:
- 使用#{}会产生1个?占位符,并使用PreparedStatement进行处理。而${}则进行的是字符串的拼接。
- 使用#{}可以预防SQL注入,而${}无法防止SQL注入。
- ${}一般用于在SQL中拼接数据库表名、视图或关键字,例如动态使用order by 进行排序时,就需要使用${}来拼接排序字段名称和排序规则。
3.resultMap 元素
resultMap是 MyBatis 中最复杂的元素,主要用于解决实体类属性名与数据库表中字段名不一致的情况,可以将查询结果映射成实体对象。
resultMap元素的构成
<!--id属性 : resultMap的唯一标识-->
<!--type属性 : resultMap结果映射的实体类完全限定名-->
<resultMap id="" type="">
<constructor><!-- 类再实例化时用来注入结果到构造方法 -->
<idArg/><!-- ID参数,结果为ID -->
<arg/><!-- 注入到构造方法的一个普通结果 -->
</constructor>
<id/><!-- 主键字段与属性的映射 -->
<result/><!-- 普通字段与属性的映射 -->
<association property=""/><!-- 用于一对一关联 -->
<collection property=""/><!-- 用于一对多、多对多关联 -->
</resultMap>
id 和 result 元素都有以下属性:
元素 | 说明 |
property | 映射到列结果的字段或属性。如果 POJO 的属性和 SQL 列名(column元素)是相同的,那么 MyBatis 就会映射到 POJO 上 |
column | 对应 SQL 列 |
javaType | 配置 Java 类型。可以是特定的类完全限定名或 MyBatis 上下文的别名 |
jdbcType | 配置数据库类型。这是 JDBC 类型,MyBatis 已经为我们做了限定,基本支持所有常用数据库类型 |
typeHandler | 类型处理器。允许你用特定的处理器来覆盖 MyBatis 默认的处理器。需要指定 jdbcType 和 javaType 相互转化的规则 |
resultMap 和 resultType 不能同时使用。
4.批处理
在结合MyBatis框架进行批处理操作时,通常使用两种方式:
(1) 使用动态SQL进行批量添加
SQL映射文件中的配置如下所示:
<insert id="insertNewWebsiteBatch">
INSERT INTO website (name, url, age, country, createtime)
VALUES
<foreach collection="list" item="website" separator=",">
(#{website.name},#{website.url},#{website.age},#{website.country},now())
</foreach>
</insert>
注意:该方法的参数为List集合,将需要批量添加的数据一次性
注意:在<foreach>遍历过程中,每次迭代遍历到的是一个Website对象传入。
(2)使用MyBatis BATCH模式进行批量添加
该方式基于MyBatis的BatchExecutor对SQL语句进行批处理执行,由于MySQL的批处理执行机制要求,必须在连接字符串中添加参数rewriteBatchedStatements=true,才可以真正开启批处理机制
①SqlSession的获取
创建SqlSession的代码如下所示:
SqlSession sqlSession = sqlSessionFactory.openSession(ExecutorType.BATCH, false);
工具类MyBatisUtils封装代码如下
public static SqlSession getSqlSession() {
SqlSession session = null;
if(factory != null ) {
// 创建普通执行模式的SqlSession
// 该模式下的执行器类型默认为ExecutorType.SIMPLE
// 所以,使用的Executor执行器对象类型为SimpleExecutor
session = factory.openSession();
}
return session;
}
public static SqlSession getBatchSqlSession() {
SqlSession session = null;
if(factory != null ) {
// 创建批处理模式的SqlSession
// 该模式下的执行器类型默认为ExecutorType.BATCH
// 所以,使用的Executor执行器对象类型为BatchExecutor
session = factory.openSession(ExecutorType.BATCH);
}
return session;
}
②SQL映射
SQL映射文件
<insert id="insertNewWebsite"
parameterType="com.apesource.entity.Website">
insert into
website(name,url,age,country,createtime)values(#{name},#{url},#{age},#{country},now())
</insert>
SQL映射接口
// 添加新网站信息
public int insertNewWebsite(Website site);
③批量执行
try (SqlSession sqlSession = MyBatisUtils.getBatchSqlSession();) {
WebsiteMapper websiteMapper = sqlSession.getMapper(WebsiteMapper.class);
// 添加5w条记录
for (int i = 0; i < 50000; i++) {
int r = websiteMapper.insertNewWebsite(new Website());
// 每1000条执行1次批处理
if (i != 0 && i % 1000 == 0) {
sqlSession.commit();
}
}
sqlSession.commit();
}
每1000条执行1次批处理时,如果执行sqlSession.commit()则代表本次批处理的中的所有SQL语句全部通过Executor一次性发送给MySQL执行,并清除本地缓存与操作。
由于commit()方法没有返回值,则意味着无法批量添加操作时,需要获取自动增长的主键值。此时就需要使用sqlSession.flushStatements()。代码如下:
try (SqlSession sqlSession = MyBatisUtils.getSqlSession(ExecutorType.BATCH);) {
WebsiteMapper websiteMapper = sqlSession.getMapper(WebsiteMapper.class);
// 添加5w条记录
for (int i = 0; i < 50000; i++) {
int r = websiteMapper.insertNewWebsite(new Website());
// 每1000条执行1次批处理
if (i != 0 && i % 1000 == 0) {
// 清空并执行批处理中的所有执行操作,并获取批处理执行结果
List<BatchResult> resultList = sqlSession.flushStatements();
for(BatchResult result : resultList) {
System.out.println("\t" + result.getParameterObjects());
}
}
}
sqlSession.commit();
}
通过执行sqlSession.flushStatements()获取批处理的BatchResult,可以获取批处理执行后的结果,该结果中包含的每个参数对象均持有数据库自动增长并回填的主键值。